feat(tariff): Tariff Lab — pure session-pricing simulator
Test rates "in time" (overnight windows, daily caps, overstay) in seconds against any tariff version, instead of waiting hours/days. No real ledger writes. - Extract priceSession() into @parking/shared: the grace/overstay wrapper over computeFee (unpaid -> entry..now; within-grace -> settled 0; grace-expired -> overstay, a fresh period from grace-expiry). PayStation.quote() now calls it so the booth and the lab can never diverge. - API (tariffs.ts, tariff:read, read-only): POST /api/tariff/simulate prices a hypothetical session (active/any version/inline structure) and returns the priceSession outcome + a 30m..3d duration curve (see where the daily cap flattens); GET /api/tariff/simulate/session/:identity prefills from a real ledger session. - UI TariffLab.tsx at Setup -> "Tariff Lab": version picker, entry/asOf times, optional payment+grace, category, and load-a-real-ticket. Admin-gated, available on-site (useful to quote a dispute). - 4 new priceSession unit tests incl. the ticket-1245791632490 overstay-not-zero regression (40 pass). i18n lab.* + nav.tariffLab (sq+en). Verified live via the UI. Wiki: tariff (priceSession + Tariff Lab as-built), log. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -44,6 +44,7 @@ export const en: Catalog = {
|
||||
setup: "Setup",
|
||||
devices: "Devices",
|
||||
tariff: "Tariff",
|
||||
tariffLab: "Tariff Lab",
|
||||
subscriptions: "Subscriptions",
|
||||
site: "Site",
|
||||
users: "Users",
|
||||
@@ -330,6 +331,36 @@ export const en: Catalog = {
|
||||
relayLabel: "Relay {{relay}} ({{direction}})",
|
||||
noRelaysConfigured: "This controller has no relays configured.",
|
||||
},
|
||||
lab: {
|
||||
title: "Tariff Lab",
|
||||
intro:
|
||||
"Test rates in time (day/night windows, daily caps, overstay) in seconds, with no waiting. Pricing uses the same logic as the booth; nothing is written to the ledger.",
|
||||
loadTicket: "Load from a real ticket",
|
||||
loadTicketPh: "Ticket number / identity",
|
||||
load: "Load",
|
||||
loaded: "Loaded session {{id}}",
|
||||
tariffVersion: "Tariff version",
|
||||
activeVersion: "Active version (current)",
|
||||
entered: "Entered",
|
||||
asOf: "As of (now/exit)",
|
||||
now: "Now",
|
||||
category: "Category",
|
||||
categoryPh: "e.g. bus (blank = car)",
|
||||
payment: "Payment",
|
||||
paid: "paid",
|
||||
graceMin: "grace (min)",
|
||||
price: "Compute price",
|
||||
pricing: "Pricing…",
|
||||
outcome: "Outcome",
|
||||
amountDue: "Amount due",
|
||||
billedPeriod: "Billed period",
|
||||
overstay: "overstay",
|
||||
settled: "settled",
|
||||
periodStart: "Period start",
|
||||
graceExpires: "Grace expires",
|
||||
curve: "Duration curve",
|
||||
curveHint: "Fee from entry at several durations — see where the daily cap flattens or windows shift.",
|
||||
},
|
||||
subs: {
|
||||
title: "Subscriptions",
|
||||
unnamed: "(unnamed)",
|
||||
|
||||
@@ -46,6 +46,7 @@ export const sq = {
|
||||
setup: "Konfigurimi",
|
||||
devices: "Pajisjet",
|
||||
tariff: "Tarifa",
|
||||
tariffLab: "Lab Tarife",
|
||||
subscriptions: "Abonimet",
|
||||
site: "Park",
|
||||
users: "Përdoruesit",
|
||||
@@ -341,6 +342,36 @@ export const sq = {
|
||||
relayLabel: "Rele {{relay}} ({{direction}})",
|
||||
noRelaysConfigured: "Ky kontrollues nuk ka rele të konfiguruar.",
|
||||
},
|
||||
lab: {
|
||||
title: "Lab Tarife",
|
||||
intro:
|
||||
"Testo tarifat në kohë (dritare ditë/natë, kufi ditor, qëndrim tej afatit) në sekonda, pa pritur orë. Çmimi llogaritet me të njëjtën logjikë si kabina; nuk shkruhet asgjë në ledger.",
|
||||
loadTicket: "Ngarko nga një biletë reale",
|
||||
loadTicketPh: "Numri i biletës / identiteti",
|
||||
load: "Ngarko",
|
||||
loaded: "U ngarkua sesioni {{id}}",
|
||||
tariffVersion: "Versioni i tarifës",
|
||||
activeVersion: "Versioni aktiv (i tanishëm)",
|
||||
entered: "Hyrja",
|
||||
asOf: "Deri më (tani/dalja)",
|
||||
now: "Tani",
|
||||
category: "Kategoria",
|
||||
categoryPh: "p.sh. bus (bosh = makinë)",
|
||||
payment: "Pagesa",
|
||||
paid: "u pagua",
|
||||
graceMin: "afati (min)",
|
||||
price: "Llogarit çmimin",
|
||||
pricing: "Duke llogaritur…",
|
||||
outcome: "Rezultati",
|
||||
amountDue: "Shuma për pagesë",
|
||||
billedPeriod: "Periudha e faturuar",
|
||||
overstay: "tej afatit",
|
||||
settled: "i shlyer",
|
||||
periodStart: "Fillimi i periudhës",
|
||||
graceExpires: "Afati skadon",
|
||||
curve: "Kurba sipas kohëzgjatjes",
|
||||
curveHint: "Tarifa nga hyrja për disa kohëzgjatje — shih ku rrafshohet kufiri ditor ose ndryshojnë dritaret.",
|
||||
},
|
||||
subs: {
|
||||
title: "Abonimet",
|
||||
unnamed: "(pa emër)",
|
||||
|
||||
Reference in New Issue
Block a user