fix: resolve KDS stage reset bug by adding dependency and enforcing state closure after downstream overrides.

This commit is contained in:
Suherdy Yacob 2026-06-01 22:00:18 +07:00
parent fe5a631a65
commit 4df8713787
2 changed files with 80 additions and 43 deletions

View File

@ -15,7 +15,7 @@ and todo status of previously processed prep order lines.
'website': '', 'website': '',
'category': 'Sales/Point of Sale', 'category': 'Sales/Point of Sale',
'version': '19.0.1.0.0', 'version': '19.0.1.0.0',
'depends': ['pos_enterprise'], 'depends': ['pos_enterprise', 'custom_preparation_display'],
'data': [], 'data': [],
'assets': {}, 'assets': {},
'installable': True, 'installable': True,

View File

@ -17,16 +17,26 @@ Problem:
So the previously completed items (stage-advanced to last stage, todo=True) reappear So the previously completed items (stage-advanced to last stage, todo=True) reappear
on the KDS card alongside the new item, effectively resetting their visual status. on the KDS card alongside the new item, effectively resetting their visual status.
Additional complication:
The custom_preparation_display module ALSO overrides _process_preparation_changes
and runs AFTER pos_kds_fix in the MRO (since it calls super() which reaches here).
After super() returns, custom_preparation_display re-searches ALL prep orders
(including old ones) and syncs parent combo states. This can revert the todo=False
we set on old last-stage states back to todo=True, defeating the fix.
Fix: Fix:
When a new pos.prep.order is created for an existing pos.order (meaning new items 1. Capture existing prep orders BEFORE super() creates a new one.
are being added to a table that already sent items to the kitchen), we find all 2. After all supers() have run (including custom_preparation_display's combo sync),
existing prep states for all PREVIOUS prep orders of the same pos.order that are do a FINAL enforcement pass that forces all old last-stage states to todo=False.
currently at the LAST stage (regardless of todo status). These represent items This runs last in the call chain so no subsequent override can undo it.
the kitchen staff already processed. We set their todo=False to ensure the filter 3. Only target states from genuinely OLD prep orders (pre-existing before this call).
in _get_open_orderlines_in_display() correctly excludes them from future reloads.
""" """
import logging
from odoo import models from odoo import models
_logger = logging.getLogger(__name__)
class PosOrder(models.Model): class PosOrder(models.Model):
_inherit = 'pos.order' _inherit = 'pos.order'
@ -35,51 +45,78 @@ class PosOrder(models.Model):
""" """
Override to fix KDS stage reset bug. Override to fix KDS stage reset bug.
Before calling super(), we record existing prep orders for this pos.order. We capture existing prep orders BEFORE super() (which may include
After super() runs (which may create a new prep order for new items), custom_preparation_display's combo sync and pos_enterprise's core logic).
we mark all states in the OLD prep orders that are at the last stage After the entire super() chain returns, we do a final enforcement pass
as todo=False so they won't reappear on the KDS display. to ensure all old last-stage states remain todo=False regardless of
what any intermediate override may have set them to.
""" """
self.ensure_one() self.ensure_one()
# Capture existing prep orders BEFORE super() creates a new one # Step 1: Capture existing prep orders BEFORE super() creates a new one.
existing_prep_orders = self.env['pos.prep.order'].search([ # These are the "old" prep orders whose states should not be reset.
existing_prep_order_ids = self.env['pos.prep.order'].search([
('pos_order_id', '=', self.id) ('pos_order_id', '=', self.id)
]) ]).ids
# Run the original logic (may create new pos.prep.order + lines + states) # Step 2: Run the full super() chain.
# This includes:
# - custom_preparation_display._process_preparation_changes (combo sync)
# - pos_enterprise._process_preparation_changes (new prep order creation)
result = super()._process_preparation_changes(options) result = super()._process_preparation_changes(options)
# If new items were added (flag_order_added), super() created a new prep order # Step 3: Only act if new items were added (a new prep order was created).
if result.get('order_added') and existing_prep_orders: if not result.get('order_added') or not existing_prep_order_ids:
# Fetch all prep displays relevant to this order return result
prep_displays = self.env['pos.prep.display'].search([
'|', # Step 4: Fetch all prep displays relevant to this order's POS config.
('pos_config_ids', '=', False), prep_displays = self.env['pos.prep.display'].search([
('pos_config_ids', 'in', self.config_id.id), '|',
('pos_config_ids', '=', False),
('pos_config_ids', 'in', self.config_id.id),
])
for prep_display in prep_displays:
stage_ids = prep_display.stage_ids.ids
if not stage_ids:
continue
last_stage_id = stage_ids[-1]
# Step 5: Find ALL states from OLD prep orders that are at the last stage.
# This covers both:
# (a) States with todo=True — kitchen staff used stage-advance button
# (b) States with todo=False — already correctly excluded by the filter
# but may have been re-set to True by custom_preparation_display's
# combo-parent sync logic.
# We unconditionally force them all to todo=False so the filter
# in _get_open_orderlines_in_display() correctly excludes them.
old_prep_lines = self.env['pos.prep.line'].search([
('prep_order_id', 'in', existing_prep_order_ids),
])
if not old_prep_lines:
continue
states_to_seal = self.env['pos.prep.state'].search([
('prep_line_id', 'in', old_prep_lines.ids),
('stage_id', '=', last_stage_id),
# Include both todo=True AND todo=False — if custom_preparation_display
# reverted a False back to True, we need to catch it.
# Using a simple search with no todo filter is intentional.
]) ])
for prep_display in prep_displays: # Separate the ones that still need updating to avoid unnecessary writes.
last_stage_id = ( states_needing_fix = states_to_seal.filtered(lambda s: s.todo)
prep_display.stage_ids.ids[-1] if states_needing_fix:
if prep_display.stage_ids.ids else False _logger.info(
"pos_kds_fix: Sealing %d old last-stage states as todo=False "
"for pos.order %s (prep orders: %s)",
len(states_needing_fix),
self.name or self.id,
existing_prep_order_ids,
) )
if not last_stage_id: # Use direct ORM write — no websocket notification is needed here.
continue # The frontend will get a LOAD_ORDERS notification from process_order()
# anyway, and get_preparation_display_order() will re-read from DB.
# Find all states from the OLD prep orders that are at the last stage states_needing_fix.write({'todo': False})
# with todo=True — these are items the kitchen advanced to "Completed"
# stage but which would still be returned by _get_open_orderlines_in_display
# because the filter only excludes (todo=False AND stage=last).
old_prep_lines = existing_prep_orders.mapped('prep_line_ids')
states_at_last_stage = self.env['pos.prep.state'].search([
('prep_line_id', 'in', old_prep_lines.ids),
('stage_id', '=', last_stage_id),
('todo', '=', True),
])
if states_at_last_stage:
# Mark them as done so the KDS filter hides them
states_at_last_stage.write({'todo': False})
return result return result