# -*- coding: utf-8 -*- from odoo import fields, models, api from odoo.fields import Domain from datetime import datetime import pytz class PosOrder(models.Model): _inherit = 'pos.order' @api.model def search_paid_order_ids(self, config_id, domain, limit, offset): """Limit paid orders list loaded on POS frontend to the current day.""" user_tz_name = self.env.context.get('tz') or self.env.user.tz or 'UTC' try: user_tz = pytz.timezone(user_tz_name) except pytz.UnknownTimeZoneError: user_tz = pytz.UTC today_local = fields.Date.to_date(fields.Date.context_today(self)) today_start_local = datetime.combine(today_local, datetime.min.time()) today_start_tz = user_tz.localize(today_start_local) today_start_utc = today_start_tz.astimezone(pytz.UTC).replace(tzinfo=None) # Restrict domain so only orders from the current day (local timezone start) are returned domain = Domain.AND([domain, [('date_order', '>=', today_start_utc)]]) return super(PosOrder, self).search_paid_order_ids(config_id, domain, limit, offset) @api.model def check_table_has_real_orders(self, table_id, local_order_ids=None): """ Check whether a restaurant table has real (non-empty, non-cancelled) orders on the server, excluding the empty local order IDs known to the calling device. This is a safety guard for the multi-device "Release table" scenario: - Device X creates an order on table T and syncs it to the server. - Device Y opens table T and sees an empty local order (sync lag). - Before allowing Device Y to release (cancel) the empty order, we verify with the server that no real orderlines exist for this table. Args: table_id (int): The restaurant.table ID to check. local_order_ids (list[int]): Server IDs of orders already known to the calling device (typically the empty local order). Returns: dict: - has_real_orders (bool): True if real draft orders with non-zero lines exist. - order_count (int): Number of matching server orders found. """ if local_order_ids is None: local_order_ids = [] # Find draft (non-finalized) orders for this table that have lines domain = [ ('table_id', '=', table_id), ('state', '=', 'draft'), ('lines', '!=', False), ] if local_order_ids: # Exclude the empty order(s) already known to the calling device so we # don't false-positive on an order that this device itself created. domain = Domain.AND([domain, [('id', 'not in', local_order_ids)]]) orders = self.env['pos.order'].search(domain) # Double-check: at least one line with non-zero qty must exist real_orders = orders.filtered(lambda o: any(line.qty != 0 for line in o.lines)) return { 'has_real_orders': bool(real_orders), 'order_count': len(real_orders), } def _process_order(self, order, existing_order): """ Override to automatically concatenate orderline customer notes into the general_customer_note so they are easily visible on the backend. """ if 'lines' in order: line_notes = [] for line in order['lines']: if len(line) >= 3 and isinstance(line[2], dict) and line[2].get('customer_note'): note = line[2]['customer_note'].strip() if note: line_notes.append(note) if line_notes: current_note = order.get('general_customer_note') or "" existing_parts = [p.strip() for p in current_note.split(" / ")] if current_note else [] new_notes = [n for n in line_notes if n not in existing_parts] if new_notes: combined_notes = " / ".join(new_notes) if current_note: order['general_customer_note'] = f"{current_note} / {combined_notes}" else: order['general_customer_note'] = combined_notes return super(PosOrder, self)._process_order(order, existing_order) @api.model def sync_from_ui(self, orders): """ 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. """ # 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: uuid = order.get('uuid') if uuid: # Use PostgreSQL transaction-level advisory lock self.env.cr.execute("SELECT pg_advisory_xact_lock(hashtext(%s))", (uuid,)) return super(PosOrder, self).sync_from_ui(orders)