diff --git a/addons/dine360_kds/__manifest__.py b/addons/dine360_kds/__manifest__.py
index 541dd54..776e9b1 100644
--- a/addons/dine360_kds/__manifest__.py
+++ b/addons/dine360_kds/__manifest__.py
@@ -22,7 +22,14 @@
'assets': {
'web.assets_backend': [
'dine360_kds/static/src/css/kds_style.css',
+ 'dine360_kds/static/src/js/kds_backend.js',
],
+ 'point_of_sale.assets_prod': [
+ 'dine360_kds/static/src/css/pos_kds.css',
+ 'dine360_kds/static/src/js/pos_kds.js',
+ # 'dine360_kds/static/src/xml/pos_kds.xml', # Temporarily disabled
+ ],
+
},
'installable': True,
'application': True,
diff --git a/addons/dine360_kds/models/__init__.py b/addons/dine360_kds/models/__init__.py
index 0c6d361..47daf9e 100644
--- a/addons/dine360_kds/models/__init__.py
+++ b/addons/dine360_kds/models/__init__.py
@@ -1,2 +1,3 @@
from . import pos_order_line
from . import product
+from . import pos_session
diff --git a/addons/dine360_kds/models/pos_order_line.py b/addons/dine360_kds/models/pos_order_line.py
index 3771477..8b221ee 100644
--- a/addons/dine360_kds/models/pos_order_line.py
+++ b/addons/dine360_kds/models/pos_order_line.py
@@ -1,4 +1,7 @@
from odoo import models, fields, api, _
+import logging
+
+_logger = logging.getLogger(__name__)
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
@@ -28,19 +31,73 @@ class PosOrderLine(models.Model):
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)
+ # Send notification to KDS backend for real-time updates
+ lines._notify_kds()
+ return lines
+
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()
diff --git a/addons/dine360_kds/models/pos_session.py b/addons/dine360_kds/models/pos_session.py
new file mode 100644
index 0000000..1c89941
--- /dev/null
+++ b/addons/dine360_kds/models/pos_session.py
@@ -0,0 +1,9 @@
+from odoo import models
+
+class PosSession(models.Model):
+ _inherit = 'pos.session'
+
+ def _loader_params_pos_order_line(self):
+ params = super()._loader_params_pos_order_line()
+ params['search_params']['fields'].extend(['preparation_status', 'preparation_time_start', 'preparation_time_end'])
+ return params
diff --git a/addons/dine360_kds/static/src/css/pos_kds.css b/addons/dine360_kds/static/src/css/pos_kds.css
new file mode 100644
index 0000000..375c0e1
--- /dev/null
+++ b/addons/dine360_kds/static/src/css/pos_kds.css
@@ -0,0 +1,29 @@
+.pos .badge {
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-weight: bold;
+ color: white;
+ margin-left: 5px;
+ vertical-align: middle;
+}
+
+.pos .badge-waiting {
+ background-color: #f0ad4e;
+}
+
+.pos .badge-preparing {
+ background-color: #5bc0de;
+}
+
+.pos .badge-ready {
+ background-color: #5cb85c;
+}
+
+.pos .badge-served {
+ background-color: #777;
+}
+
+.pos .badge-cancelled {
+ background-color: #d9534f;
+}
\ No newline at end of file
diff --git a/addons/dine360_kds/static/src/js/kds_backend.js b/addons/dine360_kds/static/src/js/kds_backend.js
new file mode 100644
index 0000000..98c2947
--- /dev/null
+++ b/addons/dine360_kds/static/src/js/kds_backend.js
@@ -0,0 +1,79 @@
+/** @odoo-module */
+
+import { registry } from "@web/core/registry";
+import { KanbanController } from "@web/views/kanban/kanban_controller";
+import { kanbanView } from "@web/views/kanban/kanban_view";
+import { onWillUnmount } from "@odoo/owl";
+
+export class KdsKanbanController extends KanbanController {
+ setup() {
+ super.setup();
+ console.log("[KDS Controller] Setup");
+
+ // Direct access to services to avoid useService potential conflicts
+ this.busService = this.env.services.bus_service;
+ this.notification = this.env.services.notification;
+
+ const kdsChannel = "kds_channel";
+
+ if (this.busService) {
+ console.log(`[KDS Controller] Subscribing to channel: ${kdsChannel}`);
+ this.busService.addChannel(kdsChannel);
+
+ const handler = this._onKdsNotification.bind(this);
+ this.busService.addEventListener("notification", handler);
+
+ onWillUnmount(() => {
+ if (this.busService) {
+ this.busService.removeEventListener("notification", handler);
+ }
+ });
+ } else {
+ console.error("[KDS Controller] Bus service not found!");
+ }
+ }
+
+ _onKdsNotification(event) {
+ // console.log("[KDS Controller] Notification received:", event);
+
+ const notifications = event.detail || [];
+ let shouldReload = false;
+
+ for (const notif of notifications) {
+ // console.log("[KDS Controller] Processing notification:", notif);
+
+ if (notif.type === "kds_new_order") {
+ console.log("[KDS Controller] New order notification:", notif.payload);
+
+ // Show notification to user
+ if (this.notification) {
+ const payload = notif.payload;
+ this.notification.add(
+ `New Order: ${payload.qty}x ${payload.product_name} - ${payload.table_name}`,
+ {
+ title: "Kitchen Display",
+ type: "info",
+ }
+ );
+ }
+
+ shouldReload = true;
+ }
+ }
+
+ if (shouldReload) {
+ // Reload the view to show the new order
+ console.log("[KDS Controller] Reloading view...");
+ this.model.load();
+ }
+ }
+}
+
+export const kdsKanbanView = {
+ ...kanbanView,
+ Controller: KdsKanbanController,
+};
+
+registry.category("views").add("kds_kanban", kdsKanbanView);
+
+console.log("[KDS Backend] kds_backend.js loaded (JS Class mode - manual services)");
diff --git a/addons/dine360_kds/static/src/js/pos_kds.js b/addons/dine360_kds/static/src/js/pos_kds.js
new file mode 100644
index 0000000..1721fe0
--- /dev/null
+++ b/addons/dine360_kds/static/src/js/pos_kds.js
@@ -0,0 +1,121 @@
+/** @odoo-module */
+
+// Immediate console log to verify file is loaded
+console.log("==============================================");
+console.log("[KDS] pos_kds.js FILE IS LOADING!");
+console.log("==============================================");
+
+import { Orderline } from "@point_of_sale/app/store/models";
+import { PosStore } from "@point_of_sale/app/store/pos_store";
+import { patch } from "@web/core/utils/patch";
+
+console.log("[KDS] Imports successful");
+
+// Patch Orderline model
+patch(Orderline.prototype, {
+ setup() {
+ super.setup(...arguments);
+ this.preparation_status = this.preparation_status || 'waiting';
+ },
+
+ init_from_JSON(json) {
+ super.init_from_JSON(...arguments);
+ if (json.preparation_status) {
+ this.preparation_status = json.preparation_status;
+ }
+ },
+
+ export_as_JSON() {
+ const json = super.export_as_JSON(...arguments);
+ json.preparation_status = this.preparation_status;
+ return json;
+ },
+
+ set_preparation_status(status) {
+ this.preparation_status = status;
+ },
+
+ get_preparation_status() {
+ return this.preparation_status;
+ },
+
+ get_preparation_status_label() {
+ const labels = {
+ 'waiting': 'Waiting',
+ 'preparing': 'Preparing',
+ 'ready': 'Ready',
+ 'served': 'Served',
+ 'cancelled': 'Cancelled'
+ };
+ return labels[this.preparation_status] || this.preparation_status;
+ }
+});
+
+console.log("[KDS] Orderline patched successfully");
+
+// Patch PosStore
+patch(PosStore.prototype, {
+ async _processData(loadedData) {
+ console.log("[KDS] _processData called!");
+ await super._processData(...arguments);
+
+ const channel = `pos_config_Channel_${this.config.id}`;
+ console.log(`[KDS] Setting up channel: ${channel}`);
+
+ try {
+ const busService = this.env.services.bus_service;
+ busService.addChannel(channel);
+ console.log("[KDS] Channel added successfully");
+
+ busService.addEventListener("notification", (event) => {
+ console.log("[KDS] *** NOTIFICATION RECEIVED ***", event);
+ const notifications = event.detail || [];
+
+ for (const notif of notifications) {
+ if (notif.type === 'kds_update') {
+ console.log("[KDS] *** KDS UPDATE ***", notif.payload);
+ this._handleKdsUpdate(notif.payload);
+ }
+ }
+ });
+
+ console.log("[KDS] Listener registered successfully");
+ } catch (error) {
+ console.error("[KDS] ERROR:", error);
+ }
+ },
+
+ _handleKdsUpdate(payload) {
+ console.log("[KDS] Handling update:", payload);
+
+ try {
+ const { order_uid, product_id, status, status_label, line_id, order_id } = payload;
+ const orders = this.get_order_list();
+
+ let order = orders.find(o => o.server_id === order_id);
+ if (!order) {
+ order = orders.find(o => o.name === order_uid || o.pos_reference === order_uid);
+ }
+
+ if (order) {
+ let line = order.get_orderlines().find(l => l.server_id === line_id);
+ if (!line) {
+ line = order.get_orderlines().find(l => l.product.id === product_id);
+ }
+
+ if (line) {
+ line.set_preparation_status(status);
+ this.env.services.notification.add(
+ `${line.product.display_name} is ${status_label}`,
+ { title: "Kitchen Update", type: "info" }
+ );
+ }
+ }
+ } catch (error) {
+ console.error("[KDS] Error in handler:", error);
+ }
+ }
+});
+
+console.log("[KDS] PosStore patched successfully");
+console.log("[KDS] MODULE FULLY LOADED!");
diff --git a/addons/dine360_kds/static/src/xml/pos_kds.xml b/addons/dine360_kds/static/src/xml/pos_kds.xml
new file mode 100644
index 0000000..07ac8a8
--- /dev/null
+++ b/addons/dine360_kds/static/src/xml/pos_kds.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/dine360_kds/views/pos_order_line_views.xml b/addons/dine360_kds/views/pos_order_line_views.xml
index b246f31..98f9bbd 100644
--- a/addons/dine360_kds/views/pos_order_line_views.xml
+++ b/addons/dine360_kds/views/pos_order_line_views.xml
@@ -4,7 +4,7 @@
pos.order.line.kds.kanban
pos.order.line
-
+
@@ -123,9 +123,9 @@
Kitchen Display System
pos.order.line
kanban,tree,form
- [('product_id.is_kitchen_item', '=', True)]
+ [('product_id.is_kitchen_item', '=', True), ('product_id.name', '!=', 'Water'), ('order_id.session_id.state', '!=', 'closed'), ('product_id.pos_categ_ids.name', '!=', 'Drinks')]
- {'search_default_in_progress': 1, 'search_default_group_status': 1}
+ {'search_default_in_progress': 1, 'search_default_group_status': 1, 'search_default_today': 1}
Welcome to the Kitchen!