From 062feeae2f3b7890711a7e06b59d2eaa27693fe5 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Thu, 18 Jun 2026 11:05:43 +0200 Subject: [PATCH] docs(wiki): update index + log for booth console, drawer, and tariff research Catalog the new concept/source pages and append chronological log entries for the tariff research, live WebSocket, booth pay/exit, active sessions, and shift drawer work. --- wiki/index.md | 5 ++++- wiki/log.md | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/wiki/index.md b/wiki/index.md index f2596d7..649ab87 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -7,7 +7,7 @@ updated: 2026-06-14 # Index Content catalog for the wiki. Start at [[overview]]. Maintained on every ingest. -Counts: 3 sources · 19 entities · 25 concepts · 5 decision records. +Counts: 4 sources · 19 entities · 27 concepts · 5 decision records. ## Overview & navigation - [[overview]] — the top-level synthesis and entry point. @@ -18,6 +18,7 @@ Counts: 3 sources · 19 entities · 25 concepts · 5 decision records. - [[parking-system-architecture]] — design notes: stack, threat model, devices, UHPPOTE, ESP32, readers, BOM, open decisions. - [[gee-qr-er80]] — datasheet: GEE QR access reader (QR/DM/1D; Wiegand/RS-232/485/USB/TCP; Linux). - [[qrcode-sdk]] — QRCode SDK v1.6.5: the reader's HTTP-GET-poll protocol + JSON verdict (beep/output). +- [[parksql2017-legacy-schema]] — predecessor SQL Server schema (Albanian market): legacy tariff/discount/membership/shift/fiscal model; confirms blocks, adds time-windows + categories, lacks postpaid sponsors. ## Entities — technology stack - [[technology-stack]] — the full stack table; all MIT/Apache/BSD, chosen to avoid lock-in. @@ -78,11 +79,13 @@ Counts: 3 sources · 19 entities · 25 concepts · 5 decision records. ## Concepts — business domain - [[parking-session]] — the core domain entity; a projection over the signed log, never a mutable table. - [[tariff]] — fee model; pure, data-driven, offline; pay-on-foot adds a walk-back grace window. +- [[tariff-time-tiers]] — design: happy-hour/off-peak/weekend/seasonal + vehicle categories via time-windowed rate cards. - [[shift]] — manned-only accountability period; explicit Start/End (not time-based); End → signed + printed Z-report (cash + POS). - [[capacity-occupancy]] — live count = open sessions; refuse entry + FULL sign when full (soft policy); exit never blocked. - [[site-metadata]] — optional park identity (name, operator, VAT, address, contact) in site_config; feeds the ticket header. - [[valet-overcapacity]] — "full" is soft: operator may valet-accept over capacity (keys handed over, custody). Manned, deferred. - [[validation-discounts]] — merchant validates a ticket → signed discount event applied at fee time. +- [[validation-sponsorship]] — design: sponsor accounts + postpaid B2B (customers park free, business billed monthly); not a permit. - [[reporting-analytics]] — revenue/occupancy/stay reports + plate-search, all projections over the signed log. - [[clock-integrity]] — fees depend on the host clock; detect/flag backdating on an offline box. - [[ticket-encoding]] — transient ticket id as QR; printed at entry, scanned at pay station + exit; plate-as-ticket alt. diff --git a/wiki/log.md b/wiki/log.md index 2178b71..0cf48b6 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -760,3 +760,23 @@ Extended `site_config` (single-row) with optional park identity: `park_name`, `o ## [2026-06-17] build | Ticket in Albanian; VAT->NIUS, drop registration; CP852 codepage Ticket header now prints in Albanian and uses NIUS instead of VAT. Renamed `site_config.vat_number` -> `nius` and DROPPED `registration_number` (regenerated migration 0001 in place; only the dev DB had it, so no migration debt; dev DB reset + re-migrated). `renderTicket()`: NIUS line (only if set), "Printuar më:" before the timestamp, "Keni humbur biletën? " footer; strings centralised in a `STR` table for future i18n. Added CP852 (Latin-2) codepage support (`ESC t 18` + a Unicode->CP852 `line()` encoder with ASCII fallback) so `ë`/`ç` render. Touched: schema, migration, routes/site.ts, web api.ts + SiteSettings.tsx, devices interfaces + printer-rongta.ts, entry-flow.ts. Byte-verified ë->0x89. See [[site-metadata]], [[ticket-encoding]]. + +## [2026-06-17] ingest | ParkSQL2017 legacy schema + tariff research + +Ingested `raw/parksql2017-legacy-schema.sql` (predecessor SQL Server 2017 schema, decoded from UTF-16; Albanian market — NIVF fiscal codes, Cupons, LostPrice1..4). Source summary in [[parksql2017-legacy-schema]]. Combined with a deep-research run (5 claims verified 3-0/2-0; synthesis + 20 claims aborted on a session limit — treat those as unverified, not refuted). Filed two design pages: [[tariff-time-tiers]] (happy-hour/off-peak/weekend/seasonal + vehicle categories via time-windowed rate cards; the hard part is wall-clock stay-slicing with a continuous block ladder) and [[validation-sponsorship]] (sponsor accounts + postpaid B2B billing; distinct from [[permit]]). Reconciled with the existing [[validation-discounts]] (cross-linked, no duplication — that page owns the signed-event discount mechanism, the new one owns sponsor/settlement). Updated [[tariff]] (new "Extensions under design" section; removed the now-addressed time-tier open item). Legacy confirms our stepped ladder + per-rate lost penalty + typed session discount; adds time-of-day windows + category axis; lacks any postpaid sponsor model (net-new). Flagged legacy anti-patterns we deliberately reject: float money, mutable rate rows, in-row image BLOBs. + +## [2026-06-17] build | Booth actions — ticket input, pay/exit modal, voucher, snapshots + +Made the booth screen operational (was a passive monitor). Backend: `site_config.exit_voucher_default` (additive migration 0002); `GET /api/session/:identity` (lookup + quote in one read), `POST /api/exit` (booth-driven, VALIDATED exit — reuses ExitFlow's paid+grace checks, no booth bypass; signs vehicle_exit + pulses an exit relay resolved via firstRelayByDirection; payment never rolled back, relay-open failure → signed anomaly + opened:false), `POST /api/voucher` (reprint paid ticket id barcode on the booth printer). Refactored ExitFlow into shared #signExit/#fireExitSnapshot/#closeSessionCache so the reader and booth paths are one validated code path. Frontend: ticket input on /booth (HID-scanner-friendly), Radix pay/exit modal (entry/now/duration/total + tender + 'Printo biletë dalje' checkbox defaulting from site_config), entry/exit SnapshotStrip (thumbnails → zoom). Verified end-to-end in-browser: scan → modal shows ALL 200 + real entry photo → pay → "barrier opened"; ledger recorded entry→payment→vehicle_exit in order; chain verify ok after. Decision recorded in [[booth-exit-flow]]. Unpaid exit correctly 409-refused (threat-model). NOT yet covered: voucher print success path (dev printers physically offline), automated tests. + +## [2026-06-17] query | Walk-back grace renews on every payment (voucher overstay) + +User flagged: customer pays, takes an exit voucher, dawdles past grace. Traced exit-flow.ts + pay-station.ts. Findings: refuse-on-expiry ✓ (no free exit) and reprice-from-entry ✓ (timer never restarts — `computeFee(enteredAt, now)`, NOT from paidAt) are both correct and deliberate. BUG: each `payment` writes its own `graceExitMin` and the exit flow reads the LATEST one, so every top-up re-grants a full walk-back window → grace doubles/repeats. Leak is TIME not money (fee always catches up from entry), bounded by increment coarseness but real. Flagged as an open question in [[booth-exit-flow]] (full analysis + 3 candidate fixes) and [[tariff]] (cross-ref + corrected the original `f(paidAt,now)` sketch to the as-built entry-based reprice). Decision deferred — fairness vs. anti-abuse business call. Recommended fix: grant grace on top-up only when it charged new money. + +## [2026-06-18] build | Active sessions panel + audited barrier re-open + +Operator escape hatch for stuck cars (damaged ticket / dead scanner / phantom barrier re-close). Key model from operator: the barrier state is ASSUMED not confirmed, so a session is "active" while OPEN or exited-but-within-grace — payment and a successful voucher scan do NOT remove it; only grace expiry does. Backend: PayStation.activeSessions() (one ledger fold, open OR within-grace, newest first), GET /api/sessions/active; ExitFlow.reopenBarrier() + POST /api/barrier/reopen — re-pulses an exit relay and signs an attributed `anomaly` (barrierReopen, operator), NEVER a 2nd vehicle_exit. Guard: no payment → 409 refuse (no-unpaid-bypass), enforced server-side AND the UI hides the button. Frontend: ActiveSessions panel on /booth (live via WS invalidation + 15s poll for grace expiry), row click → pay/exit modal, "Open barrier" only on paid rows. Verified in-browser: in-grace session stayed listed as "exiting" with the button; unpaid rows had none; click → audited anomaly #53 [op=boothtest], vehicle_exit count stayed 1 (no double-count); chain verify ok. Design recorded in [[booth-exit-flow]] (Active sessions & human-intervention barrier open). Standing gap: still no automated tests. + +## [2026-06-18] build | Drawer balance — opening float carry-over + admin cash movements + +Cash drawer that carries across shifts. New signed `cash_movement` ledger event type (shared); admin-only POST /api/cash-movement {amountMinor signed +load/-remove, reason}. ShiftService: #drawerBalanceAt(time) folds cash payments + cash_movements BY TIME (not operator — the movement is the admin's); open() auto-inherits openingFloat = drawerBalanceAt(start) and records it on shift_open; close() Z-report adds openingFloat/cashAdded/cashRemoved/expectedDrawer (= opening + taken + added − removed = next shift's opening float). Card payments excluded (settle to bank). GET /api/shift/current returns live drawerMinor. Frontend: ShiftControl shows live drawer + admin Load/Remove form + full Z-report drawer block (admin gate via router context). Verified the canonical scenario on a FRESH DB: load 5000 → shift1 takes 6500 → expected 11500 → shift2 inherits 11500, admin removes 5000, takes 4500 → expected 11000 → shift3 inherits 11000; chain ok. Also verified through the real UI (load/remove → Z-report opening 11200 removed 5000 expected 6200; both cash_movements signed+attributed; chain ok). Decision + worked example in [[shift]] (Drawer balance section). Standing gap: still no automated tests.