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': '',
'category': 'Sales/Point of Sale',
'version': '19.0.1.0.0',
'depends': ['pos_enterprise'],
'depends': ['pos_enterprise', 'custom_preparation_display'],
'data': [],
'assets': {},
'installable': True,

View File

@ -17,16 +17,26 @@ Problem:
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.
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:
When a new pos.prep.order is created for an existing pos.order (meaning new items
are being added to a table that already sent items to the kitchen), we find all
existing prep states for all PREVIOUS prep orders of the same pos.order that are
currently at the LAST stage (regardless of todo status). These represent items
the kitchen staff already processed. We set their todo=False to ensure the filter
in _get_open_orderlines_in_display() correctly excludes them from future reloads.
1. Capture existing prep orders BEFORE super() creates a new one.
2. After all supers() have run (including custom_preparation_display's combo sync),
do a FINAL enforcement pass that forces all old last-stage states to todo=False.
This runs last in the call chain so no subsequent override can undo it.
3. Only target states from genuinely OLD prep orders (pre-existing before this call).
"""
import logging
from odoo import models
_logger = logging.getLogger(__name__)
class PosOrder(models.Model):
_inherit = 'pos.order'
@ -35,51 +45,78 @@ class PosOrder(models.Model):
"""
Override to fix KDS stage reset bug.
Before calling super(), we record existing prep orders for this pos.order.
After super() runs (which may create a new prep order for new items),
we mark all states in the OLD prep orders that are at the last stage
as todo=False so they won't reappear on the KDS display.
We capture existing prep orders BEFORE super() (which may include
custom_preparation_display's combo sync and pos_enterprise's core logic).
After the entire super() chain returns, we do a final enforcement pass
to ensure all old last-stage states remain todo=False regardless of
what any intermediate override may have set them to.
"""
self.ensure_one()
# Capture existing prep orders BEFORE super() creates a new one
existing_prep_orders = self.env['pos.prep.order'].search([
# Step 1: Capture existing prep orders BEFORE super() creates a new one.
# 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)
])
]).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)
# If new items were added (flag_order_added), super() created a new prep order
if result.get('order_added') and existing_prep_orders:
# Fetch all prep displays relevant to this order
prep_displays = self.env['pos.prep.display'].search([
'|',
('pos_config_ids', '=', False),
('pos_config_ids', 'in', self.config_id.id),
# Step 3: Only act if new items were added (a new prep order was created).
if not result.get('order_added') or not existing_prep_order_ids:
return result
# Step 4: Fetch all prep displays relevant to this order's POS config.
prep_displays = self.env['pos.prep.display'].search([
'|',
('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:
last_stage_id = (
prep_display.stage_ids.ids[-1]
if prep_display.stage_ids.ids else False
# Separate the ones that still need updating to avoid unnecessary writes.
states_needing_fix = states_to_seal.filtered(lambda s: s.todo)
if states_needing_fix:
_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:
continue
# Find all states from the OLD prep orders that are at the last stage
# 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})
# Use direct ORM write — no websocket notification is needed here.
# The frontend will get a LOAD_ORDERS notification from process_order()
# anyway, and get_preparation_display_order() will re-read from DB.
states_needing_fix.write({'todo': False})
return result