forked from alaguraj/odoo-testing-addons
Revert "Print Odoo receipt HTML through QZ only"
This reverts commit ad5564aaf638eb4507dd039152e940c2594f8e7a.
This commit is contained in:
parent
c08f546702
commit
56f6086024
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Dine360 Order Channels',
|
'name': 'Dine360 Order Channels',
|
||||||
'version': '17.0.1.1',
|
'version': '17.0.1.0',
|
||||||
'category': 'Sales/Point of Sale',
|
'category': 'Sales/Point of Sale',
|
||||||
'summary': 'Multi-channel order intake: Phone, WhatsApp, Social, Kiosk, Online',
|
'summary': 'Multi-channel order intake: Phone, WhatsApp, Social, Kiosk, Online',
|
||||||
'description': """
|
'description': """
|
||||||
@ -25,6 +25,7 @@
|
|||||||
'dine360_order_channels/static/src/js/channel_panel.js',
|
'dine360_order_channels/static/src/js/channel_panel.js',
|
||||||
'dine360_order_channels/static/src/js/product_screen_patch.js',
|
'dine360_order_channels/static/src/js/product_screen_patch.js',
|
||||||
'dine360_order_channels/static/src/xml/channel_panel.xml',
|
'dine360_order_channels/static/src/xml/channel_panel.xml',
|
||||||
|
'dine360_order_channels/static/src/xml/receipt_extension.xml',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Dine360 QZ Tray Printer',
|
'name': 'Dine360 QZ Tray Printer',
|
||||||
'version': '17.0.1.4',
|
'version': '17.0.1.3',
|
||||||
'category': 'Point of Sale',
|
'category': 'Point of Sale',
|
||||||
'summary': 'Integrate Odoo POS with Star/Epson Printers via QZ Tray.',
|
'summary': 'Integrate Odoo POS with Star/Epson Printers via QZ Tray.',
|
||||||
'depends': ['point_of_sale'],
|
'depends': ['point_of_sale'],
|
||||||
|
|||||||
@ -11,7 +11,6 @@ const CR = 0x0d;
|
|||||||
const LF = 0x0a;
|
const LF = 0x0a;
|
||||||
const DEFAULT_COLUMNS = 42;
|
const DEFAULT_COLUMNS = 42;
|
||||||
const END_FEED_LINES = 8;
|
const END_FEED_LINES = 8;
|
||||||
let qzPrintQueue = Promise.resolve();
|
|
||||||
|
|
||||||
function columnsFromConfig(config) {
|
function columnsFromConfig(config) {
|
||||||
const value = Number.parseInt(config?.qz_paper_width || DEFAULT_COLUMNS, 10);
|
const value = Number.parseInt(config?.qz_paper_width || DEFAULT_COLUMNS, 10);
|
||||||
@ -26,20 +25,6 @@ function paperWidthMm(config) {
|
|||||||
return String(config?.qz_paper_width || DEFAULT_COLUMNS) === "48" ? 80 : 58;
|
return String(config?.qz_paper_width || DEFAULT_COLUMNS) === "48" ? 80 : 58;
|
||||||
}
|
}
|
||||||
|
|
||||||
function enqueueQzPrint(task) {
|
|
||||||
const next = qzPrintQueue.catch(() => undefined).then(task);
|
|
||||||
qzPrintQueue = next.catch(() => undefined);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findConfiguredPrinter(printerName) {
|
|
||||||
const name = cleanText(printerName);
|
|
||||||
if (!name) {
|
|
||||||
throw new Error("No QZ printer name is configured.");
|
|
||||||
}
|
|
||||||
return qz.printers.find(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanText(value) {
|
function cleanText(value) {
|
||||||
return String(value ?? "")
|
return String(value ?? "")
|
||||||
.normalize("NFKD")
|
.normalize("NFKD")
|
||||||
@ -53,10 +38,74 @@ function cleanText(value) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMoney(value, currency) {
|
||||||
|
const amount = Number(value || 0).toFixed(2);
|
||||||
|
const symbol = cleanText(currency?.symbol || "$");
|
||||||
|
return currency?.position === "after" ? `${amount} ${symbol}` : `${symbol}${amount}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function center(text, width) {
|
||||||
|
const value = cleanText(text);
|
||||||
|
const padding = Math.max(0, Math.floor((width - value.length) / 2));
|
||||||
|
return `${" ".repeat(padding)}${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftRight(left, right, width) {
|
||||||
|
const lhs = cleanText(left);
|
||||||
|
const rhs = cleanText(right);
|
||||||
|
const space = Math.max(1, width - lhs.length - rhs.length);
|
||||||
|
return `${lhs}${" ".repeat(space)}${rhs}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeUpper(value) {
|
||||||
|
return cleanText(value).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPhone(value) {
|
||||||
|
const clean = cleanText(value);
|
||||||
|
const digits = clean.replace(/\D/g, "");
|
||||||
|
const local = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits;
|
||||||
|
if (local.length === 10) {
|
||||||
|
return `${local.slice(0, 3)}-${local.slice(3, 6)}-${local.slice(6)}`;
|
||||||
|
}
|
||||||
|
return clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompanyAddress(company) {
|
||||||
|
const parts = [
|
||||||
|
company.street,
|
||||||
|
company.street2,
|
||||||
|
[company.city, company.state_id?.[1] || company.state_id?.name, company.zip].filter(Boolean).join(" "),
|
||||||
|
].map(cleanText).filter(Boolean);
|
||||||
|
return parts.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPlacedDate(value) {
|
||||||
|
const date = value ? new Date(value) : new Date();
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return cleanText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const weekday = date.toLocaleDateString("en-CA", { weekday: "short" });
|
||||||
|
const month = date.toLocaleDateString("en-CA", { month: "short" });
|
||||||
|
const day = date.getDate();
|
||||||
|
const suffix = day % 10 === 1 && day !== 11 ? "st" : day % 10 === 2 && day !== 12 ? "nd" : day % 10 === 3 && day !== 13 ? "rd" : "th";
|
||||||
|
const time = date.toLocaleTimeString("en-CA", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true,
|
||||||
|
}).replace(/\s/g, "");
|
||||||
|
return `${weekday}, ${month} ${day}${suffix}, ${time}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getOrderTable(order) {
|
function getOrderTable(order) {
|
||||||
return order?.getTable?.() || order?.table;
|
return order?.getTable?.() || order?.table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOrderDisplayName(receiptData, order) {
|
||||||
|
return cleanText(receiptData?.name || order?.name || "");
|
||||||
|
}
|
||||||
|
|
||||||
function wrapText(text, width) {
|
function wrapText(text, width) {
|
||||||
const clean = cleanText(text);
|
const clean = cleanText(text);
|
||||||
if (clean.length <= width) {
|
if (clean.length <= width) {
|
||||||
@ -94,24 +143,6 @@ function absoluteUrl(value) {
|
|||||||
return new URL(value, window.location.origin).href;
|
return new URL(value, window.location.origin).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function imageToDataUrl(src) {
|
|
||||||
const url = absoluteUrl(src);
|
|
||||||
if (!url || url.startsWith("data:") || url.startsWith("blob:")) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
const response = await fetch(url, { credentials: "include" });
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Unable to load receipt image: ${url}`);
|
|
||||||
}
|
|
||||||
const blob = await response.blob();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => resolve(reader.result);
|
|
||||||
reader.onerror = reject;
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectPrintableStyles() {
|
function collectPrintableStyles() {
|
||||||
return Array.from(document.querySelectorAll("link[rel='stylesheet'], style"))
|
return Array.from(document.querySelectorAll("link[rel='stylesheet'], style"))
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
@ -123,13 +154,13 @@ function collectPrintableStyles() {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildHtmlReceipt(receiptElement, pos) {
|
function buildHtmlReceipt(receiptElement, pos) {
|
||||||
const config = pos?.config || {};
|
const config = pos?.config || {};
|
||||||
const widthMm = paperWidthMm(config);
|
const widthMm = paperWidthMm(config);
|
||||||
const clone = receiptElement.cloneNode(true);
|
const clone = receiptElement.cloneNode(true);
|
||||||
await Promise.all(Array.from(clone.querySelectorAll("img[src]")).map(async (node) => {
|
for (const node of clone.querySelectorAll("[src]")) {
|
||||||
node.setAttribute("src", await imageToDataUrl(node.getAttribute("src")));
|
node.setAttribute("src", absoluteUrl(node.getAttribute("src")));
|
||||||
}));
|
}
|
||||||
for (const node of clone.querySelectorAll("[href]")) {
|
for (const node of clone.querySelectorAll("[href]")) {
|
||||||
node.setAttribute("href", absoluteUrl(node.getAttribute("href")));
|
node.setAttribute("href", absoluteUrl(node.getAttribute("href")));
|
||||||
}
|
}
|
||||||
@ -164,12 +195,6 @@ async function buildHtmlReceipt(receiptElement, pos) {
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findReceiptElement(screen) {
|
|
||||||
return screen.el?.querySelector?.(".pos-receipt")
|
|
||||||
|| document.querySelector(".receipt-screen .pos-receipt")
|
|
||||||
|| document.querySelector(".pos-receipt");
|
|
||||||
}
|
|
||||||
|
|
||||||
class EscPosBuilder {
|
class EscPosBuilder {
|
||||||
constructor(columns) {
|
constructor(columns) {
|
||||||
this.columns = columns;
|
this.columns = columns;
|
||||||
@ -249,6 +274,61 @@ class EscPosBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLogoRasterBytes(pos, columns) {
|
||||||
|
if (pos?.config?.qz_print_logo === false || !pos?.company_logo_base64) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = reject;
|
||||||
|
img.src = pos.company_logo_base64;
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxWidth = Math.min(columns * 8, 384);
|
||||||
|
const maxHeight = 120;
|
||||||
|
const ratio = Math.min(maxWidth / image.width, maxHeight / image.height, 1);
|
||||||
|
const width = Math.max(8, Math.floor((image.width * ratio) / 8) * 8);
|
||||||
|
const height = Math.max(1, Math.floor(image.height * ratio));
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
ctx.drawImage(image, 0, 0, width, height);
|
||||||
|
|
||||||
|
const pixels = ctx.getImageData(0, 0, width, height).data;
|
||||||
|
const widthBytes = Math.ceil(width / 8);
|
||||||
|
const data = [];
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
for (let xb = 0; xb < widthBytes; xb++) {
|
||||||
|
let byte = 0;
|
||||||
|
for (let bit = 0; bit < 8; bit++) {
|
||||||
|
const x = xb * 8 + bit;
|
||||||
|
const index = (y * width + x) * 4;
|
||||||
|
const alpha = pixels[index + 3];
|
||||||
|
const luminance = pixels[index] * 0.299 + pixels[index + 1] * 0.587 + pixels[index + 2] * 0.114;
|
||||||
|
if (alpha > 32 && luminance < 170) {
|
||||||
|
byte |= 0x80 >> bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.push(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
ESC, 0x61, 0x01,
|
||||||
|
GS, 0x76, 0x30, 0x00,
|
||||||
|
widthBytes & 0xff, (widthBytes >> 8) & 0xff,
|
||||||
|
height & 0xff, (height >> 8) & 0xff,
|
||||||
|
...data,
|
||||||
|
CR, LF,
|
||||||
|
ESC, 0x61, 0x00,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function addWrappedLine(builder, left, right = "", indent = 0) {
|
function addWrappedLine(builder, left, right = "", indent = 0) {
|
||||||
const width = builder.columns;
|
const width = builder.columns;
|
||||||
const rightText = cleanText(right);
|
const rightText = cleanText(right);
|
||||||
@ -266,6 +346,157 @@ function addWrappedLine(builder, left, right = "", indent = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOrderLines(builder, order, currency) {
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line(leftRight("Item", "Price", builder.columns));
|
||||||
|
builder.bold(false);
|
||||||
|
|
||||||
|
for (const line of order?.get_orderlines?.() || []) {
|
||||||
|
const product = line.get_product?.();
|
||||||
|
const name = product?.display_name || product?.name || line.get_full_product_name?.() || "";
|
||||||
|
const qty = Number(line.get_quantity?.() || 0);
|
||||||
|
const qtyText = Number.isInteger(qty) ? String(qty) : qty.toFixed(2).replace(/\.?0+$/, "");
|
||||||
|
const total = formatMoney(line.get_price_with_tax?.() || 0, currency);
|
||||||
|
addWrappedLine(builder, `${qtyText} x ${name}`, total);
|
||||||
|
|
||||||
|
const unitPrice = line.get_unit_display_price?.();
|
||||||
|
if (qty !== 1 && unitPrice !== undefined) {
|
||||||
|
builder.line(` @ ${formatMoney(unitPrice, currency)} each`);
|
||||||
|
}
|
||||||
|
const note = line.get_customer_note?.();
|
||||||
|
if (note) {
|
||||||
|
addWrappedLine(builder, `Note: ${note}`, "", 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTaxSummary(builder, receiptData, currency) {
|
||||||
|
const taxes = receiptData?.tax_details || [];
|
||||||
|
if (!taxes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const tax of taxes) {
|
||||||
|
const label = tax?.tax?.name || (tax?.tax?.amount ? `${tax.tax.amount}%` : "Tax");
|
||||||
|
builder.line(leftRight(label, formatMoney(tax.amount || 0, currency), builder.columns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPaymentLines(builder, receiptData, currency) {
|
||||||
|
const paymentLines = receiptData?.paymentlines || [];
|
||||||
|
if (!paymentLines.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
builder.separator("-");
|
||||||
|
for (const payment of paymentLines) {
|
||||||
|
builder.line(leftRight(payment.name, formatMoney(payment.amount, currency), builder.columns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildEscPosReceipt(order, pos) {
|
||||||
|
const config = pos?.config || {};
|
||||||
|
const columns = columnsFromConfig(config);
|
||||||
|
const currency = pos?.currency;
|
||||||
|
const company = pos?.company || {};
|
||||||
|
const receiptData = order?.export_for_printing?.() || {};
|
||||||
|
const builder = new EscPosBuilder(columns);
|
||||||
|
const table = getOrderTable(order);
|
||||||
|
const companyName = cleanText(company.name || "Chennora");
|
||||||
|
const fulfilmentLabel = safeUpper(receiptData.fulfilment_type_label || receiptData.fulfilment_type || "DINE-IN");
|
||||||
|
const orderName = getOrderDisplayName(receiptData, order);
|
||||||
|
const qrValue = orderName || `${companyName} ${formatMoney(order?.get_total_with_tax?.(), currency)}`;
|
||||||
|
|
||||||
|
builder.init();
|
||||||
|
builder.raw(...(await getLogoRasterBytes(pos, columns)));
|
||||||
|
builder.align("center");
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line(companyName);
|
||||||
|
builder.bold(false);
|
||||||
|
builder.line("");
|
||||||
|
|
||||||
|
const address = getCompanyAddress(company);
|
||||||
|
if (address) {
|
||||||
|
builder.lines(wrapText(`Address: ${address}`, columns).map((line) => center(line, columns)));
|
||||||
|
}
|
||||||
|
if (company.email) {
|
||||||
|
builder.line(center(`Email: ${company.email}`, columns));
|
||||||
|
}
|
||||||
|
if (company.phone) {
|
||||||
|
builder.line(center(`Phone: ${formatPhone(company.phone)}`, columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.line("");
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line(order?._printed ? "*** Duplicate Bill (1) ***" : "*** Bill ***");
|
||||||
|
if (orderName) {
|
||||||
|
builder.line(`Invoice ${orderName}`);
|
||||||
|
}
|
||||||
|
builder.line(fulfilmentLabel);
|
||||||
|
builder.bold(false);
|
||||||
|
builder.line("");
|
||||||
|
|
||||||
|
builder.align("left");
|
||||||
|
builder.separator("-");
|
||||||
|
builder.line(leftRight("Placed", formatPlacedDate(receiptData.date), columns));
|
||||||
|
builder.separator("-");
|
||||||
|
builder.line(leftRight("Cashier", companyName, columns));
|
||||||
|
if (order?.customer_count) {
|
||||||
|
builder.line(leftRight("Guests Served", order.customer_count, columns));
|
||||||
|
}
|
||||||
|
if (table?.name) {
|
||||||
|
builder.line(leftRight("Table", table.name, columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.separator("=");
|
||||||
|
addOrderLines(builder, order, currency);
|
||||||
|
builder.separator("=");
|
||||||
|
builder.line(leftRight("Price", formatMoney(order?.get_total_without_tax?.(), currency), columns));
|
||||||
|
const tax = order?.get_total_tax?.() || 0;
|
||||||
|
if (tax) {
|
||||||
|
const firstTax = receiptData?.tax_details?.[0]?.tax;
|
||||||
|
const taxLabel = firstTax?.name || (firstTax?.amount ? `HST (${firstTax.amount}%)` : "Tax");
|
||||||
|
builder.line(leftRight(taxLabel, formatMoney(tax, currency), columns));
|
||||||
|
}
|
||||||
|
if (receiptData.total_discount) {
|
||||||
|
builder.line(leftRight("Discounts", formatMoney(receiptData.total_discount, currency), columns));
|
||||||
|
}
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line(leftRight("Grand Total", formatMoney(order?.get_total_with_tax?.(), currency), columns));
|
||||||
|
builder.bold(false);
|
||||||
|
|
||||||
|
addPaymentLines(builder, receiptData, currency);
|
||||||
|
const change = receiptData.change || order?.get_change?.() || 0;
|
||||||
|
if (change > 0) {
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line(leftRight("Change", formatMoney(change, currency), columns));
|
||||||
|
builder.bold(false);
|
||||||
|
}
|
||||||
|
addTaxSummary(builder, receiptData, currency);
|
||||||
|
|
||||||
|
if (config.receipt_footer) {
|
||||||
|
builder.separator("-");
|
||||||
|
builder.align("center");
|
||||||
|
builder.lines(wrapText(config.receipt_footer, columns).map((line) => center(line, columns)));
|
||||||
|
builder.align("left");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.separator("-");
|
||||||
|
builder.align("center");
|
||||||
|
builder.bold(true);
|
||||||
|
builder.line("* PAID *");
|
||||||
|
builder.bold(false);
|
||||||
|
builder.line("");
|
||||||
|
builder.qrCode(qrValue);
|
||||||
|
builder.line("");
|
||||||
|
builder.line("Powered by dind360");
|
||||||
|
builder.align("left");
|
||||||
|
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(ReceiptScreen.prototype, {
|
patch(ReceiptScreen.prototype, {
|
||||||
async printReceipt() {
|
async printReceipt() {
|
||||||
const printerName = getBillingPrinterName(this.pos.config);
|
const printerName = getBillingPrinterName(this.pos.config);
|
||||||
@ -280,28 +511,43 @@ patch(ReceiptScreen.prototype, {
|
|||||||
await qz.websocket.connect({ retries: 2, delay: 1 });
|
await qz.websocket.connect({ retries: 2, delay: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiptElement = findReceiptElement(this);
|
const receiptElement = this.el?.querySelector?.(".pos-receipt");
|
||||||
if (!receiptElement) {
|
if (receiptElement) {
|
||||||
throw new Error("The Odoo receipt preview was not found. Billing print was stopped to avoid printing fallback data.");
|
const widthMm = paperWidthMm(this.pos.config);
|
||||||
|
const config = qz.configs.create(printerName, {
|
||||||
|
copies: 1,
|
||||||
|
margins: 0,
|
||||||
|
units: "mm",
|
||||||
|
size: { width: widthMm },
|
||||||
|
rasterize: true,
|
||||||
|
scaleContent: true,
|
||||||
|
spool: { size: 1 },
|
||||||
|
});
|
||||||
|
await qz.print(config, [{
|
||||||
|
type: "pixel",
|
||||||
|
format: "html",
|
||||||
|
flavor: "plain",
|
||||||
|
data: buildHtmlReceipt(receiptElement, this.pos),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (this.currentOrder) {
|
||||||
|
this.currentOrder._printed = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
const widthMm = paperWidthMm(this.pos.config);
|
|
||||||
const printer = await findConfiguredPrinter(printerName);
|
const config = qz.configs.create(printerName, {
|
||||||
const config = qz.configs.create(printer, {
|
encoding: "CP437",
|
||||||
copies: 1,
|
copies: 1,
|
||||||
margins: 0,
|
|
||||||
units: "mm",
|
|
||||||
size: { width: widthMm },
|
|
||||||
rasterize: true,
|
|
||||||
scaleContent: true,
|
|
||||||
spool: { size: 1 },
|
spool: { size: 1 },
|
||||||
});
|
});
|
||||||
const html = await buildHtmlReceipt(receiptElement, this.pos);
|
const payload = await buildEscPosReceipt(this.currentOrder, this.pos);
|
||||||
await enqueueQzPrint(() => qz.print(config, [{
|
await qz.print(config, [{
|
||||||
type: "pixel",
|
type: "raw",
|
||||||
format: "html",
|
format: "command",
|
||||||
flavor: "plain",
|
flavor: "base64",
|
||||||
data: html,
|
data: payload,
|
||||||
}]));
|
}]);
|
||||||
|
|
||||||
if (this.currentOrder) {
|
if (this.currentOrder) {
|
||||||
this.currentOrder._printed = true;
|
this.currentOrder._printed = true;
|
||||||
@ -311,7 +557,7 @@ patch(ReceiptScreen.prototype, {
|
|||||||
console.error("QZ Tray Print Error:", err);
|
console.error("QZ Tray Print Error:", err);
|
||||||
this.env.services.popup.add(ErrorPopup, {
|
this.env.services.popup.add(ErrorPopup, {
|
||||||
title: "QZ Tray Printer Error",
|
title: "QZ Tray Printer Error",
|
||||||
body: err?.message || "Failed to print the rendered Odoo receipt through QZ Tray.",
|
body: "Failed to print through QZ Tray. Check that QZ Tray is running, the printer name is correct, and the printer supports ESC/POS raw commands.",
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -393,19 +639,18 @@ patch(Order.prototype, {
|
|||||||
if (!qz.websocket.isActive()) {
|
if (!qz.websocket.isActive()) {
|
||||||
await qz.websocket.connect({ retries: 2, delay: 1 });
|
await qz.websocket.connect({ retries: 2, delay: 1 });
|
||||||
}
|
}
|
||||||
const printer = await findConfiguredPrinter(printerName);
|
const config = qz.configs.create(printerName, {
|
||||||
const config = qz.configs.create(printer, {
|
|
||||||
encoding: "CP437",
|
encoding: "CP437",
|
||||||
copies: 1,
|
copies: 1,
|
||||||
spool: { size: 1 },
|
spool: { size: 1 },
|
||||||
});
|
});
|
||||||
const payload = await buildEscPosKitchenTicket(this, this.pos, orderChange, cancelled);
|
const payload = await buildEscPosKitchenTicket(this, this.pos, orderChange, cancelled);
|
||||||
await enqueueQzPrint(() => qz.print(config, [{
|
await qz.print(config, [{
|
||||||
type: "raw",
|
type: "raw",
|
||||||
format: "command",
|
format: "command",
|
||||||
flavor: "base64",
|
flavor: "base64",
|
||||||
data: payload,
|
data: payload,
|
||||||
}]));
|
}]);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("QZ Tray Kitchen Print Error:", err);
|
console.error("QZ Tray Kitchen Print Error:", err);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user