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
+4
View File
@@ -528,6 +528,10 @@ export const en: Catalog = {
enterPositive: "Enter a positive amount.",
drawerNow: "Drawer now {{amount}}.",
zReport: "Z-REPORT",
viewTakings: "Takings so far",
xReport: "X-REPORT (so far)",
asOf: "as of",
xReportHint: "Read-only snapshot — nothing is recorded. The Z-report at close will sign these figures.",
payments: "Payments:",
cash: "Cash:",
card: "Card:",
+4
View File
@@ -540,6 +540,10 @@ export const sq = {
enterPositive: "Shkruaj një shumë pozitive.",
drawerNow: "Arka tani {{amount}}.",
zReport: "RAPORT Z",
viewTakings: "Arkëtimet deri tani",
xReport: "RAPORT X (deri tani)",
asOf: "deri më",
xReportHint: "Pamje vetëm për lexim — asgjë nuk regjistrohet. Raporti Z në mbyllje i nënshkruan këto shifra.",
payments: "Pagesa:",
cash: "Para:",
card: "Kartë:",