feat(pos): delivery-note slip shows company name and optional prices
The Include-prices toggle drives both the on-screen slip and the Bluetooth thermal print, so what prints matches what the operator saw. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
58f06175e1
commit
c6e35ad5d7
1 changed files with 35 additions and 11 deletions
|
|
@ -112,6 +112,10 @@ input,select{font:inherit;color:inherit}
|
|||
.dn-line:last-child{border-bottom:0}
|
||||
.dn-line .nm{flex:1;min-width:0;font-weight:500;line-height:1.35}
|
||||
.dn-line .qt{font-size:12px;color:var(--ink-soft);letter-spacing:.02em;white-space:nowrap;padding-top:2px;font-variant-numeric:tabular-nums}
|
||||
.dn-line .pr{font-size:13px;font-weight:600;color:var(--fg);white-space:nowrap;font-variant-numeric:tabular-nums;min-width:76px;text-align:right}
|
||||
.dn-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ink-soft);margin:10px 0;padding:0 2px}
|
||||
.dn-toggle input{width:16px;height:16px}
|
||||
@media print{.dn-toggle{display:none}}
|
||||
.dn-sign{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding-top:18px;margin-top:6px;border-top:1px dashed var(--muted)}
|
||||
.dn-sign > div{padding-top:8px;border-top:1.5px solid var(--fg);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft);text-align:center}
|
||||
|
||||
|
|
@ -315,7 +319,6 @@ input,select{font:inherit;color:inherit}
|
|||
<script>
|
||||
(function(){
|
||||
'use strict';
|
||||
const cfg = window.PB_CONFIG;
|
||||
const fmt = n => 'R ' + (Number(n)||0).toLocaleString('en-ZA',{minimumFractionDigits:2,maximumFractionDigits:2});
|
||||
const esc = s => String(s||'').replace(/[&<>"']/g, c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);
|
||||
const escAttr = s => String(s).replace(/"/g,'"');
|
||||
|
|
@ -338,6 +341,8 @@ if(!sale){
|
|||
if(sale.synced) document.getElementById('syncedFlag').style.display = 'inline-flex';
|
||||
else document.getElementById('successSub').textContent = 'Saved locally · will sync when online';
|
||||
if(sale.payment==='account'){
|
||||
// Stashed so the "Include prices" checkbox can re-render in place.
|
||||
window.__lastSale = sale;
|
||||
document.getElementById('deliveryWrap').style.display='block';
|
||||
document.getElementById('printDnBtn').style.display='flex';
|
||||
renderDeliveryNote(sale);
|
||||
|
|
@ -412,32 +417,43 @@ function renderReceipt(s){
|
|||
}
|
||||
|
||||
function renderDeliveryNote(s){
|
||||
const lh = s.letterhead || {};
|
||||
const date = new Date(s.createdAt);
|
||||
const dateStr = date.toLocaleDateString('en-ZA',{day:'2-digit',month:'short',year:'numeric'});
|
||||
const lines = s.items.map(it=>`<div class="dn-line">
|
||||
<div class="nm">${esc(it.description||it.sku)}</div>
|
||||
<div class="qt">${it.qty} ${esc(it.unit||'ea')}</div>
|
||||
</div>`).join('');
|
||||
// Some customers must not see prices on the slip the driver carries;
|
||||
// others want them. Default to showing, toggle re-renders in place.
|
||||
const toggle = document.getElementById('dnShowPrices');
|
||||
const showPrices = toggle ? !!toggle.checked : true;
|
||||
const lines = s.items.map(it=>{
|
||||
const price = showPrices ? `<div class="pr">${fmt((it.selling_price||0)*it.qty)}</div>` : '';
|
||||
return `<div class="dn-line">
|
||||
<div class="nm">${esc(it.description||it.sku)}</div>
|
||||
<div class="qt">${it.qty} ${esc(it.unit||'ea')}</div>
|
||||
${price}
|
||||
</div>`;
|
||||
}).join('');
|
||||
const companyLine = s.customer && s.customer.company
|
||||
? `<div class="v">${esc(s.customer.company)}</div>` : '';
|
||||
const html = `
|
||||
<div class="dn-head">
|
||||
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 7h13l3 4h2v6h-2M3 17V7M3 17a2 2 0 1 0 4 0M17 17a2 2 0 1 0 4 0"/></svg></div>
|
||||
<div class="nm">
|
||||
<div class="t">Delivery note</div>
|
||||
<div class="s">${esc(s.customer?.name || '—')}</div>
|
||||
<div class="s">${esc((s.customer && s.customer.company) || (s.customer && s.customer.name) || '—')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dn-meta">
|
||||
<div><div class="k">Customer</div><div class="v">${esc(s.customer?.name||'—')}</div></div>
|
||||
<div><div class="k">Customer</div><div class="v">${esc((s.customer && s.customer.name)||'—')}</div>${companyLine}</div>
|
||||
<div><div class="k">Date</div><div class="v">${dateStr}</div></div>
|
||||
<div><div class="k">Slip</div><div class="v">${(s.localId||'').slice(-8).toUpperCase()}</div></div>
|
||||
<div><div class="k">Sale total</div><div class="v">${fmt(s.total)}</div></div>
|
||||
${showPrices ? `<div><div class="k">Sale total</div><div class="v">${fmt(s.total)}</div></div>` : ''}
|
||||
</div>
|
||||
<label class="dn-toggle"><input type="checkbox" id="dnShowPrices" ${showPrices?'checked':''} onchange="renderDeliveryNote(window.__lastSale)"> Include prices</label>
|
||||
<div class="dn-items">${lines}</div>
|
||||
<div class="dn-sign"><div>Received by</div><div>Driver</div></div>
|
||||
`;
|
||||
document.getElementById('deliveryCard').innerHTML = html;
|
||||
}
|
||||
window.renderDeliveryNote = renderDeliveryNote;
|
||||
|
||||
// ---------------- Bluetooth thermal printing ----------------
|
||||
let btDevice = null;
|
||||
|
|
@ -554,24 +570,32 @@ async function printDelivery(){
|
|||
}
|
||||
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));
|
||||
if(sale.letterhead?.name) lines.push(sale.letterhead.name+'\n');
|
||||
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');
|
||||
lines.push(` ${it.qty} ${it.unit||'ea'}\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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue