import threading import logging from odoo import models, fields, api _logger = logging.getLogger(__name__) def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_completed): """Background worker to update KDS reports without blocking the POS transaction.""" try: with registry.cursor() as new_cr: new_env = api.Environment(new_cr, uid, context) pdis_state = new_env['pos.prep.state'].browse(pdis_state_id) if not pdis_state.exists(): return order_line = pdis_state.prep_line_id.pos_order_line_id stage = pdis_state.stage_id display_id = stage.prep_display_id.id KdsLineReport = new_env['pos.kds.report.line'].sudo() KdsOrderReport = new_env['pos.kds.report.order'].sudo() if is_reset: line_report = KdsLineReport.search([ ('pos_order_line_id', '=', order_line.id), ('prep_display_id', '=', display_id) ], limit=1) if line_report: line_report.unlink() order_report = KdsOrderReport.search([ ('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id) ], limit=1) if order_report: res = KdsLineReport._read_group( [('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id)], aggregates=['completion_time:max'] ) max_comp_time = res[0][0] if res else 0 if max_comp_time == 0: order_report.unlink() else: order_report.write({'completion_time': max_comp_time}) new_cr.commit() return # If completed prep_time = max(0, order_line.preparation_time) svc_time = max(0, order_line.service_time) comp_time = prep_time + svc_time vals = { 'pos_order_id': order_line.order_id.id, 'pos_order_line_id': order_line.id, 'product_id': order_line.product_id.id, 'prep_display_id': display_id, 'preparation_time': prep_time, 'service_time': svc_time, 'completion_time': comp_time, 'completion_datetime': fields.Datetime.now(), } line_report = KdsLineReport.search([ ('pos_order_line_id', '=', order_line.id), ('prep_display_id', '=', display_id) ], limit=1) if line_report: line_report.write(vals) else: KdsLineReport.create(vals) # Update order-level report order_report = KdsOrderReport.search([ ('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id) ], limit=1) res = KdsLineReport._read_group( [('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id)], aggregates=['completion_time:max'] ) max_comp_time = res[0][0] if res else comp_time order_vals = { 'pos_order_id': order_line.order_id.id, 'prep_display_id': display_id, 'completion_time': max_comp_time, 'completion_datetime': fields.Datetime.now(), } if order_report: order_report.write(order_vals) else: KdsOrderReport.create(order_vals) new_cr.commit() except Exception as e: _logger.error("Background KDS reporting failed: %s", e) class PosPreparationState(models.Model): _inherit = 'pos.prep.state' def _update_kds_report(self, pdis_state): if not pdis_state.prep_line_id or not pdis_state.prep_line_id.pos_order_line_id: return stage = pdis_state.stage_id if not stage: return # Determine status synchronously to decide if we need a thread if not hasattr(self.env, '_kds_display_cache'): self.env._kds_display_cache = {} display_id = stage.prep_display_id.id if display_id not in self.env._kds_display_cache: prep_display = stage.prep_display_id stage_ids = prep_display.stage_ids.ids self.env._kds_display_cache[display_id] = { 'num_stages': len(stage_ids), 'first_stage_id': stage_ids[0] if stage_ids else False, 'last_stage_id': stage_ids[-1] if stage_ids else False, 'second_last_stage_id': stage_ids[-2] if len(stage_ids) > 1 else False, } display_info = self.env._kds_display_cache[display_id] num_stages = display_info['num_stages'] is_completed = False if num_stages > 1: if stage.id == display_info['last_stage_id']: is_completed = True elif display_info['second_last_stage_id'] and stage.id == display_info['second_last_stage_id'] and not pdis_state.todo: is_completed = True elif num_stages == 1: if not pdis_state.todo: is_completed = True is_reset = False if stage.id == display_info['first_stage_id'] and pdis_state.todo: is_reset = True if not (is_completed or is_reset): return # Start background thread to handle the reporting DB operations # This makes the main POS sync call nearly instant thread = threading.Thread( target=_threaded_report_update, args=(self.env.registry, self.env.uid, self.env.context, pdis_state.id, is_reset, is_completed) ) thread.daemon = True thread.start() def _record_status_change_prep_time(self, pdis_state): super()._record_status_change_prep_time(pdis_state) self._update_kds_report(pdis_state) def _record_stage_change_prep_time(self, pdis_state, old_last_stage_change, prep_order_completion_time): super()._record_stage_change_prep_time(pdis_state, old_last_stage_change, prep_order_completion_time) self._update_kds_report(pdis_state)