diff --git a/models/pos_kds_report.py b/models/pos_kds_report.py
index 82c8060..0181a2d 100644
--- a/models/pos_kds_report.py
+++ b/models/pos_kds_report.py
@@ -6,24 +6,24 @@ class PosKdsReportLine(models.Model):
_description = 'KDS Line Completion Report'
_order = 'completion_datetime desc'
- pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade')
- pos_order_line_id = fields.Many2one('pos.order.line', string='Order Line', required=True, ondelete='cascade')
- product_id = fields.Many2one('product.product', string='Product', required=True)
- pos_category_id = fields.Many2one('pos.category', string='POS Category', compute='_compute_pos_category', store=True)
- pos_config_id = fields.Many2one('pos.config', string='POS Shop', compute='_compute_pos_config', store=True)
- prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True)
+ pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade', index=True)
+ pos_order_line_id = fields.Many2one('pos.order.line', string='Order Line', required=True, ondelete='cascade', index=True)
+ product_id = fields.Many2one('product.product', string='Product', required=True, index=True)
+ pos_category_id = fields.Many2one('pos.category', string='POS Category', compute='_compute_pos_category', store=True, index=True)
+ pos_config_id = fields.Many2one('pos.config', string='POS Shop', compute='_compute_pos_config', store=True, index=True)
+ prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True, index=True)
state = fields.Selection([
('in_prep', 'In Preparation'),
('ready', 'Ready'),
('done', 'Completed'),
('cancelled', 'Cancelled')
- ], string='Status', default='done')
+ ], string='Status', default='done', index=True)
preparation_time = fields.Integer('Preparation Time (s)', help="Seconds taken to prepare")
service_time = fields.Integer('Service Time (s)', help="Seconds taken to serve")
completion_time = fields.Integer('Completion Time (s)', help="Total seconds taken to complete (prep + service)")
- completion_datetime = fields.Datetime('Completion Date', default=fields.Datetime.now)
+ completion_datetime = fields.Datetime('Completion Date', default=fields.Datetime.now, index=True)
@api.depends('product_id')
def _compute_pos_category(self):
@@ -46,21 +46,21 @@ class PosKdsReportOrder(models.Model):
_description = 'KDS Order Completion Report'
_order = 'completion_datetime desc'
- pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade')
- pos_config_id = fields.Many2one('pos.config', string='POS Shop', compute='_compute_pos_config', store=True)
- prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True)
+ pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade', index=True)
+ pos_config_id = fields.Many2one('pos.config', string='POS Shop', compute='_compute_pos_config', store=True, index=True)
+ prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True, index=True)
state = fields.Selection([
('in_prep', 'In Preparation'),
('ready', 'Ready'),
('done', 'Completed'),
('cancelled', 'Cancelled')
- ], string='Status', default='done')
+ ], string='Status', default='done', index=True)
preparation_time = fields.Integer('Preparation Time (s)', help="Max preparation time across lines")
service_time = fields.Integer('Service Time (s)', help="Max service time across lines")
completion_time = fields.Integer('Completion Time (s)', help="Max completion time across all lines of the order on this display")
- completion_datetime = fields.Datetime('Completion Date', default=fields.Datetime.now)
+ completion_datetime = fields.Datetime('Completion Date', default=fields.Datetime.now, index=True)
@api.depends('pos_order_id')
def _compute_pos_config(self):
diff --git a/models/pos_prep_state.py b/models/pos_prep_state.py
index 9e2e5b3..916dfd8 100644
--- a/models/pos_prep_state.py
+++ b/models/pos_prep_state.py
@@ -1,36 +1,53 @@
-from odoo import models
+import threading
+import logging
+from odoo import models, fields, api
+_logger = logging.getLogger(__name__)
-class PosPreparationState(models.Model):
- _inherit = 'pos.prep.state'
-
- def _update_kds_report(self, pdis_state):
- if not pdis_state.prep_line_id.pos_order_line_id:
- return
-
- order_line = pdis_state.prep_line_id.pos_order_line_id
- prep_display = pdis_state.stage_id.prep_display_id
-
- is_completed = False
- if len(pdis_state.stage_id.prep_display_id.stage_ids) > 1:
- if pdis_state.stage_id.is_stage_position(-1):
- is_completed = True
- elif pdis_state.stage_id.is_stage_position(-2) and not pdis_state.todo:
- is_completed = True
- else:
- if pdis_state.stage_id.is_stage_position(0) and not pdis_state.todo:
- is_completed = True
+def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_completed):
+ """Background worker to update KDS reports without blocking the POS transaction."""
+ try:
+ with registry.cursor() as new_cr:
+ new_env = api.Environment(new_cr, uid, context)
+ pdis_state = new_env['pos.prep.state'].browse(pdis_state_id)
+ if not pdis_state.exists():
+ return
- is_reset = False
- if pdis_state.stage_id.is_stage_position(0) and pdis_state.todo:
- is_reset = True
+ order_line = pdis_state.prep_line_id.pos_order_line_id
+ stage = pdis_state.stage_id
+ display_id = stage.prep_display_id.id
- line_report = self.env['pos.kds.report.line'].search([
- ('pos_order_line_id', '=', order_line.id),
- ('prep_display_id', '=', prep_display.id)
- ], limit=1)
-
- if is_completed:
+ KdsLineReport = new_env['pos.kds.report.line'].sudo()
+ KdsOrderReport = new_env['pos.kds.report.order'].sudo()
+
+ if is_reset:
+ line_report = KdsLineReport.search([
+ ('pos_order_line_id', '=', order_line.id),
+ ('prep_display_id', '=', display_id)
+ ], limit=1)
+
+ if line_report:
+ line_report.unlink()
+
+ order_report = KdsOrderReport.search([
+ ('pos_order_id', '=', order_line.order_id.id),
+ ('prep_display_id', '=', display_id)
+ ], limit=1)
+
+ if order_report:
+ res = KdsLineReport._read_group(
+ [('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id)],
+ aggregates=['completion_time:max']
+ )
+ max_comp_time = res[0][0] if res else 0
+ if max_comp_time == 0:
+ order_report.unlink()
+ else:
+ order_report.write({'completion_time': max_comp_time})
+ new_cr.commit()
+ return
+
+ # If completed
prep_time = max(0, order_line.preparation_time)
svc_time = max(0, order_line.service_time)
comp_time = prep_time + svc_time
@@ -39,60 +56,104 @@ class PosPreparationState(models.Model):
'pos_order_id': order_line.order_id.id,
'pos_order_line_id': order_line.id,
'product_id': order_line.product_id.id,
- 'prep_display_id': prep_display.id,
+ 'prep_display_id': display_id,
'preparation_time': prep_time,
'service_time': svc_time,
'completion_time': comp_time,
+ 'completion_datetime': fields.Datetime.now(),
}
+
+ line_report = KdsLineReport.search([
+ ('pos_order_line_id', '=', order_line.id),
+ ('prep_display_id', '=', display_id)
+ ], limit=1)
+
if line_report:
line_report.write(vals)
else:
- self.env['pos.kds.report.line'].create(vals)
+ KdsLineReport.create(vals)
# Update order-level report
- order_report = self.env['pos.kds.report.order'].search([
+ order_report = KdsOrderReport.search([
('pos_order_id', '=', order_line.order_id.id),
- ('prep_display_id', '=', prep_display.id)
+ ('prep_display_id', '=', display_id)
], limit=1)
- # Re-calculate max completion time for the order on this KDS
- all_line_reports = self.env['pos.kds.report.line'].search([
- ('pos_order_id', '=', order_line.order_id.id),
- ('prep_display_id', '=', prep_display.id)
- ])
- max_comp_time = max(all_line_reports.mapped('completion_time')) if all_line_reports else comp_time
+ res = KdsLineReport._read_group(
+ [('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id)],
+ aggregates=['completion_time:max']
+ )
+ max_comp_time = res[0][0] if res else comp_time
order_vals = {
'pos_order_id': order_line.order_id.id,
- 'prep_display_id': prep_display.id,
+ 'prep_display_id': display_id,
'completion_time': max_comp_time,
+ 'completion_datetime': fields.Datetime.now(),
}
if order_report:
order_report.write(order_vals)
else:
- self.env['pos.kds.report.order'].create(order_vals)
+ KdsOrderReport.create(order_vals)
- elif is_reset:
- if line_report:
- line_report.unlink()
+ new_cr.commit()
+ except Exception as e:
+ _logger.error("Background KDS reporting failed: %s", e)
+
+class PosPreparationState(models.Model):
+ _inherit = 'pos.prep.state'
+
+ def _update_kds_report(self, pdis_state):
+ if not pdis_state.prep_line_id or not pdis_state.prep_line_id.pos_order_line_id:
+ return
- # We don't necessarily delete the order record since other lines might still be complete.
- # But we could re-calculate the max if needed. Let's re-eval.
- all_line_reports = self.env['pos.kds.report.line'].search([
- ('pos_order_id', '=', order_line.order_id.id),
- ('prep_display_id', '=', prep_display.id)
- ])
- order_report = self.env['pos.kds.report.order'].search([
- ('pos_order_id', '=', order_line.order_id.id),
- ('prep_display_id', '=', prep_display.id)
- ], limit=1)
+ stage = pdis_state.stage_id
+ if not stage:
+ return
+
+ # Determine status synchronously to decide if we need a thread
+ if not hasattr(self.env, '_kds_display_cache'):
+ self.env._kds_display_cache = {}
+
+ display_id = stage.prep_display_id.id
+ if display_id not in self.env._kds_display_cache:
+ prep_display = stage.prep_display_id
+ stage_ids = prep_display.stage_ids.ids
+ self.env._kds_display_cache[display_id] = {
+ 'num_stages': len(stage_ids),
+ 'first_stage_id': stage_ids[0] if stage_ids else False,
+ 'last_stage_id': stage_ids[-1] if stage_ids else False,
+ 'second_last_stage_id': stage_ids[-2] if len(stage_ids) > 1 else False,
+ }
+
+ display_info = self.env._kds_display_cache[display_id]
+ num_stages = display_info['num_stages']
+
+ is_completed = False
+ if num_stages > 1:
+ if stage.id == display_info['last_stage_id']:
+ is_completed = True
+ elif display_info['second_last_stage_id'] and stage.id == display_info['second_last_stage_id'] and not pdis_state.todo:
+ is_completed = True
+ elif num_stages == 1:
+ if not pdis_state.todo:
+ is_completed = True
+
+ is_reset = False
+ if stage.id == display_info['first_stage_id'] and pdis_state.todo:
+ is_reset = True
- if order_report:
- if not all_line_reports:
- order_report.unlink()
- else:
- max_comp_time = max(all_line_reports.mapped('completion_time'))
- order_report.write({'completion_time': max_comp_time})
+ if not (is_completed or is_reset):
+ return
+
+ # Start background thread to handle the reporting DB operations
+ # This makes the main POS sync call nearly instant
+ thread = threading.Thread(
+ target=_threaded_report_update,
+ args=(self.env.registry, self.env.uid, self.env.context, pdis_state.id, is_reset, is_completed)
+ )
+ thread.daemon = True
+ thread.start()
def _record_status_change_prep_time(self, pdis_state):
super()._record_status_change_prep_time(pdis_state)
diff --git a/views/pos_kds_report_views.xml b/views/pos_kds_report_views.xml
index ca1f53e..e442b52 100644
--- a/views/pos_kds_report_views.xml
+++ b/views/pos_kds_report_views.xml
@@ -11,9 +11,7 @@
-
-
@@ -25,7 +23,7 @@
-
+
@@ -78,9 +76,7 @@
-
-
@@ -91,7 +87,7 @@
-
+