feat(pos): add ePOS-Print XML builder for sale receipts
This commit is contained in:
parent
4f54051b66
commit
93eeea15e8
1 changed files with 53 additions and 1 deletions
|
|
@ -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(`<image width="${logo.width}" height="${logo.height}" color="color_1" mode="mono" align="center">${logo.base64}</image>`);
|
||||
parts.push('<feed line="1"/>');
|
||||
parts.push('<text align="center" width="2" height="2">WFSALES POS </text>');
|
||||
if (s.letterhead && s.letterhead.name) {
|
||||
parts.push(`<text align="center">${xmlEscape(s.letterhead.name)} </text>`);
|
||||
}
|
||||
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(`<text align="center">${addrLines.join(' ')} </text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
const d = new Date(s.createdAt);
|
||||
parts.push(`<text align="left">Date: ${xmlEscape(d.toLocaleString('en-ZA'))} Slip: ${xmlEscape((s.localId || '').slice(-8).toUpperCase())} </text>`);
|
||||
if (s.customer) parts.push(`<text align="left">Customer: ${xmlEscape(s.customer.name)} </text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
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(`<text align="left">${itemLines}</text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
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(`<text align="left">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)} </text>`);
|
||||
}
|
||||
parts.push(`<text align="left">Subtotal${' '.repeat(22)}R ${sub.toFixed(2)} VAT 15%${' '.repeat(22)}R ${vat.toFixed(2)} </text>`);
|
||||
parts.push(`<text align="left" em="true">TOTAL${' '.repeat(28)}R ${s.total.toFixed(2)} </text>`);
|
||||
const paidLabel = s.payment === 'cash' ? `CASH R ${(s.cashGiven || 0).toFixed(2)}` : s.payment.toUpperCase();
|
||||
parts.push(`<text align="left">Paid: ${xmlEscape(paidLabel)} </text>`);
|
||||
if (s.payment === 'cash') parts.push(`<text align="left">Change${' '.repeat(24)}R ${(s.change || 0).toFixed(2)} </text>`);
|
||||
parts.push('<text align="left">-------------------------------- </text>');
|
||||
if (s.letterhead && s.letterhead.footer_text) parts.push(`<text align="center">${xmlEscape(s.letterhead.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>`;
|
||||
}
|
||||
|
||||
return { getSettings, setSettings, imageToRaster, buildEposXml };
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue