forked from alaguraj/odoo-testing-addons
Update QZ receipt print layout
This commit is contained in:
parent
c94c1f06eb
commit
bbc7603c5b
@ -53,6 +53,55 @@ function leftRight(left, right, width) {
|
|||||||
return `${lhs}${" ".repeat(space)}${rhs}`;
|
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) {
|
||||||
|
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) {
|
||||||
@ -139,6 +188,27 @@ class EscPosBuilder {
|
|||||||
separator(char = "-") {
|
separator(char = "-") {
|
||||||
this.line(char.repeat(this.columns));
|
this.line(char.repeat(this.columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qrCode(value) {
|
||||||
|
const data = cleanText(value);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bytes = Array.from(data).map((char) => char.charCodeAt(0) & 0xff);
|
||||||
|
const storeLength = bytes.length + 3;
|
||||||
|
this.align("center");
|
||||||
|
this.raw(GS, 0x28, 0x6b, 0x04, 0x00, 0x31, 0x41, 0x32, 0x00);
|
||||||
|
this.raw(GS, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x43, 0x06);
|
||||||
|
this.raw(GS, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x45, 0x31);
|
||||||
|
this.raw(
|
||||||
|
GS, 0x28, 0x6b,
|
||||||
|
storeLength & 0xff,
|
||||||
|
(storeLength >> 8) & 0xff,
|
||||||
|
0x31, 0x50, 0x30,
|
||||||
|
...bytes
|
||||||
|
);
|
||||||
|
this.raw(GS, 0x28, 0x6b, 0x03, 0x00, 0x31, 0x51, 0x30);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLogoRasterBytes(pos, columns) {
|
async function getLogoRasterBytes(pos, columns) {
|
||||||
@ -215,9 +285,8 @@ function addWrappedLine(builder, left, right = "", indent = 0) {
|
|||||||
|
|
||||||
function addOrderLines(builder, order, currency) {
|
function addOrderLines(builder, order, currency) {
|
||||||
builder.bold(true);
|
builder.bold(true);
|
||||||
builder.line(leftRight("ITEM", "TOTAL", builder.columns));
|
builder.line(leftRight("Item", "Price", builder.columns));
|
||||||
builder.bold(false);
|
builder.bold(false);
|
||||||
builder.separator("-");
|
|
||||||
|
|
||||||
for (const line of order?.get_orderlines?.() || []) {
|
for (const line of order?.get_orderlines?.() || []) {
|
||||||
const product = line.get_product?.();
|
const product = line.get_product?.();
|
||||||
@ -238,15 +307,11 @@ function addOrderLines(builder, order, currency) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTaxes(builder, receiptData, currency) {
|
function addTaxSummary(builder, receiptData, currency) {
|
||||||
const taxes = receiptData?.tax_details || [];
|
const taxes = receiptData?.tax_details || [];
|
||||||
if (!taxes.length) {
|
if (!taxes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
builder.separator("-");
|
|
||||||
builder.bold(true);
|
|
||||||
builder.line("TAX SUMMARY");
|
|
||||||
builder.bold(false);
|
|
||||||
for (const tax of taxes) {
|
for (const tax of taxes) {
|
||||||
const label = tax?.tax?.name || (tax?.tax?.amount ? `${tax.tax.amount}%` : "Tax");
|
const label = tax?.tax?.name || (tax?.tax?.amount ? `${tax.tax.amount}%` : "Tax");
|
||||||
builder.line(leftRight(label, formatMoney(tax.amount || 0, currency), builder.columns));
|
builder.line(leftRight(label, formatMoney(tax.amount || 0, currency), builder.columns));
|
||||||
@ -271,68 +336,78 @@ async function buildEscPosReceipt(order, pos) {
|
|||||||
const company = pos?.company || {};
|
const company = pos?.company || {};
|
||||||
const receiptData = order?.export_for_printing?.() || {};
|
const receiptData = order?.export_for_printing?.() || {};
|
||||||
const builder = new EscPosBuilder(columns);
|
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.init();
|
||||||
builder.raw(...(await getLogoRasterBytes(pos, columns)));
|
builder.raw(...(await getLogoRasterBytes(pos, columns)));
|
||||||
builder.align("center");
|
builder.align("center");
|
||||||
builder.bold(true);
|
builder.bold(true);
|
||||||
builder.size("doubleHeight");
|
builder.line(companyName);
|
||||||
builder.line(company.name || "Chennora");
|
|
||||||
builder.size("normal");
|
|
||||||
builder.bold(false);
|
builder.bold(false);
|
||||||
if (company.street) builder.line(company.street);
|
builder.line("");
|
||||||
if (company.city) builder.line(company.city);
|
|
||||||
if (company.phone) builder.line(`Tel: ${company.phone}`);
|
const address = getCompanyAddress(company);
|
||||||
if (company.email) builder.line(company.email);
|
if (address) {
|
||||||
if (company.website) builder.line(company.website);
|
builder.lines(wrapText(`Address: ${address}`, columns).map((line) => center(line, columns)));
|
||||||
if (config.receipt_header) {
|
}
|
||||||
builder.line("");
|
if (company.email) {
|
||||||
builder.lines(wrapText(config.receipt_header, columns).map((line) => center(line, columns)));
|
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.align("left");
|
||||||
builder.separator("=");
|
builder.separator("-");
|
||||||
if (receiptData.name) builder.line(leftRight("Receipt", receiptData.name, columns));
|
builder.line(leftRight("Placed", formatPlacedDate(receiptData.date), columns));
|
||||||
if (receiptData.date) builder.line(leftRight("Date", receiptData.date, columns));
|
builder.separator("-");
|
||||||
if (receiptData.cashier || receiptData.headerData?.cashier) {
|
builder.line(leftRight("Cashier", companyName, columns));
|
||||||
builder.line(leftRight("Served by", receiptData.cashier || receiptData.headerData.cashier, columns));
|
|
||||||
}
|
|
||||||
if (order?.table?.name) {
|
|
||||||
builder.line(leftRight("Table", order.table.name, columns));
|
|
||||||
}
|
|
||||||
if (order?.customer_count) {
|
if (order?.customer_count) {
|
||||||
builder.line(leftRight("Guests", order.customer_count, columns));
|
builder.line(leftRight("Guests Served", order.customer_count, columns));
|
||||||
}
|
}
|
||||||
if (receiptData.order_source_label) {
|
if (table?.name) {
|
||||||
builder.line(leftRight("Source", receiptData.order_source_label, columns));
|
builder.line(leftRight("Table", table.name, columns));
|
||||||
}
|
|
||||||
if (receiptData.fulfilment_type_label) {
|
|
||||||
builder.line(leftRight("Fulfilment", receiptData.fulfilment_type_label, columns));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.separator("=");
|
builder.separator("=");
|
||||||
addOrderLines(builder, order, currency);
|
addOrderLines(builder, order, currency);
|
||||||
builder.separator("=");
|
builder.separator("=");
|
||||||
builder.line(leftRight("Subtotal", formatMoney(order?.get_total_without_tax?.(), currency), columns));
|
builder.line(leftRight("Price", formatMoney(order?.get_total_without_tax?.(), currency), columns));
|
||||||
const tax = order?.get_total_tax?.() || 0;
|
const tax = order?.get_total_tax?.() || 0;
|
||||||
if (tax) {
|
if (tax) {
|
||||||
builder.line(leftRight("Tax", formatMoney(tax, currency), columns));
|
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) {
|
if (receiptData.total_discount) {
|
||||||
builder.line(leftRight("Discounts", formatMoney(receiptData.total_discount, currency), columns));
|
builder.line(leftRight("Discounts", formatMoney(receiptData.total_discount, currency), columns));
|
||||||
}
|
}
|
||||||
builder.bold(true);
|
builder.bold(true);
|
||||||
builder.size("doubleHeight");
|
builder.line(leftRight("Grand Total", formatMoney(order?.get_total_with_tax?.(), currency), columns));
|
||||||
builder.line(leftRight("TOTAL", formatMoney(order?.get_total_with_tax?.(), currency), columns));
|
|
||||||
builder.size("normal");
|
|
||||||
builder.bold(false);
|
builder.bold(false);
|
||||||
|
|
||||||
addPaymentLines(builder, receiptData, currency);
|
addPaymentLines(builder, receiptData, currency);
|
||||||
const change = receiptData.change || order?.get_change?.() || 0;
|
const change = receiptData.change || order?.get_change?.() || 0;
|
||||||
if (change > 0) {
|
if (change > 0) {
|
||||||
|
builder.bold(true);
|
||||||
builder.line(leftRight("Change", formatMoney(change, currency), columns));
|
builder.line(leftRight("Change", formatMoney(change, currency), columns));
|
||||||
|
builder.bold(false);
|
||||||
}
|
}
|
||||||
addTaxes(builder, receiptData, currency);
|
addTaxSummary(builder, receiptData, currency);
|
||||||
|
|
||||||
if (config.receipt_footer) {
|
if (config.receipt_footer) {
|
||||||
builder.separator("-");
|
builder.separator("-");
|
||||||
@ -344,11 +419,12 @@ async function buildEscPosReceipt(order, pos) {
|
|||||||
builder.separator("-");
|
builder.separator("-");
|
||||||
builder.align("center");
|
builder.align("center");
|
||||||
builder.bold(true);
|
builder.bold(true);
|
||||||
builder.line("THANK YOU");
|
builder.line("* PAID *");
|
||||||
builder.bold(false);
|
builder.bold(false);
|
||||||
builder.line("Please visit again");
|
|
||||||
builder.line("");
|
builder.line("");
|
||||||
builder.line("Powered by Dine360");
|
builder.qrCode(qrValue);
|
||||||
|
builder.line("");
|
||||||
|
builder.line("Powered by dind360");
|
||||||
builder.align("left");
|
builder.align("left");
|
||||||
builder.feed(END_FEED_LINES);
|
builder.feed(END_FEED_LINES);
|
||||||
if (config.qz_enable_cutter !== false) {
|
if (config.qz_enable_cutter !== false) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user