Skip to main content

Cashier Management — Gap Analysis (AC Review)

Document cross-referencing Overall acceptance criteria with current implementation in:

  • Backend: kioskgaming_backend/src/modules/cashier-management/
  • Cashier portal: kioskgaming_cashier/
  • Spec: spec.md
  • QC: QC checklist

Review date: 2026-06-10


Quick overview

GroupStatus
Core flow (login, shift, opening, deposit/redeem, blind close)~70% present
Strict enforcement (device, limit, handover, auto-lock UI)Gaps remain
Shop-side (create cashier, no-show, reconciliation)Mostly in Shop portal — Cashier only consumes

Cashier portal currently has 7 screens: Login, My Shifts, Opening Cash, Active Shift, Cash Drop, Closing, Handover — matches QC ship gate.


Acceptance Criteria — Cross-reference matrix

#Acceptance CriteriaStatusNotes
1Shop creates Cashier belonging to exactly one Shop✅ BackendModel shopId; Shop UI only creates username/password
2Cashier login with email, username, or phone✅ Backend / ⚠️ Shop UIfindByIdentifier; Shop form missing email/phone
3Shop creates shift with date, time, Cashier, devicecashierShiftScheduleBizService
4Block Cashier shift overlapSHIFT_OVERLAP 409
5Block device shift overlapSame overlap logic
6Cashier only starts shift on assigned device⚠️ PartialOnly checks fingerprint if present; missing deviceId comparison
7Cashier enters opening cash before transactionsStart shift requires denomination
8Cashier only cash tx with PlayerpaymentMethod = 'cash' hardcoded
9Deposit does not require Player OTPCounter flow has no OTP
10Redemption per limit and approval✅ Backend / ⚠️ FELogic exists; UI shows wrong state when pending
11Every tx stores cashierId, shiftId, shopId, deviceIdCashierTransaction model
12Cashier only views own shift txlistByCashier filter
13Cashier cannot edit/cancel/reverse txNo API
14Closing count is blind countblindCloseShift + ClosingCountPage
15Auto-calculate Expected + DiscrepancyexpectedClosingCashService
16Discrepancy → Shop Manager reviewpending_manager_review + notify
17No-show handled by Shop only, no Admin notifynoShowJob → shop only
18Device used by multiple Cashiers across shiftsOverlap validation allows sequential reuse
19Block Device ≠ Suspend CashierSeparate services
20Handover by denomination + recipient confirmation⚠️ PartialAPI exists; cross-cashier flow not correct
21Auto-Locked still requires reconciliation⚠️ Backend ✅ / FE ❌BE allows close; FE blocks auto_locked
22Every important action has audit log⚠️ PartialMissing create/suspend cashier, revoke device

Already met — No immediate work needed

Authentication

  • Login with identifier (username / email / phone): CashierRepository.findByIdentifier, cashierAuthBizService.login
  • Cashier portal: CashierLoginPage sends identifier + deviceFingerprint

Shift scheduling & overlap

  • Create scheduled shift with cashier + device + time: cashierShiftScheduleBizService
  • Block cashier and device overlap: _assertNoOverlap409 SHIFT_OVERLAP

Start shift & opening cash

  • Start shift with denomination: cashierShiftOpsBizService.startShift
  • UI: OpeningCashPagePOST /cashier/shifts/:id/start
  • Opening cash required before shift active → transactions only when shift active

Transactions

  • Deposit cash, no OTP: cashierTransactionBizService.depositCredits (paymentMethod = 'cash')
  • Redemption approval: cashierRedemptionApprovalBizService (threshold + daily limit via env)
  • Full metadata recorded: cashierId, shiftId, shopId, deviceId on CashierTransaction
  • No API to edit/cancel/reverse transactions

Blind closing & discrepancy

  • Blind close: blindCloseShift — expected returned only after submit
  • UI: ClosingCountPage does not show expected before submit
  • Discrepancy → pending_manager_review + notify shop (notifyShiftDiscrepancy)

Jobs & notifications

  • No-show: noShowJob → status no_show, audit, notify shop (not admin)
  • Auto-lock: autoLockJob → status auto_locked, notify shop
  • Worker: cashierManagementWorker.js (60s tick)

Device & cashier status

  • Block device revokes sessions: cashierDeviceBizService.setDeviceStatus
  • Suspend cashier revokes sessions + suspend shift: shopCashierManagementBizService._setStatus
  • Two flows are separate

Audit (existing portion)

  • Shift started/closed, deposit, redeem, cash drop, handover initiate/confirm, redemption approve/reject, shift reconciled

