feat(tariff-lab): DB-backed draft tariffs + named published versions
Experimenting used to mean publishing — churning the immutable version
history and risking real tickets pricing against a half-baked card while
the admin iterated. The lab is now a true sandbox:
- tariff_drafts table (migration 0021): MUTABLE by design — the one
exception to "editing publishes a version"; a draft prices nothing and
signs nothing. Drafts are validated + tz-stamped on save exactly like a
publish, so a saved draft always simulates and never fails at publish.
- CRUD under /api/tariff/drafts (list tariff:read, mutations
tariff:update); publishing a draft goes through the normal immutable
POST /api/tariff/versions path.
- Lab UI rebuilt: sidebar lists lab drafts AND the full published history
(click any to price against it); main pane cut to pure entry/exit
(ticket loader, payment, category inputs dropped); the composer form is
extracted to TariffEditorForm.tsx and reused in a modal (new drafts
prefill from the active card); per-draft Publish with confirm.
- tariff_versions.name (migration 0022): optional label stamped at
publish — carried from the lab draft, or typed in the composer's new
optional field — so history reads "Winter 2027", not UUID prefixes.
- Includes the composer UI + sq/en labels for the package mode (engine
landed in d9e6c13) and the "Flat price / hour" relabel.
5 new server integration tests (RBAC, roundtrip, validation, tz-stamp +
simulate + publish w/ name); server suite 288 green.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+26
-16
@@ -322,25 +322,30 @@ export const sq = {
|
||||
bandDuration: "Kohëzgjatja e brezit",
|
||||
hoursUnit: "orë",
|
||||
egHours: "p.sh. 2",
|
||||
pricePerIncrement: "Çmimi / interval",
|
||||
pricePerIncrement: "Çmimi / interval (orë)",
|
||||
thereafter: "më pas (i hapur)",
|
||||
remove: "Hiq",
|
||||
addBlock: "+ Shto bllok",
|
||||
publishNewVersion: "Publiko version të ri",
|
||||
publishing: "Duke publikuar…",
|
||||
versionNamePh: "Emri i versionit (opsional), p.sh. Vera 2026",
|
||||
publishedOk: "U publikua versioni i ri i tarifës — tani është tarifa aktive.",
|
||||
defaultCard: "Tarifa bazë (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",
|
||||
modeFlat: "Çmim fiks / orë",
|
||||
modeStepped: "Sipas kohëzgjatjes (deri-në)",
|
||||
modePackage: "Paketë dritareje (një total)",
|
||||
packageHint:
|
||||
"NJË çmim total për çdo prani brenda dritares së këtij niveli — largimi më herët kushton njësoj. Prekja e dritares në dy net të ndryshme e faturon paketën dy herë (një herë për natë). Orët jashtë dritares vlerësohen me tarifën bazë.",
|
||||
packageTotal: "Çmimi i paketës",
|
||||
steppedHint:
|
||||
"Vendos çmimin TOTAL për një qëndrim deri në një kohë të caktuar (p.sh. deri 3 orë = 500). Fiton rreshti i parë me kufi ≥ kohëzgjatjes (kufiri përfshihet). Totali i rreshtit të fundit përsëritet si çmim ditor për qëndrime më të gjata.",
|
||||
stepUpTo: "Deri në",
|
||||
stepTotal: "Çmimi total",
|
||||
addStep: "+ Shto rresht",
|
||||
steppedTiersConflict:
|
||||
"⚠ Nivelet kohore/sezonale NUK zbatohen kur tarifa bazë është 'Sipas kohëzgjatjes (deri-në)' — motori i shpërfill plotësisht. Hiqi nivelet, ose ndrysho tarifën bazë në 'Shkallë orësh' a 'Çmim fiks'. Publikimi bllokohet derisa kjo të rregullohet.",
|
||||
"⚠ Nivelet kohore/sezonale NUK zbatohen kur tarifa bazë është 'Sipas kohëzgjatjes (deri-në)' — motori i shpërfill plotësisht. Hiqi nivelet, ose ndrysho tarifën bazë në 'Shkallë orësh' a 'Çmim fiks / orë'. Publikimi bllokohet derisa kjo të rregullohet.",
|
||||
tiersAdvanced: "Të avancuara: nivele kohore & sezonale",
|
||||
tiersHint: "Opsionale. Shto nivele tarifore që vlejnë vetëm në orë/ditë/data ose kategori të caktuara (p.sh. orë e lirë, tarifë nate, fundjavë, autobus). Pa nivele, publikohet vetëm tarifa bazë.",
|
||||
tierName: "Emri",
|
||||
@@ -525,21 +530,26 @@ export const sq = {
|
||||
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)",
|
||||
"Kompozo tarifa eksperimentale dhe llogarit qëndrime hipotetike kundrejt tyre — asgjë nuk hyn në fuqi pa u publikuar. Çmimi llogaritet me të njëjtën logjikë si kabina; nuk shkruhet asgjë në ledger.",
|
||||
drafts: "Tarifa laboratori",
|
||||
newDraft: "Draft i ri",
|
||||
activeTariff: "Tarifa aktive",
|
||||
published: "Versione të publikuara",
|
||||
noDrafts: "Ende pa tarifa laboratori — krijo një draft për të eksperimentuar.",
|
||||
edit: "Ndrysho",
|
||||
publish: "Publiko",
|
||||
delete: "Fshi",
|
||||
draftName: "Emri",
|
||||
draftNamePh: "p.sh. Propozimi i dimrit",
|
||||
saveDraft: "Ruaj draftin",
|
||||
savingDraft: "Duke ruajtur…",
|
||||
newDraftTitle: "Tarifë e re laboratori",
|
||||
editDraftTitle: "Ndrysho tarifën e laboratorit",
|
||||
confirmPublish: 'Të publikohet "{{name}}" si karta e re aktive e çmimeve? Hyn në fuqi menjëherë.',
|
||||
confirmDelete: 'Të fshihet tarifa e laboratorit "{{name}}"?',
|
||||
entered: "Hyrja",
|
||||
asOf: "Deri më (tani/dalja)",
|
||||
exit: "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",
|
||||
|
||||
Reference in New Issue
Block a user