From 4badd35b547b26fcaa68be7bd45d1408c4ef1bd2 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 19 Jun 2026 10:25:24 +0700 Subject: [PATCH] feat: add deadlock protection for prep state updates and implement status transition logic in POS KDS order component --- models/__init__.py | 1 + models/pos_prep_state.py | 22 ++++++++++ .../src/app/components/order/order_patch.js | 44 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 models/pos_prep_state.py diff --git a/models/__init__.py b/models/__init__.py index db45af7..734ac56 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import pos_order from . import pos_prep_display +from . import pos_prep_state diff --git a/models/pos_prep_state.py b/models/pos_prep_state.py new file mode 100644 index 0000000..45814db --- /dev/null +++ b/models/pos_prep_state.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +import logging +from odoo import models, api + +_logger = logging.getLogger(__name__) + +class PosPreparationState(models.Model): + _inherit = 'pos.prep.state' + + def change_state_status(self, todos, prep_display_id): + if self: + # Sort IDs to guarantee consistent locking order and avoid deadlocks + sorted_ids = tuple(sorted(self.ids)) + self.env.cr.execute('SELECT id FROM pos_prep_state WHERE id IN %s FOR UPDATE', [sorted_ids]) + return super().change_state_status(todos, prep_display_id) + + def change_state_stage(self, stages, prep_display_id): + if self: + # Sort IDs to guarantee consistent locking order and avoid deadlocks + sorted_ids = tuple(sorted(self.ids)) + self.env.cr.execute('SELECT id FROM pos_prep_state WHERE id IN %s FOR UPDATE', [sorted_ids]) + return super().change_state_stage(stages, prep_display_id) diff --git a/static/src/app/components/order/order_patch.js b/static/src/app/components/order/order_patch.js index 5029cd6..f020d80 100644 --- a/static/src/app/components/order/order_patch.js +++ b/static/src/app/components/order/order_patch.js @@ -43,6 +43,50 @@ patch(Order.prototype, { return [{ text: String(parsed), colorIndex: 0 }]; } return []; + }, + async changeStateStageAnimation(states) { + if (states.length === this.props.order.states.length) { + await new Promise((resolve, reject) => { + this.state.changeStageTimeout = setTimeout(async () => { + try { + this.lastStageChange = await this.prepDisplay.changeStateStage(states); + this.clearChangeTimeout(); + resolve(); + } catch (err) { + reject(err); + } + }, 250); + }); + } else { + this.lastStageChange = await this.prepDisplay.changeStateStage(states); + } + }, + async changeOrderlineStatus(state) { + if (this.actionInProgress) { + return; + } + try { + this.actionInProgress = true; + const lastStage = this.prepDisplay.lastStage.id; + if (this.props.order.stage.id === lastStage) { + return; + } + const newState = !state.todo; + state.todo = newState; + if (state.prep_line_id.combo_line_ids.length > 0) { + this.getChildPreparationLineStates(state.prep_line_id).forEach((state_line) => { + state_line.todo = newState; + }); + } + + if (this.props.order.states.some((state) => state.todo)) { + await this.prepDisplay.syncStateStatus(this.props.order.states); + } else { + await this.changeStateStageAnimation(this.props.order.states); + } + } finally { + this.actionInProgress = false; + } } });