tariff composer: admin publishes rate-card versions (pay station now operable)
validateTariffStructure (shared): non-negative ints, ascending block bounds, only the last block open-ended — a malformed card can't be published. Routes: GET /api/tariff (active + history, any signed-in role), POST /api/tariff/versions (publish an immutable, effective-dated version; admin only). The single site tariff row is created lazily. Editing = publish a new version; past sessions keep their pricing. Web: TariffComposer in the admin shell — edit currency, grace windows, increment, daily cap, lost-ticket fee, and add/remove rate blocks (major-unit input -> minor on submit); shows active version + history. Verified via inject: empty -> active null; invalid blocks -> 400 with problem; valid -> 201; readonly publish -> 403; after publishing, the pay station quote returns 404 (no session) instead of 409 (no tariff) -- it now prices against the active card.
This commit is contained in:
@@ -96,6 +96,22 @@ because the chain + reconciliation depend on the result being reproducible.
|
||||
**As-built:** `computeFee(enteredAt, asOf, structure)` in `packages/shared` (pure). Unit-tested
|
||||
across grace, block steps, daily cap, and multi-day reset.
|
||||
|
||||
### Composer (as-built 2026-06-15)
|
||||
|
||||
The admin authors the rate card at runtime — no hand-seeding:
|
||||
|
||||
- **API** (`apps/server/src/routes/tariffs.ts`): `GET /api/tariff` (active version + history; any
|
||||
signed-in role) and `POST /api/tariff/versions` (publish a new immutable version; **admin only**).
|
||||
Publishing validates the structure via `validateTariffStructure` (shared) — non-negative integers,
|
||||
ordered/ascending block bounds, only the last block open-ended — so a malformed card can never be
|
||||
published. The single site `tariffs` row is created lazily on first read/publish.
|
||||
- **UI** (`apps/web/src/TariffComposer.tsx`, admin shell): edit currency, grace windows, increment,
|
||||
daily cap, lost-ticket fee, and add/remove rate blocks; amounts entered in major units, converted
|
||||
to integer minor units on submit. Shows the active version + history; "Publish" creates a new
|
||||
version (past sessions keep their pricing).
|
||||
- Ships **blank** — until a version is published, `GET /api/tariff` returns `active: null` and the
|
||||
pay station returns `409 no active tariff`. Verified end to end (publish → pay station prices).
|
||||
|
||||
## The pay-on-foot consequence
|
||||
|
||||
Because payment is decoupled from exit ([[parking-session]] lifecycle), the tariff has **two
|
||||
|
||||
Reference in New Issue
Block a user