refactor: offload KDS report updates to background thread and simplify pivot view fields
This commit is contained in:
parent
81f12f331c
commit
c6aa68fe39
@ -6,24 +6,24 @@ class PosKdsReportLine(models.Model):
|
|||||||
_description = 'KDS Line Completion Report'
|
_description = 'KDS Line Completion Report'
|
||||||
_order = 'completion_datetime desc'
|
_order = 'completion_datetime desc'
|
||||||
|
|
||||||
pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade')
|
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')
|
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)
|
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)
|
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)
|
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)
|
prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True, index=True)
|
||||||
|
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('in_prep', 'In Preparation'),
|
('in_prep', 'In Preparation'),
|
||||||
('ready', 'Ready'),
|
('ready', 'Ready'),
|
||||||
('done', 'Completed'),
|
('done', 'Completed'),
|
||||||
('cancelled', 'Cancelled')
|
('cancelled', 'Cancelled')
|
||||||
], string='Status', default='done')
|
], string='Status', default='done', index=True)
|
||||||
|
|
||||||
preparation_time = fields.Integer('Preparation Time (s)', help="Seconds taken to prepare")
|
preparation_time = fields.Integer('Preparation Time (s)', help="Seconds taken to prepare")
|
||||||
service_time = fields.Integer('Service Time (s)', help="Seconds taken to serve")
|
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_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')
|
@api.depends('product_id')
|
||||||
def _compute_pos_category(self):
|
def _compute_pos_category(self):
|
||||||
@ -46,21 +46,21 @@ class PosKdsReportOrder(models.Model):
|
|||||||
_description = 'KDS Order Completion Report'
|
_description = 'KDS Order Completion Report'
|
||||||
_order = 'completion_datetime desc'
|
_order = 'completion_datetime desc'
|
||||||
|
|
||||||
pos_order_id = fields.Many2one('pos.order', string='Order', required=True, ondelete='cascade')
|
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)
|
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)
|
prep_display_id = fields.Many2one('pos.prep.display', string='Preparation Display', required=True, index=True)
|
||||||
|
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('in_prep', 'In Preparation'),
|
('in_prep', 'In Preparation'),
|
||||||
('ready', 'Ready'),
|
('ready', 'Ready'),
|
||||||
('done', 'Completed'),
|
('done', 'Completed'),
|
||||||
('cancelled', 'Cancelled')
|
('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")
|
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")
|
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_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')
|
@api.depends('pos_order_id')
|
||||||
def _compute_pos_config(self):
|
def _compute_pos_config(self):
|
||||||
|
|||||||
@ -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):
|
def _threaded_report_update(registry, uid, context, pdis_state_id, is_reset, is_completed):
|
||||||
_inherit = 'pos.prep.state'
|
"""Background worker to update KDS reports without blocking the POS transaction."""
|
||||||
|
try:
|
||||||
def _update_kds_report(self, pdis_state):
|
with registry.cursor() as new_cr:
|
||||||
if not pdis_state.prep_line_id.pos_order_line_id:
|
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
|
return
|
||||||
|
|
||||||
order_line = pdis_state.prep_line_id.pos_order_line_id
|
order_line = pdis_state.prep_line_id.pos_order_line_id
|
||||||
prep_display = pdis_state.stage_id.prep_display_id
|
stage = pdis_state.stage_id
|
||||||
|
display_id = stage.prep_display_id.id
|
||||||
|
|
||||||
is_completed = False
|
KdsLineReport = new_env['pos.kds.report.line'].sudo()
|
||||||
if len(pdis_state.stage_id.prep_display_id.stage_ids) > 1:
|
KdsOrderReport = new_env['pos.kds.report.order'].sudo()
|
||||||
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
|
|
||||||
|
|
||||||
is_reset = False
|
if is_reset:
|
||||||
if pdis_state.stage_id.is_stage_position(0) and pdis_state.todo:
|
line_report = KdsLineReport.search([
|
||||||
is_reset = True
|
|
||||||
|
|
||||||
line_report = self.env['pos.kds.report.line'].search([
|
|
||||||
('pos_order_line_id', '=', order_line.id),
|
('pos_order_line_id', '=', order_line.id),
|
||||||
('prep_display_id', '=', prep_display.id)
|
('prep_display_id', '=', display_id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
if is_completed:
|
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)
|
prep_time = max(0, order_line.preparation_time)
|
||||||
svc_time = max(0, order_line.service_time)
|
svc_time = max(0, order_line.service_time)
|
||||||
comp_time = prep_time + svc_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_id': order_line.order_id.id,
|
||||||
'pos_order_line_id': order_line.id,
|
'pos_order_line_id': order_line.id,
|
||||||
'product_id': order_line.product_id.id,
|
'product_id': order_line.product_id.id,
|
||||||
'prep_display_id': prep_display.id,
|
'prep_display_id': display_id,
|
||||||
'preparation_time': prep_time,
|
'preparation_time': prep_time,
|
||||||
'service_time': svc_time,
|
'service_time': svc_time,
|
||||||
'completion_time': comp_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:
|
if line_report:
|
||||||
line_report.write(vals)
|
line_report.write(vals)
|
||||||
else:
|
else:
|
||||||
self.env['pos.kds.report.line'].create(vals)
|
KdsLineReport.create(vals)
|
||||||
|
|
||||||
# Update order-level report
|
# 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),
|
('pos_order_id', '=', order_line.order_id.id),
|
||||||
('prep_display_id', '=', prep_display.id)
|
('prep_display_id', '=', display_id)
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
# Re-calculate max completion time for the order on this KDS
|
res = KdsLineReport._read_group(
|
||||||
all_line_reports = self.env['pos.kds.report.line'].search([
|
[('pos_order_id', '=', order_line.order_id.id), ('prep_display_id', '=', display_id)],
|
||||||
('pos_order_id', '=', order_line.order_id.id),
|
aggregates=['completion_time:max']
|
||||||
('prep_display_id', '=', prep_display.id)
|
)
|
||||||
])
|
max_comp_time = res[0][0] if res else comp_time
|
||||||
max_comp_time = max(all_line_reports.mapped('completion_time')) if all_line_reports else comp_time
|
|
||||||
|
|
||||||
order_vals = {
|
order_vals = {
|
||||||
'pos_order_id': order_line.order_id.id,
|
'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_time': max_comp_time,
|
||||||
|
'completion_datetime': fields.Datetime.now(),
|
||||||
}
|
}
|
||||||
if order_report:
|
if order_report:
|
||||||
order_report.write(order_vals)
|
order_report.write(order_vals)
|
||||||
else:
|
else:
|
||||||
self.env['pos.kds.report.order'].create(order_vals)
|
KdsOrderReport.create(order_vals)
|
||||||
|
|
||||||
elif is_reset:
|
new_cr.commit()
|
||||||
if line_report:
|
except Exception as e:
|
||||||
line_report.unlink()
|
_logger.error("Background KDS reporting failed: %s", e)
|
||||||
|
|
||||||
# We don't necessarily delete the order record since other lines might still be complete.
|
class PosPreparationState(models.Model):
|
||||||
# But we could re-calculate the max if needed. Let's re-eval.
|
_inherit = 'pos.prep.state'
|
||||||
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)
|
|
||||||
|
|
||||||
if order_report:
|
def _update_kds_report(self, pdis_state):
|
||||||
if not all_line_reports:
|
if not pdis_state.prep_line_id or not pdis_state.prep_line_id.pos_order_line_id:
|
||||||
order_report.unlink()
|
return
|
||||||
else:
|
|
||||||
max_comp_time = max(all_line_reports.mapped('completion_time'))
|
stage = pdis_state.stage_id
|
||||||
order_report.write({'completion_time': max_comp_time})
|
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 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):
|
def _record_status_change_prep_time(self, pdis_state):
|
||||||
super()._record_status_change_prep_time(pdis_state)
|
super()._record_status_change_prep_time(pdis_state)
|
||||||
|
|||||||
@ -11,9 +11,7 @@
|
|||||||
<field name="pos_category_id" type="row"/>
|
<field name="pos_category_id" type="row"/>
|
||||||
<field name="product_id" type="row"/>
|
<field name="product_id" type="row"/>
|
||||||
<field name="state" type="col"/>
|
<field name="state" type="col"/>
|
||||||
<field name="completion_time" type="measure" operator="avg"/>
|
|
||||||
<field name="preparation_time" type="measure" operator="avg"/>
|
<field name="preparation_time" type="measure" operator="avg"/>
|
||||||
<field name="service_time" type="measure" operator="avg"/>
|
|
||||||
</pivot>
|
</pivot>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -25,7 +23,7 @@
|
|||||||
<graph string="KDS Product Analysis" type="bar" sample="1">
|
<graph string="KDS Product Analysis" type="bar" sample="1">
|
||||||
<field name="pos_category_id" type="row"/>
|
<field name="pos_category_id" type="row"/>
|
||||||
<field name="product_id" type="row"/>
|
<field name="product_id" type="row"/>
|
||||||
<field name="completion_time" type="measure" operator="avg"/>
|
<field name="preparation_time" type="measure" operator="avg"/>
|
||||||
</graph>
|
</graph>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -78,9 +76,7 @@
|
|||||||
<field name="completion_datetime" type="row" interval="day"/>
|
<field name="completion_datetime" type="row" interval="day"/>
|
||||||
<field name="prep_display_id" type="row"/>
|
<field name="prep_display_id" type="row"/>
|
||||||
<field name="state" type="col"/>
|
<field name="state" type="col"/>
|
||||||
<field name="completion_time" type="measure" operator="avg"/>
|
|
||||||
<field name="preparation_time" type="measure" operator="avg"/>
|
<field name="preparation_time" type="measure" operator="avg"/>
|
||||||
<field name="service_time" type="measure" operator="avg"/>
|
|
||||||
</pivot>
|
</pivot>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -91,7 +87,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<graph string="KDS Order Analysis" type="line" sample="1">
|
<graph string="KDS Order Analysis" type="line" sample="1">
|
||||||
<field name="completion_datetime" type="row" interval="day"/>
|
<field name="completion_datetime" type="row" interval="day"/>
|
||||||
<field name="completion_time" type="measure" operator="avg"/>
|
<field name="preparation_time" type="measure" operator="avg"/>
|
||||||
</graph>
|
</graph>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user