Important gaps — Cashier needs work

P0 — Auto-Locked: Cashier cannot close on UI

Issue: Backend blindCloseShift allows active, auto_locked, suspended. Frontend shiftAccess.ts only treats active as operable (SHIFT_JOIN_STATUSES = ['active']).

Impact: ClosingCountPage, CashDropPage, ActiveShiftPage redirect/block when auto_locked.

AC violation: Auto-Locked shifts must still perform reconciliation.

Required work:

  • Extend canJoinShift or add canCloseShift to include auto_locked (and possibly suspended)
  • CTA "Close shift" on MyShiftsPage when shift is auto_locked
  • Banner indicating shift is locked

Related files:

  • kioskgaming_cashier/src/utils/shiftAccess.ts
  • kioskgaming_cashier/src/pages/ClosingCountPage.tsx
  • kioskgaming_cashier/src/pages/MyShiftsPage.tsx

P0 — Start shift on correct assigned device (not strict enough)

Issue: startShift only:

  1. Checks assigned device has status === 'active'
  2. Checks fingerprint if both request and device record have fingerprint

Missing:

  • Compare deviceId from request/session with shift.deviceId
  • Require fingerprint when device already has fingerprint
  • Reject when device not yet approved by shop (if assigned device is pending)

File: kioskgaming_backend/src/modules/cashier-management/services/biz/cashierShiftOpsBizService.js (around lines 102–116)

Add:

if (shift.deviceId && deviceId && String(deviceId) !== String(shift.deviceId)) {
throw { code: 'DEVICE_MISMATCH', statusCode: 403 };
}

P1 — Redemption pending approval — wrong UX

Issue: Backend returns pendingApproval: true when approval needed. ActiveShiftPage always toasts "Redeem completed".

Required work:

  • Check res.data.pendingApproval in doRedeem
  • Show pending_approval status in transaction list
  • Toast: "Awaiting Shop Manager approval" instead of completed
  • Do not let cashier treat as cash already paid to player

File: kioskgaming_cashier/src/pages/ActiveShiftPage.tsx


P1 — Handover between 2 Cashiers — flow does not match spec

Spec (§10): Cashier A hands over → Cashier B (or Manager) confirms. Closing A → Opening B.

IssueDetail
UI initiate only lists own shiftsassignedShifts.filter(s => s.id !== id) — cannot select Cashier B's shift
Confirm does not verify recipientconfirmHandover does not check cashierId === pending.toCashierId
Does not close shift A after handoverOnly creates record, no shift A transition
No handover discrepancy notifySpec §15 requires Shop Manager notification

