feat(F-186): completed feature
This commit is contained in:
80
work/artifacts/F-186/architect.md
Normal file
80
work/artifacts/F-186/architect.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
6
work/artifacts/F-186/documenter.md
Normal file
6
work/artifacts/F-186/documenter.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# F-186 — Documentation
|
||||
|
||||
- Added `docs/pos/POS_CHECKOUT.md` with admin setup, cashier state flow, stock/free line contracts, mixed payment/change invariants, ticket print/email, reporting capture and follow-up roadmap.
|
||||
- Added a precedence notice in `docs/pos/POS_API.md` because its discovery-era single-payment request is superseded by the implemented F-186 contract.
|
||||
- Documented that custom method labels do not imply real provider integrations.
|
||||
- Documented that paid sales are `COMPLETED`; pending balances and returns remain F-188/F-189.
|
||||
56
work/artifacts/F-186/implementer.md
Normal file
56
work/artifacts/F-186/implementer.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# F-186 — Implementer evidence
|
||||
|
||||
## Implemented
|
||||
|
||||
### POS cashier
|
||||
- Eight quick-product slots rendered in a 4×2 touch grid.
|
||||
- Touch-sized line-discount button; hidden when `terminal.settings.lineDiscountsEnabled=false`.
|
||||
- Touch payment modal per configured method with “total” / “partial” choice.
|
||||
- Mixed payment allocations remain visible with paid and outstanding totals and can be removed.
|
||||
- Cash tender captures overpayment and previews/records change.
|
||||
- Separate “Confirmar y cerrar ticket” step; disabled until applied amounts equal total.
|
||||
- Receipt modal supports browser print and SMTP email; cashier is only reset after one delivery action completes.
|
||||
- Free-item modal adds non-stock service/product lines by name and positive price.
|
||||
|
||||
### Admin
|
||||
- Terminal config expanded from 4 to 8 quick slots.
|
||||
- Per-terminal discount switch for self-payment terminals.
|
||||
- Payment-method management supports custom active/inactive cash/card/other methods (Bizum, Stripe, Apple Pay labels, etc.; no external processor integration).
|
||||
- Company identity, ticket prefix/next number/padding, header/footer and return-policy settings.
|
||||
|
||||
### Backend and persistence
|
||||
- Migration `054_pos_checkout_receipts.js` adds transaction-safe receipt settings/sequence, immutable receipt numbers, and nullable stock IDs for CHECK-constrained free lines.
|
||||
- POS sale now reloads catalog prices/names/SKU server-side, locks/decrements stock, rejects disabled discounts/methods, validates exact payment allocation and cash tender, and commits as `COMPLETED`.
|
||||
- Payment transactions preserve configured method, tender and change in immutable raw data.
|
||||
- Each payment is captured in `reporting_payment_lines`; this addresses new POS sales not reaching reporting.
|
||||
- Cash session expected balance increases by applied cash, never by tendered cash (change excluded).
|
||||
- Rich receipt builder contains company, time, ticket number, item details, totals, payment allocations, change and return policy.
|
||||
- Receipt access is terminal-scoped for cashier/manager and unrestricted only for admin.
|
||||
- Receipt email uses existing SMTP settings and cannot inject custom content.
|
||||
- POS cashier creation was corrected to use `backoffice_users` with Argon2 instead of invalid identity rows/SHA-256.
|
||||
|
||||
## Tests and verification
|
||||
- `cd project && npm run typecheck` — PASS.
|
||||
- `cd project && npm test` — PASS: 268 unit tests; DB tests skipped without env.
|
||||
- `TEST_DATABASE_URL=... npx vitest run --no-file-parallelism` — PASS: 78 files / 350 tests, including all real-PostgreSQL integration files sequentially.
|
||||
- New `payment-allocation.test.ts` — PASS: mixed partial allocation, cash change, exact total, disabled method and legacy mapping.
|
||||
- New `pos-checkout-receipts.itest.ts` — PASS: complete state, stock decrement, free line, mixed payment/change, reporting capture, receipt sequence, cash expected amount and idempotent replay.
|
||||
- Migration fresh up / no-op second up / full down / re-up — PASS.
|
||||
- POS Next.js production build — PASS.
|
||||
- Admin Next.js production build — PASS (pre-existing dynamic upload tracing warnings only).
|
||||
- `./scripts/verify.sh` — PASS.
|
||||
- `git diff --check` — PASS.
|
||||
- Targeted ESLint and Prettier for all changed backend/admin/POS files — PASS.
|
||||
|
||||
## Known repository-wide pre-existing checks
|
||||
- Root `npm run lint` remains red on 9 unrelated pre-existing errors in `generate-thumbnails.mjs`, `log-broadcaster.ts`, old `store-repository.test.ts`, and reporting files. Changed files pass targeted ESLint/Prettier.
|
||||
- Boundary violations were reduced from 8 to 1 by adding the POS public index and removing old deep imports. The remaining violation is pre-existing `security.routes.ts -> infrastructure/logging/log-broadcaster.ts` and is outside F-186.
|
||||
|
||||
## Follow-up tickets captured from user additions
|
||||
- F-187 cashier deletion/deactivation.
|
||||
- F-188 pending-payment orders (paid POS orders are already `COMPLETED` here).
|
||||
- F-189 negative quantity returns and return tickets.
|
||||
- F-190 full reporting update/refresh audit (new POS payment lines are captured here).
|
||||
- F-191 terminal and daily close reconciliation.
|
||||
- F-192 simplified storefront VAT label.
|
||||
- F-193 storefront expiry/weight and shipping-weight calculation.
|
||||
19
work/artifacts/F-186/leader-close.json
Normal file
19
work/artifacts/F-186/leader-close.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"feature_id": "F-186",
|
||||
"agent": "leader",
|
||||
"stage": "close",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "reviewer/security/qa gates APPROVED", "ok": true },
|
||||
{ "item": "350/350 tests with real PostgreSQL sequential", "ok": true },
|
||||
{ "item": "backend/POS/admin typecheck and production builds", "ok": true },
|
||||
{ "item": "migration up/no-op/down/up", "ok": true },
|
||||
{ "item": "verify.sh final exit 0", "ok": true },
|
||||
{ "item": "documentation and follow-up tickets F-187..F-193", "ok": true }
|
||||
],
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Unrelated pre-existing upload files are intentionally excluded from the feature commit.",
|
||||
"Repository-wide lint debt is documented in implementer evidence; changed-file lint is green."
|
||||
]
|
||||
}
|
||||
31
work/artifacts/F-186/qa.json
Normal file
31
work/artifacts/F-186/qa.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"feature_id": "F-186",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"verdict": "APPROVED",
|
||||
"acceptance": [
|
||||
{ "id": 1, "criterion": "Admin configures and POS renders eight quick products", "ok": true, "evidence": "Admin and touch API enforce length 8; POS renders 8 slots" },
|
||||
{ "id": 2, "criterion": "Touch line discount can be disabled per terminal", "ok": true, "evidence": "Touch-sized button plus UI hiding and backend POS_DISCOUNTS_DISABLED guard" },
|
||||
{ "id": 3, "criterion": "Enabled configurable payment methods only", "ok": true, "evidence": "Admin CRUD/toggle; config filters active; sale validates active store methods" },
|
||||
{ "id": 4, "criterion": "Full/partial mixed allocations display paid and remaining", "ok": true, "evidence": "PaymentModal and cashier allocation list; payment-allocation unit test" },
|
||||
{ "id": 5, "criterion": "Cash overpayment records change", "ok": true, "evidence": "Unit and PostgreSQL integration assertions for tendered 1000/applied 500/change 500" },
|
||||
{ "id": 6, "criterion": "Exact allocation and explicit confirmation before completed sale", "ok": true, "evidence": "Confirm button state plus backend exact-total validation; order integration state COMPLETED" },
|
||||
{ "id": 7, "criterion": "Complete configurable numbered receipt", "ok": true, "evidence": "Receipt builder/UI/email includes company, time, number, lines, quantities, totals, methods, change, return policy" },
|
||||
{ "id": 8, "criterion": "Print/email then cashier reset", "ok": true, "evidence": "Receipt modal is blocking and resetCashier is only called by completed print/email action" },
|
||||
{ "id": 9, "criterion": "Free item without inventory mutation", "ok": true, "evidence": "DB CHECK and integration test verify NULL stock IDs while stocked line alone decrements inventory" },
|
||||
{ "id": 10, "criterion": "Compatibility and green regression", "ok": true, "evidence": "350/350 tests pass with real PostgreSQL sequentially; backend/POS/admin typecheck and builds pass; verify.sh green" }
|
||||
],
|
||||
"regression": {
|
||||
"unit": "268 passed",
|
||||
"real_postgresql_sequential": "350 passed across 78 files",
|
||||
"migration_cycle": "fresh up, second no-op, full down, re-up passed",
|
||||
"pos_build": "passed",
|
||||
"admin_build": "passed",
|
||||
"verify": "passed"
|
||||
},
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"No physical printer or SMTP server was available for hardware delivery; browser print and SMTP dispatch paths were build/type/security validated.",
|
||||
"New user requests for pending balances, returns and daily close are separately queued as F-188/F-189/F-191."
|
||||
]
|
||||
}
|
||||
21
work/artifacts/F-186/reviewer.json
Normal file
21
work/artifacts/F-186/reviewer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"feature_id": "F-186",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "Eight-slot quick product and terminal discount contracts agree across admin, API and POS", "ok": true },
|
||||
{ "item": "Payment allocation requires an enabled method and exact authoritative total", "ok": true },
|
||||
{ "item": "Cash change and expected cash use tendered/applied values correctly", "ok": true },
|
||||
{ "item": "Sale, stock, reporting lines, payment rows and receipt sequence are atomic and idempotent", "ok": true },
|
||||
{ "item": "Free items are DB-constrained and excluded from inventory mutation", "ok": true },
|
||||
{ "item": "Receipt delivery precedes cashier reset", "ok": true },
|
||||
{ "item": "Unit, integration, migration, typecheck and production builds pass", "ok": true }
|
||||
],
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Browser print APIs cannot distinguish a confirmed print from a cancelled print dialog; reset occurs when the print dialog returns.",
|
||||
"Pending-payment orders and return tickets are intentionally tracked in F-188/F-189, not mixed into this fully-paid checkout contract.",
|
||||
"Repository-wide lint debt outside changed files remains; targeted changed-file lint is green."
|
||||
]
|
||||
}
|
||||
29
work/artifacts/F-186/security.json
Normal file
29
work/artifacts/F-186/security.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"feature_id": "F-186",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "Admin configuration endpoints require admin role", "ok": true },
|
||||
{ "item": "Sale and receipt endpoints require authenticated POS roles", "ok": true },
|
||||
{ "item": "Bound terminal header must match sale terminal/session", "ok": true },
|
||||
{ "item": "Non-admin receipt reads/email are scoped to the originating terminal", "ok": true },
|
||||
{ "item": "Catalog price/name/SKU and enabled payment methods are server-authoritative", "ok": true },
|
||||
{ "item": "All new SQL uses bound parameters and transactional row locks", "ok": true },
|
||||
{ "item": "Receipt email validates destination and uses fixed generated content", "ok": true },
|
||||
{ "item": "No PAN, processor credentials or arbitrary provider payload accepted", "ok": true },
|
||||
{ "item": "POS cashier passwords use Argon2 and backoffice identity storage", "ok": true },
|
||||
{ "item": "Secret scan of changed diff", "ok": true },
|
||||
{ "item": "Production dependency audit: backend, POS and admin", "ok": true }
|
||||
],
|
||||
"dependency_audit": {
|
||||
"backend": "0 vulnerabilities",
|
||||
"pos": "0 vulnerabilities",
|
||||
"admin": "0 vulnerabilities"
|
||||
},
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Review is scoped to F-186 endpoints in the legacy monolithic POS route file; unrelated historical placeholder endpoints require separate hardening.",
|
||||
"External card/Bizum/Stripe/Apple Pay processing remains out of scope; labels only, so no PCI card data is handled."
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,32 @@
|
||||
# F-185 — Integration suite stability
|
||||
# F-186 — POS configurable checkout, mixed payments and receipts
|
||||
|
||||
Run real-PostgreSQL integration files sequentially because each resets shared schema. Add forward migration correcting reporting_payment_lines currency default to literal EUR.
|
||||
Complete the TPV cashier flow for touch terminals and self-payment use cases.
|
||||
|
||||
## Scope
|
||||
- Increase configurable quick products from 6 to 8.
|
||||
- Replace line discount text/inline interaction with a touch-sized button; allow admins to disable line discounts per terminal.
|
||||
- Let admins configure and enable payment methods (cash, card, Bizum, Stripe, Apple Pay, or another named method).
|
||||
- Payment modal must allocate either the full remaining amount or a partial amount. Keep partial allocations visible in the cashier and permit another method until the total is covered.
|
||||
- For cash, accept tendered amount above the outstanding amount and calculate change.
|
||||
- Require an explicit final confirmation after payment allocation before closing the sale.
|
||||
- Generate a receipt with company identity, date/time, configurable ticket numbering, item name, quantity, subtotal, totals, payment methods/amounts, cash change, and return policy.
|
||||
- Offer print and email delivery. Clear the cashier only after print/email action succeeds or is explicitly completed.
|
||||
- Add a free-item flow for a non-stock product/service with required name and positive price; free items must not mutate inventory.
|
||||
- Keep monetary validation and sale completion authoritative on the backend.
|
||||
|
||||
## Out of scope
|
||||
- Real integrations with external payment processors.
|
||||
- Certified fiscal-printer protocols or country-specific fiscal certification.
|
||||
- Hardware-specific printer drivers; browser print is sufficient.
|
||||
|
||||
## Acceptance
|
||||
1. Admin can configure up to eight quick products and POS renders all configured slots.
|
||||
2. Line discount is a touch target and is absent/blocked when the terminal disables discounts.
|
||||
3. Enabled admin payment methods appear in POS; disabled methods cannot be submitted.
|
||||
4. Cashier supports full and partial payment allocations, displays paid/remaining totals, and permits mixed methods.
|
||||
5. Cash tender above the remaining total displays and records change; non-cash overpayment is rejected.
|
||||
6. A sale can only be confirmed when allocations cover the exact total, and requires explicit confirmation.
|
||||
7. Receipt contains all requested company, numbering, line, total, payment, change, and return-policy data.
|
||||
8. Receipt supports browser print and email delivery; cashier resets only after delivery completion.
|
||||
9. A free item can be added with name and positive price and does not reserve or decrement stock.
|
||||
10. Existing POS sale/reporting contracts remain compatible and tests plus `verify.sh` are green.
|
||||
|
||||
@@ -488,3 +488,10 @@
|
||||
- PG16 no soporta `ADD CONSTRAINT IF NOT EXISTS` → usar `DO $$ IF NOT EXISTS`
|
||||
- Batch de rutas múltiples en un archivo requiere cuidado con el balanceo de llaves/paréntesis
|
||||
- El script de análisis de Python para balanceo de llaves cuenta `}` dentro de literales de template como cierres de bloque (da falsos positivos)
|
||||
|
||||
## F-186 cerrada (2026-08-22) — POS configurable checkout, mixed payments and receipts
|
||||
- Gates: reviewer APPROVED, security APPROVED, qa APPROVED, verify.sh exit 0.
|
||||
- Entregable: 8 productos rápidos, descuentos táctiles/desactivables, métodos configurables, pagos parciales/mixtos, cambio en efectivo, confirmación explícita, artículos libres y tickets numerados por impresión/email.
|
||||
- Integridad: venta `COMPLETED`, stock/pagos/reporting/caja/secuencia atómicos e idempotentes; precio de catálogo y métodos validados en backend.
|
||||
- Evidencia: 350/350 tests con PostgreSQL real en secuencia, builds backend/POS/admin verdes; `work/artifacts/F-186/`.
|
||||
- Seguimiento solicitado: F-187..F-193.
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"state": "waiting",
|
||||
"next_agent": "leader",
|
||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||
"updated_at": "2026-08-22T17:30:45Z",
|
||||
"updated_at": "2026-08-22T20:08:30Z",
|
||||
"timeline": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user