5.1 KiB
F-186 — Architecture
Decision
Implement the request as one coherent POS checkout state machine while preserving the existing POST /pos/sales entry point. Payment allocation remains client-side draft state; the server receives and atomically validates the final allocation only after explicit user confirmation.
Persistence
Add migration 054_pos_checkout_receipts.js:
pos_receipt_settings(store_id PK, prefix, next_number, padding, return_policy, updated_at)provides configurable, transaction-safe numbering and return text.orders_orders.receipt_numberstores the immutable human ticket number with a unique partial index.orders_items.product_idandvariant_idbecome nullable andorders_items.is_free_itemis added. A CHECK requires both IDs for stocked lines and both NULL for free lines.- Seed receipt settings for the default store. Existing rows remain stocked lines.
Terminal JSON settings remain appropriate for device-specific switches:
touchNavigationEnabledquickProductVariantIds(exactly eight slots)lineDiscountsEnabled(default true)
Existing pos_payment_methods remains the source of truth for configurable methods. No provider SDK integration is introduced.
Sale contract
Stock line request: { kind:'stock', variantId, quantity, discountCents }.
Free line request: { kind:'free', name, unitPriceCents, quantity }.
Legacy stocked line fields remain accepted for compatibility but are ignored for price/name/SKU snapshots.
Payment request: { methodCode, amountCents, tenderedCents? }. Legacy { kind } maps to the seeded code where possible.
Server invariants:
- Session belongs to terminal and is open.
- Terminal identity from request header must match body terminal.
- Stock lines are reloaded from catalog/pricing for the session store and inventory rows are locked.
- Discounts are blocked if terminal settings disable them; otherwise validated against authoritative gross price.
- Free items require a trimmed name, positive cent price, positive integer quantity, and never touch inventory.
- Every method code must be active for the store.
- Applied payment amounts equal the sale total exactly. Only cash may have
tenderedCents, which must be >= applied amount. Change istendered-applied. - Sale, item snapshots, stock movement, payment transactions, reporting payment lines, cash-session expected amount, receipt sequence and receipt number commit in one transaction.
- Cash session expected balance increases by the applied cash amount, not tendered cash, so change is not counted as revenue/cash retained.
Receipt
The sale response includes a structured receipt assembled from immutable order/item/payment rows plus store/terminal/session metadata. It contains:
- company name, address, tax ID, contact email/phone
- receipt number, date/time, order/session/terminal/cashier
- item quantity, unit amount, subtotal, discount, tax and line total
- subtotal, discount, tax and total
- method labels/codes, applied amount, cash tendered and change
- return policy
GET /pos/sales/:id/receipt and /print return the same shape. POST /pos/sales/:id/receipt/email validates an email address and uses the existing SMTP-backed transactional email helper. Browser print remains the hardware-neutral print implementation.
Frontend state machine
editing cart -> allocating payments -> ready to confirm -> submitting -> receipt -> delivered/reset
- Clicking any configured method opens a touch modal defaulting to the remaining total, with “total” and “partial” controls.
- Allocations remain visible and removable in the cashier; paid and remaining totals are displayed.
- Cash modal also captures tendered amount and previews change.
- Confirmation is a separate action and is enabled only when remaining is zero.
- On successful sale the cart is retained while the receipt modal is open.
- Print calls
window.print()after rendering a print-only receipt. Email calls the backend endpoint. Either successful action resets cashier for the next sale. - Add a touch-sized
Artículo libremodal for name and positive euro price. - Discount UI is rendered only when
lineDiscountsEnabled !== false.
Admin
Expand terminal touch configuration to eight quick slots and a line-discount switch. Add store receipt/company fields and receipt numbering controls. Add payment-method creation, activation and ordering for cash/card/other labels (e.g. Bizum, Stripe, Apple Pay).
Security
- Admin-only configuration routes.
- POS roles only for sale/receipt delivery.
- Header terminal binding checked against body/session.
- No client-supplied catalog price/name trusted.
- Receipt email endpoint accepts only the generated receipt and validated destination; no arbitrary subject/body.
- Payment processor credentials and card PAN are out of scope and never accepted.
Tests
- Unit tests for monetary allocation/change/free-line normalization and disabled discounts.
- Integration tests where DB is available for migration, receipt sequence, free item, method validation and stock mutation.
- Typecheck/build all three affected packages; run root tests sequentially for real PostgreSQL integration files.