Fix QZ multi-printer fallback and kitchen hook

This commit is contained in:
metatroncubeswdev 2026-05-07 11:25:37 -04:00
parent e7c67653e4
commit c94c1f06eb
4 changed files with 65 additions and 47 deletions

View File

@ -1,6 +1,6 @@
{
'name': 'Dine360 QZ Tray Printer',
'version': '17.0.1.2',
'version': '17.0.1.3',
'category': 'Point of Sale',
'summary': 'Integrate Odoo POS with Star/Epson Printers via QZ Tray.',
'depends': ['point_of_sale'],

View File

@ -4,6 +4,7 @@ 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("Legacy QZ Printer Name", help="Previous single-printer setting kept for compatibility")
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(

View File

@ -5,6 +5,7 @@ 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)

View File

@ -3,7 +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";
import { Order } from "@point_of_sale/app/store/models";
const ESC = 0x1b;
const GS = 0x1d;
@ -17,6 +17,10 @@ function columnsFromConfig(config) {
return Number.isFinite(value) ? value : DEFAULT_COLUMNS;
}
function getBillingPrinterName(config) {
return config?.qz_billing_printer_name || config?.qz_printer_name || "";
}
function cleanText(value) {
return String(value ?? "")
.normalize("NFKD")
@ -356,7 +360,8 @@ async function buildEscPosReceipt(order, pos) {
patch(ReceiptScreen.prototype, {
async printReceipt() {
if (this.pos.config.use_qz_printer && this.pos.config.qz_billing_printer_name) {
const printerName = getBillingPrinterName(this.pos.config);
if (this.pos.config.use_qz_printer && printerName) {
try {
if (!window.qz) {
console.error("QZ Tray library not loaded.");
@ -367,7 +372,7 @@ patch(ReceiptScreen.prototype, {
await qz.websocket.connect({ retries: 2, delay: 1 });
}
const config = qz.configs.create(this.pos.config.qz_billing_printer_name, {
const config = qz.configs.create(printerName, {
encoding: "CP437",
copies: 1,
spool: { size: 1 },
@ -397,10 +402,13 @@ patch(ReceiptScreen.prototype, {
},
});
async function buildEscPosKitchenTicket(order, pos, changes) {
async function buildEscPosKitchenTicket(order, pos, orderChange, cancelled = false) {
const config = pos?.config || {};
const columns = columnsFromConfig(config);
const builder = new EscPosBuilder(columns);
const table = order.getTable?.() || order.table;
const newLines = orderChange?.new || [];
const cancelledLines = orderChange?.cancelled || [];
builder.init();
builder.align("center");
@ -412,19 +420,25 @@ async function buildEscPosKitchenTicket(order, pos, changes) {
builder.align("left");
if (order.name) builder.line(`Order: ${order.name}`);
if (order.table?.name) builder.line(`Table: ${order.table.name}`);
if (table?.name) builder.line(`Table: ${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) {
if (cancelled) {
builder.bold(true);
builder.line(category.name.toUpperCase());
builder.line("ORDER CANCELLED");
builder.bold(false);
}
for (const line of category.orderlines || []) {
const qty = line.quantity;
builder.separator("=");
function printSection(title, lines) {
if (!lines.length) {
return;
}
builder.bold(true);
builder.line(title);
builder.bold(false);
for (const line of lines) {
const qty = Number(line.quantity || 0);
const qtyText = Number.isInteger(qty) ? String(qty) : qty.toFixed(2).replace(/\.?0+$/, "");
addWrappedLine(builder, `${qtyText} x ${line.name}`);
if (line.note) {
@ -434,6 +448,9 @@ async function buildEscPosKitchenTicket(order, pos, changes) {
builder.line("");
}
printSection("NEW ITEMS", newLines);
printSection("CANCELLED ITEMS", cancelledLines);
builder.feed(END_FEED_LINES);
if (config.qz_enable_cutter !== false) {
builder.cut(config.qz_cut_mode || "partial");
@ -442,11 +459,14 @@ async function buildEscPosKitchenTicket(order, pos, changes) {
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) {
patch(Order.prototype, {
async printChanges(cancelled) {
const printerName = this.pos.config.qz_kitchen_printer_name;
if (this.pos.config.use_qz_printer && printerName) {
const orderChange = this.changesToOrder(cancelled);
if (!orderChange.new.length && !orderChange.cancelled.length) {
return true;
}
try {
if (!window.qz) {
console.error("QZ Tray library not loaded.");
@ -455,28 +475,24 @@ patch(PosStore.prototype, {
if (!qz.websocket.isActive()) {
await qz.websocket.connect({ retries: 2, delay: 1 });
}
const config = qz.configs.create(this.config.qz_kitchen_printer_name, {
const config = qz.configs.create(printerName, {
encoding: "CP437",
copies: 1,
spool: { size: 1 },
});
const payload = await buildEscPosKitchenTicket(order, this, changes);
const payload = await buildEscPosKitchenTicket(this, this.pos, orderChange, cancelled);
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 false;
}
}
return true;
}
return super.sendOrderToPrinter(...arguments);
return super.printChanges(...arguments);
},
});