diff --git a/public/pos/receipt.html b/public/pos/receipt.html
index 08b29ce..849ba99 100644
--- a/public/pos/receipt.html
+++ b/public/pos/receipt.html
@@ -525,10 +525,26 @@ async function printThermalNetwork(settings){
}
status.textContent = 'Sending to ' + settings.ip + '…';
try{
- const logo = await window.EposPrint.imageToRaster('/wfdash-logo.png', 384).catch(()=>null);
- const xml = window.EposPrint.buildEposXml(sale, logo);
+ // Account sales print a delivery note (no payment line, has
+ // received-by / driver lines) — that's the document the driver
+ // hands to the customer at drop-off, not a "paid" receipt. Cash
+ // / 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);
+ successLabel = 'Delivery note printed · ';
+ } else {
+ const logo = await window.EposPrint.imageToRaster('/wfdash-logo.png', 384).catch(()=>null);
+ xml = window.EposPrint.buildEposXml(sale, logo);
+ successLabel = 'Printed · ';
+ }
await window.EposPrint.printViaNetwork(settings.ip, xml);
- status.textContent = 'Printed · ' + settings.ip;
+ status.textContent = successLabel + settings.ip;
}catch(e){
status.textContent = 'Print failed: ' + e.message;
errBox.classList.add('err');
@@ -568,14 +584,21 @@ async function printThermalBluetooth(){
}
if(!btChar) throw new Error('No writable characteristic on printer.');
status.textContent = 'Printing…';
- const payload = buildEscPos(sale);
+ // Account sales print a delivery note (received-by / driver lines,
+ // no payment line). Everything else prints the standard sale slip.
+ const dnToggle = document.getElementById('dnShowPrices');
+ const showPrices = dnToggle ? !!dnToggle.checked : true;
+ const text = sale.payment === 'account'
+ ? buildEscPosDelivery(sale, showPrices)
+ : buildEscPos(sale);
const CHUNK = 80;
- for(let i=0;isetTimeout(r,30));
}
- status.textContent = 'Printed · ' + (btDevice.name||'printer');
+ status.textContent = (sale.payment === 'account' ? 'Delivery note printed · ' : 'Printed · ') + (btDevice.name||'printer');
}catch(e){
if(e.name==='NotFoundError') status.textContent = 'Cancelled.';
else status.textContent = 'Print failed: ' + e.message;
@@ -679,38 +702,9 @@ async function printDelivery(){
btn.disabled = false;
return;
}
- const E = (n) => String.fromCharCode(n);
- const ESC = E(0x1B);
- // Mirror the on-screen "Include prices" toggle so the printed slip
- // matches what the operator just checked.
const dnToggle = document.getElementById('dnShowPrices');
const showPrices = dnToggle ? !!dnToggle.checked : true;
- const lines = [];
- lines.push(ESC+'@');
- lines.push(ESC+'a'+E(1));
- lines.push(ESC+'!'+E(0x10));
- lines.push('WFSALES POS\n');
- lines.push(ESC+'!'+E(0x00));
- lines.push('Delivery Note\n');
- lines.push(ESC+'a'+E(0));
- if(sale.customer?.company) lines.push('Company: ' + sale.customer.company + '\n');
- lines.push('Customer: ' + (sale.customer?.name||'—') + '\n');
- const d = new Date(sale.createdAt);
- lines.push('Date: ' + d.toLocaleDateString('en-ZA')+'\n');
- lines.push('--------------------------------\n');
- for(const it of sale.items){
- lines.push((it.description||it.sku)+'\n');
- const qtyLine = ` ${it.qty} ${it.unit||'ea'}`;
- lines.push(showPrices
- ? `${qtyLine} ${fmt((it.selling_price||0)*it.qty)}\n`
- : `${qtyLine}\n`);
- }
- lines.push('--------------------------------\n');
- if(showPrices) lines.push('TOTAL: ' + fmt(sale.total) + '\n');
- lines.push('Received by: __________ Driver: __________\n');
- lines.push('\n\n');
- lines.push(String.fromCharCode(0x1D)+'V'+String.fromCharCode(0x41)+String.fromCharCode(0x10));
- const text = lines.join('');
+ const text = buildEscPosDelivery(sale, showPrices);
const bytes = new Uint8Array(text.length);
for(let i=0;i String.fromCharCode(n);
+ const ESC = E(0x1B);
+ const lines = [];
+ lines.push(ESC+'@');
+ lines.push(ESC+'a'+E(1));
+ lines.push(ESC+'!'+E(0x10));
+ lines.push('WFSALES POS\n');
+ lines.push(ESC+'!'+E(0x00));
+ lines.push('Delivery Note\n');
+ lines.push(ESC+'a'+E(0));
+ if(s.letterhead && s.letterhead.name) lines.push(s.letterhead.name + '\n');
+ if(s.letterhead){
+ if(s.letterhead.address) lines.push(s.letterhead.address.split('\n')[0]+'\n');
+ if(s.letterhead.phone) lines.push('Tel: '+s.letterhead.phone+'\n');
+ if(s.letterhead.vat_number) lines.push('VAT: '+s.letterhead.vat_number+'\n');
+ }
+ if(s.customer && s.customer.company) lines.push('Company: ' + s.customer.company + '\n');
+ lines.push('Customer: ' + (s.customer && s.customer.name || '—') + '\n');
+ const d = new Date(s.createdAt);
+ lines.push('Date: ' + d.toLocaleDateString('en-ZA')+'\n');
+ lines.push('--------------------------------\n');
+ for(const it of s.items){
+ lines.push((it.description||it.sku)+'\n');
+ const qtyLine = ` ${it.qty} ${it.unit||'ea'}`;
+ lines.push(showPrices
+ ? `${qtyLine} ${fmt((it.selling_price||0)*it.qty)}\n`
+ : `${qtyLine}\n`);
+ }
+ lines.push('--------------------------------\n');
+ if(showPrices) lines.push('TOTAL: ' + fmt(s.total) + '\n');
+ lines.push('Received by: __________ Driver: __________\n');
+ lines.push('\n\n');
+ lines.push(E(0x1D)+'V'+E(0x41)+E(0x10));
+ return lines.join('');
+}
+
function togglePrinterSettings(){
const form = document.getElementById('printerSettingsForm');
form.style.display = form.style.display === 'none' ? 'flex' : 'none';
diff --git a/public/pos/sw.js b/public/pos/sw.js
index 618009e..8249748 100644
--- a/public/pos/sw.js
+++ b/public/pos/sw.js
@@ -1,5 +1,5 @@
// Service worker — cache app shell, network-first for HTML + theme.js, cache-first for other static.
-const CACHE = "pos-v19";
+const CACHE = "pos-v20";
const APP_SHELL = [
'./',
'./index.html',