refactor: improve printer cut reliability, add buffer flushing, and enhance receipt metadata parsing for Odoo 19

This commit is contained in:
Suherdy Yacob 2026-06-01 10:35:08 +07:00
parent e71f979e3b
commit 3a28e7ac08
3 changed files with 67 additions and 12 deletions

View File

@ -69,6 +69,7 @@ export class EscPosGenerator {
const cmds = []; const cmds = [];
for (let i = 0; i < lines; i++) cmds.push(...FEED_LINE); for (let i = 0; i < lines; i++) cmds.push(...FEED_LINE);
cmds.push(...CUT_PAPER); cmds.push(...CUT_PAPER);
cmds.push(...INIT); // Force printer buffer flush and reset
return new Uint8Array(cmds); 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) // Feed and cut (set feed to 6 lines to ensure the footer clears the tear bar)
cmds.push(...this.feedAndCut(4)); cmds.push(...this.feedAndCut(6));
return new Uint8Array(cmds); return new Uint8Array(cmds);
} }

View File

@ -55,7 +55,7 @@ export class EscPosGraphics {
commands.push(...rasterCommands); commands.push(...rasterCommands);
// Feed paper and cut // Feed paper and cut
commands.push(...this._feedAndCut(4)); commands.push(...this._feedAndCut(6));
const result = new Uint8Array(commands); const result = new Uint8Array(commands);
@ -221,6 +221,9 @@ export class EscPosGraphics {
// m = 0 (full cut), 1 (partial cut) // m = 0 (full cut), 1 (partial cut)
commands.push(GS, 0x56, 0x00); commands.push(GS, 0x56, 0x00);
// Initialize printer to force buffer flush and reset
commands.push(ESC, 0x40);
return commands; return commands;
} }

View File

@ -682,19 +682,31 @@ patch(PosPrinterService.prototype, {
} catch (_) { dateStr = new Date().toLocaleString(); } } catch (_) { dateStr = new Date().toLocaleString(); }
// Table name — from pos_restaurant.ReceiptHeader patch // Table name — from pos_restaurant.ReceiptHeader patch
// order.table_id or order.self_ordering_table_id → table.table_number
let tableName = ''; 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 (table) {
if (order.customer_count) { if (typeof table === 'number' || typeof table === 'string') {
tableName = `Table ${table.table_number}, Guests: ${order.customer_count}`; const tableId = parseInt(table);
} else { if (pos && pos.models && pos.models['restaurant.table']) {
tableName = `Table ${table.table_number}`; 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 = { 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(), date: dateStr || new Date().toLocaleString(),
cashier: formatFirstLastName((typeof order.getCashierName === 'function' ? order.getCashierName() : '') || ''), cashier: formatFirstLastName((typeof order.getCashierName === 'function' ? order.getCashierName() : '') || ''),
customer: order.partner_id?.name || null, customer: order.partner_id?.name || null,
@ -834,13 +846,52 @@ patch(PosPrinterService.prototype, {
taxId: getText('.pos-receipt-tax-id') || '' 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 // Parse order info
// Odoo 19: order reference is in .pos-receipt-vat, date in #order-date, cashier in .cashier // 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 = { 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(), date: getText('#order-date') || getText('.pos-receipt-date') || new Date().toLocaleString(),
cashier: formatFirstLastName(getText('.cashier') || getText('.pos-receipt-cashier') || ''), 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 // Parse order lines