refactor(pos): make buildEposCashupXml block-driven, fix broken logo image tag
Also fixes a logo-ordering bug this surfaced: slip-layout.js's sale and cashup defaults put header_title before logo, which (once a real logo renders instead of the always-empty placeholder used in earlier regression tests) reversed the original image-then-title print order. Reordered both to logo-first and added the missing logo block to cashup's default catalog, since cashup's original code already attempted (brokenly) to print one.
This commit is contained in:
parent
317b79ed29
commit
d47394723b
3 changed files with 83 additions and 63 deletions
|
|
@ -444,7 +444,11 @@ async function printCashup(){
|
|||
denoms,
|
||||
};
|
||||
const settings = window.EposPrint.getSettings();
|
||||
const xml = window.EposPrint.buildEposCashupXml(letterhead, report);
|
||||
const cashupBlocks = window.PosSlipLayout.getBlocksForPrint(letterhead.id, 'cashup');
|
||||
const cashupLogo = letterhead.logo_url
|
||||
? await window.EposPrint.imageToRaster(letterhead.logo_url, 384).catch(() => null)
|
||||
: null;
|
||||
const xml = window.EposPrint.buildEposCashupXml(letterhead, report, cashupBlocks, cashupLogo);
|
||||
if(settings.type === 'network'){
|
||||
if(!settings.ip){
|
||||
alert('Set the printer IP address in Printer settings first.');
|
||||
|
|
|
|||
|
|
@ -236,69 +236,84 @@ window.EposPrint = (function () {
|
|||
// variance: number,
|
||||
// denoms: [{ label: "R200", value: 200, count: 2 }, ...],
|
||||
// }
|
||||
// `letterhead` is the same shape as buildEposXml/Xml takes.
|
||||
function buildEposCashupXml(letterhead, report) {
|
||||
function cashupBlockRenderers(lh, r, logo) {
|
||||
return {
|
||||
header_title: function (block) {
|
||||
const title = block.text || 'WFSALES POS';
|
||||
return '<feed line="1"/>'
|
||||
+ `<text align="center" width="2" height="2">${xmlEscape(title)} </text>`
|
||||
+ '<text align="center" width="1" height="1">CASH-UP REPORT </text>';
|
||||
},
|
||||
logo: function () {
|
||||
return logo ? `<image width="${logo.width}" height="${logo.height}" color="color_1" mode="mono" align="center">${logo.base64}</image>` : '';
|
||||
},
|
||||
business_info: function () {
|
||||
const parts = [];
|
||||
if (lh.display_name) parts.push(`<text align="center">${xmlEscape(lh.display_name)} </text>`);
|
||||
if (lh.address) parts.push(`<text align="center">${xmlEscape(lh.address.split('\n')[0])} </text>`);
|
||||
if (lh.phone) parts.push(`<text align="center">Tel: ${xmlEscape(lh.phone)} </text>`);
|
||||
if (lh.vat_number) parts.push(`<text align="center">VAT: ${xmlEscape(lh.vat_number)} </text>`);
|
||||
return parts.join('');
|
||||
},
|
||||
shift_date: function () {
|
||||
let out = '<text align="left">-------------------------------- </text>';
|
||||
if (r.shiftLabel || r.dateLabel) out += `<text align="left">Shift: ${xmlEscape(r.shiftLabel || '')} Date: ${xmlEscape(r.dateLabel || '')} </text>`;
|
||||
if (r.counts) out += `<text align="left">Total sales: ${r.counts.cash + r.counts.card + r.counts.eft + r.counts.account} </text>`;
|
||||
return out;
|
||||
},
|
||||
payment_breakdown: function () {
|
||||
const lines = [
|
||||
`Cash ${pad((r.counts && r.counts.cash) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.cash) || 0)}`,
|
||||
`Card ${pad((r.counts && r.counts.card) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.card) || 0)}`,
|
||||
`EFT ${pad((r.counts && r.counts.eft) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.eft) || 0)}`,
|
||||
`Acct ${pad((r.counts && r.counts.account) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.account) || 0)}`,
|
||||
];
|
||||
return '<text align="left">-------------------------------- </text>'
|
||||
+ '<text align="left">PAYMENT METHOD BREAKDOWN </text>'
|
||||
+ `<text align="left">${lines.join(' ')} </text>`;
|
||||
},
|
||||
grand_total: function () {
|
||||
return '<text align="left">-------------------------------- </text>'
|
||||
+ `<text align="left" em="true">GRAND TOTAL${' '.repeat(12)}R ${fmtMoney(r.grand || 0)} </text>`;
|
||||
},
|
||||
cash_reconciliation: function () {
|
||||
return '<text align="left">-------------------------------- </text>'
|
||||
+ '<text align="left">CASH RECONCILIATION </text>'
|
||||
+ `<text align="left">Cash sales: R ${fmtMoney((r.totals && r.totals.cash) || 0)} </text>`
|
||||
+ `<text align="left">Cash counted: R ${fmtMoney(r.cashCounted || 0)} </text>`
|
||||
+ `<text align="left" em="${((r.variance || 0) === 0) ? 'true' : 'false'}">Variance: R ${fmtMoney(r.variance || 0)} </text>`;
|
||||
},
|
||||
denominations: function () {
|
||||
if (!Array.isArray(r.denoms) || !r.denoms.length) return '';
|
||||
const dlines = r.denoms
|
||||
.filter(function (d) { return (d.count || 0) > 0; })
|
||||
.map(function (d) { return `${d.label} x ${pad(d.count || 0, 3)} = R ${fmtMoney((d.value || 0) * (d.count || 0))}`; });
|
||||
if (!dlines.length) return '';
|
||||
return '<text align="left">-------------------------------- </text>'
|
||||
+ '<text align="left">DENOMINATIONS COUNTED </text>'
|
||||
+ `<text align="left">${dlines.join(' ')} </text>`;
|
||||
},
|
||||
footer: function (block) {
|
||||
const text = block.text || lh.footer_text;
|
||||
if (!text) return '';
|
||||
return '<text align="left">-------------------------------- </text>'
|
||||
+ `<text align="center">${xmlEscape(text)} </text>`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// `blocks` is the resolved block list for 'cashup'. `logo` is a
|
||||
// pre-rasterized {width,height,base64} (see imageToRaster) or null —
|
||||
// rasterizing is async, so the caller does it before calling this.
|
||||
function buildEposCashupXml(letterhead, report, blocks, logo) {
|
||||
const lh = letterhead || {};
|
||||
const r = report || {};
|
||||
const parts = [];
|
||||
if (lh.logo_url) {
|
||||
parts.push(`<image width="384" height="auto" color="color_1" mode="mono" align="center">${(typeof window !== 'undefined' ? '' : '')}</image>`);
|
||||
}
|
||||
parts.push('<feed line="1"/>');
|
||||
parts.push('<text align="center" width="2" height="2">WFSALES POS </text>');
|
||||
parts.push('<text align="center" width="1" height="1">CASH-UP REPORT </text>');
|
||||
if (lh.display_name) {
|
||||
parts.push(`<text align="center">${xmlEscape(lh.display_name)} </text>`);
|
||||
}
|
||||
if (lh.address) {
|
||||
parts.push(`<text align="center">${xmlEscape(lh.address.split('\n')[0])} </text>`);
|
||||
}
|
||||
if (lh.phone) {
|
||||
parts.push(`<text align="center">Tel: ${xmlEscape(lh.phone)} </text>`);
|
||||
}
|
||||
if (lh.vat_number) {
|
||||
parts.push(`<text align="center">VAT: ${xmlEscape(lh.vat_number)} </text>`);
|
||||
}
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
if (r.shiftLabel || r.dateLabel) {
|
||||
parts.push(`<text align="left">Shift: ${xmlEscape(r.shiftLabel || '')} Date: ${xmlEscape(r.dateLabel || '')} </text>`);
|
||||
}
|
||||
if (r.counts) {
|
||||
parts.push(`<text align="left">Total sales: ${r.counts.cash + r.counts.card + r.counts.eft + r.counts.account} </text>`);
|
||||
}
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
parts.push('<text align="left">PAYMENT METHOD BREAKDOWN </text>');
|
||||
const lines = [
|
||||
`Cash ${pad((r.counts && r.counts.cash) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.cash) || 0)}`,
|
||||
`Card ${pad((r.counts && r.counts.card) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.card) || 0)}`,
|
||||
`EFT ${pad((r.counts && r.counts.eft) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.eft) || 0)}`,
|
||||
`Acct ${pad((r.counts && r.counts.account) || 0, 3)} sales R ${fmtMoney((r.totals && r.totals.account) || 0)}`,
|
||||
];
|
||||
parts.push(`<text align="left">${lines.join(' ')} </text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
parts.push(`<text align="left" em="true">GRAND TOTAL${' '.repeat(12)}R ${fmtMoney(r.grand || 0)} </text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
parts.push('<text align="left">CASH RECONCILIATION </text>');
|
||||
parts.push(`<text align="left">Cash sales: R ${fmtMoney((r.totals && r.totals.cash) || 0)} </text>`);
|
||||
parts.push(`<text align="left">Cash counted: R ${fmtMoney(r.cashCounted || 0)} </text>`);
|
||||
parts.push(`<text align="left" em="${((r.variance || 0) === 0) ? 'true' : 'false'}">Variance: R ${fmtMoney(r.variance || 0)} </text>`);
|
||||
if (Array.isArray(r.denoms) && r.denoms.length) {
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
parts.push('<text align="left">DENOMINATIONS COUNTED </text>');
|
||||
const dlines = r.denoms
|
||||
.filter(d => (d.count || 0) > 0)
|
||||
.map(d => `${d.label} x ${pad(d.count || 0, 3)} = R ${fmtMoney((d.value || 0) * (d.count || 0))}`);
|
||||
if (dlines.length) {
|
||||
parts.push(`<text align="left">${dlines.join(' ')} </text>`);
|
||||
}
|
||||
}
|
||||
if (lh.footer_text) {
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
parts.push(`<text align="center">${xmlEscape(lh.footer_text)} </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>`;
|
||||
const renderers = cashupBlockRenderers(lh, r, logo || null);
|
||||
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>`;
|
||||
}
|
||||
|
||||
// Small helper for padded numbers in the cashup slip.
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ window.PosSlipLayout = (function () {
|
|||
// consistency with the other two slip types.
|
||||
const DEFAULTS = {
|
||||
sale: [
|
||||
{ id: 'header_title', enabled: true, text: null },
|
||||
{ id: 'logo', enabled: true },
|
||||
{ id: 'header_title', enabled: true, text: null },
|
||||
{ id: 'business_info', enabled: true },
|
||||
{ id: 'date_slip', enabled: true },
|
||||
{ id: 'customer', enabled: true },
|
||||
|
|
@ -63,6 +63,7 @@ window.PosSlipLayout = (function () {
|
|||
{ id: 'signature', enabled: true },
|
||||
],
|
||||
cashup: [
|
||||
{ id: 'logo', enabled: true },
|
||||
{ id: 'header_title', enabled: true, text: null },
|
||||
{ id: 'business_info', enabled: true },
|
||||
{ id: 'shift_date', enabled: true },
|
||||
|
|
|
|||
Loading…
Reference in a new issue