feat: mid-shift X-report (read-only takings-so-far)

Let the operator see, on demand during an open shift, the opening float
inherited, cash/card collected so far, pay-ins/pay-outs, and the current
expected drawer balance — without closing.

GET /api/shift/report (shift:read; 204 when no shift is open) returns the same
drawer projection the Z-report computes. Factored that math into a shared
ShiftService.#summariseWindow(open, asOf) used by BOTH the X-report (asOf=now,
read-only) and close()'s Z-report (asOf=endedAt, signed), so the two can't
drift. The X-report appends NOTHING — it's a snapshot, not an accountability
mark; the Z-report at close remains the signed record.

UI: a "Takings so far" button on the shift control reveals a cyan X-report
panel; the header still shows the live drawer total for the at-a-glance figure.

Verified against a copy of the live DB: X figures match drawerBalance(), the
drawer identity holds, zero events appended, chain still verifies.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-20 16:22:55 +02:00
parent 2835f78635
commit cb68cbafdb
8 changed files with 166 additions and 21 deletions
+10 -6
View File
@@ -204,11 +204,15 @@ reconciles the signed Z-report against the actual drawer and the bank/POS batch
- **Shift ↔ session boundary:** a vehicle may enter under one shift and pay under another — the
Z-report sums by **payment time** (when cash/card was taken), which is the operator who handled
the money. Confirm that's the intended accountability (vs. by entry).
- **Mid-shift report / X-report — REQUESTED 2026-06-20, not yet built.** The operator wants to see,
on demand during the shift, the **opening float inherited**, **cash collected so far**, the
pay-ins/pay-outs, and the **current expected drawer balance** — without closing. It's the same
drawer projection the Z-report computes, just read-only and mid-shift. (The header already shows the
live drawer *total*; this is the full breakdown.) Deferred behind the voucher re-model done the same
day; build next if wanted.
- **Mid-shift report / X-report — BUILT 2026-06-20.** On demand during the shift, the operator sees
the **opening float inherited**, **cash/card collected so far**, the **pay-ins/pay-outs**, and the
**current expected drawer balance** — without closing. `GET /api/shift/report` (`shift:read`, 204
when no shift is open) returns the SAME drawer projection the Z-report computes, factored into a
shared `ShiftService.#summariseWindow(open, asOf)` so X (asOf = now, read-only) and Z (asOf =
endedAt, signed) can never drift. **It appends NOTHING** — it's not an accountability mark (the
Z-report at close is the signed record). UI: a "Takings so far" button on the shift control opens a
cyan X-report panel; the header still shows the live drawer *total* for the at-a-glance number.
Verified against a copy of the live DB: matches `drawerBalance()`, drawer identity holds, 0 events
appended, chain still verifies.
- **Multiple lanes/booths** — whether a shift is per-operator, per-booth, or per-site
(relates to [[open-questions]] #1 lane topology).
+13
View File
@@ -1118,3 +1118,16 @@ three types. Verified against a COPY of the live DB with the real signing module
Updated [[shift]] (Drawer balance section, math, worked example, open items). Prompted by the
operator-balance question; the live mid-shift **X-report** breakdown is logged as REQUESTED,
not yet built (see [[shift]] Open).
## [2026-06-20] feat | Mid-shift X-report (read-only takings-so-far)
The operator can now see, on demand during an open shift, the opening float inherited,
cash/card collected so far, pay-ins/pay-outs, and the current expected drawer balance —
without closing. `GET /api/shift/report` (shift:read; 204 when no shift open) returns the
SAME projection the Z-report prints, factored into a shared `ShiftService.#summariseWindow
(open, asOf)` so X (asOf=now, read-only) and Z (asOf=endedAt, signed) can't drift. Appends
NOTHING — it's a snapshot, not an accountability mark (the Z at close is the signed record).
UI: a "Takings so far" button on the shift control reveals a cyan X-report panel; the header
keeps the live drawer total. Verified on a copy of the live DB: matches drawerBalance(),
drawer identity holds (expected = opening + cash + added − removed), 0 events appended, chain
verifies. Build + lint 12/12. Resolves the X-report item flagged the same day in [[shift]].