Skip to content

feat(cash-sessions): per-staff shifts - #1395

Merged
khaira777 merged 23 commits into
mainfrom
fm/flocafe-1339-per-staff-shifts
Oct 29, 2026
Merged

khaira777 merged 23 commits into
mainfrom
fm/flocafe-1339-per-staff-shifts

Conversation

@khaira777

@khaira777 khaira777 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1339

Intent

Implement opt-in one cash shift per staff member (feat(cash): opt-in one cash shift per staff member), stacked on top of paid-bill payment-method correction while its cash shift and business day remain open:

  • Add an opt-in store setting "One shift per staff member" (off by default). With the setting off, shift behavior is unchanged (409 session_already_open when opening a second shift).
  • When the setting is on, each user opens and closes their own shift: at most one open shift per user ((opened_by) WHERE status = 'open'). Day close waits until all shifts are closed and adds them all up.
  • Money follows the person taking the payment: cash payments, cash refunds, drawer movements, and cash expenses go to the actor's open shift (getOpenSession(db, userId)). If the actor has no open shift, existing rules apply (blocked with 409 only when require_open_shift is on, otherwise NO_CASH_SESSION_ID).
  • Shared physical drawer is allowed (the drawer kicks for whoever takes cash), and documentation warns that two shifts sharing one physical drawer make individual drawer counts unreliable.
  • Owner and manager can see and close anyone's shift; a cashier can only close their own shift.
  • Turning the setting off is refused with HTTP 409 while more than one shift is open.
  • The X report can show one shift or all open shifts; the Z report for a shift stays per shift, and day close covers all shifts.
  • Replace cash_sessions_one_open in a non-destructive migration with a per-user partial unique index (opened_by) WHERE status = 'open' and enforce "only one open overall" in code when the setting is off. Upgrade from a DB with one open shift keeps it open and valid.
  • Update useCashSession.ts, ShiftOpenModal, ShiftCloseModal, StatusBar, Settings toggle, docs/architecture/business-time.md, docs/reference/roles-and-permissions.md, docs/reference/api.md (and user docs warning if present), and extend tests/cash-sessions.test.ts and tests/upgrade-path.test.ts.

What Changed

  • Added database migration v120 (add_per_staff_cash_sessions) to replace cash_sessions_one_open with a per-user partial unique index cash_sessions_one_open_per_user on (opened_by) WHERE status = 'open', seed the opt-in one_shift_per_staff setting ('false' by default), and block disabling one_shift_per_staff (409 multiple_shifts_open) while multiple shifts are open.
  • Updated shift gating and cash attribution (getOpenSession(db, userId)) across bill payments, payment-method corrections, refunds, expenses, drawer movements, and printer drawer kicks so cash activity attaches to the actor's open shift when one_shift_per_staff is enabled, while POST /api/cash-closures waits for all open shifts on or before the business date to close and sums per-staff shift opening floats into the day's opening_float movement.
  • Added GET /api/cash-sessions/open, optional session_id/user_id targeting on shift and movement routes with owner/manager vs. own-shift access checks, per-shift ?session_id scoping on GET /api/reports/x-report, and frontend/i18n support in useCashSession, useCashClose, ShiftCloseModal, CashCloseModal, PosTopbar, StatusBar, and PaymentMethodsSettings.

Risk Assessment

✅ Low: The per-staff cash shifts implementation, migration v120, actor-scoped cash routing, shift-scoped X-report closure metadata, payment-method correction integration, UI controls, documentation, and behavioral test coverage are complete and consistent with the stated acceptance criteria.

Testing

Ran focused backend suites (npm run test:cash-sessions, npm run test:upgrade-path, npm run test:cash-closures, tests/payment-method-correction.test.ts, npm run test:expenses, npm run test:refunds), compiled backend and static frontend (npm run build, npm run build:frontend), verified translations and docs (npm run i18n:check, check-docs.cjs), and drove the real running server and Chromium browser end-to-end across all per-staff shift scenarios with screenshots and API evidence captured in the run evidence directory.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Store setting "One shift per staff member" defaults off (blocking a second shift with HTTP 409 session_already_open) and can be enabled by an owner/manager in Settings -> Payments with a shared physic… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/01-settings-one-shift-per-staff-enabled.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live-per-s…
When "One shift per staff member" is on, multiple staff members open concurrent shifts (at most one open shift per user), and turning the setting off while more than one shift is open is refused with… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/02-disable-blocked-while-multiple-shifts-open.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live…
Money follows the actor taking the payment: cash bill payments, cash refunds, drawer movements, and cash expenses attach to the actor's open shift, while a sessionless actor is blocked with HTTP 409 w… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live-per-staff-shifts-evidence.json, npm run test:cash-sessions, npm run test:expenses, and npm run test:refunds
Owner and manager can inspect and close any staff member's shift and filter the X report between all shifts and a single shift, while a cashier is restricted to viewing and closing only their own shif… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/03-pos-shift-close-staff-selector.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/04-dashboard-x-repo…
Day close waits until all staff shifts are closed (returning HTTP 409 open_shift_blocks_day_close while any shift remains open), aggregates all shifts on close, and preserves shift-scoped alreadyClose… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/04-dashboard-x-report-all-open-shifts-and-blocker.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-…
Database migration v120 non-destructively replaces global cash_sessions_one_open with per-user partial unique index cash_sessions_one_open_per_user while keeping an existing open shift valid, and paym… ✅ pass live npm run test:upgrade-path and node tests/run-electron-node-test.cjs tests/payment-method-correction.test.ts