Required work:

  • API/UI select incoming shift by shop (other cashier's shift, scheduled, same device if needed)
  • Verify receiver on confirm
  • Close shift A / prepare open shift B per spec
  • Notify if discrepancy !== 0

Files:

  • kioskgaming_backend/src/modules/cashier-management/services/biz/cashierHandoverBizService.js
  • kioskgaming_cashier/src/pages/HandoverPage.tsx

P2 — Shop creates Cashier missing email/phone

Issue: Model has email, phone, loginMethod. updateCashierProfile supports patch. Shop UI only creates username + password (CashiersPage.tsx).

AC impact: Cashier login with email/username/phone — backend ready but shop cannot enter email/phone on create.

Required work (Shop portal):

  • Create/edit cashier form: fullName, email, phone, loginMethod
  • Validate unique email/phone within shop

P2 — Transaction limit for Deposit — not implemented

Spec §5.1: System checks transaction limit.

Cashier module does not call transactionLimitsValidationService. Redemption has threshold/daily via env; deposit has none.

Required work: Validate min/max per-tx and daily deposit limit in depositCredits.


P2 — Audit log incomplete (spec §14)

Missing:

  • cashier_created
  • cashier_suspended / cashier_terminated / cashier_unsuspended
  • device_access_revoked

shopCashierManagementBizService suspend/terminate/create does not call auditBiz despite injection.


P3 — Revoke Cashier access on device — not implemented

Table cashier_device_access + CashierDeviceAccessRepository exist, but not wired in bootstrapCashierManagement — no API, no enforce on login/start shift.

Spec §11.2: Revoke one cashier's access on one device; device stays active for other cashiers.


P3 — Auto-lock — missing warning and Cashier notify

Spec §9:

  1. Warn Cashier before scheduled end
  2. Closing grace period for in-progress transactions
  3. Notify Cashier + Shop Manager

Current: Job sets auto_locked + Telegram shop (notifyAutoLock). No in-app warning, no cashier notify.

Required work (Cashier FE):

  • Countdown banner before scheduledEndAt
  • Banner when auto_locked
  • (Optional) polling/WebSocket for shift status

Minor gaps — Should fix

GapNotes
loginMethod not enforcedAuth does not check configured method
Logout only clears localStorageDoes not call cashier-auth/logout — server session remains until TTL
Player balance not displayedDeposit/redeem prone to wrong amounts
Shift pending_reconciliation / completedCashier can view but UI lacks clear reconciliation summary
Handover initiate does not require closing firstSpec: A stops transactions + closing count before handover
Device pending approval UXStart fails if shop not approved — needs clearer guidance
Thin test coverageMostly unit helper/state machine; missing integration tx/device/handover
openedAt set when creating scheduled shiftopeningCashBalance: 0, openedAt: scheduledStartAt — may confuse reporting

Acceptance criteria to add to checklist

Items in spec but not in overall AC list:

  1. Device must be Shop-approved before starting shift on that device.
  2. Cashier receives warning before scheduled end and when shift auto-locked.
  3. Redemption pending approval — Cashier sees pending state, not treated as complete.
  4. Handover cross-cashier — A selects B's shift; only B (or Manager) can confirm.
  5. Deposit transaction limits — per-tx and/or daily.
  6. Cashier views Player balance on deposit/redeem (read-only).
  7. Revoke device access per cashier — separate from block device.
  8. Server-side logout — revoke session on Sign out.
  9. Cashier views shift report after completed (read-only, no shop balance).
  10. Shop Portal deposit vs Cashier deposit — two parallel flows: Shop keeps CDN add credits; Cashier deposit links shift/cash ledger.

Implementation priority proposal

flowchart TD
P0A[Fix auto_locked closing UI] --> P0B[Device ID validation on start]
P0B --> P1A[Redemption pending UX]
P1A --> P1B[Handover cross-cashier flow]
P1B --> P2A[Deposit limits + audit gaps]
P2A --> P3A[Auto-lock warnings + cashier notify]
PriorityItemReason
P0Auto-locked → allow close on Cashier appBlocks daily reconciliation
P0Device ID match shift.deviceIdSecurity / fraud
P1Redemption pending approval UXPrevents wrong cash payout
P1Handover A→B per specNext shift cannot start
P2Shop form email/phone cashierLogin AC not usable in practice
P2Deposit transaction limitsCompliance
P3Audit suspend/create, revoke device accessFull audit trail
P3Auto-lock warnings + notify cashierSpec §9

File map — Cashier module

Backend (kioskgaming_backend/src/modules/cashier-management/)

FileRole
services/biz/cashierAuthBizService.jsLogin/logout
services/biz/cashierShiftScheduleBizService.jsCreate/edit shift, overlap
services/biz/cashierShiftOpsBizService.jsStart shift, blind close
services/biz/cashierTransactionBizService.jsDeposit/redeem
services/biz/cashierRedemptionApprovalBizService.jsApproval rules
services/biz/cashierHandoverBizService.jsHandover
services/biz/cashierCashDropBizService.jsCash drop
services/biz/cashierReconciliationBizService.jsManager reconcile
jobs/noShowJob.jsNo-show detection
jobs/autoLockJob.jsAuto-lock
utils/shiftStateMachine.jsStatus transitions
ports/cashierNotifyPort.jsTelegram notifications

Cashier portal (kioskgaming_cashier/src/)

FileScreen
pages/CashierLoginPage.tsxLogin
pages/MyShiftsPage.tsxShift list
pages/OpeningCashPage.tsxOpening cash / start
pages/ActiveShiftPage.tsxDeposit/redeem + tx list
pages/CashDropPage.tsxCash drop
pages/ClosingCountPage.tsxBlind close
pages/HandoverPage.tsxHandover
utils/shiftAccess.tsAccess by status
services/cashierApi.tsAPI client

Conclusion

Cashier core (login, assigned shifts, opening cash, deposit/redeem cash, blind close, cash drop, list own shift tx) has a relatively complete backend + FE framework.

Main gaps are in enforcement + business UX:

  • Stricter device assignment (deviceId === shift.deviceId)
  • Auto-locked reconciliation on Cashier app
  • Handover between two cashiers
  • Redemption pending state on UI
  • Transaction limits for deposit
  • Auto-lock notify/warning for cashier
  • Shop creates cashier with email/phone so login AC works in practice

References