Files
mercadodevida/work/artifacts/F-186/architect.md
2026-08-22 22:08:30 +02:00

81 lines
5.1 KiB
Markdown

# 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_number` stores the immutable human ticket number with a unique partial index.
- `orders_items.product_id` and `variant_id` become nullable and `orders_items.is_free_item` is 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:
- `touchNavigationEnabled`
- `quickProductVariantIds` (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:
1. Session belongs to terminal and is open.
2. Terminal identity from request header must match body terminal.
3. Stock lines are reloaded from catalog/pricing for the session store and inventory rows are locked.
4. Discounts are blocked if terminal settings disable them; otherwise validated against authoritative gross price.
5. Free items require a trimmed name, positive cent price, positive integer quantity, and never touch inventory.
6. Every method code must be active for the store.
7. Applied payment amounts equal the sale total exactly. Only cash may have `tenderedCents`, which must be >= applied amount. Change is `tendered-applied`.
8. Sale, item snapshots, stock movement, payment transactions, reporting payment lines, cash-session expected amount, receipt sequence and receipt number commit in one transaction.
9. 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 libre` modal 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.