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}`;
|
||||
}
|
||||
|
||||
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) {
|
||||
const clean = cleanText(text);
|
||||
if (clean.length <= width) {
|
||||
@ -139,6 +188,27 @@ class EscPosBuilder {
|
||||
separator(char = "-") {
|
||||
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) {
|
||||
@ -215,9 +285,8 @@ function addWrappedLine(builder, left, right = "", indent = 0) {
|
||||
|
||||
function addOrderLines(builder, order, currency) {
|
||||
builder.bold(true);
|
||||
builder.line(leftRight("ITEM", "TOTAL", builder.columns));
|
||||
builder.line(leftRight("Item", "Price", builder.columns));
|
||||
builder.bold(false);
|
||||
builder.separator("-");
|
||||
|
||||
for (const line of order?.get_orderlines?.() || []) {
|
||||
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 || [];
|
||||
if (!taxes.length) {
|
||||
return;
|
||||
}
|
||||
builder.separator("-");
|
||||
builder.bold(true);
|
||||
builder.line("TAX SUMMARY");
|
||||
builder.bold(false);
|
||||
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));
|
||||
@ -271,68 +336,78 @@ async function buildEscPosReceipt(order, pos) {
|
||||
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.size("doubleHeight");
|
||||
builder.line(company.name || "Chennora");
|
||||
builder.size("normal");
|
||||
builder.line(companyName);
|
||||
builder.bold(false);
|
||||
if (company.street) builder.line(company.street);
|
||||
if (company.city) builder.line(company.city);
|
||||
if (company.phone) builder.line(`Tel: ${company.phone}`);
|
||||
if (company.email) builder.line(company.email);
|
||||
if (company.website) builder.line(company.website);
|
||||
if (config.receipt_header) {
|
||||
builder.line("");
|
||||
builder.lines(wrapText(config.receipt_header, columns).map((line) => center(line, columns)));
|
||||
|
||||
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("=");
|
||||
if (receiptData.name) builder.line(leftRight("Receipt", receiptData.name, columns));
|
||||
if (receiptData.date) builder.line(leftRight("Date", receiptData.date, columns));
|
||||
if (receiptData.cashier || receiptData.headerData?.cashier) {
|
||||
builder.line(leftRight("Served by", receiptData.cashier || receiptData.headerData.cashier, columns));
|
||||
}
|
||||
if (order?.table?.name) {
|
||||
builder.line(leftRight("Table", order.table.name, columns));
|
||||
}
|
||||
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", order.customer_count, columns));
|
||||
builder.line(leftRight("Guests Served", order.customer_count, columns));
|
||||
}
|
||||
if (receiptData.order_source_label) {
|
||||
builder.line(leftRight("Source", receiptData.order_source_label, columns));
|
||||
}
|
||||
if (receiptData.fulfilment_type_label) {
|
||||
builder.line(leftRight("Fulfilment", receiptData.fulfilment_type_label, columns));
|
||||
if (table?.name) {
|
||||
builder.line(leftRight("Table", table.name, columns));
|
||||
}
|
||||
|
||||
builder.separator("=");
|
||||
addOrderLines(builder, order, currency);
|
||||
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;
|
||||
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) {
|
||||
builder.line(leftRight("Discounts", formatMoney(receiptData.total_discount, currency), columns));
|
||||
}
|
||||
builder.bold(true);
|
||||
builder.size("doubleHeight");
|
||||
builder.line(leftRight("TOTAL", formatMoney(order?.get_total_with_tax?.(), currency), columns));
|
||||
builder.size("normal");
|
||||
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);
|
||||
}
|
||||
addTaxes(builder, receiptData, currency);
|
||||
addTaxSummary(builder, receiptData, currency);
|
||||
|
||||
if (config.receipt_footer) {
|
||||
builder.separator("-");
|
||||
@ -344,11 +419,12 @@ async function buildEscPosReceipt(order, pos) {
|
||||
builder.separator("-");
|
||||
builder.align("center");
|
||||
builder.bold(true);
|
||||
builder.line("THANK YOU");
|
||||
builder.line("* PAID *");
|
||||
builder.bold(false);
|
||||
builder.line("Please visit again");
|
||||
builder.line("");
|
||||
builder.line("Powered by Dine360");
|
||||
builder.qrCode(qrValue);
|
||||
builder.line("");
|
||||
builder.line("Powered by dind360");
|
||||
builder.align("left");
|
||||
builder.feed(END_FEED_LINES);
|
||||
if (config.qz_enable_cutter !== false) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user