diff --git a/addons/dine360_qz_printer/models/pos_config.py b/addons/dine360_qz_printer/models/pos_config.py
index 66174d4..56d40d4 100644
--- a/addons/dine360_qz_printer/models/pos_config.py
+++ b/addons/dine360_qz_printer/models/pos_config.py
@@ -4,7 +4,8 @@ class PosConfig(models.Model):
_inherit = 'pos.config'
use_qz_printer = fields.Boolean("Use QZ Tray Printer", help="Print directly using QZ Tray locally")
- qz_printer_name = fields.Char("QZ Printer Name", help="Name of the printer mapped in QZ Tray")
+ qz_billing_printer_name = fields.Char("Billing Printer Name", help="Name of the billing printer mapped in QZ Tray")
+ qz_kitchen_printer_name = fields.Char("Kitchen Printer Name", help="Name of the kitchen printer mapped in QZ Tray")
qz_paper_width = fields.Selection(
[('42', '58mm / 42 columns'), ('48', '80mm / 48 columns')],
string="QZ Receipt Width",
diff --git a/addons/dine360_qz_printer/models/res_config_settings.py b/addons/dine360_qz_printer/models/res_config_settings.py
index 035a086..48a1d53 100644
--- a/addons/dine360_qz_printer/models/res_config_settings.py
+++ b/addons/dine360_qz_printer/models/res_config_settings.py
@@ -5,7 +5,8 @@ class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
use_qz_printer = fields.Boolean(related='pos_config_id.use_qz_printer', readonly=False)
- qz_printer_name = fields.Char(related='pos_config_id.qz_printer_name', readonly=False)
+ qz_billing_printer_name = fields.Char(related='pos_config_id.qz_billing_printer_name', readonly=False)
+ qz_kitchen_printer_name = fields.Char(related='pos_config_id.qz_kitchen_printer_name', readonly=False)
qz_paper_width = fields.Selection(related='pos_config_id.qz_paper_width', readonly=False)
qz_print_logo = fields.Boolean(related='pos_config_id.qz_print_logo', readonly=False)
qz_enable_cutter = fields.Boolean(related='pos_config_id.qz_enable_cutter', readonly=False)
diff --git a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
index ffb3274..566e6ee 100644
--- a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
+++ b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js
@@ -3,6 +3,7 @@
import { patch } from "@web/core/utils/patch";
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
+import { PosStore } from "@point_of_sale/app/store/pos_store";
const ESC = 0x1b;
const GS = 0x1d;
@@ -355,7 +356,7 @@ async function buildEscPosReceipt(order, pos) {
patch(ReceiptScreen.prototype, {
async printReceipt() {
- if (this.pos.config.use_qz_printer && this.pos.config.qz_printer_name) {
+ if (this.pos.config.use_qz_printer && this.pos.config.qz_billing_printer_name) {
try {
if (!window.qz) {
console.error("QZ Tray library not loaded.");
@@ -366,7 +367,7 @@ patch(ReceiptScreen.prototype, {
await qz.websocket.connect({ retries: 2, delay: 1 });
}
- const config = qz.configs.create(this.pos.config.qz_printer_name, {
+ const config = qz.configs.create(this.pos.config.qz_billing_printer_name, {
encoding: "CP437",
copies: 1,
spool: { size: 1 },
@@ -395,3 +396,87 @@ patch(ReceiptScreen.prototype, {
return super.printReceipt(...arguments);
},
});
+
+async function buildEscPosKitchenTicket(order, pos, changes) {
+ const config = pos?.config || {};
+ const columns = columnsFromConfig(config);
+ const builder = new EscPosBuilder(columns);
+
+ builder.init();
+ builder.align("center");
+ builder.bold(true);
+ builder.size("doubleHeight");
+ builder.line("KITCHEN ORDER");
+ builder.size("normal");
+ builder.line("");
+
+ builder.align("left");
+ if (order.name) builder.line(`Order: ${order.name}`);
+ if (order.table?.name) builder.line(`Table: ${order.table.name}`);
+ if (order.customer_count) builder.line(`Guests: ${order.customer_count}`);
+ builder.line(`Time: ${new Date().toLocaleTimeString()}`);
+ builder.separator("=");
+
+ for (const category of Object.values(changes.categories || {})) {
+ if (category.name) {
+ builder.bold(true);
+ builder.line(category.name.toUpperCase());
+ builder.bold(false);
+ }
+ for (const line of category.orderlines || []) {
+ const qty = line.quantity;
+ const qtyText = Number.isInteger(qty) ? String(qty) : qty.toFixed(2).replace(/\.?0+$/, "");
+ addWrappedLine(builder, `${qtyText} x ${line.name}`);
+ if (line.note) {
+ addWrappedLine(builder, `Note: ${line.note}`, "", 2);
+ }
+ }
+ builder.line("");
+ }
+
+ builder.feed(END_FEED_LINES);
+ if (config.qz_enable_cutter !== false) {
+ builder.cut(config.qz_cut_mode || "partial");
+ }
+ builder.feed(1);
+ return bytesToBase64(builder.bytes);
+}
+
+patch(PosStore.prototype, {
+ async sendOrderToPrinter(order) {
+ if (this.config.use_qz_printer && this.config.qz_kitchen_printer_name) {
+ const changes = order.computeChanges();
+ if (Object.keys(changes.categories).length > 0) {
+ try {
+ if (!window.qz) {
+ console.error("QZ Tray library not loaded.");
+ return false;
+ }
+ if (!qz.websocket.isActive()) {
+ await qz.websocket.connect({ retries: 2, delay: 1 });
+ }
+
+ const config = qz.configs.create(this.config.qz_kitchen_printer_name, {
+ encoding: "CP437",
+ copies: 1,
+ spool: { size: 1 },
+ });
+ const payload = await buildEscPosKitchenTicket(order, this, changes);
+ await qz.print(config, [{
+ type: "raw",
+ format: "command",
+ flavor: "base64",
+ data: payload,
+ }]);
+ order.saved_quantity = order.get_orderlines().reduce((acc, line) => acc + line.get_quantity(), 0);
+ return true;
+ } catch (err) {
+ console.error("QZ Tray Kitchen Print Error:", err);
+ // Fallback or show error
+ }
+ }
+ return true;
+ }
+ return super.sendOrderToPrinter(...arguments);
+ },
+});
diff --git a/addons/dine360_qz_printer/views/pos_config_views.xml b/addons/dine360_qz_printer/views/pos_config_views.xml
index e798595..1b3d3cf 100644
--- a/addons/dine360_qz_printer/views/pos_config_views.xml
+++ b/addons/dine360_qz_printer/views/pos_config_views.xml
@@ -11,8 +11,12 @@