feat(shift): site-wide single-open shift + booth money-path gate
A shift becomes a SITE-WIDE accountability period — at most one open at a
time — so every taking is unambiguously attributed to one operator. Login
stays decoupled from shifts (an operator can log in off-shift to review).
Backend:
- ShiftService.currentOpenShift()/requireOpenShift(); open() refuses when ANY
shift is open and throws ShiftAlreadyOpenError{heldBy} (self vs. other).
- requireShift preHandler gates /api/pay, /api/exit, /api/voucher,
/api/barrier/reopen → 409 {code:"no_shift"}; read-only lookups stay open.
- GET /api/shift/current returns site-wide {open:{startedAt,operator},isMine}.
- GET /api/events?since=<iso> for per-shift log scoping (db: re-export gte).
Frontend:
- Header shift button: open / close-mine / disabled-when-another-holds-it.
- Pay/exit modal gate banner (one-click open; "held by X" when another's);
pay/exit/voucher disabled until this operator's shift is open.
- Active-Sessions barrier re-open gated the same way.
- Live feed scoped to the open shift's window; shared useShift() Query
invalidated over the WS on shift_open/shift_z_report/cash_movement.
- sq/en strings for the control + gate.
Wiki: shift.md (site-wide single-open + gate; superseded per-operator note),
booth-console.md (header control + gate), log entry.
Verified: site-wide invariant + heldBy + handover + chain integrity on a
fresh migrated DB (11/11); db/server/web build clean.
This commit is contained in:
@@ -28,7 +28,7 @@ export const sq = {
|
||||
site: "Vendi",
|
||||
},
|
||||
status: {
|
||||
live: "DREJTPËRDREJT",
|
||||
live: "LIVE",
|
||||
connecting: "DUKE U LIDHUR",
|
||||
offline: "JASHTË LINJE",
|
||||
},
|
||||
@@ -43,7 +43,7 @@ export const sq = {
|
||||
uncapped: "pa kufi",
|
||||
free: "lirë",
|
||||
lotFull: "● parkimi plot",
|
||||
liveFeed: "Aktiviteti i drejtpërdrejtë",
|
||||
liveFeed: "Aktiviteti live",
|
||||
events: "ngjarje",
|
||||
noEventsYet: "Asnjë ngjarje ende — hyrjet dhe daljet do të shfaqen këtu.",
|
||||
activeSessions: "Sesionet aktive",
|
||||
@@ -185,6 +185,20 @@ export const sq = {
|
||||
expectedDrawer: "Arka e pritshme:",
|
||||
printedToReceipt: "Printuar te printeri i kabinës.",
|
||||
recordedNoPrinter: "Regjistruar (pa printer për të printuar).",
|
||||
// Header shift control + the booth shift gate.
|
||||
headerNoShift: "Asnjë turn",
|
||||
headerOpen: "Hap turnin",
|
||||
headerClose: "Mbyll turnin",
|
||||
headerHeldBy: "Turn i hapur nga {{operator}}",
|
||||
headerHeldByShort: "Turni: {{operator}}",
|
||||
gateTitle: "Hap një turn për të proceduar biletat",
|
||||
gateBody:
|
||||
"Asnjë turn nuk është i hapur. Hap turnin tënd që pagesat dhe daljet të regjistrohen te ky turn.",
|
||||
gateOtherTitle: "Turni i hapur i përket një operatori tjetër",
|
||||
gateOtherBody:
|
||||
"{{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…",
|
||||
},
|
||||
pay: {
|
||||
ticket: "Bileta",
|
||||
|
||||
Reference in New Issue
Block a user