feat(F-187): completed feature
This commit is contained in:
91
work/artifacts/F-187/architect.md
Normal file
91
work/artifacts/F-187/architect.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# F-187 — Architecture
|
||||
|
||||
## Decision
|
||||
|
||||
Model cashier removal as an account lifecycle on `backoffice_users`; never delete the row referenced by POS history.
|
||||
|
||||
Migration `055_pos_cashier_lifecycle.js` adds:
|
||||
|
||||
- `active boolean NOT NULL DEFAULT true`
|
||||
- `deactivated_at timestamptz NULL`
|
||||
- `deleted_at timestamptz NULL`
|
||||
- consistency check: deleted implies inactive; active implies no lifecycle timestamps
|
||||
- index over POS cashier role/status
|
||||
|
||||
Existing accounts remain active. Down removes only lifecycle columns/index/check.
|
||||
|
||||
## Semantics
|
||||
|
||||
| Action | Result | Reversible | Sessions |
|
||||
|---|---|---:|---|
|
||||
| Deactivate | `active=false`, `deactivated_at=now()` | yes | revoke all |
|
||||
| Reactivate | `active=true`, timestamps null | yes, unless deleted | remain revoked |
|
||||
| Delete | `active=false`, `deleted_at=now()` | no | revoke all |
|
||||
|
||||
Delete is a tombstone rather than physical SQL deletion. The UUID and email remain available to historical receipt/session/reporting joins. Deleted cashiers are returned by the admin list with status `deleted`, but cannot be mutated again.
|
||||
|
||||
Only rows whose current role is `pos_cashier` can be targeted. Managers, editors and administrators remain out of scope.
|
||||
|
||||
## API
|
||||
|
||||
All endpoints use backoffice authentication and `requireRole(admin)`.
|
||||
|
||||
- `GET /pos/users`: existing endpoint gains `active`, `deactivatedAt`, `deletedAt`, `status`; remains POS staff list-compatible.
|
||||
- `POST /pos/users`: existing creation contract; lifecycle defaults active.
|
||||
- `PATCH /pos/users/:id/status` body `{ "active": boolean }`: deactivate/reactivate cashier.
|
||||
- `DELETE /pos/users/:id`: irreversible soft deletion, HTTP 204.
|
||||
|
||||
Errors:
|
||||
|
||||
- `POS_CASHIER_NOT_FOUND` (404): target absent or not `pos_cashier`.
|
||||
- `POS_CASHIER_HAS_OPEN_SESSION` (409): close register first.
|
||||
- `POS_CASHIER_DELETED` (409): attempted status change on tombstone.
|
||||
- `POS_CASHIER_ALREADY_DELETED` (409): repeat deletion.
|
||||
|
||||
Mutations use a transaction and lock the cashier row `FOR UPDATE`. They check open cash sessions before lifecycle mutation, revoke `backoffice_sessions`, and append `pos.cashier.deactivated`, `pos.cashier.reactivated` or `pos.cashier.deleted` to `security_audit_log` in the same transaction.
|
||||
|
||||
## Race safety
|
||||
|
||||
`PgCashSessionRepository.open` must also lock the target `backoffice_users` row inside its transaction and require `active=true AND deleted_at IS NULL` before inserting. This serializes cash-session opening against deactivation/deletion:
|
||||
|
||||
- open wins: lifecycle mutation sees the open session and returns 409;
|
||||
- lifecycle wins: opening sees inactive/deleted and fails.
|
||||
|
||||
## Authentication
|
||||
|
||||
Defense in depth at both entry paths:
|
||||
|
||||
- `PgBackofficeUserRepository.findByEmail` only returns active, non-deleted accounts, so login gives the existing generic invalid-credentials response.
|
||||
- `createBackofficeSessionAuthenticator` includes the same lifecycle predicate, so sessions are invalid even before revocation completes and after database restore/races.
|
||||
- combined authentication inherits the backoffice check.
|
||||
|
||||
No account-state detail is exposed by login.
|
||||
|
||||
## Admin UI
|
||||
|
||||
Add a **Cajeros** section to the TPV admin page:
|
||||
|
||||
- create form (email/password) fixed to `pos_cashier`;
|
||||
- table with email, status and creation date;
|
||||
- active: Deactivate + Delete;
|
||||
- inactive: Reactivate + Delete;
|
||||
- deleted: no mutation actions;
|
||||
- native explicit confirmations name the cashier and explain open-register/history behavior.
|
||||
|
||||
The admin page reloads cashier state independently from store-scoped terminal/payment configuration.
|
||||
|
||||
## Tests
|
||||
|
||||
PostgreSQL integration coverage:
|
||||
|
||||
1. migration defaults existing cashier active;
|
||||
2. non-admin receives 403;
|
||||
3. deactivation revokes sessions and blocks authentication/login lookup;
|
||||
4. reactivation works without restoring revoked sessions;
|
||||
5. open cash session blocks deactivation and deletion;
|
||||
6. deletion keeps the same cashier row and historical `pos_cash_sessions.user_id` join;
|
||||
7. deleted cashier cannot reactivate;
|
||||
8. non-cashier target behaves as not found;
|
||||
9. migration up/no-op/down/up remains green.
|
||||
|
||||
Targeted typecheck/build covers admin UI contract.
|
||||
12
work/artifacts/F-187/documenter.md
Normal file
12
work/artifacts/F-187/documenter.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# F-187 — Documentation
|
||||
|
||||
Updated `docs/pos/POS_CHECKOUT.md` with:
|
||||
|
||||
- cashier active/inactive/deleted semantics;
|
||||
- session revocation and fresh-login behavior;
|
||||
- irreversible soft deletion and historical-attribution guarantee;
|
||||
- mandatory cash-session close before removal;
|
||||
- admin lifecycle API endpoints and role restrictions;
|
||||
- audit event behavior.
|
||||
|
||||
Removed F-187 from the future-work list now that the lifecycle is implemented.
|
||||
36
work/artifacts/F-187/implementer.md
Normal file
36
work/artifacts/F-187/implementer.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# F-187 — Implementer evidence
|
||||
|
||||
## Delivered
|
||||
|
||||
- Added reversible migration `055_pos_cashier_lifecycle.js` with active/deactivated/deleted account lifecycle, consistency constraint and cashier status index.
|
||||
- Extended backoffice domain roles to include POS manager/cashier and lifecycle fields.
|
||||
- Blocked inactive/deleted accounts in both credential lookup and live session authentication.
|
||||
- Made cash-session opening transactional and serialized against lifecycle mutation using the same `backoffice_users ... FOR UPDATE` row lock.
|
||||
- Extended admin-only POS user list with lifecycle status.
|
||||
- Added admin-only cashier activate/deactivate and soft-delete endpoints.
|
||||
- Lifecycle mutations reject open cash sessions, revoke every live session, preserve the cashier row/UUID and write an atomic security audit event.
|
||||
- Added TPV admin cashier UI for create, status display, confirmed deactivate/reactivate and irreversible deletion.
|
||||
- Added real PostgreSQL integration coverage in `pos-cashier-lifecycle.itest.ts`.
|
||||
|
||||
## Validation
|
||||
|
||||
- Backend typecheck: PASS.
|
||||
- Admin typecheck: PASS.
|
||||
- Backend production build: PASS.
|
||||
- Admin production build: PASS (only pre-existing Turbopack upload tracing warnings).
|
||||
- Targeted ESLint for every changed TypeScript/TSX file: PASS.
|
||||
- Prettier for every changed source/migration/test file: PASS.
|
||||
- POS unit tests: 12/12 PASS.
|
||||
- Full unit suite without DB: 268/268 PASS.
|
||||
- F-187 PostgreSQL integration: 4/4 PASS.
|
||||
- Full real-PostgreSQL sequential suite: 354/354 PASS across 79 files.
|
||||
- Migration fresh up / second no-op / full down / re-up: 4/4 PASS.
|
||||
- `git diff --check`: PASS.
|
||||
- `./scripts/verify.sh`: PASS.
|
||||
|
||||
## Repository baseline notes
|
||||
|
||||
- Full backend lint remains red on 9 pre-existing errors in thumbnail script, log broadcaster, an old POS test, and reporting files; all F-187 changed files pass targeted ESLint and Prettier.
|
||||
- Boundary check retains the single pre-existing security-module import violation; F-187 adds no module-boundary violation.
|
||||
- Full admin lint has zero errors and 23 pre-existing warnings.
|
||||
- Existing untracked upload JPGs were not touched.
|
||||
20
work/artifacts/F-187/leader-close.json
Normal file
20
work/artifacts/F-187/leader-close.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"feature_id": "F-187",
|
||||
"agent": "leader",
|
||||
"stage": "close",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "reviewer/security/qa gates APPROVED", "ok": true },
|
||||
{ "item": "354/354 tests with real PostgreSQL sequential", "ok": true },
|
||||
{ "item": "migration up/no-op/down/up", "ok": true },
|
||||
{ "item": "backend and admin typecheck/build", "ok": true },
|
||||
{ "item": "targeted changed-file lint and formatting", "ok": true },
|
||||
{ "item": "verify.sh final exit 0", "ok": true },
|
||||
{ "item": "operator/API documentation updated", "ok": true }
|
||||
],
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Unrelated upload JPGs are excluded from the feature commit.",
|
||||
"Global backend lint debt and one security boundary violation predate F-187; changed files are clean."
|
||||
]
|
||||
}
|
||||
33
work/artifacts/F-187/qa.json
Normal file
33
work/artifacts/F-187/qa.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"feature_id": "F-187",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"verdict": "APPROVED",
|
||||
"acceptance": [
|
||||
{ "id": 1, "criterion": "Admin TPV lists cashier lifecycle status", "ok": true, "evidence": "GET /pos/users real-DB assertion and admin status badges" },
|
||||
{ "id": 2, "criterion": "Admin creates active cashier", "ok": true, "evidence": "Fixed-role create form and server default/response active" },
|
||||
{ "id": 3, "criterion": "Deactivate and reactivate non-deleted cashier", "ok": true, "evidence": "PATCH lifecycle integration assertions" },
|
||||
{ "id": 4, "criterion": "Deactivate revokes and blocks current/future auth", "ok": true, "evidence": "DB revoked_at, old-cookie 401 and login 401 assertions" },
|
||||
{ "id": 5, "criterion": "Confirmed deletion is irreversible", "ok": true, "evidence": "Explicit UI warning, DELETE 204 and reactivation 409" },
|
||||
{ "id": 6, "criterion": "Open cash session blocks removal", "ok": true, "evidence": "PATCH and DELETE both return POS_CASHIER_HAS_OPEN_SESSION" },
|
||||
{ "id": 7, "criterion": "Historical attribution survives deletion", "ok": true, "evidence": "Post-delete join keeps cashier ID through pos_cash_sessions" },
|
||||
{ "id": 8, "criterion": "Admin-only and cashier-role-only", "ok": true, "evidence": "Cashier list 403 and manager target 404" },
|
||||
{ "id": 9, "criterion": "Migration reversible with active default", "ok": true, "evidence": "Fresh/no-op/down/re-up 4/4 and inserted accounts default active" },
|
||||
{ "id": 10, "criterion": "Regression and builds green", "ok": true, "evidence": "354/354 real-DB tests, typechecks, backend/admin builds, verify.sh" }
|
||||
],
|
||||
"regression": {
|
||||
"unit_without_db": "268 passed",
|
||||
"pos_unit": "12 passed",
|
||||
"f187_real_postgresql": "4 passed",
|
||||
"real_postgresql_sequential": "354 passed across 79 files",
|
||||
"migration_cycle": "4 passed",
|
||||
"backend_build": "passed",
|
||||
"admin_build": "passed",
|
||||
"verify": "passed"
|
||||
},
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Production admin UI was compile/type/lint validated; no browser automation harness exists for native confirm dialogs.",
|
||||
"Backend global lint baseline remains red outside changed files; targeted F-187 lint is green."
|
||||
]
|
||||
}
|
||||
23
work/artifacts/F-187/reviewer.json
Normal file
23
work/artifacts/F-187/reviewer.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"feature_id": "F-187",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "Soft deletion preserves backoffice user UUID and historical joins", "ok": true },
|
||||
{ "item": "Lifecycle check constraint permits only coherent active/inactive/deleted states", "ok": true },
|
||||
{ "item": "Login lookup and live session authentication both reject unavailable accounts", "ok": true },
|
||||
{ "item": "Deactivation/deletion revokes sessions in the lifecycle transaction", "ok": true },
|
||||
{ "item": "Cash-session opening and lifecycle mutations serialize on the same user row lock", "ok": true },
|
||||
{ "item": "Open cash sessions block deactivation/deletion", "ok": true },
|
||||
{ "item": "Only admin can mutate and only pos_cashier rows can be targeted", "ok": true },
|
||||
{ "item": "Lifecycle audit event is atomic with each successful mutation", "ok": true },
|
||||
{ "item": "Admin UI exposes explicit statuses and confirmations", "ok": true },
|
||||
{ "item": "Migration, integration regression, typecheck and builds pass", "ok": true }
|
||||
],
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Deleted cashier email is intentionally retained and remains unique to keep historical receipts human-readable; recreating the same address is not supported.",
|
||||
"Manager/editor/admin lifecycle remains out of F-187 scope."
|
||||
]
|
||||
}
|
||||
29
work/artifacts/F-187/security.json
Normal file
29
work/artifacts/F-187/security.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"feature_id": "F-187",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{ "item": "Cashier list/create/status/delete require admin role", "ok": true },
|
||||
{ "item": "Mutation lookup is restricted to pos_cashier targets", "ok": true },
|
||||
{ "item": "Every lifecycle input and UUID is schema validated", "ok": true },
|
||||
{ "item": "All lifecycle SQL uses bound parameters", "ok": true },
|
||||
{ "item": "Deactivation/deletion revokes all live sessions atomically", "ok": true },
|
||||
{ "item": "Authenticator independently rejects inactive/deleted users", "ok": true },
|
||||
{ "item": "Credential lookup preserves generic anti-enumeration failure", "ok": true },
|
||||
{ "item": "Cash-session opening race is serialized by row lock", "ok": true },
|
||||
{ "item": "Open-register guard prevents abandoning accountable cash", "ok": true },
|
||||
{ "item": "Security audit events record actor/action/target atomically", "ok": true },
|
||||
{ "item": "Changed-diff secret scan", "ok": true },
|
||||
{ "item": "Backend and admin production dependency audit", "ok": true }
|
||||
],
|
||||
"dependency_audit": {
|
||||
"backend": "0 vulnerabilities",
|
||||
"admin": "0 vulnerabilities"
|
||||
},
|
||||
"issues": [],
|
||||
"notes": [
|
||||
"Soft deletion intentionally retains cashier email for human-readable historical attribution; this is account removal, not a personal-data erasure workflow.",
|
||||
"No user-supplied audit metadata is accepted."
|
||||
]
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
# F-186 — POS configurable checkout, mixed payments and receipts
|
||||
# F-187 — Admin can deactivate and delete POS cashiers
|
||||
|
||||
Complete the TPV cashier flow for touch terminals and self-payment use cases.
|
||||
Allow administrators to safely remove cashier access without breaking historical POS attribution.
|
||||
|
||||
## 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.
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
@@ -495,3 +495,10 @@
|
||||
- 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.
|
||||
|
||||
## F-187 cerrada (2026-08-22) — Admin can deactivate and delete POS cashiers
|
||||
- Gates: reviewer APPROVED, security APPROVED, qa APPROVED, verify.sh exit 0.
|
||||
- Entregable: alta/listado de cajeros en Admin TPV, desactivación/reactivación y baja lógica irreversible con estados visibles.
|
||||
- Seguridad: login y sesiones bloquean cuentas inactivas/eliminadas; revocación y auditoría atómicas; sesión de caja abierta impide la baja.
|
||||
- Integridad: la fila/UUID se conserva para atribución histórica y la apertura de caja se serializa con la baja mediante bloqueo de fila.
|
||||
- Evidencia: 354/354 tests con PostgreSQL real en secuencia, migración up/no-op/down/up y builds backend/admin verdes; `work/artifacts/F-187/`.
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"state": "waiting",
|
||||
"next_agent": "leader",
|
||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||
"updated_at": "2026-08-22T20:08:30Z",
|
||||
"updated_at": "2026-08-22T20:23:50Z",
|
||||
"timeline": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user