from odoo import models, fields, api, _ import logging _logger = logging.getLogger(__name__) class PosOrderLine(models.Model): _inherit = 'pos.order.line' preparation_status = fields.Selection([ ('waiting', 'Waiting'), ('preparing', 'Preparing'), ('ready', 'Ready'), ('served', 'Served'), ('cancelled', 'Cancelled') ], string='Preparation Status', default='waiting', tracking=True, group_expand='_read_group_preparation_status') @api.model def _read_group_preparation_status(self, stages, domain, order): return ['waiting', 'preparing', 'ready', 'served'] color = fields.Integer(string='Color', default=0) preparation_time_start = fields.Datetime(string='Start Time') preparation_time_end = fields.Datetime(string='Ready Time') cooking_time = fields.Integer(string='Cooking Time (min)', compute='_compute_cooking_time', store=True) table_id = fields.Many2one('restaurant.table', related='order_id.table_id', string='Table', store=True) floor_id = fields.Many2one('restaurant.floor', related='order_id.table_id.floor_id', string='Floor', store=True) @api.depends('preparation_time_start', 'preparation_time_end') def _compute_cooking_time(self): for line in self: if line.preparation_time_start and line.preparation_time_end: diff = line.preparation_time_end - line.preparation_time_start line.cooking_time = int(diff.total_seconds() / 60) else: line.cooking_time = 0 def _notify_pos(self): """Send notification to POS when order line status changes""" _logger.info("=== _notify_pos called for %s lines ===" % len(self)) for line in self: _logger.info(f"Processing line {line.id}, order: {line.order_id.name}, config: {line.order_id.config_id}") if line.order_id.config_id: channel_name = "pos_config_Channel_%s" % line.order_id.config_id.id payload = { 'line_id': line.id, 'order_id': line.order_id.id, 'status': line.preparation_status, 'status_label': dict(self._fields['preparation_status']._description_selection(self.env)).get(line.preparation_status), 'order_uid': line.order_id.pos_reference, 'product_id': line.product_id.id, 'qty': line.qty, } _logger.info(f"KDS NOTIFICATION: Sending update for Line {line.id} Status {line.preparation_status} to {channel_name}") self.env['bus.bus']._sendone(channel_name, 'kds_update', payload) else: _logger.warning(f"Line {line.id} has no config_id - cannot send notification") def _notify_kds(self): """Send notification to KDS backend when new order lines are created""" _logger.info("=== _notify_kds called for %s lines ===" % len(self)) for line in self: # Only notify for kitchen items if line.product_id.is_kitchen_item and line.product_id.name != 'Water': # Send to global KDS channel kds_channel = "kds_channel" payload = { 'line_id': line.id, 'order_id': line.order_id.id, 'product_name': line.product_id.name, 'qty': line.qty, 'table_name': line.table_id.name if line.table_id else '', 'floor_name': line.floor_id.name if line.floor_id else '', 'customer_note': line.customer_note or '', 'preparation_status': line.preparation_status, 'create_date': line.create_date.isoformat() if line.create_date else '', } _logger.info(f"KDS BACKEND NOTIFICATION: New order line {line.id} for {line.product_id.name}") self.env['bus.bus']._sendone(kds_channel, 'kds_new_order', payload) @api.model_create_multi def create(self, vals_list): """Override create to send notifications to KDS when new orders are added""" lines = super(PosOrderLine, self).create(vals_list) # Skip KDS notification if flagged (online orders wait for cashier confirmation) if not self.env.context.get('skip_kds_notify'): # Send notification to KDS backend only for new items (waiting status) waiting_lines = lines.filtered(lambda l: l.preparation_status == 'waiting') if waiting_lines: waiting_lines._notify_kds() return lines def write(self, vals): """Prevent resetting 'served' or 'ready' status back to 'waiting' during POS sync""" if 'preparation_status' in vals and vals['preparation_status'] == 'waiting': for line in self: if line.preparation_status in ['served', 'ready', 'preparing']: # Keep the current status if it's already being processed or served actual_vals = vals.copy() actual_vals['preparation_status'] = line.preparation_status super(PosOrderLine, line).write(actual_vals) # Handle lines that are actually allowed to be updated to waiting remaining_lines = self.filtered(lambda l: l.preparation_status not in ['served', 'ready', 'preparing']) if remaining_lines: res = super(PosOrderLine, remaining_lines).write(vals) # If quantity changed or status is waiting, notify KDS to refresh remaining_lines._notify_kds() return res return True res = super(PosOrderLine, self).write(vals) # Notify KDS for quantity changes on active items if 'qty' in vals: active_lines = self.filtered(lambda l: l.preparation_status in ['waiting', 'preparing']) if active_lines: active_lines._notify_kds() return res def action_start_preparing(self): self.write({ 'preparation_status': 'preparing', 'preparation_time_start': fields.Datetime.now() }) self._notify_pos() def action_mark_ready(self): self.write({ 'preparation_status': 'ready', 'preparation_time_end': fields.Datetime.now() }) self._notify_pos() def action_mark_served(self): self.write({ 'preparation_status': 'served' }) self._notify_pos() class PosOrder(models.Model): _inherit = 'pos.order' @api.model def _prepare_order_line_vals(self, line, session_id=None): res = super()._prepare_order_line_vals(line, session_id) if 'preparation_status' in line: res['preparation_status'] = line['preparation_status'] return res