feat(tariff): V2 — legacy-parity pricing (time-of-day, category, seasonal, flat)
Bring the legacy ParkSQL2017 pricing BREADTH onto our engine while keeping
integer-minor-unit money + immutable signed versions (rejecting legacy's
float money / mutable rows). TariffStructure becomes a discriminated union:
V1 = the original bare ladder (UNCHANGED, verbatim algorithm, golden-
regression-tested against the live version); V2 = {version:2, tz, shared
knobs, defaultCard, windowedCards[]} where each card is flat OR a block
ladder and may be scoped by wall-clock hour window / day-of-week / date
range / vehicle category.
computeFeeV2 prices by stepping one increment at a time, advancing the
ladder by ELAPSED minutes (continuous) while selecting the active card by
WALL-CLOCK time in the version's FROZEN tz. Decisions: tz is a per-site
setting (site_config.timezone, default Europe/Tirane) stamped server-side
into each version on publish — never the host clock (reproducibility);
default-card cap governs a mixed day; precedence = specificity
(date>dow>hour) -> priority -> name (total, order-independent), validation
rejects ambiguous ties; category = a card FIELD, frozen in the signed
vehicle_entry payload (site_config.default_vehicle_category default), read
at both pricing call-sites.
Composer: default card front-and-centre (flat/ladder toggle), tiers under
an "Advanced" disclosure; emits BARE V1 when no tiers (back-compat). DB:
migrations 0005 (timezone) + 0006 (default_vehicle_category). Stood up
vitest in @parking/shared (was zero tests on the ledger-feeding fee fn);
36 tests incl. golden V1 regression, happy-hour/overnight/dow/flat/category/
cap edges, precedence shuffle-invariance, Europe/Tirane DST determinism,
validation matrix — all green. No event-chain change.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -120,6 +120,28 @@ export const en: Catalog = {
|
||||
publishNewVersion: "Publish new version",
|
||||
publishing: "Publishing…",
|
||||
publishedOk: "New tariff version published — it's now the active rate card.",
|
||||
defaultCard: "Default card (always active)",
|
||||
defaultCardHint: "The base rate applied when no time/seasonal tier matches. This alone is enough for most car parks.",
|
||||
modeLadder: "Hourly ladder",
|
||||
modeFlat: "Flat price",
|
||||
tiersAdvanced: "Advanced: time & seasonal tiers",
|
||||
tiersHint: "Optional. Add cards that apply only at certain hours/days/dates or for a category (e.g. happy hour, night rate, weekend, bus). With no tiers, the simple card is published.",
|
||||
tierName: "Name",
|
||||
tierPriority: "Priority",
|
||||
tierCategory: "Category",
|
||||
tierCategoryPh: "e.g. bus",
|
||||
tierDays: "Days",
|
||||
tierHours: "Hours",
|
||||
tierDates: "Dates",
|
||||
tierOvernight: "(crosses midnight)",
|
||||
addTier: "+ Add tier",
|
||||
dow1: "Mon",
|
||||
dow2: "Tue",
|
||||
dow3: "Wed",
|
||||
dow4: "Thu",
|
||||
dow5: "Fri",
|
||||
dow6: "Sat",
|
||||
dow0: "Sun",
|
||||
},
|
||||
subs: {
|
||||
title: "Subscriptions",
|
||||
|
||||
@@ -122,6 +122,28 @@ export const sq = {
|
||||
publishNewVersion: "Publiko version të ri",
|
||||
publishing: "Duke publikuar…",
|
||||
publishedOk: "U publikua versioni i ri i tarifës — tani është karta tarifore aktive.",
|
||||
defaultCard: "Karta e parazgjedhur (gjithmonë aktive)",
|
||||
defaultCardHint: "Çmimi bazë i zbatuar kur asnjë nivel kohor/sezonal nuk vlen. Kjo e vetme është mjaftueshëm për shumicën e parkimeve.",
|
||||
modeLadder: "Shkallë orësh",
|
||||
modeFlat: "Çmim fiks",
|
||||
tiersAdvanced: "Të avancuara: nivele kohore & sezonale",
|
||||
tiersHint: "Opsionale. Shto karta që vlejnë vetëm në orë/ditë/data ose kategori të caktuara (p.sh. orë e lirë, tarifë nate, fundjavë, autobus). Pa nivele, publikohet karta e thjeshtë.",
|
||||
tierName: "Emri",
|
||||
tierPriority: "Përparësia",
|
||||
tierCategory: "Kategoria",
|
||||
tierCategoryPh: "p.sh. autobus",
|
||||
tierDays: "Ditët",
|
||||
tierHours: "Orët",
|
||||
tierDates: "Datat",
|
||||
tierOvernight: "(kalon mesnatën)",
|
||||
addTier: "+ Shto nivel",
|
||||
dow1: "Hën",
|
||||
dow2: "Mar",
|
||||
dow3: "Mër",
|
||||
dow4: "Enj",
|
||||
dow5: "Pre",
|
||||
dow6: "Sht",
|
||||
dow0: "Die",
|
||||
},
|
||||
subs: {
|
||||
title: "Abonimet",
|
||||
|
||||
Reference in New Issue
Block a user