feat: implement transaction-level session locking in pos_order and idempotent payment creation in pos_payment
This commit is contained in:
parent
52565d3f92
commit
932db6e5f7
@ -2,4 +2,5 @@
|
||||
from . import pos_config
|
||||
from . import res_config_settings
|
||||
from . import pos_order
|
||||
from . import pos_payment
|
||||
|
||||
|
||||
@ -106,7 +106,34 @@ class PosOrder(models.Model):
|
||||
Override to prevent concurrent processing of the same order uuid.
|
||||
Uses pg_advisory_xact_lock on the order's uuid to force subsequent
|
||||
duplicate requests to wait until the first request completes/commits.
|
||||
Also, locks the session associated with the incoming orders at the database
|
||||
transaction level to serialize updates affecting the same session and prevent
|
||||
serialization failures (concurrent updates on pos.session).
|
||||
"""
|
||||
# Extract unique session IDs from the orders list
|
||||
session_ids = set()
|
||||
for order in orders:
|
||||
session_id = order.get('session_id')
|
||||
if session_id:
|
||||
session_ids.add(session_id)
|
||||
|
||||
if session_ids:
|
||||
# Also find if any of these sessions are closed/closing, and if so, their potential open rescue/config sessions
|
||||
sessions = self.env['pos.session'].browse(session_ids).exists()
|
||||
closed_or_closing = sessions.filtered(lambda s: s.state in ('closing_control', 'closed'))
|
||||
if closed_or_closing:
|
||||
# Find open sessions for those configs
|
||||
open_sessions = self.env['pos.session'].search([
|
||||
('state', '=', 'opened'),
|
||||
('config_id', 'in', closed_or_closing.mapped('config_id').ids)
|
||||
])
|
||||
session_ids.update(open_sessions.ids)
|
||||
|
||||
# De-duplicate, filter to existing, sort to prevent deadlocks, and lock using FOR UPDATE
|
||||
resolved_session_ids = sorted(list(session_ids))
|
||||
if resolved_session_ids:
|
||||
self.env.cr.execute("SELECT id FROM pos_session WHERE id IN %s FOR UPDATE", (tuple(resolved_session_ids),))
|
||||
|
||||
# Sort orders by uuid to prevent potential deadlocks when locking multiple orders
|
||||
sorted_orders = sorted(orders, key=lambda x: x.get('uuid') or '')
|
||||
for order in sorted_orders:
|
||||
|
||||
53
models/pos_payment.py
Normal file
53
models/pos_payment.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, api
|
||||
|
||||
class PosPayment(models.Model):
|
||||
_inherit = 'pos.payment'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# 1. Extract and sort unique uuids to prevent deadlocks
|
||||
uuids = []
|
||||
for vals in vals_list:
|
||||
if isinstance(vals, dict) and vals.get('uuid'):
|
||||
uuids.append(vals['uuid'])
|
||||
|
||||
sorted_uuids = sorted(list(set(uuids)))
|
||||
|
||||
# 2. Acquire transaction-level PostgreSQL advisory locks on the sorted hashes of uuids
|
||||
for uuid in sorted_uuids:
|
||||
self.env.cr.execute("SELECT pg_advisory_xact_lock(hashtext(%s))", (uuid,))
|
||||
|
||||
# 3. Check which payments already exist in the database
|
||||
existing_payments_map = {}
|
||||
if sorted_uuids:
|
||||
existing_payments = self.search([('uuid', 'in', sorted_uuids)])
|
||||
for payment in existing_payments:
|
||||
existing_payments_map[payment.uuid] = payment
|
||||
|
||||
# 4. Prepare values to create (only those that don't already exist)
|
||||
final_payments_list = [None] * len(vals_list)
|
||||
vals_to_create = []
|
||||
create_indices = []
|
||||
|
||||
for i, vals in enumerate(vals_list):
|
||||
uuid = vals.get('uuid') if isinstance(vals, dict) else None
|
||||
if uuid and uuid in existing_payments_map:
|
||||
final_payments_list[i] = existing_payments_map[uuid]
|
||||
else:
|
||||
vals_to_create.append(vals)
|
||||
create_indices.append(i)
|
||||
|
||||
# 5. Create new payments
|
||||
if vals_to_create:
|
||||
created_payments = super(PosPayment, self).create(vals_to_create)
|
||||
for idx, payment in zip(create_indices, created_payments):
|
||||
final_payments_list[idx] = payment
|
||||
|
||||
# 6. Reconstruct and return the final combined recordset
|
||||
final_payments = self.env['pos.payment']
|
||||
for payment in final_payments_list:
|
||||
if payment:
|
||||
final_payments += payment
|
||||
|
||||
return final_payments
|
||||
Loading…
Reference in New Issue
Block a user