From 243ebdb4d5953a739bef7826cf35aa8d2b3dedec Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Thu, 7 May 2026 13:58:24 +0700 Subject: [PATCH] refactor: move KDS report logic into background thread and optimize state lookup --- models/pos_prep_state.py | 103 +++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/models/pos_prep_state.py b/models/pos_prep_state.py index 158056d..5a2549b 100644 --- a/models/pos_prep_state.py +++ b/models/pos_prep_state.py @@ -6,15 +6,16 @@ from odoo import models, fields, api _logger = logging.getLogger(__name__) -def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_completed): +def _threaded_report_update(registry, uid, context, pdis_state_id): """ - Background worker with Retry Logic and Row Locking. + Final Optimized Background Worker. + Moves all logic (including stage lookups) to the background. """ - max_retries = 5 + max_retries = 3 for attempt in range(max_retries): try: - # Staggered start to avoid simultaneous collisions - time.sleep(random.uniform(0.05, 0.2)) + # Staggered start + time.sleep(random.uniform(0.1, 0.3)) with registry.cursor() as new_cr: new_env = api.Environment(new_cr, uid, context) @@ -27,22 +28,48 @@ def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_ return order = order_line.order_id - order_name = order.name or order.pos_reference or "Unknown Order" + order_name = order.name or order.pos_reference or "Order" stage = pdis_state.stage_id - display_id = stage.prep_display_id.id + if not stage: + return + + display = stage.prep_display_id + display_id = display.id order_id = order.id + # Fetch stage info inside the thread + stage_ids = display.stage_ids.ids + if not stage_ids: + return + + first_stage_id = stage_ids[0] + last_stage_id = stage_ids[-1] + second_last_stage_id = stage_ids[-2] if len(stage_ids) > 1 else False + + is_completed = False + if len(stage_ids) > 1: + if stage.id == last_stage_id: + is_completed = True + elif second_last_stage_id and stage.id == second_last_stage_id and not pdis_state.todo: + is_completed = True + else: + if not pdis_state.todo: + is_completed = True + + is_reset = (stage.id == first_stage_id and pdis_state.todo) + + if not (is_completed or is_reset): + return + KdsLineReport = new_env['pos.kds.report.line'].sudo() KdsOrderReport = new_env['pos.kds.report.order'].sudo() - # --- ROW LOCKING --- - # Attempt to lock the existing order report if it exists + # Row Locking new_cr.execute(""" SELECT id FROM pos_kds_report_order WHERE pos_order_id = %s AND prep_display_id = %s FOR UPDATE """, (order_id, display_id)) - # ------------------- if is_reset: line_report = KdsLineReport.search([ @@ -68,7 +95,6 @@ def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_ else: order_report.write({'completion_time': max_comp_time}) new_cr.commit() - _logger.info("POS_KDS_PERF: Reset report SUCCESS for %s", order_name) return # If completed @@ -97,7 +123,6 @@ def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_ else: KdsLineReport.create(vals) - # Update order-level report order_report = KdsOrderReport.search([ ('pos_order_id', '=', order_id), ('prep_display_id', '=', display_id) @@ -122,74 +147,36 @@ def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_ new_cr.commit() _logger.info("POS_KDS_PERF: Background report update SUCCESS for order %s", order_name) - return # Exit loop on success + return except Exception as e: if "could not serialize access" in str(e) or "concurrent update" in str(e): - _logger.warning("POS_KDS_PERF: Concurrent update detected, retrying (%s/%s)...", attempt + 1, max_retries) continue else: _logger.error("POS_KDS_PERF: Background KDS reporting failed: %s", e) break else: - _logger.error("POS_KDS_PERF: Failed to update report after %s retries", max_retries) + _logger.error("POS_KDS_PERF: Failed to update report after retries") 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: + def _update_kds_report_async(self, pdis_state): + """Ultra-fast hook that only starts the thread.""" + if not pdis_state.prep_line_id: return - stage = pdis_state.stage_id - if not stage: - return - - 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 - thread = threading.Thread( target=_threaded_report_update, - args=(self.env.registry, self.env.uid, self.env.context, pdis_state.id, is_reset, is_completed) + args=(self.env.registry, self.env.uid, self.env.context, pdis_state.id) ) 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) + self._update_kds_report_async(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) + self._update_kds_report_async(pdis_state)