refactor(pos): make buildEposDeliveryXml block-driven

This commit is contained in:
Dave 2026-09-04 13:28:45 +02:00
parent 81197ad641
commit 317b79ed29
2 changed files with 54 additions and 31 deletions

View file

@ -172,29 +172,51 @@ window.EposPrint = (function () {
}
}
// Mirrors the delivery-note ESC/POS builder in receipt.html's
// printDelivery() — same fields, same "Include prices" toggle.
function buildEposDeliveryXml(s, showPrices) {
const parts = [];
parts.push('<text align="center" width="1" height="2">WFSALES POS&#10;</text>');
parts.push('<text align="center">Delivery Note&#10;</text>');
if (s.customer && s.customer.company) parts.push(`<text align="left">Company: ${xmlEscape(s.customer.company)}&#10;</text>`);
parts.push(`<text align="left">Customer: ${xmlEscape((s.customer && s.customer.name) || '—')}&#10;</text>`);
const d = new Date(s.createdAt);
parts.push(`<text align="left">Date: ${xmlEscape(d.toLocaleDateString('en-ZA'))}&#10;</text>`);
parts.push('<text align="left">--------------------------------&#10;</text>');
const itemLines = s.items.map(it => {
const qtyLine = ` ${it.qty} ${it.unit || 'ea'}`;
const priceSuffix = showPrices ? ` R ${((it.selling_price || 0) * it.qty).toFixed(2)}` : '';
return `${xmlEscape(it.description || it.sku)}&#10;${xmlEscape(qtyLine + priceSuffix)}&#10;`;
}).join('');
parts.push(`<text align="left">${itemLines}</text>`);
parts.push('<text align="left">--------------------------------&#10;</text>');
if (showPrices) parts.push(`<text align="left">TOTAL: R ${s.total.toFixed(2)}&#10;</text>`);
parts.push('<text align="left">Received by: __________ Driver: __________&#10;</text>');
parts.push('<feed line="3"/>');
parts.push('<cut type="feed"/>');
return `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><epos-print xmlns="http://www.epson-pos.com/schemas/2011/03/epos-print">${parts.join('')}</epos-print></s:Body></s:Envelope>`;
function deliveryBlockRenderers(s, showPricing) {
return {
header_title: function (block) {
const title = block.text || 'WFSALES POS';
return `<text align="center" width="1" height="2">${xmlEscape(title)}&#10;</text><text align="center">Delivery Note&#10;</text>`;
},
customer: function () {
let out = '';
if (s.customer && s.customer.company) out += `<text align="left">Company: ${xmlEscape(s.customer.company)}&#10;</text>`;
out += `<text align="left">Customer: ${xmlEscape((s.customer && s.customer.name) || '—')}&#10;</text>`;
return out;
},
date: function () {
const d = new Date(s.createdAt);
return `<text align="left">Date: ${xmlEscape(d.toLocaleDateString('en-ZA'))}&#10;</text>`;
},
items: function () {
const itemLines = s.items.map(function (it) {
const qtyLine = ` ${it.qty} ${it.unit || 'ea'}`;
const priceSuffix = showPricing ? ` R ${((it.selling_price || 0) * it.qty).toFixed(2)}` : '';
return `${xmlEscape(it.description || it.sku)}&#10;${xmlEscape(qtyLine + priceSuffix)}&#10;`;
}).join('');
return `<text align="left">--------------------------------&#10;${itemLines}</text>`;
},
totals: function () {
let out = '<text align="left">--------------------------------&#10;</text>';
if (showPricing) out += `<text align="left">TOTAL: R ${s.total.toFixed(2)}&#10;</text>`;
return out;
},
signature: function () {
return '<text align="left">Received by: __________ Driver: __________&#10;</text>';
},
};
}
// `blocks` is the resolved block list for 'delivery'. `showPricing`
// is the combined "Show pricing" setting — it gates the per-item
// price and the totals block together (see slip-layout.js).
function buildEposDeliveryXml(s, blocks, showPricing) {
const renderers = deliveryBlockRenderers(s, showPricing);
const body = blocks
.filter(function (b) { return b.enabled; })
.map(function (b) { return renderers[b.id] ? renderers[b.id](b) : ''; })
.join('');
return `<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><epos-print xmlns="http://www.epson-pos.com/schemas/2011/03/epos-print">${body}<feed line="3"/><cut type="feed"/></epos-print></s:Body></s:Envelope>`;
}
// Mirrors the cashup page's "Print cash-up" button. Renders an

View file

@ -532,12 +532,11 @@ async function printThermalNetwork(settings){
// / card / EFT get the standard sale slip.
let xml, successLabel;
if (sale.payment === 'account') {
// Logos don't always render cleanly on the TM-T20II at small
// sizes; the delivery note skips the logo so the address + items
// get the most space. If you want the logo back, set `logo`
// here and pass it to buildEposDeliveryXml the same way
// buildEposXml takes it.
xml = window.EposPrint.buildEposDeliveryXml(sale);
const letterheadId = sale.letterhead && sale.letterhead.id;
const deliveryBlocks = window.PosSlipLayout.getBlocksForPrint(letterheadId, 'delivery');
const dnToggle = document.getElementById('dnShowPrices');
const showPricing = dnToggle ? !!dnToggle.checked : window.PosSlipLayout.getShowPricingForPrint(letterheadId);
xml = window.EposPrint.buildEposDeliveryXml(sale, deliveryBlocks, showPricing);
successLabel = 'Delivery note printed · ';
} else {
const logo = await window.EposPrint.imageToRaster('/wfdash-logo.png', 384).catch(()=>null);
@ -685,10 +684,12 @@ async function printDelivery(){
btn.disabled = false;
return;
}
const letterheadIdNet = sale.letterhead && sale.letterhead.id;
const dnToggleNet = document.getElementById('dnShowPrices');
const showPricesNet = dnToggleNet ? !!dnToggleNet.checked : true;
const showPricesNet = dnToggleNet ? !!dnToggleNet.checked : window.PosSlipLayout.getShowPricingForPrint(letterheadIdNet);
try{
const xml = window.EposPrint.buildEposDeliveryXml(sale, showPricesNet);
const deliveryBlocksNet = window.PosSlipLayout.getBlocksForPrint(letterheadIdNet, 'delivery');
const xml = window.EposPrint.buildEposDeliveryXml(sale, deliveryBlocksNet, showPricesNet);
await window.EposPrint.printViaNetwork(printerSettings.ip, xml);
status.textContent = 'Delivery note printed · ' + printerSettings.ip;
}catch(e){