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:
@@ -17,8 +17,10 @@ Each printer instance (a `devices` row, category `printer`) declares a **role**
|
||||
config:
|
||||
|
||||
- **`entry-dispenser`** — outside, at the lane. Prints the entry ticket the driver takes.
|
||||
- **`booth-receipt`** — inside the booth. Prints receipts at exit/payment, AND serves as the
|
||||
**backup** for entry tickets.
|
||||
- **`booth-receipt`** — inside the booth. Prints receipts at exit/payment, the booth till's
|
||||
Z-reports and vouchers, AND serves as the **backup** for entry tickets (and for wash slips).
|
||||
- **`wash-desk`** — at the Car Wash desk (added 2026-09-06). Prints the wash till's slips:
|
||||
its Z-report and drawer vouchers (see [[shift]] §Tills). Nothing else ever prints here.
|
||||
|
||||
It also declares a **`failoverRank`** (higher = preferred within a role) to order multiple
|
||||
printers of the same role deterministically (ties broken by id).
|
||||
@@ -33,6 +35,16 @@ The reverse is **deliberately not** done: a **receipt** never prints on the outs
|
||||
Receipts are a booth-only job; an entry dispenser falling back to print receipts makes no
|
||||
physical sense.
|
||||
|
||||
For a **wash slip** (`wantRole = wash-desk`): the desk printers first, then the **booth
|
||||
printer** — a site that has not bought a desk printer keeps printing the wash till's Z-report
|
||||
and vouchers in the booth, exactly as it did before the role existed. Nothing falls back *to*
|
||||
the wash desk: a booth receipt or an entry ticket never prints there. `ShiftService` resolves
|
||||
the role from the till (`TILL_PRINTER_ROLE`: booth → `booth-receipt`, carwash → `wash-desk`)
|
||||
and keeps one legacy fallback — a booth with a single printer that carries no booth role still
|
||||
prints its slips on it. `printerRoleOf(config)` is the one reading of the saved `role` field,
|
||||
so every loader (entry flow, booth receipts, shift slips) agrees on what a printer is; the
|
||||
device footer shows a desk printer as "at wash desk".
|
||||
|
||||
## Where the logic lives
|
||||
|
||||
- The driver (`rongta`) is **role-agnostic** — role/rank are just config; the transport doesn't
|
||||
|
||||
+19
-2
@@ -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
|
||||
|
||||
|
||||
@@ -522,11 +522,12 @@ control against the unrecorded-wash vector, and it must sit with the person hold
|
||||
→ Roles); a permission-scoped live feed for module desks (the WS is `report:read` only —
|
||||
the wash desk polls, 5 s / 15 s).
|
||||
|
||||
**Known follow-ups.** A shift's *activity log* (right pane of the hub, Drawer "today") is
|
||||
still a time window over the whole chain, so a booth shift's log shows wash events in that
|
||||
window (money figures are per till; the log is not). A separate wash bucket on the booth's
|
||||
Z-report (booth-paid washes ride `chargeLines`) is still open. Bay slips print on the booth
|
||||
printer until a wash-desk printer role exists.
|
||||
**Follow-ups, closed 2026-09-06** (details on [[shift]] §Tills and [[printer-roles-failover]]):
|
||||
the activity log is per till (`tillOfEvent`, `/api/events?till=`; a feed-permission role
|
||||
reads its module's events without `event:read`); the booth Z-report carries
|
||||
`chargesByModuleMinor` (a booth-paid wash is out of the ticket bucket, printed
|
||||
`Lavazh (në biletë)`); the wash till's slips print on a `wash-desk` printer, falling back
|
||||
to the booth's.
|
||||
|
||||
## Review log — issues and ideas from the first hands-on pass (2026-09-05)
|
||||
|
||||
|
||||
+21
@@ -3049,3 +3049,24 @@ design error: the discount ENGINE (validation program rows + `applyValidation()`
|
||||
`validation` module is only the merchant's scan screen. Fixed: `dependsOn: ["parking"]`; the
|
||||
program routes are plain site:read/site:update; the merchant routes (mine/lookup/apply/void)
|
||||
stay module-gated. Tests updated. Recorded on [[venue-modules]] (v1 answers item 4 + As-built).
|
||||
|
||||
## [2026-09-06] ingest | Tills follow-ups closed: per-till activity log, wash bucket on the Z, wash-desk printer
|
||||
The three "known follow-ups" of the Tills decision are built. (1) `tillOfEvent(type, payload)`
|
||||
in `@parking/shared` — money events by payload `till`, other events by their owning module's
|
||||
till, everything else booth — is applied by `/api/events?till=` in SQL and passed by the hub
|
||||
log, the Drawer "today" panel and the booth feed; the events route now admits module-feed
|
||||
roles (a wash operator's `carwash:read`) and returns only their module's types, the same rule
|
||||
the live socket uses. (2) `chargesByModuleMinor` on the shift report/summary/signed payload:
|
||||
module charges on the till's payments by module; the ticket bucket excludes them; printed
|
||||
`Lavazh (në biletë)` on the booth slip; the wash till's slip prints `Lavazh:` for its own
|
||||
takings. (3) Printer role `wash-desk`: the wash till's Z-report and vouchers print there with
|
||||
failover to the booth printer; `printerRoleOf()` is the one reading of the role field so a
|
||||
desk printer is never mistaken for an entry dispenser; footer label "at wash desk". Updated
|
||||
[[shift]] §Tills, [[printer-roles-failover]], [[venue-modules]].
|
||||
|
||||
## [2026-09-06] ingest | Wash operator job could not load the desk's price list
|
||||
User built a role from the "Wash operator" chip (carwash:read/create/update/cash) and the desk's
|
||||
category/service pickers stayed empty. Cause: `GET /api/carwash/settings` was guarded by
|
||||
`site:read` only — the price list is Setup's data AND the desk's working data. Fixed with a
|
||||
new `requireAnyPermission(...)` guard (auth.ts): the read opens to `carwash:read` OR
|
||||
`site:read`; the write stays `site:update`. Regression test in carwash.test.ts.
|
||||
|
||||
Reference in New Issue
Block a user