Print QZ receipts as raw ESC POS
This commit is contained in:
parent
66af3e010a
commit
43284d9f1a
@ -3,6 +3,55 @@
|
|||||||
import { patch } from "@web/core/utils/patch";
|
import { patch } from "@web/core/utils/patch";
|
||||||
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
|
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
|
||||||
|
|
||||||
|
const RECEIPT_COLUMNS = 42;
|
||||||
|
|
||||||
|
function normalizeReceiptText(text) {
|
||||||
|
return (text || "")
|
||||||
|
.replace(/\u00a0/g, " ")
|
||||||
|
.replace(/[ \t]+/g, " ")
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapLine(line, width = RECEIPT_COLUMNS) {
|
||||||
|
if (line.length <= width) {
|
||||||
|
return [line];
|
||||||
|
}
|
||||||
|
const wrapped = [];
|
||||||
|
let remaining = line;
|
||||||
|
while (remaining.length > width) {
|
||||||
|
let breakpoint = remaining.lastIndexOf(" ", width);
|
||||||
|
if (breakpoint <= 0) {
|
||||||
|
breakpoint = width;
|
||||||
|
}
|
||||||
|
wrapped.push(remaining.slice(0, breakpoint).trimEnd());
|
||||||
|
remaining = remaining.slice(breakpoint).trimStart();
|
||||||
|
}
|
||||||
|
if (remaining) {
|
||||||
|
wrapped.push(remaining);
|
||||||
|
}
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEscPosReceipt(receiptElement) {
|
||||||
|
const ESC = "\x1B";
|
||||||
|
const GS = "\x1D";
|
||||||
|
const lines = normalizeReceiptText(receiptElement.innerText);
|
||||||
|
const body = lines.flatMap((line) => wrapLine(line)).join("\n");
|
||||||
|
|
||||||
|
return [
|
||||||
|
ESC + "@", // Initialize printer
|
||||||
|
ESC + "a" + "\x01", // Center
|
||||||
|
ESC + "E" + "\x01", // Bold on
|
||||||
|
body,
|
||||||
|
ESC + "E" + "\x00", // Bold off
|
||||||
|
ESC + "a" + "\x00", // Left align
|
||||||
|
"\n\n\n",
|
||||||
|
GS + "V" + "\x00", // Full cut on Epson-compatible printers
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
|
||||||
patch(ReceiptScreen.prototype, {
|
patch(ReceiptScreen.prototype, {
|
||||||
async printReceipt() {
|
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_printer_name) {
|
||||||
@ -17,52 +66,24 @@ patch(ReceiptScreen.prototype, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const printerName = this.pos.config.qz_printer_name;
|
const printerName = this.pos.config.qz_printer_name;
|
||||||
const config = qz.configs.create(printerName);
|
const config = qz.configs.create(printerName, {
|
||||||
|
encoding: "CP437",
|
||||||
|
spool: { end: "\n" },
|
||||||
|
});
|
||||||
|
|
||||||
// Get inner HTML of receipt
|
const receiptElement = document.querySelector(".pos-receipt") || document.querySelector(".pos-receipt-container");
|
||||||
const receiptElement = document.querySelector('.pos-receipt-container');
|
|
||||||
if (!receiptElement) {
|
if (!receiptElement) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiptHtml = receiptElement.innerHTML;
|
const printData = buildEscPosReceipt(receiptElement);
|
||||||
|
|
||||||
// Odoo receipt styling is necessary for QZ pixel print
|
|
||||||
const printData = `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
@page { margin: 0; }
|
|
||||||
body {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 300px; /* Adjust based on printer paper width, 80mm = ~300px */
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
.pos-receipt { width: 100%; text-align: center; }
|
|
||||||
.pos-receipt .pos-receipt-logo { max-width: 50%; margin: 0 auto; }
|
|
||||||
.pos-receipt .pos-receipt-contact { text-align: center; font-size: 12px; margin-bottom: 10px; }
|
|
||||||
.pos-receipt .receipt-orderlines { width: 100%; text-align: left; }
|
|
||||||
.pos-receipt .receipt-orderlines td { padding: 2px 0; }
|
|
||||||
.pos-receipt .receipt-total { width: 100%; font-weight: bold; font-size: 16px; margin-top: 10px; text-align: right; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="pos-receipt">
|
|
||||||
${receiptHtml}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await qz.print(config, [
|
await qz.print(config, [
|
||||||
{
|
{
|
||||||
type: 'pixel',
|
type: "raw",
|
||||||
format: 'html',
|
format: "plain",
|
||||||
flavor: 'plain',
|
flavor: "plain",
|
||||||
data: printData
|
data: printData,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user