From 3a28e7ac084f8c0eb126550648c443e06e655742 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Mon, 1 Jun 2026 10:35:08 +0700 Subject: [PATCH] refactor: improve printer cut reliability, add buffer flushing, and enhance receipt metadata parsing for Odoo 19 --- static/src/js/escpos_generator.js | 5 +- static/src/js/escpos_graphics.js | 5 +- static/src/js/pos_receipt_printer.js | 69 ++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/static/src/js/escpos_generator.js b/static/src/js/escpos_generator.js index 1ba1dc2..95e1355 100755 --- a/static/src/js/escpos_generator.js +++ b/static/src/js/escpos_generator.js @@ -69,6 +69,7 @@ export class EscPosGenerator { const cmds = []; for (let i = 0; i < lines; i++) cmds.push(...FEED_LINE); cmds.push(...CUT_PAPER); + cmds.push(...INIT); // Force printer buffer flush and reset return new Uint8Array(cmds); } @@ -337,8 +338,8 @@ export class EscPosGenerator { } } - // Feed and cut (set feed to 4 lines to ensure the footer clears the cutter blade before cutting) - cmds.push(...this.feedAndCut(4)); + // Feed and cut (set feed to 6 lines to ensure the footer clears the tear bar) + cmds.push(...this.feedAndCut(6)); return new Uint8Array(cmds); } diff --git a/static/src/js/escpos_graphics.js b/static/src/js/escpos_graphics.js index 49e3a7f..5e9e85a 100755 --- a/static/src/js/escpos_graphics.js +++ b/static/src/js/escpos_graphics.js @@ -55,7 +55,7 @@ export class EscPosGraphics { commands.push(...rasterCommands); // Feed paper and cut - commands.push(...this._feedAndCut(4)); + commands.push(...this._feedAndCut(6)); const result = new Uint8Array(commands); @@ -221,6 +221,9 @@ export class EscPosGraphics { // m = 0 (full cut), 1 (partial cut) commands.push(GS, 0x56, 0x00); + // Initialize printer to force buffer flush and reset + commands.push(ESC, 0x40); + return commands; } diff --git a/static/src/js/pos_receipt_printer.js b/static/src/js/pos_receipt_printer.js index 69f6004..29bdd52 100755 --- a/static/src/js/pos_receipt_printer.js +++ b/static/src/js/pos_receipt_printer.js @@ -682,19 +682,31 @@ patch(PosPrinterService.prototype, { } catch (_) { dateStr = new Date().toLocaleString(); } // Table name — from pos_restaurant.ReceiptHeader patch - // order.table_id or order.self_ordering_table_id → table.table_number let tableName = ''; - const table = order.table_id || order.self_ordering_table_id || null; + let table = order.table_id || order.self_ordering_table_id || null; if (table) { - if (order.customer_count) { - tableName = `Table ${table.table_number}, Guests: ${order.customer_count}`; - } else { - tableName = `Table ${table.table_number}`; + if (typeof table === 'number' || typeof table === 'string') { + const tableId = parseInt(table); + if (pos && pos.models && pos.models['restaurant.table']) { + table = pos.models['restaurant.table'].get(tableId) || { table_number: tableId }; + } else if (pos && pos.tables) { + table = pos.tables.find(t => t.id === tableId) || { table_number: tableId }; + } else { + table = { table_number: tableId }; + } + } + const number = table.table_number || table.name || ''; + if (number) { + if (order.customer_count) { + tableName = `Table ${number}, Guests: ${order.customer_count}`; + } else { + tableName = `Table ${number}`; + } } } const orderData = { - orderName: order.pos_reference || order.name || '', + orderName: (typeof order.getName === 'function' ? order.getName() : '') || order.pos_reference || order.name || '', date: dateStr || new Date().toLocaleString(), cashier: formatFirstLastName((typeof order.getCashierName === 'function' ? order.getCashierName() : '') || ''), customer: order.partner_id?.name || null, @@ -834,13 +846,52 @@ patch(PosPrinterService.prototype, { taxId: getText('.pos-receipt-tax-id') || '' }; + // Helper to find text by pattern in any descendant element, prioritizing the most specific (deepest/shortest) match + const findTextByPattern = (pattern) => { + const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT, null, false); + let bestText = ''; + + while (walker.nextNode()) { + const node = walker.currentNode; + // Exclude script, style, and large container elements + if (['SCRIPT', 'STYLE', 'UL', 'OL', 'TBODY', 'TABLE', 'HTML', 'BODY', 'DIV.pos-receipt'].includes(node.tagName)) { + continue; + } + const text = node.textContent.trim(); + if (pattern.test(text)) { + // We want the most specific leaf node, which has the shortest text containing the pattern + if (!bestText || text.length < bestText.length) { + bestText = text; + } + } + } + return bestText; + }; + // Parse order info // Odoo 19: order reference is in .pos-receipt-vat, date in #order-date, cashier in .cashier + let orderName = getText('.pos-receipt-vat') || getText('.pos-receipt-order-name') || getText('.order-name') || getText('.pos-receipt-order-data') || ''; + if (!orderName) { + const possibleOrderText = findTextByPattern(/\b(Order|Ticket|Ref|Invoice)\b/i); + if (possibleOrderText) { + orderName = possibleOrderText.split('\n')[0].trim(); + } + } + + let tableName = getText('.pos-receipt-table') || getText('.table-name') || ''; + if (!tableName) { + const possibleTableText = findTextByPattern(/\bTable\s+\d+/i) || findTextByPattern(/\bTable\b/i); + if (possibleTableText) { + tableName = possibleTableText.split('\n')[0].trim(); + } + } + const orderData = { - orderName: getText('.pos-receipt-vat') || getText('.pos-receipt-order-name') || getText('.order-name') || getText('.pos-receipt-order-data') || '', + orderName: orderName, date: getText('#order-date') || getText('.pos-receipt-date') || new Date().toLocaleString(), cashier: formatFirstLastName(getText('.cashier') || getText('.pos-receipt-cashier') || ''), - customer: getText('.pos-receipt-customer') || getText('.customer') || null + customer: getText('.pos-receipt-customer') || getText('.customer') || null, + tableName: tableName }; // Parse order lines