feat(drawer): operator records cash movements, admin reviews after (own /drawer route)
Rework drawer cash movements from synchronous admin-authorization-at-creation
(operator typed an admin's password inline for every receipt/disbursement) to
operator-records-freely -> admin-reviews-after.
- New `drawer` resource: drawer:create (operator records; admin-revocable per
role) + drawer:review (admin authorizes/denies). Migration 0018 grants the
default operator role drawer:create; admin gets all in code.
- New signed `cash_review` ledger event { refId, decision, reviewedBy, note? }.
A DENIAL is a FLAG, not a reversal: it never appends reversing cash and never
touches the drawer balance (the correction is settled outside the app). This
is what keeps a late review from leaking into the next operator's inherited
drawer — a denial that lands after the reviewed shift closed moves no cash.
Regression test: op1 disburses -> closes -> op2 inherits -> admin denies ->
op2 drawer unchanged.
- Move the feature OFF the polluted /shifts route to a top-level /drawer
(operator: record + own; admin: review queue + all). routes/drawer.ts lifted
from routes/shift.ts (retired the authorizer-password gate; kept shift:cash
for its other job = admin-sees-all-shifts). New DrawerManager.tsx.
Display fixes bundled:
- Render cash_review in the event-detail modal (decision / reviewed-by / note /
movement ref) — previously showed nothing.
- Relabel the shift drawer figures for clarity: Daily takings / Receipts /
Disbursements (was Cash payments / Cash added / Cash removed).
- Hide the Card figure everywhere when CARD_PAYMENTS_ENABLED is false (no POS
on-site), matching the card-tender gate.
shared/db/server/web all typecheck; 225 server tests pass (incl. the drawer
review + cross-shift-leak regression); web build + i18n parity green. Verified
end-to-end via Playwright. Recorded in wiki/concepts/shift.md.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
-- Drawer redesign (2026-07-01): operators RECORD cash movements freely; admins REVIEW them
|
||||
-- after the fact (authorize/deny — a flag, not a reversal). New `drawer` resource with two
|
||||
-- permissions in @parking/shared: drawer:create + drawer:review.
|
||||
--
|
||||
-- The built-in `admin` role gets ALL permissions in code (auth.ts ADMIN_PERMS = new
|
||||
-- Set(PERMISSIONS)), so it needs NO seed row here. This grants the default `operator` role
|
||||
-- the ability to record movements (drawer:create) — matching the prior behaviour where an
|
||||
-- operator could raise a voucher. An admin can revoke it per-role in the Roles UI (it's just
|
||||
-- data). drawer:review is admin-only, so it is NOT granted to operator.
|
||||
--
|
||||
-- Idempotent: role_permissions has a UNIQUE(role_id, permission) index, so re-running is a
|
||||
-- no-op via OR IGNORE. See wiki/concepts/shift.md.
|
||||
INSERT OR IGNORE INTO `role_permissions` (`role_id`, `permission`) VALUES
|
||||
('operator','drawer:create');
|
||||
@@ -127,6 +127,13 @@
|
||||
"when": 1781886000000,
|
||||
"tag": "0017_backup_retention",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "6",
|
||||
"when": 1781886100000,
|
||||
"tag": "0018_drawer_permissions",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user