diff --git a/public/pos/cashup.html b/public/pos/cashup.html
index 8b0120b..3f60efe 100644
--- a/public/pos/cashup.html
+++ b/public/pos/cashup.html
@@ -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.');
diff --git a/public/pos/epos-print.js b/public/pos/epos-print.js
index ef5daf0..cd686f5 100644
--- a/public/pos/epos-print.js
+++ b/public/pos/epos-print.js
@@ -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 ''
+ + `${xmlEscape(title)}
`
+ + 'CASH-UP REPORT
';
+ },
+ logo: function () {
+ return logo ? `${logo.base64}` : '';
+ },
+ business_info: function () {
+ const parts = [];
+ if (lh.display_name) parts.push(`${xmlEscape(lh.display_name)}
`);
+ if (lh.address) parts.push(`${xmlEscape(lh.address.split('\n')[0])}
`);
+ if (lh.phone) parts.push(`Tel: ${xmlEscape(lh.phone)}
`);
+ if (lh.vat_number) parts.push(`VAT: ${xmlEscape(lh.vat_number)}
`);
+ return parts.join('');
+ },
+ shift_date: function () {
+ let out = '--------------------------------
';
+ if (r.shiftLabel || r.dateLabel) out += `Shift: ${xmlEscape(r.shiftLabel || '')} Date: ${xmlEscape(r.dateLabel || '')}
`;
+ if (r.counts) out += `Total sales: ${r.counts.cash + r.counts.card + r.counts.eft + r.counts.account}
`;
+ 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 '--------------------------------
'
+ + 'PAYMENT METHOD BREAKDOWN
'
+ + `${lines.join('
')}
`;
+ },
+ grand_total: function () {
+ return '--------------------------------
'
+ + `GRAND TOTAL${' '.repeat(12)}R ${fmtMoney(r.grand || 0)}
`;
+ },
+ cash_reconciliation: function () {
+ return '--------------------------------
'
+ + 'CASH RECONCILIATION
'
+ + `Cash sales: R ${fmtMoney((r.totals && r.totals.cash) || 0)}
`
+ + `Cash counted: R ${fmtMoney(r.cashCounted || 0)}
`
+ + `Variance: R ${fmtMoney(r.variance || 0)}
`;
+ },
+ 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 '--------------------------------
'
+ + 'DENOMINATIONS COUNTED
'
+ + `${dlines.join('
')}
`;
+ },
+ footer: function (block) {
+ const text = block.text || lh.footer_text;
+ if (!text) return '';
+ return '--------------------------------
'
+ + `${xmlEscape(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(`${(typeof window !== 'undefined' ? '' : '')}`);
- }
- parts.push('');
- parts.push('WFSALES POS
');
- parts.push('CASH-UP REPORT
');
- if (lh.display_name) {
- parts.push(`${xmlEscape(lh.display_name)}
`);
- }
- if (lh.address) {
- parts.push(`${xmlEscape(lh.address.split('\n')[0])}
`);
- }
- if (lh.phone) {
- parts.push(`Tel: ${xmlEscape(lh.phone)}
`);
- }
- if (lh.vat_number) {
- parts.push(`VAT: ${xmlEscape(lh.vat_number)}
`);
- }
- parts.push('--------------------------------
');
- if (r.shiftLabel || r.dateLabel) {
- parts.push(`Shift: ${xmlEscape(r.shiftLabel || '')} Date: ${xmlEscape(r.dateLabel || '')}
`);
- }
- if (r.counts) {
- parts.push(`Total sales: ${r.counts.cash + r.counts.card + r.counts.eft + r.counts.account}
`);
- }
- parts.push('--------------------------------
');
- parts.push('PAYMENT METHOD BREAKDOWN
');
- 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(`${lines.join('
')}
`);
- parts.push('--------------------------------
');
- parts.push(`GRAND TOTAL${' '.repeat(12)}R ${fmtMoney(r.grand || 0)}
`);
- parts.push('--------------------------------
');
- parts.push('CASH RECONCILIATION
');
- parts.push(`Cash sales: R ${fmtMoney((r.totals && r.totals.cash) || 0)}
`);
- parts.push(`Cash counted: R ${fmtMoney(r.cashCounted || 0)}
`);
- parts.push(`Variance: R ${fmtMoney(r.variance || 0)}
`);
- if (Array.isArray(r.denoms) && r.denoms.length) {
- parts.push('--------------------------------
');
- parts.push('DENOMINATIONS COUNTED
');
- 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(`${dlines.join('
')}
`);
- }
- }
- if (lh.footer_text) {
- parts.push('--------------------------------
');
- parts.push(`${xmlEscape(lh.footer_text)}
`);
- }
- parts.push('');
- parts.push('');
- return `${parts.join('')}`;
+ 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 `${body}`;
}
// Small helper for padded numbers in the cashup slip.
diff --git a/public/pos/slip-layout.js b/public/pos/slip-layout.js
index 89a0037..d362d52 100644
--- a/public/pos/slip-layout.js
+++ b/public/pos/slip-layout.js
@@ -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 },