commit fe5a631a6509fafbfe475fc953b070bff7f2566b Author: Suherdy Yacob Date: Mon Jun 1 14:33:58 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..503d983 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +*.egg + +# Odoo +*.pyc +.DS_Store + +# IDE +.vscode/ +.idea/ +*.swp +*.swo diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..2bb6dca --- /dev/null +++ b/README.rst @@ -0,0 +1,93 @@ +POS KDS Stage Reset Fix +======================= + +**Module:** ``pos_kds_fix`` + +**Version:** 19.0.1.0.0 + +**Author:** Suherdy Yacob + +**Category:** Sales/Point of Sale + +**License:** GPL-3 + +Overview +-------- + +This module fixes a bug in the Odoo 19 Preparation Display (Kitchen Display System) +where previously completed order items are incorrectly reset back to the "To Prepare" +stage when a customer adds a new item to the same table. + +The Bug +------- + +**Scenario:** + +1. Table 1 sends 5 items to the KDS. +2. Kitchen staff advances all 5 items through the stages to "Completed". +3. Customer adds 1 new item to Table 1. +4. The new item correctly appears on the KDS in "To Prepare" stage. +5. **BUG:** The 5 previously completed items reappear on the KDS as "To Prepare". + +**Root Cause:** + +When a new item is added to an existing POS order that was already sent to the +kitchen, Odoo creates a new ``pos.prep.order`` record for the new item. The KDS +frontend then reloads all open prep states for that POS order via the bus +notification ``LOAD_ORDERS``. + +The ``_get_open_orderlines_in_display()`` filter excludes only states where: + +- ``todo = False`` AND ``stage = last_stage`` + +When kitchen staff advances items to the last stage using the stage-advance button, +the state is set to: + +- ``todo = True``, ``stage = last_stage`` + +These states pass the filter and are returned to the KDS frontend, causing the +completed items to reappear. + +The Fix +------- + +This module overrides ``PosOrder._process_preparation_changes()`` to detect when +a new ``pos.prep.order`` is being created for an existing POS order (i.e., new +items added to an already-sent order). When that happens, all states from the +**previous** prep orders that are at the last stage (regardless of ``todo`` status) +are automatically set to ``todo=False``. This ensures the KDS filter correctly +excludes them from subsequent reloads. + +Technical Details +----------------- + +**Overridden Method:** ``pos.order._process_preparation_changes()`` + +**Model:** ``pos.order`` + +**Logic:** + +- Capture existing ``pos.prep.order`` IDs before calling ``super()``. +- After ``super()`` runs, check if ``result['order_added']`` is ``True`` + (indicating new lines were added and a new prep ticket was created). +- For each relevant prep display, find all ``pos.prep.state`` records from the + old prep orders that are at the last stage with ``todo=True``. +- Set those states to ``todo=False`` to hide them from the KDS. + +Dependencies +------------ + +- ``pos_enterprise`` + +Installation +------------ + +1. Copy the ``pos_kds_fix`` folder into your Odoo addons directory. +2. Update the apps list in Odoo settings. +3. Search for "POS KDS Stage Reset Fix" and install it. +4. No additional configuration is required. + +Compatibility +------------- + +- Odoo 19 Enterprise (requires ``pos_enterprise`` module) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..116e2c5 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'POS KDS Stage Reset Fix', + 'summary': 'Fix KDS bug: completed items stage reset when new items are ordered for the same table', + 'description': """ +Fix for Preparation Display (Kitchen Display System) bug: + +When a table already has items in Completed/Done stage on the KDS, and the customer +adds a new item, the previously completed items were incorrectly reset back to +"To Prepare" stage. This module prevents that regression by preserving the stage +and todo status of previously processed prep order lines. + """, + 'author': 'Suherdy Yacob', + 'license': 'GPL-3', + 'website': '', + 'category': 'Sales/Point of Sale', + 'version': '19.0.1.0.0', + 'depends': ['pos_enterprise'], + 'data': [], + 'assets': {}, + 'installable': True, + 'auto_install': False, +} diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..9e9e7ad --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import pos_order diff --git a/models/pos_order.py b/models/pos_order.py new file mode 100644 index 0000000..568593b --- /dev/null +++ b/models/pos_order.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +""" +Fix for KDS (Kitchen Display System) / Preparation Display bug: + +Problem: + When Table X has N items that are already in the "Completed" stage on the KDS, + and the customer adds a new item, Odoo creates a new pos.prep.order for the + same pos.order. The KDS frontend receives a LOAD_ORDERS notification and reloads + all open prep states for that pos.order via get_preparation_display_order(). + + The _get_open_orderlines_in_display() filter excludes ONLY states where: + todo=False AND stage=last_stage + + Items that were advanced to the last stage via the stage-advance button have: + todo=True, stage=last_stage <-- these ARE returned and shown as "To Prepare" + + 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. + +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. +""" +from odoo import models + + +class PosOrder(models.Model): + _inherit = 'pos.order' + + def _process_preparation_changes(self, options): + """ + 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. + """ + self.ensure_one() + + # Capture existing prep orders BEFORE super() creates a new one + existing_prep_orders = self.env['pos.prep.order'].search([ + ('pos_order_id', '=', self.id) + ]) + + # Run the original logic (may create new pos.prep.order + lines + states) + 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), + ]) + + for prep_display in prep_displays: + last_stage_id = ( + prep_display.stage_ids.ids[-1] + if prep_display.stage_ids.ids else False + ) + 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}) + + return result