Receipt engine
Before v0.7.252 the Till printed a receipt and the detail panel printed a slightly different receipt and the estimate printed a receipt on estimate paper. Three renderers, three sets of near-parallel HTML, three chances for the shop's GST # to say one thing on the sale slip and another on the estimate. v0.7.252–.259 collapsed them into one code path: one receiptHtml() renderer, one openReceiptPrompt() post-transaction prompt, one letterhead read from shop_config.
| Shipped | v0.7.252 (post-sale prompt) → v0.7.259 (final polish) |
| Substrate | src/modules/sales/receipt.ts (or equivalent module-local, since receipts are Sales-shaped) + shop_config letterhead fields |
The post-sale prompt
openReceiptPrompt(txn) fires after every sale / refund / deposit / estimate. Three buttons: Print, Email-soon, No receipt. Enter = Print; Esc = skip (via the Core keyboard layer); localStorage carries an always-auto-print opt-in for an operator who's decided they want the receipt every time without the extra click.
Deposits get the same prompt with a balance-owing line the receipt renders (v0.7.296) — "Total $2,200 · $500 paid · balance owing $1,700" — and the header line reflects the deposit type (Layaway / Special order).
Gift-card issuance renders the issued card number in the receipt header (v0.7.326, audit Tier-1 #8) so the operator hands the customer a slip with the card number on it, not a slip that just says "gift card".
One receipt, five docLabels
The docLabel parameter switches the receipt's headline for the transaction type:
| docLabel | Used for | Notes |
|---|---|---|
| Sale | Posted sale (transactions.transaction_type = 'sale') | Thermal-format default |
| Refund | Refund (refund) | Signs the money lines negative; header "Refund of TRX-YYYY-NNNNNN" |
| Estimate | Estimate (estimate) | Full-page Letter format, not thermal; terms + validity block; buildEstimateFromCart (v0.7.257) |
| Layaway / Deposit | Deposit (deposit_layaway / deposit_special_order) | Balance-owing line prominent |
| Invoice | Deposit pickup that rings the balance | Same layout as Sale |
Every one is one function call on the same renderer with a different docLabel and the transaction snapshot. Changing the letterhead once changes it on every doc.
Industry-standard retail anatomy
v0.7.253 pulled the receipt onto the retail-industry format:
- Letterhead block — shop name, address, phone, GST # + PST # (from
shop_config) - Transaction ref —
TRX-YYYY-NNNNNN+ occurred-at date/time - "Served by" — the cashier's initials (v0.7.255, from
__actor.nameon the transaction) - Line rows — SKU + qty @ unit-price + line total; humanized product name (no all-caps, no underscore-in-name)
- Subtotal / discount / GST / PST / Total — right-aligned column; tax name humanized (v0.7.259 stripped the underscore from
bike_pst_exemptinto readable prose) - Tendered / Change — under Total for cash sales; brand + last-4 for card (v0.7.292 / .293 via
payLabel()) - Footer text — "Thank you" line + shop-configurable footer
Estimates print as a Letter quote
v0.7.257 broke the estimate off the thermal-slip layout — an estimate goes to the customer as a full-page Letter quote with:
- Full letterhead + shop registration numbers up top
- Customer name + attention block
- The estimated lines with per-line unit price + total
- Terms + validity line ("Valid 30 days from the issue date …" — configurable)
- Signature block
Print-on-estimate-create prompt lands the same way as a sale (v0.7.258) — Print / Email-soon / No receipt.
The unified detail-panel Print
v0.7.256 unified the Till detail-panel Print button onto the same receiptHtml. A staffer reprinting a historic transaction from the ledger gets the exact same paper the original customer got; a staffer printing a refund gets the refund-flavoured version of the same receipt; a staffer printing a special-order pickup gets the invoice-flavoured version. One renderer, no drift.
What NOT to do
- Don't build a second receipt renderer for a special case. A new transaction type gets a new
docLabel, not a new file. - Don't hard-code the letterhead in the renderer. Every field comes from
shop_config— the fork template ships blank and the shop fills it in on Settings. - Don't auto-print without the opt-in. Auto-print is a per-operator decision; a busy Till is a bad place to guess at operator intent.
- Don't stack a second "Print" prompt on top of the auto-print. Once the prompt's fired and the operator's dismissed it, the moment is over.
See also
- Sales module — the transaction paths that fire the prompt
- Settings module — the letterhead + tax-number fields in
shop_config - Core keyboard layer — the Enter=Print / Esc=skip binding