feat(carwash): Car Wash v1 + per-till shifts + site-level pay-at + till access by module permission
Car Wash — the pilot venue module (wiki/decisions/venue-modules.md): - Master data (categories × services price matrix) at /setup/carwash; the desk at /wash (ticket lookup → order; open queue oldest-first: Done / Paid cash / Paid card / Void; Finished list). Orders freeze names + price; their life is signed (carwash_order, carwash_payment). Migration 0027. - Where money is taken is a SITE setting (carwash_config.pay_at, migration 0028, signed config_change on a flip) — no per-order radio; a stale client is refused (409). - Core seams: PayStation charge providers (a booth-paid wash rides the parking payment as chargeLines) + applyValidation() shared with the merchant route. A bay-paid, done wash signs the $0 parking payment so the exit reader releases the car. - "Parking discount" modes for the wash: free while the wash runs (+ tolerance) and wash price off the fee (floored at 0), resolved at done and anchored at the order's intake (the entry-anchored version comped a 74-day stay); typed-amount and percent hidden for the wash. Long durations render y/d/h/m. Tills — a shift belongs to a till, not the site (wiki/concepts/shift.md §Tills): - TillId booth|carwash; every money event names its till (absent = booth, so the chain re-folds identically). ShiftService is per till: single-open, folds, X/Z-reports, vouchers, carry-forward. A bay payment needs the carwash shift. - Working a till needs that till's module permission (manifest tillPermission; 403 till_forbidden); /api/shift/tills lists only the role's tills. - Web: ShiftButton per till (header = booth, wash desk = carwash); shift hub lists every open shift with till badges + filter; drawer hub switches tills. Modules: landing per module (index route resolves booth → module landing → shifts → profile); guards bounce to "/", /booth needs session:read. Tests: carwash e2e suite (settings, intake, booth/bay paths, modes, void, gate, pay-at policy, till permissions), 6 per-till shift tests; suite green (1 pre-existing flaky backup test under the parallel run). Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
@@ -67,13 +67,74 @@ export const sq = {
|
||||
name: {
|
||||
parking: "Parkimi",
|
||||
validation: "Validime tregtare (Bar)",
|
||||
carwash: "Lavazh",
|
||||
},
|
||||
},
|
||||
wash: {
|
||||
tillTitle: "Arka e lavazhit",
|
||||
tillHint: "Paratë e marra te lavazhi regjistrohen në arkën e lavazhit — hap fillimisht turnin e lavazhit. Turni i kabinës nuk vlen.",
|
||||
tillOtherHint: "{{operator}} e ka turnin e lavazhit; vetëm ai mund të marrë para te lavazhi.",
|
||||
drawerNow: "Arka e lavazhit tani",
|
||||
intake: "Lavazh i ri",
|
||||
ticketPh: "Bileta e parkimit (skano ose shkruaj)",
|
||||
lookup: "Kërko",
|
||||
notFound: "Nuk ka sesion për këtë biletë.",
|
||||
closed: "Ky sesion është mbyllur.",
|
||||
ticket: "Bileta",
|
||||
plate: "Targa",
|
||||
enteredAt: "Hyri",
|
||||
alreadyOpen: "Kjo biletë ka tashmë një porosi lavazhi të hapur.",
|
||||
category: "Kategoria e mjetit",
|
||||
service: "Shërbimi",
|
||||
price: "Çmimi",
|
||||
noPrice: "nuk ka çmim për këtë kombinim",
|
||||
payAt: "Pagesa",
|
||||
payAtBooth: "Në kabinë",
|
||||
payAtBay: "Në lavazh",
|
||||
payAtBoothHint: "Shtohet në llogarinë e parkimit; barriera e daljes hapet pas pagesës në kabinë.",
|
||||
payAtBayHint: "Paratë merren këtu; klienti del duke skanuar biletën te lexuesi i daljes (sponsorizimi i parkimit duhet ta mbulojë tarifën).",
|
||||
create: "Krijo porosinë",
|
||||
created: "Porosia u krijua.",
|
||||
queue: "Porositë e hapura",
|
||||
empty: "Asgjë për të larë.",
|
||||
time: "Ora",
|
||||
what: "Lavazhi",
|
||||
status: "Statusi",
|
||||
statusOpen: "në proces",
|
||||
statusDone: "mbaroi",
|
||||
paid: "paguar",
|
||||
unpaid: "papaguar",
|
||||
done: "Mbaroi",
|
||||
payCash: "Paguar cash",
|
||||
payCard: "Paguar me kartë",
|
||||
void: "Anulo",
|
||||
voidReason: "Arsyeja",
|
||||
categories: "Kategoritë e mjeteve",
|
||||
services: "Shërbimet",
|
||||
prices: "Çmimet",
|
||||
pricesHint: "Një çmim për çdo kategori × shërbim. Lëre bosh një qelizë që ai kombinim të mos shitet.",
|
||||
addCategory: "kategori",
|
||||
addService: "shërbim",
|
||||
active: "aktiv",
|
||||
save: "Ruaj",
|
||||
saved: "U ruajt.",
|
||||
finished: "Të mbaruara",
|
||||
finishedEmpty: "Ende asnjë lavazh i mbaruar.",
|
||||
voided: "anuluar",
|
||||
by: "Nga",
|
||||
cash: "cash",
|
||||
card: "kartë",
|
||||
sponsorship: "Zbritje parkimi",
|
||||
sponsorshipHint: "Çfarë i zbritet tarifës së parkimit të klientit kur lavazhi mbaron. Zbatohet automatikisht kur lavazhi shënohet i mbaruar.",
|
||||
sponsorshipLabel: "Lavazh",
|
||||
},
|
||||
update: {
|
||||
available: "Përditësim i disponueshëm",
|
||||
prompt: "Versioni {{version}} është i disponueshëm. Ta instaloj tani dhe ta rinis? (Instalimi kërkon fjalëkalimin e administratorit.)",
|
||||
},
|
||||
nav: {
|
||||
wash: "Lavazh",
|
||||
carwash: "Lavazh",
|
||||
booth: "Kabina",
|
||||
shift: "Turni",
|
||||
setup: "Konfigurimi",
|
||||
@@ -261,6 +322,9 @@ export const sq = {
|
||||
evtCashReview: "SHQYRTIM",
|
||||
evtConfigChange: "KONFIG",
|
||||
evtValidation: "VALIDIM",
|
||||
evtCarwashOrder: "LAVAZH",
|
||||
evtCarwashPayment: "PAGESË LAVAZHI",
|
||||
charges: "Shtesa",
|
||||
decision: { authorize: "autorizuar", deny: "refuzuar" },
|
||||
evtAnomaly: "ANOMALI",
|
||||
evtRefused: "REFUZUAR",
|
||||
@@ -788,6 +852,11 @@ export const sq = {
|
||||
modeTimeCredit: "Minutat e para falas",
|
||||
modeFixed: "Zbritje shume (shkruhet në skanim)",
|
||||
modePercent: "Zbritje në përqindje",
|
||||
modeDoneTolerance: "Falas gjatë lavazhit (+ tolerancë)",
|
||||
modeDoneToleranceHint: "Koha nga porosia e lavazhit deri te \"mbaroi\", plus minutat e tolerancës, zbritet nga parkimi. Koha e parkuar para porosisë dhe pas tolerancës paguhet sipas tarifës.",
|
||||
modeWashPrice: "Çmimi i lavazhit zbritet nga parkimi",
|
||||
modeWashPriceHint: "Tarifa e parkimit minus çmimin e lavazhit; asnjëherë nën zero.",
|
||||
toleranceMinutes: "Toleranca pas mbarimit (minuta)",
|
||||
minutes: "Minuta falas",
|
||||
percent: "Përqindja (%)",
|
||||
maxAmount: "Tavani i zbritjes për validim",
|
||||
@@ -917,6 +986,18 @@ export const sq = {
|
||||
"{{operator}} ka një turn të hapur. Vetëm një turn mund të jetë i hapur njëkohësisht — ai duhet të mbyllë turnin para se ti të hapësh tëndin.",
|
||||
openNow: "Hap turnin tani",
|
||||
opening: "Duke hapur…",
|
||||
// Arkat — turni i përket një arke (kabina / lavazhi), jo gjithë sitit.
|
||||
tillOpen: "Hap turnin e {{till}}",
|
||||
tillClose: "Mbyll turnin e {{till}}",
|
||||
tillHeldByShort: "{{till}}: {{operator}}",
|
||||
tillNoShift: "Pa turn {{till}}",
|
||||
},
|
||||
till: {
|
||||
booth: "kabinës",
|
||||
carwash: "lavazhit",
|
||||
boothLong: "Arka e kabinës",
|
||||
carwashLong: "Arka e lavazhit",
|
||||
all: "Të gjitha arkat",
|
||||
},
|
||||
shifts: {
|
||||
title: "Historiku i turneve",
|
||||
|
||||
Reference in New Issue
Block a user