diff --git a/README.rst b/README.rst index 3612d94..bc54b60 100644 --- a/README.rst +++ b/README.rst @@ -90,13 +90,16 @@ Installation Performance Optimization ------------------------ -This module also optimizes the Preparation Display's rendering and idle CPU usage for low RAM devices: +This module also optimizes the Preparation Display's rendering, backend loading, and idle CPU usage: - Order Pagination: Limits rendering to 16 order cards per page, significantly reducing DOM nodes. - Pagination Controls: Adds Page Navigation buttons at the bottom of the main orders area. - Timer Refresh Rate: Reduces the order age/duration update frequency from 1 second to 15 seconds. +- Backend Query Optimization: Eliminates N+1 query loops when loading the KDS list, reducing loading times from minutes to sub-second. +- Robust Notes Parsing: Gracefully parses and handles custom kitchen notes to prevent blank page crashes. Compatibility ------------- - Odoo 19 Enterprise (requires ``pos_enterprise`` module) + diff --git a/models/__init__.py b/models/__init__.py index 9e9e7ad..db45af7 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- from . import pos_order +from . import pos_prep_display diff --git a/models/pos_prep_display.py b/models/pos_prep_display.py new file mode 100644 index 0000000..4495f27 --- /dev/null +++ b/models/pos_prep_display.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from odoo import models, api, fields + +class PosPrepDisplay(models.Model): + _inherit = 'pos.prep.display' + + @api.depends('stage_ids', 'pos_config_ids', 'category_ids') + def _compute_order_count(self): + for preparation_display in self: + open_pdis_lines = preparation_display._get_open_orderlines_in_display() + progress_orders = open_pdis_lines.filtered( + lambda state: state.stage_id.id != preparation_display.stage_ids[-1].id + ).prep_line_id.prep_order_id + preparation_display.order_count = len(progress_orders) + + completed_order_times = [] + open_prep_order_ids = open_pdis_lines.prep_line_id.prep_order_id.ids + + # Fetch at most the last 100 completed preparation orders to avoid huge loops and timeouts + done_orders = self.env['pos.prep.order'].search( + [('id', 'not in', open_prep_order_ids)], + order='id desc', + limit=100 + ) + + if done_orders: + # Batch query all pos.prep.state for these done orders in a single query + states = self.env['pos.prep.state'].search([ + ('prep_line_id.prep_order_id', 'in', done_orders.ids), + ('stage_id', 'in', preparation_display.stage_ids.ids) + ]) + + # Group states by preparation order id in Python + states_by_order = {} + for state in states: + order_id = state.prep_line_id.prep_order_id.id + if order_id not in states_by_order: + states_by_order[order_id] = [] + states_by_order[order_id].append(state) + + for order in done_orders: + order_lines = states_by_order.get(order.id, []) + if order_lines and order.pos_order_id and order.pos_order_id.create_date: + max_write_date = max(state.write_date for state in order_lines) + completed_order_times.append((max_write_date - order.pos_order_id.create_date).total_seconds()) + + preparation_display.average_time = round(sum(completed_order_times) / len(completed_order_times) / 60) if completed_order_times else 0 diff --git a/static/src/app/components/order/order_patch.js b/static/src/app/components/order/order_patch.js index 1cba417..5029cd6 100644 --- a/static/src/app/components/order/order_patch.js +++ b/static/src/app/components/order/order_patch.js @@ -18,10 +18,31 @@ patch(Order.prototype, { } }, get pdisNotes() { + let parsed; try { - return JSON.parse(this.order.pos_order_id.internal_note || "[]"); + parsed = JSON.parse(this.order.pos_order_id.internal_note || "[]"); } catch (e) { return [{ text: this.order.pos_order_id.internal_note, colorIndex: 0 }]; } + if (Array.isArray(parsed)) { + return parsed.map(item => { + if (typeof item === 'string') { + return { text: item, colorIndex: 0 }; + } else if (item && typeof item === 'object' && item.text) { + return item; + } else { + return { text: JSON.stringify(item), colorIndex: 0 }; + } + }); + } else if (parsed && typeof parsed === 'object') { + if (parsed.text) { + return [parsed]; + } + return [{ text: JSON.stringify(parsed), colorIndex: 0 }]; + } else if (parsed) { + return [{ text: String(parsed), colorIndex: 0 }]; + } + return []; } }); + diff --git a/static/src/app/components/orderline/orderline_patch.js b/static/src/app/components/orderline/orderline_patch.js index 9a77314..9602122 100644 --- a/static/src/app/components/orderline/orderline_patch.js +++ b/static/src/app/components/orderline/orderline_patch.js @@ -5,10 +5,31 @@ import { patch } from "@web/core/utils/patch"; patch(Orderline.prototype, { get internalNotes() { + let parsed; try { - return JSON.parse(this.preparation_line.internal_note || "[]"); + parsed = JSON.parse(this.preparation_line.internal_note || "[]"); } catch (e) { return [{ text: this.preparation_line.internal_note, colorIndex: 0 }]; } + if (Array.isArray(parsed)) { + return parsed.map(item => { + if (typeof item === 'string') { + return { text: item, colorIndex: 0 }; + } else if (item && typeof item === 'object' && item.text) { + return item; + } else { + return { text: JSON.stringify(item), colorIndex: 0 }; + } + }); + } else if (parsed && typeof parsed === 'object') { + if (parsed.text) { + return [parsed]; + } + return [{ text: JSON.stringify(parsed), colorIndex: 0 }]; + } else if (parsed) { + return [{ text: String(parsed), colorIndex: 0 }]; + } + return []; } }); +