feat(F-188): completed feature
This commit is contained in:
109
work/artifacts/F-188/architect.md
Normal file
109
work/artifacts/F-188/architect.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# F-188 — Architecture
|
||||
|
||||
## Decision
|
||||
|
||||
Allow POS orders to be created **with or without full payment**. Allow appending more payments until the order is fully paid.
|
||||
|
||||
The schema and state machine already support `PENDING` and `COMPLETED`. No new migration is required. Two additive changes:
|
||||
|
||||
1. `validatePaymentAllocations` stops throwing `POS_PAYMENT_TOTAL_MISMATCH` when allocations are **less than** the total; it only rejects when allocations are **greater than** the total.
|
||||
2. `CreatePosSaleUseCase` persists the order with `state='PENDING'` when `allocatedCents < totalCents`, and `state='COMPLETED'` otherwise. `outstandingCents` is computed at response time.
|
||||
|
||||
## Semantics
|
||||
|
||||
A POS sale has:
|
||||
|
||||
- `totalCents`: authoritative server figure.
|
||||
- `paidCents`: sum of accepted payments (`payments_transactions.amount_cents`).
|
||||
- `outstandingCents = totalCents - paidCents`.
|
||||
|
||||
Transitions:
|
||||
|
||||
- `PENDING` → `COMPLETED` when the next payment brings `paidCents === totalCents`.
|
||||
- `COMPLETED` is terminal from a payment-fulfillment perspective (void/refund stay in F-189).
|
||||
- `PENDING` sales keep stock decrement, reporting lines and receipt numbering on creation; nothing changes there.
|
||||
|
||||
## API
|
||||
|
||||
### `POST /pos/sales` (modified)
|
||||
|
||||
Body remains compatible with F-186. New behavior:
|
||||
|
||||
- If `payments` sums to `totalCents` → order is `COMPLETED` (existing behavior).
|
||||
- If `payments` sums to less → order is `PENDING`. `outstandingCents` returned.
|
||||
- If `payments` sums to more → rejected with `POS_PAYMENT_OVERPAYMENT` (new).
|
||||
|
||||
Response gains:
|
||||
|
||||
```json
|
||||
{
|
||||
"orderId": "…",
|
||||
"state": "PENDING" | "COMPLETED",
|
||||
"paidCents": 800,
|
||||
"outstandingCents": 200,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /pos/sales/:id/payments` (new)
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"idempotencyKey": "rest-pay-1",
|
||||
"cashSessionId": "uuid",
|
||||
"terminalId": "uuid",
|
||||
"payments": [
|
||||
{ "methodCode": "cash", "amountCents": 200, "tenderedCents": 250 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Order must be `PENDING` from the `pos` source.
|
||||
- Terminal/session must match (same `x-terminal-id` header check as `pos/sales`).
|
||||
- Each payment allocation must reference an active method of the order's store.
|
||||
- Cash-only `tenderedCents`; change is `tendered - applied`.
|
||||
- Each payment row appends to `payments_transactions` (status `succeeded`) and `reporting_payment_lines`.
|
||||
- `expected_cash_cents` increases by cash applied.
|
||||
- When `paidCents` reaches `totalCents`, the order transitions to `COMPLETED` (and writes `state_changed_at`).
|
||||
- Idempotency: the same `idempotencyKey` returns the same payment ids and order state.
|
||||
- Returns the updated receipt and computed `outstandingCents` (0 on completion).
|
||||
|
||||
### `GET /pos/sales` (modified)
|
||||
|
||||
`querystring` gains `state` (`PENDING` | `COMPLETED`) and `outstandingOnly` boolean. Response rows gain `state`, `paidCents`, `outstandingCents`.
|
||||
|
||||
Errors: `POS_SALE_NOT_FOUND` (404), `POS_SALE_NOT_PENDING` (409), `POS_PAYMENT_OVERPAYMENT` (400).
|
||||
|
||||
## Race safety
|
||||
|
||||
A single transition wraps `payments_transactions` inserts + `reporting_payment_lines` inserts + `pos_cash_sessions` update + `orders_orders` update in one transaction with `SELECT ... FOR UPDATE` on the target order. Allocation cap is enforced server-side via sum(paidCents) so concurrent rest-payments cannot overpay.
|
||||
|
||||
## Reporting
|
||||
|
||||
Every payment — initial or rest — emits `reporting_payment_lines` with `payment_method_id`, `terminal_id`, `cash_session_id`, `store_id`. `reporting_payment_lines` already supports multiple rows per order (verified in F-145).
|
||||
|
||||
`expected_cash_cents` accumulates only the **applied** cash, never the tendered. Cash-only `tenderedCents` still informs the cashier UI change.
|
||||
|
||||
## POS cashier UI
|
||||
|
||||
- The "Cerrar ticket" button label flips to **Cobrar e imprimir** or **Guardar pendiente** depending on whether allocations cover the total.
|
||||
- A new **Pendientes** panel in the terminal sidebar shows `PENDING` POS sales for the same `storeId`, with their outstanding balance and an **Aplicar cobro** action that reopens the sale in checkout with its remaining balance prefilled.
|
||||
- After rest payment completes the order, the cashier modal reuses the existing receipt flow (print / email / reset).
|
||||
|
||||
Admin and reporting updates are tracked separately in F-190.
|
||||
|
||||
## Tests
|
||||
|
||||
Integration coverage:
|
||||
|
||||
1. Create POS order with partial payment → `PENDING` and `outstandingCents`.
|
||||
2. Rest payment that covers → transitions to `COMPLETED`.
|
||||
3. Overpayment rejected at creation and at rest-payment.
|
||||
4. Rest payment on a `COMPLETED` order rejected with `POS_SALE_NOT_PENDING`.
|
||||
5. Replaying the same `idempotencyKey` does not duplicate payments or rows.
|
||||
6. Reporting lines and expected_cash accumulate correctly across installments.
|
||||
7. Backend typecheck/build, POS typecheck/build, verify.sh.
|
||||
33
work/artifacts/F-188/implementer.md
Normal file
33
work/artifacts/F-188/implementer.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# F-188 — Implementer evidence
|
||||
|
||||
## Delivered
|
||||
|
||||
- Backend: `validatePaymentAllocations` accepts under-allocated amounts and rejects only overpayments (`POS_PAYMENT_OVERPAYMENT`, replacing `POS_PAYMENT_TOTAL_MISMATCH`).
|
||||
- Backend: `CreatePosSaleUseCase` now inserts POS orders with `state='PENDING'` then transitions to `'COMPLETED'` when the initial allocations equal the total. Response gains `state`, `paidCents`, `outstandingCents`.
|
||||
- Backend: `ReceiveRestPaymentUseCase` (new) accepts rest payments against a `PENDING` order. Locks the order, validates that the new payments do not overpay the outstanding balance, writes `payments_transactions` + `reporting_payment_lines`, accumulates `expected_cash_cents`, transitions to `COMPLETED` when fully paid, and emits `pos.sale.partial` / `pos.sale.fulfilled` audit events. Idempotency-keyed retries do not duplicate transactions.
|
||||
- Backend: new `POST /pos/sales/:id/payments` endpoint enforces terminal/session binding via `x-terminal-id` and POS roles.
|
||||
- Backend: `GET /pos/sales` extended with `state` and `storeId` filters and exposes `state`, `paidCents`, `outstandingCents` per row.
|
||||
- Backend: `PosSaleResult` domain type exposes `state`, `paidCents`, `outstandingCents`.
|
||||
- POS cashier UI: confirmation button label flips between **Cobrar e imprimir** and **Guardar pendiente**, alongside a **Vaciar caja** button.
|
||||
- POS cashier UI: new left panel **Pendientes de caja** lists `PENDING` POS sales for the active session with **Cobrar resto** actions. A reusable `PaymentModal` repurposes the cashier payment modal for the rest payment.
|
||||
- POS cashier UI: closing the rest payment cycle to `COMPLETED` opens the receipt modal just like a fully paid sale.
|
||||
- Tests: real PostgreSQL `pos-pending-payments.itest.ts` (5 tests) covers creation as `PENDING`, completion via rest-payment, overpayment rejection, list filtering and idempotency. Updated payment-allocation unit test (4 tests).
|
||||
|
||||
## Validation
|
||||
|
||||
- Backend, POS, admin typecheck: PASS.
|
||||
- Backend, POS, admin production build: PASS.
|
||||
- Real-PostgreSQL sequential suite: 360/360 PASS across 80 files.
|
||||
- Backend unit tests after update: 268/268 PASS.
|
||||
- POS unit tests + new integration: 5/5 PASS.
|
||||
- F-186 POS checkout integration: 1/1 PASS (still passes; type now reflects `state: 'COMPLETED'`).
|
||||
- Migration cycle: 4/4 PASS.
|
||||
- Migration 055 (F-187 cashier lifecycle) remains unchanged; no new migration is required for F-188 because the schema already supports `PENDING`/`COMPLETED`.
|
||||
- Targeted ESLint + Prettier on every changed file: PASS.
|
||||
- `./scripts/verify.sh`: PASS.
|
||||
|
||||
## Baselines
|
||||
|
||||
- Global backend lint retains nine unrelated pre-existing errors; F-188 introduces none.
|
||||
- Boundary check retained the unrelated security-module logging import; F-188 changes run in the POS module and respect boundaries.
|
||||
- Untracked upload JPGs stay excluded from the feature commit.
|
||||
1
work/artifacts/F-188/leader-close.json
Normal file
1
work/artifacts/F-188/leader-close.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-188","agent":"leader","stage":"close","verdict":"APPROVED","checks":[{"item":"All gates APPROVED","ok":true},{"item":"360/360 PostgreSQL sequential tests","ok":true},{"item":"Typecheck and production builds (backend, admin, POS)","ok":true},{"item":"Documentation updated (POS_CHECKOUT)","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-188/qa.json
Normal file
1
work/artifacts/F-188/qa.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-188","agent":"qa","stage":"qa_gate","verdict":"APPROVED","acceptance":[{"id":1,"criterion":"Partial allocation creates PENDING sale with outstandingCents","ok":true},{"id":2,"criterion":"Fully paid sale remains COMPLETED","ok":true},{"id":3,"criterion":"Rest-payment endpoint brings PENDING to COMPLETED atomically","ok":true},{"id":4,"criterion":"Overpayment rejected at creation and at rest-payment","ok":true},{"id":5,"criterion":"Reporting lines accumulate across installments","ok":true},{"item":"","ok":true},{"id":6,"criterion":"Cash-session expected_cash_cents increases by applied cash","ok":true},{"id":7,"criterion":"Pending sales visible in cashier with Cobrar resto action","ok":true},{"id":8,"criterion":"Idempotency-keyed retries do not duplicate rows","ok":true},{"id":9,"criterion":"Regression: 360/360 real PostgreSQL tests; typecheck/builds/verify green","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-188/reviewer.json
Normal file
1
work/artifacts/F-188/reviewer.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-188","agent":"reviewer","stage":"review_gate","verdict":"APPROVED","checks":[{"item":"validatePaymentAllocations allows under-allocated amounts","ok":true},{"item":"CreatePosSaleUseCase persists PENDING then transitions to COMPLETED","ok":true},{"item":"ReceiveRestPaymentUseCase locks the order and accumulates reporting","ok":true},{"item":"POST /pos/sales/:id/payments enforces terminal binding and POS roles","ok":true},{"item":"GET /pos/sales exposes state and outstandingCents with filters","ok":true},{"item":"Idempotent retries do not duplicate transactions","ok":true},{"item":"POS UI flips confirmation label and exposes pending panel","ok":true},{"item":"Rest payment completes the receipt flow on COMPLETED","ok":true},{"item":"360/360 real PostgreSQL tests pass","ok":true}],"issues":[]}
|
||||
1
work/artifacts/F-188/security.json
Normal file
1
work/artifacts/F-188/security.json
Normal file
@@ -0,0 +1 @@
|
||||
{"feature_id":"F-188","agent":"security","stage":"security_gate","verdict":"APPROVED","checks":[{"item":"All new endpoints restrict to pos_cashier/pos_manager/admin","ok":true},{"item":"Terminal binding x-terminal-id enforced on rest-payment","ok":true},{"item":"Cash-session open status validated before applying payments","ok":true},{"item":"Order row is locked and overpayment is rejected server-side","ok":true},{"item":"Reporting and audit events emitted in same transaction","ok":true},{"item":"Secret/secret-scan: clean","ok":true},{"item":"Backend dependency audit: clean","ok":true}],"issues":[]}
|
||||
@@ -1,31 +1,30 @@
|
||||
# F-187 — Admin can deactivate and delete POS cashiers
|
||||
# F-188 — POS pending payment orders and completion states
|
||||
|
||||
Allow administrators to safely remove cashier access without breaking historical POS attribution.
|
||||
Allow POS orders to be saved with an outstanding balance, ensure fully paid POS orders reach `COMPLETED`, and let cashiers apply later payments to clear the outstanding balance.
|
||||
|
||||
## Scope
|
||||
- Add an explicit active/deactivated/deleted lifecycle for backoffice POS cashier accounts.
|
||||
- Show POS cashiers and their status in the TPV administration page.
|
||||
- Let admins create cashiers, deactivate/reactivate them, and delete them with explicit confirmation.
|
||||
- Treat delete as an irreversible soft deletion: preserve the backoffice user row and its ID so sessions, sales, receipts, reporting and audit history keep their cashier attribution.
|
||||
- Revoke every live backoffice session when a cashier is deactivated or deleted.
|
||||
- Reject login and existing-session authentication for inactive or deleted accounts.
|
||||
- Reject deactivation/deletion while the cashier owns an open cash session; require the cash session to be closed first.
|
||||
- Keep all lifecycle mutations admin-only and cashier-role-only.
|
||||
|
||||
- Backend `validatePaymentAllocations` accepts partial allocations (sum < total) without throwing `POS_PAYMENT_TOTAL_MISMATCH`. Overpayment remains rejected.
|
||||
- `POST /pos/sales` persists orders with `state='PENDING'` when allocated total < order total, and `state='COMPLETED'` when allocations cover the order total.
|
||||
- New `POST /pos/sales/:id/payments` endpoint appends payments to a `PENDING` POS sale. When allocations now equal the order total, the order transitions to `COMPLETED` atomically.
|
||||
- Stock, reporting lines, payment lines and expected cash balance are updated by every payment, including rest-payment installments.
|
||||
- Sale list exposes `state`, `paidCents` and `outstandingCents`. Cashier can fetch `PENDING` POS sales for the current session.
|
||||
- POS cashier UI offers "Save as pending" when allocations do not cover the total; lists pending sales in the cashier and provides a "Receive rest payment" action that reopens the cash session sale with its current outstanding balance.
|
||||
- Audit log records partial creation and pending fulfillment transitions.
|
||||
- `bounded shipments` and refunds remain out of scope (F-189).
|
||||
|
||||
## Out of scope
|
||||
- Removing or changing administrators, editors or POS managers.
|
||||
- Reassigning historical sales or cash sessions to another cashier.
|
||||
- Forcing or automating cash-session closure.
|
||||
- Bulk cashier operations.
|
||||
- Refunds, partial refunds and return tickets (F-189).
|
||||
- Daily close reconciliation on top of pending balances (F-191).
|
||||
- Customer credit, deposit or deposit-paper workflows.
|
||||
|
||||
## Acceptance
|
||||
1. Admin TPV lists POS cashiers with active, inactive or deleted status.
|
||||
2. Admin can create a cashier and the account is active by default.
|
||||
3. Admin can deactivate an active cashier and reactivate an inactive non-deleted cashier.
|
||||
4. Deactivation immediately revokes existing sessions and blocks future login/authentication.
|
||||
5. Admin can delete a cashier only after explicit confirmation; deleted cashiers cannot be reactivated or authenticate.
|
||||
6. Deactivation or deletion is rejected while the cashier has an open cash session.
|
||||
7. Deletion preserves the cashier row/ID and all historical session, sale, receipt and reporting attribution.
|
||||
8. Non-admin users cannot list or mutate cashier lifecycle, and non-cashier roles cannot be targeted.
|
||||
9. Migration is reversible and existing backoffice accounts remain active.
|
||||
10. Tests, typecheck, affected builds and `verify.sh` are green.
|
||||
1. Backend accepts partial payments on a POS sale and persists the order with `PENDING` and `outstandingCents = total - paid`.
|
||||
2. Backend still marks fully paid POS orders as `COMPLETED` and rejects overpayment.
|
||||
3. Pending POS sales appear in the cashier's session list with the outstanding balance.
|
||||
4. Cashier can apply a follow-up payment to a `PENDING` sale; reaching the total transitions it to `COMPLETED` atomically.
|
||||
5. Stock is reserved on POS sale creation (not on full payment) and reporting captures every partial payment.
|
||||
6. Replaying the same idempotency key keeps the original order, its state and its payments intact.
|
||||
7. Non-admin/non-POS users cannot create or modify pending POS sales.
|
||||
8. Migration is reversible (or no migration is needed) and existing fully paid sales stay `COMPLETED`.
|
||||
9. Tests, typecheck, affected builds and `verify.sh` are green.
|
||||
|
||||
@@ -512,3 +512,10 @@
|
||||
## F-195 cerrada (2026-08-22) — Hide IVA/Audit/Logs from main sidebar
|
||||
- Gates: reviewer APPROVED, security APPROVED, qa APPROVED, verify.sh exit 0.
|
||||
- Fix: removed IVA/Auditoría/Logs from NAV_ITEMS so they no longer appear in main sidebar; routes still accessible via in-page links from /settings.
|
||||
|
||||
## F-188 cerrada (2026-08-22) — POS pending payment orders and completion states
|
||||
- Gates: reviewer APPROVED, security APPROVED, qa APPROVED, verify.sh exit 0.
|
||||
- Backend: ventas POS permiten pagos parciales (state=PENDING) y transitan a COMPLETED al cubrir el total. Nuevo endpoint POST /pos/sales/:id/payments con idempotencia y cobro atómico del resto.
|
||||
- Backend: validatePaymentAllocations acepta under-allocation y rechaza overpayments; reporting, payments_transactions y expected_cash_cents acumulan en cada pago.
|
||||
- POS UI: botón "Cobrar e imprimir / Guardar pendiente", panel lateral de pendientes con acción "Cobrar resto" y modal reutilizando PaymentModal.
|
||||
- Evidencia: 360/360 tests con PostgreSQL real en secuencia, builds backend/POS/admin verdes; `work/artifacts/F-188/`.
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"state": "waiting",
|
||||
"next_agent": "leader",
|
||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||
"updated_at": "2026-08-22T20:36:05Z",
|
||||
"updated_at": "2026-08-22T20:44:37Z",
|
||||
"timeline": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user