Settings - One shift per staff member toggle enabled with shared drawer warning
Settings - Disabling one shift per staff member blocked (409) with error toast while multiple shifts are open
POS - Owner/Manager actions menu with Open shift and Close shift when staff shifts are open
Dashboard Close Day / X-Report - All shifts scope and open-shift blocker listing both concurrent shifts
Dashboard Close Day / X-Report - Filtered to single staff shift (#1 - Alice Cashier)

Evidence: Live API & SQLite verification payload for per-staff shifts, attribution, close boundaries, shift-scoped X-report closure metadata, and day close reconciliation
{
  "defaultOffSecondOpenBlocked": {
    "status": 409,
    "body": {
      "error": "A shift is already open",
      "reason_code": "cash.session_rejected",
      "operation": "cash.session.open",
      "stage": "validation",
      "classification": "expected_rejection",
      "outcome": "not_applied",
      "correlation_id": "012a85d7-1b6e-4bac-a051-daf4b0610087",
      "display_code": "session_already_open"
    }
  },
  "perStaffConcurrentOpen": {
    "aliceShift": {
      "id": 1,
      "opened_by": "u-alice",
      "opened_by_name": "Alice Cashier",
      "opened_at": "2026-10-29 00:55:14",
      "opening_float_cents": 5000,
      "status": "open",
      "closed_at": null,
      "closed_by": null,
      "closure_id": null
    },
    "bobShift": {
      "id": 2,
      "opened_by": "u-bob",
      "opened_by_name": "Bob Cashier",
      "opened_at": "2026-10-29 00:55:16",
      "opening_float_cents": 3000,
      "status": "open",
      "closed_at": null,
      "closed_by": null,
      "closure_id": null
    },
    "bobDuplicateShiftBlocked": {
      "status": 409,
      "body": {
        "error": "You already have an open shift",
        "reason_code": "cash.session_rejected",
        "operation": "cash.session.open",
        "stage": "validation",
        "classification": "expected_rejection",
        "outcome": "not_applied",
        "correlation_id": "813ab6f8-9864-4db1-80f8-3dde9c456fa9",
        "display_code": "session_already_open"
      }
    },
    "disableWhileTwoShiftsOpenStatus": 409
  },
  "moneyFollowsActor": {
    "aliceCurrentShift": {
      "id": 1,
      "opened_by": "u-alice",
      "opened_by_name": "Alice Cashier",
      "opened_at": "2026-10-29 00:55:14",
      "opening_float_cents": 5000,
      "status": "open",
      "closed_at": null,
      "closed_by": null,
      "closure_id": null,
      "expected_cash_cents": 7000
    },
    "bobCurrentShift": {
      "id": 2,
      "opened_by": "u-bob",
      "opened_by_name": "Bob Cashier",
      "opened_at": "2026-10-29 00:55:16",
      "opening_float_cents": 3000,
      "status": "open",
      "closed_at": null,
      "closed_by": null,
      "closure_id": null,
      "expected_cash_cents": 7000
    }
  },
  "closeBoundariesAndDayClose": {
    "aliceCrossCloseBobStatus": 403,
    "aliceCrossCloseBobCode": "session_owner_required",
    "dayCloseBlockedBoth": {
      "status": 409,
      "display_code": "open_shift_blocks_day_close",
      "blocking_shifts_count": 2
    },
    "aliceCloseOwn": {
      "closure_id": 1,
      "variance_cents": 500,
      "expected_cash_cents": 7000,
      "counted_cash_cents": 7500
    },
    "xAliceAfterShiftCloseBeforeDayClose": {
      "alreadyClosed": true,
      "zNumber": 1,
      "openShiftsRemaining": 1
    },
    "dayCloseBlockedBobStillOpenStatus": 409,
    "managerCloseBob": {
      "closure_id": 2,
      "variance_cents": -500,
      "expected_cash_cents": 7000,
      "counted_cash_cents": 6500
    },
    "dayCloseSuccessZReport": {
      "status": 201,
      "z_number": 3,
      "opening_float_cents": 8000,
      "expected_cash_cents": 14000,
      "counted_cash_cents": 14000,
      "variance_cents": 0,
      "gross_collected_cents": 6000,
      "refunded_cents": 0,
      "net_collected_cents": 6000
    },
    "xAliceAfterDayClose": {
      "alreadyClosed": true,
      "zNumber": 1,
      "dayZNumber": 3
    },
    "disableSettingAfterShiftsClosedStatus": 200
  }
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Store setting "One shift per staff member" defaults off (blocking a second shift with HTTP 409 session_already_open) and can be enabled by an owner/manager in Settings -> Payments with a shared physic… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/01-settings-one-shift-per-staff-enabled.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live-per-s…
When "One shift per staff member" is on, multiple staff members open concurrent shifts (at most one open shift per user), and turning the setting off while more than one shift is open is refused with… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/02-disable-blocked-while-multiple-shifts-open.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live…
Money follows the actor taking the payment: cash bill payments, cash refunds, drawer movements, and cash expenses attach to the actor's open shift, while a sessionless actor is blocked with HTTP 409 w… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-live-per-staff-shifts-evidence.json, npm run test:cash-sessions, npm run test:expenses, and npm run test:refunds
Owner and manager can inspect and close any staff member's shift and filter the X report between all shifts and a single shift, while a cashier is restricted to viewing and closing only their own shif… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/03-pos-shift-close-staff-selector.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/04-dashboard-x-repo…
Day close waits until all staff shifts are closed (returning HTTP 409 open_shift_blocks_day_close while any shift remains open), aggregates all shifts on close, and preserves shift-scoped alreadyClose… ✅ pass live ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/04-dashboard-x-report-all-open-shifts-and-blocker.png, ~/.no-mistakes/evidence/01M62HAPQY03726MW8ZHMV4WCG/06-…
Database migration v120 non-destructively replaces global cash_sessions_one_open with per-user partial unique index cash_sessions_one_open_per_user while keeping an existing open shift valid, and paym… ✅ pass live npm run test:upgrade-path and node tests/run-electron-node-test.cjs tests/payment-method-correction.test.ts
  • npm run test:cash-sessions
  • npm run test:upgrade-path
  • npm run test:cash-closures
  • node tests/run-electron-node-test.cjs tests/payment-method-correction.test.ts
  • npm run test:expenses
  • npm run test:refunds
  • npm run build && npm run build:frontend
  • npm run i18n:check
  • node scripts/docs/check-docs.cjs --links --index --policy
  • Live Playwright + Express server verification driving Settings toggle, concurrent per-staff shift open/close, actor-scoped cash attribution, shift-scoped X-report filter & closure metadata, and day close reconciliation
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Allow 4-6 digit PINs for all staff roles to support tile-based PIN quick user switching on the lock/login screen while keeping all approval paths strictly restricted to active owner/manager PINs.

Fixes #1338
- Use non-blocking per-request rate limit on POST /api/auth/pin-login while preserving per-user lockout
- Serialize per-user PIN verification with withPinVerificationLock and audit missing credential requests
- Re-read bill/order state and compute charge breakdowns inside the write transaction on PATCH /api/bills/:id/charges
- Guard GET /api/settings/auto_lock_minutes with requireAnyPermission across staff roles
- Pass explicit Bearer token to POST /auth/logout before clearing storage on lock/logout
- Exclude autoLockMinutes from localStorage persistence, reset on session changes, and guard stale save rollbacks
- Update kitchenDeliveryOverrideInvalid across all 24 locales to use Staff Approval PIN wording
…c.ts` line 270 to assert `page.getByText('Invalid Staff Approval PIN')` instead of `'Invalid manager PIN'`, matching the updated `pos.kitchenDeliveryOverrideInvalid` translation (`"Invalid Staff Approval PIN. Please try again."`) rendered by `PaymentModal.tsx` when `POST /api/bills/:id/payments` returns 403 `Invalid manager PIN`. The downstream `End-to-End Playwright Tests` and `Required Checks Gate` CI failures were caused by this Shard 1/4 E2E failure. Verification run and passed: - `npm run lint` - `npm run build && npm run build:frontend` - `npm run i18n:check` - `cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/approval-manager-selection.spec.ts e2e/pin-quick-switch.spec.ts` (4 passed) - `cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/kitchen-delivery-payment-retry.spec.ts e2e/kitchen/kitchen-delivery-billing-gate.spec.ts` (5 passed) - `node scripts/ci/check-changed-format.cjs --base 1278f91` - `git diff --check`
…heck (`test:orders-search`): - **Invariant**: Frontend components imported by `OrdersPage` must resolve `OrderDetailPanel` through the `@/components/orders/OrderDetailPanel` module specifier rather than a relative `./OrderDetailPanel` path, so Node-based `OrdersPage` unit tests that stub `@/components/orders/OrderDetailPanel` and provide a minimal `react` hook stub do not load the real `OrderDetailPanel` module and its `@radix-ui/react-slot` (`React.forwardRef`) dependency at module-load time. - **Root-cause fix**: Updated `frontend/src/components/orders/CorrectPaymentMethodModal.tsx` line 13 to import `normalizeBillPaymentDetails` from `'@/components/orders/OrderDetailPanel'` instead of `'./OrderDetailPanel'`. **Verification**: - `npm run test:orders-search` (passed) - `npm run test:payment-method-correction` (passed) - `npm run test:card-transaction-id` (passed) - `npm run test:ui-regressions-621-623-626` (passed) - `npm run test:password-policy` (passed) - `npm run lint` (passed) - `node scripts/ci/check-changed-format.cjs --base 1278f91` (passed) - `npm run build:frontend` (passed) - `git diff --check` (passed)
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add per-staff cash shifts, payment corrections, and PIN quick switch

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add opt-in per-staff shifts while preserving default single-shift behavior.
• Allow approved corrections to paid-bill payment methods without changing amounts.
• Add PIN quick switching and idle terminal locking while restricting approvals to owners and
 managers.
Diagram

graph TD
  Settings["Store settings"] --> ShiftGate["Shift gate"] --> Ledger["Cash ledger"] --> Reports["Shift reports"] --> DayClose["Day close"]
  Auth["PIN authentication"] --> POS["Shared POS"] --> ShiftGate
  POS --> Correction["Payment correction"] --> Ledger
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Separate physical drawers per staff member
  • ➕ Makes individual shift counts physically reconcilable.
  • ➖ Requires drawer assignment and hardware workflows beyond this opt-in change.

Recommendation: Keep the opt-in per-user index and application-level single-shift gate: they preserve existing installations without rebuilding shift data. The shared-drawer warning correctly distinguishes ledger attribution from physically isolated cash counts.

Files changed (81) +7721 / -773

Enhancement (30) +2976 / -266
layout.tsxAuto-lock idle dashboard sessions +58/-0

Auto-lock idle dashboard sessions

• Loads the store timeout and locks authenticated terminals after inactivity.

frontend/src/app/(dashboard)/layout.tsx

page.tsxConnect paid orders to payment correction +23/-0

Connect paid orders to payment correction

• Opens the correction modal for authorized users and refreshes orders afterward.

frontend/src/app/(dashboard)/orders/page.tsx

page.tsxReset sensitive checkout state on user switches +26/-0

Reset sensitive checkout state on user switches

• Clears privileged checkout and shift-modal state when users change and exposes staff-shift closure.

frontend/src/app/(dashboard)/pos/page.tsx

page.tsxOffer PIN setup for every staff role +25/-28

Offer PIN setup for every staff role

• Keeps the PIN input available for operational staff.

frontend/src/app/(dashboard)/staff/page.tsx

page.tsxAdd staff tile picker and PIN keypad +297/-30

Add staff tile picker and PIN keypad

• Supports selected-user PIN sign-in alongside email and password login.

frontend/src/app/auth/login/page.tsx

CashCloseModal.tsxAdd X-report shift scope selector +38/-0

Add X-report shift scope selector

• Lets operators inspect an open shift or full-day report and see shifts blocking day close.

frontend/src/components/dashboard/CashCloseModal.tsx

ShiftCloseModal.tsxSelect a shift to close +40/-3

Select a shift to close

• Supports choosing among visible shifts and displays the selected shift's cash preview.

frontend/src/components/dashboard/ShiftCloseModal.tsx

MenuActionHandler.tsxHandle terminal-lock menu actions +5/-0

Handle terminal-lock menu actions

• Routes lock commands to the auth store.

frontend/src/components/layout/MenuActionHandler.tsx

Sidebar.tsxAdd sidebar lock control +14/-1

Add sidebar lock control

• Offers terminal locking in the user menu.

frontend/src/components/layout/Sidebar.tsx

CorrectPaymentMethodModal.tsxAdd approved payment correction form +385/-0

Add approved payment correction form

• Selects a paid bill and non-wallet line, then submits an idempotent method correction with an approver PIN.

frontend/src/components/orders/CorrectPaymentMethodModal.tsx

OrderDetailPanel.tsxExpose eligible paid-bill corrections +29/-0

Expose eligible paid-bill corrections

• Adds correction actions for paid bills with non-wallet payment lines.

frontend/src/components/orders/OrderDetailPanel.tsx

PosTopbar.tsxAdd POS lock and staff-shift actions +26/-0

Add POS lock and staff-shift actions

• Adds a terminal-lock button and an option to close another visible shift.

frontend/src/components/pos/PosTopbar.tsx

useCashClose.tsFetch shift-scoped X reports +29/-3

Fetch shift-scoped X reports

• Reloads the X report when its date or selected shift changes.

frontend/src/hooks/useCashClose.ts

useCashSession.tsTrack own and visible open shifts +75/-22

Track own and visible open shifts

• Fetches current and visible shifts and supports selecting another shift for preview or closure.

frontend/src/hooks/useCashSession.ts

auth.tsManage PIN sign-in and terminal locking +123/-13

Manage PIN sign-in and terminal locking

• Adds PIN login and lock transitions, revokes the presented token, and guards against stale auth responses.

frontend/src/store/auth.ts

session-events.tsBroadcast frontend session boundaries +22/-0

Broadcast frontend session boundaries

• Lets dependent stores clear privileged state during login, logout, lock, or PIN switching.

frontend/src/store/session-events.ts

security.tsExtend authentication rate limiting +20/-1

Extend authentication rate limiting

• Supports skipping successful requests and auditing endpoint rate-limit rejections.

main/middleware/security.ts

auth.tsAdd selected-user PIN authentication +238/-1

Add selected-user PIN authentication

• Lists eligible staff and verifies PINs against the selected user with serialized lockout checks, audits, and token issuance.

main/routes/auth.ts

bills.tsAdd audited payment correction and tighten approvals +1108/-58

Add audited payment correction and tighten approvals

• Adds transactional paid-bill method correction with idempotency and settlement guards. Attributes tenders to the actor's shift and rechecks charge-waiver state inside its transaction.

main/routes/bills.ts

cash-closures.tsAttribute movements and guard multi-shift day close +46/-15

Attribute movements and guard multi-shift day close

• Uses the actor's shift for movements unless another is authorized and reports all shifts blocking day close.

main/routes/cash-closures.ts

cash-sessions.tsSupport concurrent staff-owned cash shifts +105/-11

Support concurrent staff-owned cash shifts

• Enforces shift policy, lists visible shifts, supports authorized queries and previews, and sums concurrent opening floats.

main/routes/cash-sessions.ts

expenses.tsPass expense actor into shift context +61/-39

Pass expense actor into shift context

• Makes the expense context reflect the requesting staff member's open shift and reformats nearby payment routes.

main/routes/expenses.ts

printers.tsApply actor-aware drawer-kick gate +8/-2

Apply actor-aware drawer-kick gate

• Requires the caller's open shift, while allowing owners and managers to use any open shift.

main/routes/printers.ts

refunds.tsIdentify actor in refund shift checks +1/-0

Identify actor in refund shift checks

• Passes the authenticated refund initiator into precondition validation.

main/routes/refunds.ts

reports.tsReport one shift or the full business day +107/-2

Report one shift or the full business day

• Adds shift-scoped X reporting and exposes relevant open shifts in full-day reports.

main/routes/reports.ts

staff.tsAllow sign-in PINs for operational staff +7/-17

Allow sign-in PINs for operational staff

• Removes role-based PIN rejection and preserves PINs when editing operational roles.

main/routes/staff.ts

cash-day-close-guards.tsFind all shifts blocking day close +10/-5

Find all shifts blocking day close

• Replaces the single-open-shift assumption with shifts opened on or before the closing business date.

main/services/cash-day-close-guards.ts

expenses.tsAttach cash expense entries to actor shifts +6/-6

Attach cash expense entries to actor shifts

• Uses the payment or reversal actor's open session for expense movements and session availability.

main/services/expenses.ts

refund.tsAttach cash refunds to actor shifts +3/-2

Attach cash refunds to actor shifts

• Uses the refund creator for cash shift checks and the recorded session ID.

main/services/refund.ts

shift-session-gate.tsSelect open shifts by policy and actor +41/-7

Select open shifts by policy and actor

• Adds per-staff selection and open-shift listing while retaining shared behavior when the setting is off.

main/services/shift-session-gate.ts

Bug fix (5) +83 / -20
StatusBar.tsxRefresh shift status after user changes +1/-1

Refresh shift status after user changes

• Re-runs shift-status synchronization when the active user changes.

frontend/src/components/layout/StatusBar.tsx

CartPanel.tsxScope price-override modal to active user +15/-5

Scope price-override modal to active user

• Prevents a previous staff member's override modal remaining available after switching.

frontend/src/components/pos/CartPanel.tsx

api.tsIgnore obsolete session authorization failures +7/-0

Ignore obsolete session authorization failures

• Prevents a late 401 from an old token clearing the newly signed-in user's session.

frontend/src/lib/api.ts

cart.tsClear privileged cart adjustments on session changes +33/-0

Clear privileged cart adjustments on session changes

• Preserves ordinary cart lines while removing price overrides and charge waivers at session boundaries.

frontend/src/store/cart.ts

orders.tsRestrict order approval PIN candidates +27/-14

Restrict order approval PIN candidates

• Filters approver lookups and transaction rechecks to eligible owner and manager accounts.

main/routes/orders.ts

Refactor (1) +30 / -17
cash-drawer-movements.tsShare business-time helper for cash writes +30/-17

Share business-time helper for cash writes

• Moves timezone resolution into the shared service for closures and payment corrections.

main/services/cash-drawer-movements.ts

Tests (9) +3545 / -254
approval-manager-selection.spec.tsAlign approval assertion with revised terminology +1/-1

Align approval assertion with revised terminology

• Expects the Staff Approval PIN error text.

frontend/e2e/approval-manager-selection.spec.ts

database-export-import.spec.tsUpdate export fixture schema version +1/-1

Update export fixture schema version

• Moves the contaminated export fixture's schema version to 120.

frontend/e2e/database-export-import.spec.ts

pin-quick-switch.spec.tsExercise lock-to-PIN-switch checkout +171/-0

Exercise lock-to-PIN-switch checkout

• Covers token revocation, staff selection, preserved cart items, and checkout as the new user.

frontend/e2e/pin-quick-switch.spec.ts

backup-restore-production.test.tsExpect schema version 120 after recovery +2/-2

Expect schema version 120 after recovery

• Updates the recovered production database migration assertion.

tests/backup-restore-production.test.ts

cash-sessions.test.tsCover concurrent staff cash shifts +598/-3

Cover concurrent staff cash shifts

• Tests opt-in and legacy policy, actor-attributed cash, reporting, close guards, permissions, and float aggregation.

tests/cash-sessions.test.ts

payment-method-correction.test.tsCover paid-bill tender reclassification +1012/-0

Cover paid-bill tender reclassification

• Exercises authorization, immutable amounts, shift and day boundaries, refunds, split checks, idempotency, and reporting effects.

tests/payment-method-correction.test.ts

pin-quick-switch.test.tsCover PIN authentication and approval isolation +927/-0

Cover PIN authentication and approval isolation

• Tests staff PINs, selected-user sign-in, picker visibility, rate limits, audits, session boundaries, and approvals.

tests/pin-quick-switch.test.ts

staff-authz.test.tsVerify operational-role PIN policy +256/-67

Verify operational-role PIN policy

• Updates expectations to allow valid operational staff PINs while retaining validation coverage.

tests/staff-authz.test.ts

upgrade-path.test.tsVerify non-destructive schema upgrades +577/-180

Verify non-destructive schema upgrades

• Extends migration checks for the audit and per-user shift schema, including existing data preservation.

tests/upgrade-path.test.ts

Documentation (6) +106 / -35
authentication-and-authorization.mdDocument PIN switching and approval boundaries +38/-6

Document PIN switching and approval boundaries

• Describes selected-user PIN sign-in, lockout, audit, terminal locking, and owner/manager-only approvals.

docs/architecture/authentication-and-authorization.md

business-time.mdDocument per-staff shifts and shared drawers +16/-2

Document per-staff shifts and shared drawers

• Explains actor-scoped cash attribution, combined day close, and the limitation of individual counts with a shared drawer.

docs/architecture/business-time.md

frontend.mdDocument frontend session state +2/-2

Document frontend session state

• Updates auth and POS-settings store descriptions for terminal locking and non-persisted idle-lock configuration.

docs/architecture/frontend.md

order-lifecycle.mdDocument actor shift gates and tender corrections +7/-4

Document actor shift gates and tender corrections

• Clarifies cash tender attribution and when a paid bill's recorded method can change.

docs/architecture/order-lifecycle.md

api.mdSpecify new authentication, shift, and correction APIs +21/-15

Specify new authentication, shift, and correction APIs

• Documents PIN routes, payment correction, shift selection, reporting scope, settings, and error contracts.

docs/reference/api.md

roles-and-permissions.mdClarify shift ownership and approval authority +22/-6

Clarify shift ownership and approval authority

• Distinguishes PIN sign-in from owner/manager approval and describes who may inspect, close, or correct cash activity.

docs/reference/roles-and-permissions.md

Other (30) +981 / -181
page.tsxConfigure terminal idle auto-lock +40/-1

Configure terminal idle auto-lock

• Adds timeout selection and guards against stale save rollbacks.

frontend/src/app/(dashboard)/settings/page.tsx

PaymentMethodsSettings.tsxAdd cash-shift policy toggles +88/-8

Add cash-shift policy toggles

• Exposes require-open-shift and per-staff-shift settings with a shared-drawer warning.

frontend/src/components/settings/PaymentMethodsSettings.tsx

ar.jsonLocalize new workflows in Arabic +29/-7

Localize new workflows in Arabic

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ar.json

bn.jsonLocalize new workflows in Bengali +29/-7

Localize new workflows in Bengali

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/bn.json

de.jsonLocalize new workflows in German +29/-7

Localize new workflows in German

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/de.json

en.jsonAdd English copy for new workflows +29/-7

Add English copy for new workflows

• Adds quick-switch, correction, shift, and auto-lock messages and clarifies approval PIN wording.

frontend/src/lib/i18n/messages/en.json

es.jsonLocalize new workflows in Spanish +29/-7

Localize new workflows in Spanish

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/es.json

fa.jsonLocalize new workflows in Persian +29/-7

Localize new workflows in Persian

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/fa.json

fil.jsonLocalize new workflows in Filipino +29/-7

Localize new workflows in Filipino

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/fil.json

fr.jsonLocalize new workflows in French +29/-7

Localize new workflows in French

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/fr.json

hi.jsonLocalize new workflows in Hindi +29/-7

Localize new workflows in Hindi

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/hi.json

id.jsonLocalize new workflows in Indonesian +29/-7

Localize new workflows in Indonesian

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/id.json

it.jsonLocalize new workflows in Italian +29/-7

Localize new workflows in Italian

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/it.json

ja.jsonLocalize new workflows in Japanese +29/-7

Localize new workflows in Japanese

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ja.json

ko.jsonLocalize new workflows in Korean +29/-7

Localize new workflows in Korean

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ko.json

ne.jsonLocalize new workflows in Nepali +29/-7

Localize new workflows in Nepali

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ne.json

nl.jsonLocalize new workflows in Dutch +29/-7

Localize new workflows in Dutch

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/nl.json

pt.jsonLocalize new workflows in Portuguese +29/-7

Localize new workflows in Portuguese

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/pt.json

ru.jsonLocalize new workflows in Russian +29/-7

Localize new workflows in Russian

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ru.json

sq.jsonLocalize new workflows in Albanian +29/-7

Localize new workflows in Albanian

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/sq.json

th.jsonLocalize new workflows in Thai +29/-7

Localize new workflows in Thai

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/th.json

tr.jsonLocalize new workflows in Turkish +29/-7

Localize new workflows in Turkish

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/tr.json

ur.jsonLocalize new workflows in Urdu +29/-7

Localize new workflows in Urdu

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/ur.json

vi.jsonLocalize new workflows in Vietnamese +29/-7

Localize new workflows in Vietnamese

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/vi.json

zh-tw.jsonLocalize new workflows in Traditional Chinese +29/-7

Localize new workflows in Traditional Chinese

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/zh-tw.json

zh.jsonLocalize new workflows in Simplified Chinese +29/-7

Localize new workflows in Simplified Chinese

• Adds quick-switch, correction, shift, and auto-lock messages and revises approval PIN wording.

frontend/src/lib/i18n/messages/zh.json

pos-settings.tsStore server-synced idle-lock duration +24/-2

Store server-synced idle-lock duration

• Normalizes supported timeout values and excludes idle-lock settings from local persistence.

frontend/src/store/pos-settings.ts

db.tsMigrate shift uniqueness and add authentication audit +58/-0

Migrate shift uniqueness and add authentication audit

• Adds an auth audit table and replaces the global open-shift index with a per-user partial index; defaults per-staff shifts off.

main/db.ts

settings.tsValidate staff-shift and idle-lock settings +71/-0

Validate staff-shift and idle-lock settings

• Adds defaults and validation, exposes idle lock to staff, and blocks disabling per-staff shifts while multiple remain open.

main/routes/settings.ts

package.jsonWire new authentication and payment tests +4/-2

Wire new authentication and payment tests

• Adds PIN quick-switch and payment-correction scripts to existing test groups.

package.json

@khaira777 khaira777 changed the title feat: add per-staff cash shifts, payment method correction, and PIN quick switch feat(cash): opt-in one cash shift per staff Oct 28, 2026
@qodo-code-review

qodo-code-review Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Cashiers can read another shift's X report ✓ Resolved
Description
The session_id branch in the X-report route loads any requested cash session after checking only
reports.view, without verifying that a non-manager owns the shift. A cashier or other staff member
granted that configurable permission can request another user's session ID and receive its financial
totals, staff sales, and cash movements.
Code

main/routes/reports.ts[R1185-1191]

+    if (req.query.session_id !== undefined && String(req.query.session_id).trim() !== '') {
+      const sessionId = Number(req.query.session_id);
+      if (!Number.isInteger(sessionId) || sessionId <= 0) {
+        return res.status(400).json({ error: 'Invalid session_id', code: 'session_id_invalid' });
+      }
+      const session = db.prepare(`SELECT * FROM cash_sessions WHERE id = ?`).get(sessionId) as
+        | CashSessionRow
Relevance

●●● Strong

Missing ownership authorization exposes another staff member’s financial report, conflicting with
the PR’s shift-permission intent.

PR-#535

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed X-report branch accepts and loads an arbitrary session ID, while the route is gated only
by the general reports.view permission; it contains no role or opened_by ownership check. This
allows any non-manager who has that configurable permission to view another staff member's shift
data, violating the requirement that cashiers may only access their own shift.

Enforce shift permissions and aggregate day close
main/routes/reports.ts[1176-1195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
The X-report endpoint accepts an arbitrary `session_id` and returns that shift's financial data without checking whether a non-owner/manager caller opened the shift.

Fix Focus Areas
- main/routes/reports.ts[1185-1195]

Recommended Fix
After loading the requested session, require the caller to be an owner or manager, or require `session.opened_by` to match the authenticated caller for other roles. Return HTTP 403 when the ownership check fails, before calculating or returning the report.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. PIN lockout can be bypassed by changing IP ✓ Resolved
Description
POST /api/auth/pin-login keys its five-attempt, 15-minute lockout by
${canonicalIp}:pin:${userId} rather than by user, while owner and manager approval PINs now grant
full login sessions without opt-in. Because the unauthenticated user picker exposes valid account
IDs, someone able to change source addresses can get five fresh guesses at the same four-digit PIN
from each address; the separate 600-request-per-15-minute endpoint limiter is also IP-keyed and
skips successful requests.
Code

main/routes/auth.ts[R416-418]

+        | { status: 200; user: VerifiedUser };
+
+      const verificationResult = await withPinVerificationLock<PinLoginOutcome>(
Relevance

●●● Strong

Security rate-limit bypass is concrete and conflicts with the stated per-user lockout intent.

PR-#489

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The public picker reveals active PIN-enabled user IDs, and the key passed to checkLoginRateLimit,
incrementFailedLogin, and resetSuccessfulLogin contains both the client IP and user ID, giving
each pair a separate failed-attempt count. The additional endpoint limiter is IP-based rather than
account-wide. Staff validation permits 4–6 digit PINs for every role, and a successful PIN login
signs a normal JWT with the user's role, including owner.

main/routes/auth.ts[416-418]
main/routes/auth.ts[434-436]
main/routes/auth.ts[518-524]
main/middleware/security.ts[152-185]
main/routes/auth.ts[358-374]
main/routes/auth.ts[320-348]
main/routes/auth.ts[358-375]
main/routes/auth.ts[402-403]
main/routes/auth.ts[438-497]
main/middleware/security.ts[156-184]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
PIN-login failed-attempt lockout is scoped to an IP/account pair, so changing source IP gives fresh guesses against the same user's PIN, including owner and manager PINs that now grant full sessions.

## Fix Focus Areas
- main/routes/auth.ts[402-403]
- main/routes/auth.ts[416-500]

## Recommended Fix
Add a failed-attempt key for the target account independent of IP, such as `pin-user:${userId}`. Check, increment, and reset it alongside the existing IP/user key, and refuse login when either key is locked. Use the per-user key with `withPinVerificationLock` so checks and increments for one account are serialized across source IPs. Consider making owner and manager PIN sign-in opt-in or requiring six digits for it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Staff PIN guidance misleads owners 🔗 Cross-repo conflict ≡ Correctness
Description
main/routes/staff.ts now accepts PINs for operational roles, but flopos.com tells owners that
cashier and server PINs cannot be used for sign-in and that only owners and managers can have PINs.
When an owner follows the website’s staff setup instructions, they will miss the new quick-switch
option for those staff members.
Code

main/routes/staff.ts[L314-317]

-    if (isOperationalRole(role) && hasNonEmptyPin(pin)) {
-      captureStaffValidation('fc.backend.staff.create.validation', 'pin_role_mismatch');
-      return res.status(400).json({ error: 'PINs are only permitted for owner and manager roles' });
-    }
Relevance

●●● Strong

Website guidance contradicts the PR’s new operational-role PIN behavior and should be updated for
consistency.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR removes the role-based PIN rejection and documents sign-in PINs for every staff role, while
the website explicitly instructs owners that operational-role sign-in PINs do not exist.

FloCafe -> flopos.com
docs/architecture/authentication-and-authorization.md[194-203]
External repo: FreeOpenSourcePOS/flopos.com, docs-content/staff/staff-roles.php [31-40]
External repo: FreeOpenSourcePOS/flopos.com, docs-content/staff/staff-roles.php [70-75]
External repo: FreeOpenSourcePOS/flopos.com, docs-content/staff/security-pins.php [29-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The website’s staff setup instructions contradict the new PIN sign-in behavior for operational roles.
## Fix Focus Areas
- main/routes/staff.ts[314-317]
- /cross_repos/flopos.com/docs-content/staff/staff-roles.php[31-40]
- /cross_repos/flopos.com/docs-content/staff/staff-roles.php[70-75]
- /cross_repos/flopos.com/docs-content/staff/security-pins.php[29-42]
## Recommended Fix
Coordinate a flopos.com documentation update explaining that any staff role can use a 4–6 digit sign-in PIN, while privileged approvals still require an active owner or manager PIN. Regenerate the website search index if it is derived from these pages.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


4. Late movements miss the shift count ✓ Resolved
Description
The cash-movement route resolves targetSessionId before entering withTxn() and inserts the
movement without checking the session's status again. If that shift closes between the lookup and
insertion, its closure snapshot excludes the movement even though the movement is recorded against
the closed shift.
Code

main/routes/cash-closures.ts[975]

+        targetSessionId = getOpenSession(db, createdBy)?.id ?? NO_CASH_SESSION_ID;
Relevance

●●● Strong

Pre-transaction session selection creates a clear closure race and risks unreconciled cash
movements.

PR-#836

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both explicit and actor-default session selection happen before the transaction, while insertion
uses the captured ID inside it. Shift closing creates its closure snapshot and then marks the
session closed, leaving a window in which the later movement is not counted.

main/routes/cash-closures.ts[952-987]
main/routes/cash-sessions.ts[180-194]
main/routes/cash-sessions.ts[505-513]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A movement can use a shift selected before a concurrent shift close commits, leaving that movement out of the closure snapshot.

## Fix Focus Areas
- main/routes/cash-closures.ts[952-986]

## Recommended Fix
Resolve and authorize the requested or actor-owned open shift inside the movement's write transaction, immediately before insertion. Return a conflict if the shift is no longer open.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
5. Correcting a no-shift payment moves it into the manager's shift ✓ Resolved
Description
The payment-method correction route treats cash_session_id: 0 and missing session IDs as grounds
to use getOpenSession(db, effectiveActorUserId), then writes the correcting actor’s shift ID onto
the line after only checking its timestamp against that shift’s opened_at. With per-staff shifts,
a manager correcting cash taken by a staff member without an open shift can thereby bring cash that
was never counted in a drawer into the manager’s expected cash and Z tender totals; legacy lines
with NULL session IDs can likewise become pinned to whoever corrects them.
Code

main/routes/bills.ts[R5946-5949]

+        } else {
+          const openSession = getOpenSession(db, effectiveActorUserId);
+          if (!openSession) {
+            throw paymentValidationError(
Relevance

●●● Strong

Session attribution affects financial reconciliation; similar cash-session ownership mismatches were
accepted.

PR-#836

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Normal payment application writes NO_CASH_SESSION_ID = 0 when the actor has no shift to prevent
later shift attribution, but the correction route treats 0 or a missing ID as a reason to select
the correcting actor’s open shift and persists it as cash_session_id: effectiveSessionId. Its
timestamp guard passes when that shift opened before the payment, while expected cash and Z tender
calculations select lines whose recorded session ID matches the shift, so the corrected line enters
the corrector’s totals.

main/services/shift-session-gate.ts[25-30]
main/routes/bills.ts[5929-5964]
main/routes/bills.ts[6292-6297]
main/routes/cash-closures.ts[726-730]
main/services/shift-session-gate.ts[23-27]
main/routes/bills.ts[3782-3787]
main/routes/bills.ts[5929-5963]
main/routes/bills.ts[6293-6298]
main/routes/cash-closures.ts[696-718]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `PATCH /bills/:id/payments/:index/method` route assigns the correcting actor’s open shift to payment lines with `cash_session_id: 0` or missing ownership. This moves sessionless payments or legacy lines into the corrector’s shift totals.

## Fix Focus Areas
- main/routes/bills.ts[5929-5964]
- main/routes/bills.ts[6024-6036]
- main/routes/bills.ts[6292-6298]

## Recommended Fix
Distinguish the explicit sessionless marker `0` from legacy missing ownership, and do not replace either with an unrelated correcting actor’s open session ID. When the line has no positive session ID, either reject correction with `409 shift_closed` because its owning open shift cannot be established, or preserve its original `cash_session_id` (`0` or absent) under an explicitly defined correction rule and rely only on the closed-day check. Apply the same rule to the split-sibling fallback branch, and add a test in which a manager with an open shift corrects a line recorded with `cash_session_id: 0`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

6. Single-shift X report lists other staff's sales ✓ Resolved
Description
The new session_id branch of GET /x-report takes staffSales and taxComponents from
computePeriodAggregates. That function selects every bill whose paid_at falls in the shift's
time window, whichever shift took the payment. When per-staff shifts overlap, the 'one shift' X
report for user A shows B's staff sales and tax figures, even though its tender and cash totals
cover only A's shift.
Code

main/routes/reports.ts[R1231-1238]

+          staffSales: sessionAggregates.staffSales.map((row) => ({
+            user_id: row.user_id,
+            name: row.name,
+            role: row.role,
+            revenue: row.revenue_cents / minorFactor,
+            orderCount: row.orderCount,
+          })),
+          taxComponents: sessionAggregates.taxComponents,
Relevance

●●● Strong

Time-window aggregation contradicts per-session reporting and can mix overlapping staff shifts.

PR-#836

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
computePeriodAggregates builds its staff sales and tax lists from bills selected by b.paid_at
window only. sessionFinancialTotals overrides only bill/refund counts, the gross/refunded/net
totals and the payment methods. So staff and tax figures in the per-shift X cover every concurrent
shift.

main/routes/cash-closures.ts[836-873]
main/routes/cash-closures.ts[697-709]
main/routes/reports.ts[1205-1214]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The per-shift X report shows staff sales and tax components for every bill paid during the shift window, including bills settled into other concurrent per-staff shifts.

## Fix Focus Areas
- main/routes/reports.ts[1205-1238]
- main/routes/cash-closures.ts[836-873]

## Recommended Fix
For the session branch, compute staff sales and tax components only from bills that have a payment line with `cash_session_id = session.id`, using the same rule as `sessionFinancialTotals`. Otherwise, hide or label these sections as store-wide when `one_shift_per_staff` is on.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Shift reports show another day's close ✓ Resolved
Description
The session-specific X-report branch loads a shift by ID without reconciling its business date with
the requested date, then combines that shift's totals with day-close metadata queried for the
requested date. When those dates differ, the response identifies the shift's business day but
reports the other day's closed state, Z number, and prior cash.
Code

main/routes/reports.ts[R1245-1248]

+          priorClosedCashCents: priorRow?.counted_cash_cents ?? null,
+          priorBusinessDate: priorRow?.business_date ?? null,
+          alreadyClosed: !!closedRow,
+          zNumber: closedRow?.z_number,
Relevance

● Weak

A closely matching report-date consistency concern was rejected in the recent reporting precedent.

PR-#695

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The closure and prior-close queries use date, but the new session branch accepts any session ID
and reports the session's independently derived business date alongside metadata from those earlier
queries.

main/routes/reports.ts[1163-1183]
main/routes/reports.ts[1190-1196]
main/routes/reports.ts[1215-1219]
main/routes/reports.ts[1245-1248]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A session X report can combine one shift's totals with closure metadata from a different requested business date.

## Fix Focus Areas
- main/routes/reports.ts[1190-1196]
- main/routes/reports.ts[1245-1248]

## Recommended Fix
Either reject a `session_id` whose business date differs from the requested `date`, or query all closure and prior-close metadata using the selected session's derived business date.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Linked PRs:
  🌐 PR #39
  🌐 PR #10
  🌐 PR #2
⚠️ Tickets: not configured — No jira, linear integration is connected for the linked tickets
✅ Compliance rules (platform): 5 rules
✅ Cross-repo context — repo relationships
  Explored: repo: FreeOpenSourcePOS/flopos.com (sha: a2f87d48) — View relationship
Review mode: Auto: 🧠 Deep: Dense cross-cutting cash, payment, authentication, migration, and UI changes.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread main/routes/reports.ts Resolved
Comment thread main/routes/cash-closures.ts Resolved
Comment thread main/routes/bills.ts Resolved
Comment thread main/routes/auth.ts Resolved
Comment thread main/routes/reports.ts Resolved
Comment thread main/routes/staff.ts Resolved
…e_shift_per_staff` is disabled (default), `getOpenSession` must preserve the store-wide open-session lookup (`SELECT * FROM cash_sessions WHERE status = 'open' LIMIT 1`), and when `one_shift_per_staff` is enabled, `getOpenSession` must look up only the specified actor's open session (`SELECT * FROM cash_sessions WHERE status = 'open' AND opened_by = ? LIMIT 1`) or return `undefined` when no actor user ID is provided. - **Root Cause:** [`getOpenSession`](file:///Users/gurkiratkhaira/.no-mistakes/worktrees/de2296f2e6f8/01M61KJ2A25N0GR48JQ5KGCY4B/main/services/shift-session-gate.ts#L48-L62) in [`main/services/shift-session-gate.ts`](file:///Users/gurkiratkhaira/.no-mistakes/worktrees/de2296f2e6f8/01M61KJ2A25N0GR48JQ5KGCY4B/main/services/shift-session-gate.ts) changed the store-wide open-session SQL query to `SELECT * FROM cash_sessions WHERE status = 'open' ORDER BY id ASC LIMIT 1` and kept an unrequired fallback to that query when `userId` was empty in per-staff mode. In [`tests/diagnostics-event.test.ts`](file:///Users/gurkiratkhaira/.no-mistakes/worktrees/de2296f2e6f8/01M61KJ2A25N0GR48JQ5KGCY4B/tests/diagnostics-event.test.ts#L1186-L1234) (section `4ab`), the test intercepts `SELECT * FROM cash_sessions WHERE status = 'open' LIMIT 1` to inject a synthetic database lookup failure during `POST /api/refunds`; because the SQL string no longer matched, the lookup returned `undefined` and triggered a `cash_session_required` validation rejection instead of preserving the database failure. ### Fix - Updated [`getOpenSession`](file:///Users/gurkiratkhaira/.no-mistakes/worktrees/de2296f2e6f8/01M61KJ2A25N0GR48JQ5KGCY4B/main/services/shift-session-gate.ts#L48-L62) in [`main/services/shift-session-gate.ts`](file:///Users/gurkiratkhaira/.no-mistakes/worktrees/de2296f2e6f8/01M61KJ2A25N0GR48JQ5KGCY4B/main/services/shift-session-gate.ts) to use `SELECT * FROM cash_sessions WHERE status = 'open' LIMIT 1` when `one_shift_per_staff` is off and removed the fallback query when `one_shift_per_staff` is on and `userId` is empty. ### Verification - `npm run test:diagnostics` (passed: all 7 suites including `tests/diagnostics-event.test.ts` with 471 passed, 0 failed) - `npm run lint` (passed) - `npm run build` (passed) - `npm run test:cash-sessions` (passed) - `npm run test:cash-closures` (passed) - `npm run test:upgrade-path` (passed) - `git diff --check` (passed)
@khaira777 khaira777 changed the title feat(cash): opt-in one cash shift per staff feat: add per-staff cash shifts, payment method correction, and PIN quick switch Oct 28, 2026
@khaira777 khaira777 changed the title feat: add per-staff cash shifts, payment method correction, and PIN quick switch feat(cash): opt-in one cash shift per staff Oct 28, 2026
@khaira777 khaira777 changed the title feat(cash): opt-in one cash shift per staff feat(cash): opt-in one cash shift per staff member Oct 29, 2026
@khaira777 khaira777 changed the title feat(cash): opt-in one cash shift per staff member feat(cash-sessions): per-staff shifts Oct 29, 2026
@khaira777
khaira777 merged commit 7837a33 into main Oct 29, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1339-per-staff-shifts branch October 29, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cash): opt-in one cash shift per staff member

1 participant