diff --git a/public/pos/epos-print.js b/public/pos/epos-print.js
index 93ffb2b..0f0e646 100644
--- a/public/pos/epos-print.js
+++ b/public/pos/epos-print.js
@@ -68,5 +68,57 @@ window.EposPrint = (function () {
});
}
- return { getSettings, setSettings, imageToRaster };
+ function xmlEscape(s) {
+ return String(s == null ? '' : s).replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]));
+ }
+
+ // Mirrors buildEscPos() in receipt.html — same fields, same discount
+ // math — but emits ePOS-Print XML tags instead of raw ESC/POS bytes.
+ function buildEposXml(s, logo) {
+ const parts = [];
+ if (logo) parts.push(`${logo.base64}`);
+ parts.push('');
+ parts.push('WFSALES POS
');
+ if (s.letterhead && s.letterhead.name) {
+ parts.push(`${xmlEscape(s.letterhead.name)}
`);
+ }
+ const addrLines = [];
+ if (s.letterhead && s.letterhead.address) addrLines.push(xmlEscape(s.letterhead.address.split('\n')[0]));
+ if (s.letterhead && s.letterhead.phone) addrLines.push('Tel: ' + xmlEscape(s.letterhead.phone));
+ if (s.letterhead && s.letterhead.vat_number) addrLines.push('VAT: ' + xmlEscape(s.letterhead.vat_number));
+ if (addrLines.length) parts.push(`${addrLines.join('
')}
`);
+ parts.push('--------------------------------
');
+ const d = new Date(s.createdAt);
+ parts.push(`Date: ${xmlEscape(d.toLocaleString('en-ZA'))}
Slip: ${xmlEscape((s.localId || '').slice(-8).toUpperCase())}
`);
+ if (s.customer) parts.push(`Customer: ${xmlEscape(s.customer.name)}
`);
+ parts.push('--------------------------------
');
+ const itemLines = s.items.map(it => {
+ const priceLine = ` ${it.qty} x ${(it.selling_price || 0).toFixed(2)} ${(it.selling_price * it.qty).toFixed(2)}`;
+ return `${xmlEscape(it.description || it.sku)}
${xmlEscape(priceLine)}
`;
+ }).join('');
+ parts.push(`${itemLines}`);
+ parts.push('--------------------------------
');
+ const sub = s.sub != null ? s.sub : (s.total - (s.vat || 0));
+ const vat = s.vat != null ? s.vat : (s.total * 15 / 115);
+ const discount = s.discount || { kind: 'none', input: 0, amount: 0 };
+ const hasDiscount = discount.kind !== 'none' && Number(discount.amount) > 0;
+ if (hasDiscount) {
+ const gross = Number(s.gross) || s.items.reduce((sum, it) => sum + (Number(it.selling_price) || 0) * (Number(it.qty) || 0), 0);
+ const grossSub = gross / 1.15;
+ const grossVat = gross - grossSub;
+ parts.push(`Subtotal (gross)${' '.repeat(10)}R ${grossSub.toFixed(2)}
VAT 15% (gross)${' '.repeat(10)}R ${grossVat.toFixed(2)}
Total (gross)${' '.repeat(13)}R ${gross.toFixed(2)}
Discount${discount.kind === 'percent' ? ` (${discount.input}%)` : ''}${' '.repeat(15)}- R ${discount.amount.toFixed(2)}
`);
+ }
+ parts.push(`Subtotal${' '.repeat(22)}R ${sub.toFixed(2)}
VAT 15%${' '.repeat(22)}R ${vat.toFixed(2)}
`);
+ parts.push(`TOTAL${' '.repeat(28)}R ${s.total.toFixed(2)}
`);
+ const paidLabel = s.payment === 'cash' ? `CASH R ${(s.cashGiven || 0).toFixed(2)}` : s.payment.toUpperCase();
+ parts.push(`Paid: ${xmlEscape(paidLabel)}
`);
+ if (s.payment === 'cash') parts.push(`Change${' '.repeat(24)}R ${(s.change || 0).toFixed(2)}
`);
+ parts.push('--------------------------------
');
+ if (s.letterhead && s.letterhead.footer_text) parts.push(`${xmlEscape(s.letterhead.footer_text)}
`);
+ parts.push('');
+ parts.push('');
+ return `${parts.join('')}`;
+ }
+
+ return { getSettings, setSettings, imageToRaster, buildEposXml };
})();