feat(tills): per-till activity log, wash bucket on the booth Z-report, wash-desk printer role

Closes the three known follow-ups of the Tills decision (venue-modules.md):

- Activity log per till: `tillOfEvent(type, payload)` in @parking/shared (money events
  by payload till, other events by their owning module's till, everything else booth),
  applied by `/api/events?till=` in SQL and passed by the hub log, the Drawer "today"
  panel and the booth feed (history + live pushes). The events route admits a role that
  holds a module feed permission without event:read and returns only that module's
  event types — the live-socket rule.
- Booth Z-report: `chargesByModuleMinor` sums the chargeLines on the till's payments by
  module; the ticket bucket excludes them (Bileta = parking only); printed
  "Lavazh (në biletë)" only when any was taken. The wash till's slip prints "Lavazh:".
- Printer role `wash-desk`: the wash till's Z-report and vouchers print there, falling
  back to the booth printer; nothing falls back to the desk. `printerRoleOf()` is the
  one reading of the role field (the entry/booth loaders treated any non-booth role as
  an entry dispenser). Footer label "at wash desk".

Also: `GET /api/carwash/settings` opens to carwash:read OR site:read (new
requireAnyPermission) — the Wash operator job could not load the desk's category and
service pickers. Tests for all four; wiki (shift, printer-roles-failover, venue-modules,
log) updated.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-06 12:37:26 +02:00
parent ea304bbfd1
commit e14e31a840
27 changed files with 414 additions and 91 deletions
+19 -2
View File
@@ -305,8 +305,25 @@ manifest (Car Wash → `carwash`; a future Bar → `bar`). Rules:
touch the booth by construction; the header button, the hub's start buttons and the
drawer switch never offer a till the server would refuse. (A first cut that borrowed
`session:read` as "works the booth till" lived for a few hours and is gone.)
- Not done: the per-shift *activity log* is still a time window over the whole chain (money
figures are per till, the event list is not); bay slips print on the booth printer.
- **The activity log is per till too (2026-09-06).** `tillOfEvent(type, payload)` in
`@parking/shared` extends the money rule to every event: a money event names its till, any
other event belongs to the till of the module that owns its type (a `carwash_order` is
wash-desk activity though no money moved), everything else — entries, exits, barrier
commands, pre-till events — is the booth's. `/api/events?till=` applies the same rule in
SQL (so the page limit applies after the filter); the hub's shift log, the Drawer "today"
panel and the booth feed (history and live pushes) pass their till. The events route also
admits a role without `event:read` that holds a module's feed permission, and then returns
only that module's event types — the live-socket rule, so a wash operator's hub shows the
wash shift's log.
- **The booth Z-report breaks module money out (2026-09-06).** `chargesByModuleMinor`
(`{ carwash: <minor> }`, only when any was taken) sums the `chargeLines` on the till's
payments by owning module; the ticket bucket EXCLUDES it, so `Bileta` is parking money only
and ticket + subscriptions + Σcharges = cash + card. Printed as `Lavazh (në biletë): X` on
the booth slip; the wash till's own slip prints its takings under `Lavazh:` (it sells no
tickets or subscriptions). Signed on `shift_z_report`; older reports read back as `{}`.
- **The wash till prints on its own printer (2026-09-06).** Printer role `wash-desk`; the
wash till's Z-report and vouchers go there, falling back to the booth printer — see
[[printer-roles-failover]].
## Where the fraud control actually lives