feat(drawer): drawer hub — balance now, this-shift figure, daily activity, shift history; busy spinners
/drawer was record + review only: no current balance, no sight of the open shift's incomings, no daily activity, no shift history. Rebuilt as a hub: - Drawer now: the till's running balance (new GET /api/drawer/balance, shift:read — exposes the service's existing drawerBalance(); the drawer is one site-wide till, same exposure the X-report already had) with the open shift's X-report breakdown alongside (float + takings + vouchers = expected = balance) and a "This shift: ±X" figure (expected − opening float — the shift's own contribution vs what it inherited). - Today's cash activity: every cash payment + voucher since local midnight from the signed chain, live, with day totals (card never enters the till). - Record + movements/review: the 2026-07-01 flow, unchanged. - Closed shifts: drawer-focused history via the scope-aware /api/shifts (float → takings ± vouchers → expected per shift). Also: every shift open/close button (header, /shifts, pay modal, end- shift confirm) now shows an animated spinner + dims while busy — the old label-swap-only feedback read as a dead click when a shift open ran slow. The slowness itself (drawer/shift reads fold the WHOLE chain, O(chain)) is recorded as an open item in wiki/concepts/shift.md with the fix sketch: fold from the last z-report's signed expectedDrawerMinor forward. No new ledger surface — one read-only endpoint; RBAC test added. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+37
-1
@@ -2,7 +2,7 @@
|
||||
type: concept
|
||||
tags: [parking, domain, business, shifts, anti-fraud]
|
||||
sources: []
|
||||
updated: 2026-07-01
|
||||
updated: 2026-07-05
|
||||
status: open
|
||||
---
|
||||
|
||||
@@ -237,6 +237,35 @@ after**. This removes the friction while keeping accountability.
|
||||
+ own movements; admin: the review queue + all movements). `/shifts` is now just open/close +
|
||||
Z-report. Server: `routes/drawer.ts` (lifted out of `routes/shift.ts`); UI: `DrawerManager.tsx`.
|
||||
|
||||
## Drawer hub — the page answers "what's in the till and why" (2026-07-05)
|
||||
|
||||
Operator feedback: `/drawer` was **too simple** — record + review only, with no current balance, no
|
||||
sight of the open shift's incomings, no daily activity, no shift history. Rebuilt as a hub of five
|
||||
panels, all reads over data that already existed:
|
||||
|
||||
- **Drawer now** — the running balance (new `GET /api/drawer/balance`, `shift:read`; a passthrough to
|
||||
the service's existing `drawerBalance()`, which was never exposed). While a shift is open, the
|
||||
X-report breakdown sits beside it so the number is always explainable: *opening float + cash takings
|
||||
(with payment count — the "current shift incomings") + vouchers in − out = expected = balance*, and
|
||||
a **"This shift: ±X"** figure under the balance shows the shift's OWN contribution (expected −
|
||||
opening float), separating what this operator moved from what they inherited. With no shift open it
|
||||
reads as the carried-forward closing balance.
|
||||
- **Today's cash activity** — every drawer-touching signed event since local midnight (cash payments +
|
||||
vouchers; card never enters the till), live (15s), with day totals. Source: the existing
|
||||
`/api/events` window query, filtered client-side (`event:read`).
|
||||
- **Record** + **movements/review** — the 2026-07-01 flow, unchanged.
|
||||
- **Closed shifts** — drawer-focused history via the existing scope-aware `/api/shifts`
|
||||
(operators: own; admins: all): float → takings ± vouchers, expected drawer per shift.
|
||||
|
||||
Visibility note: the balance endpoint is `shift:read` on purpose — the drawer is a **single site-wide
|
||||
till**, the same exposure the open shift's X-report already had, not per-operator data.
|
||||
|
||||
**Busy feedback on shift buttons (2026-07-05).** Opening a shift can take seconds (see the open item
|
||||
below), and the buttons' only feedback was a label swap ("Opening…") — subtle enough that operators
|
||||
read a slow open as a dead click. Every shift open/close button (header, /shifts, the pay-modal's
|
||||
"open shift now", the end-shift confirm) now pairs the busy label with an animated spinner
|
||||
(`ui/Spinner.tsx`, reusable) and dims while disabled.
|
||||
|
||||
## Where the fraud control actually lives
|
||||
|
||||
Deliberately **not** in a shift-close ceremony. Because every payment is a **signed event in the
|
||||
@@ -253,6 +282,13 @@ reconciles the signed Z-report against the actual drawer and the bank/POS batch
|
||||
|
||||
## Open
|
||||
|
||||
- **Drawer/shift reads fold the WHOLE chain — O(chain) growth (flagged 2026-07-05).**
|
||||
`#drawerBalanceAt`, `currentOpenShift`, and `listShifts` select every ledger event and fold in JS.
|
||||
Fine pre-opening; after months of operation this is a linearly growing pause on every shift open,
|
||||
X-report, and drawer-balance read (the observed "opening a shift is slow"). Clean fix when it
|
||||
bites: fold **from the last `shift_z_report` forward** — its `expectedDrawerMinor` is already the
|
||||
signed balance snapshot at that point — instead of from genesis. Not built; the UI got busy
|
||||
spinners in the meantime.
|
||||
- **Drawer carry-over (decided 2026-06-18, built; vouchers re-modelled 2026-06-20; review reworked
|
||||
2026-07-01):** opening float auto-inherits the prior shift's expected drawer; drawer movements are the
|
||||
**`cash_in` / `cash_out` voucher pair** (Mandat Arkëtimi / Mandat Pagese — direction is the type),
|
||||
|
||||
Reference in New Issue
Block a user