Foodops
All docs

Counter · Updated 2026-08-22 · vf4d01a7

Session Handover

Audio narration for this article is generated with the weekly docs refresh.

Plain-English summary

A session handover is when one cashier passes an open register to another mid-shift without closing the session and counting the cash. The outgoing cashier stops working on the till; the incoming cashier picks up where they left off. The mechanism is a lock transfer: tapping Join on an open session sets that session's "locked by" field to the incoming cashier, while the original session record preserves who opened it (OpenedByUserId is never overwritten). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:JoinSession @main — sets LockedByUserId/LockedTime only; OpenedByUserId set once at OpenRegister and not changed]

The cashier who is currently "locked" to the session is who subsequent activity is associated with; there is no separate audit-trail event recording the moment of transfer.

When you'd use this

  • Shift change in a busy restaurant where closing and re-opening the register would interrupt ongoing service
  • A supervisor temporarily takes over a register to handle a complex transaction, then hands it back
  • A cashier needs a short break and a colleague covers
  • A staffing change where a mid-shift cash count is not operationally needed

Why handover instead of close-and-reopen

Closing a session requires a denomination-by-denomination cash count on the Close Register form. That count is the official till reconciliation — managers use it for variance reports. Closing mid-shift just to change cashiers produces an incomplete session with a partial-day cash count; the incoming cashier then opens a fresh session with a new float. Reports end up showing two sessions for what was operationally one shift, complicating daily summaries.

Handover avoids this: one session, one float, one close-count at end of shift. The session keeps a single cash record across the whole shift regardless of how many cashiers held the lock. .

How a handover happens

There are two paths in Foodops:

Path 1 — Join from the Counter landing screen (the primary flow)

This is how inter-shift handovers happen in practice. The act of joining transfers the lock to the incoming cashier — no extra step is needed.

  1. The outgoing cashier steps away. The session remains open on the register.
  2. The incoming cashier navigates to the Counter.
  3. The register appears in the Open Sessions section as a card showing the outlet, register name, the date the session was opened, and the name of the cashier who opened it, with Join and Details buttons. [code: foodops.counter.app/src/pages/CounterPage.tsx — Open Sessions cards render outletName, registerName, openedTime, openedByUserName @main]
  4. Tapping Join immediately calls the join API (POST /sessions/join), which sets the session's lock to the incoming cashier and redirects them to the POS screen. There is no intermediate confirmation modal. [code: foodops.counter.app/src/pages/CounterPage.tsx:handleJoinSession + src/services/session/sessionApiService.ts:joinSession; hubits-api-sales/.../SessionsController.cs:JoinSession returns RedirectUrl = /sessions/{id}/pos @main]
  5. The incoming cashier lands on the POS / order-taking screen and is now the session's locked user.

The incoming cashier's name becomes the session's "locked by" value. Note: there is no broadcast of the join to other devices (see the troubleshooting note about stale cashier names below).

Path 2 — Add Shift Handover from Session Details (explicit record)

When a cashier wants to record a mid-shift cash count (the "Add Shift Handover" action — note: this is a cash reconciliation, not a cashier-transfer record):

  1. From the Counter screen, tap Details on the active session card → Session Details modal.
  2. Tap the Handover tab (titled "Cash Reconciliation & Handover").
  3. Tap Add Shift Handover to open the Cash Reconciliation modal.
  4. Enter the denomination cash count (and any per-payment-method counts) plus notes, then submit.
  5. The reconciliation appears in the Handover tab as a shift card.

The Handover tab is titled Cash Reconciliation & Handover. When the session is open it shows an Add Shift Handover button; tapping it opens the Cash Reconciliation modal, which collects a denomination cash count alongside a Comments/Notes field and a per-payment-method opening/expected/counted/difference breakdown. Below the button the tab lists prior reconciliation "shifts" as cards (shift name, start/end time, reconciled-by, payment-method table, notes) — NOT a list of cashier join events. When no reconciliation has been done the tab shows "No handover records found for this session — Handover history will appear here once cash reconciliation is performed." [code: foodops.counter.app/src/components/session/SessionHandoverTab.tsx @main — "Add Shift Handover" opens CashReconciliationModal; rows come from reconciliationApiService.getReconciliationHistory; backend = hubits-api-sales/.../SessionsController.cs:ShowCashReconciliation / SaveCashReconciliation / GetReconciliationHistory]

Cash Reconciliation & Handover form — denomination count table with Payment Method Summary and Comments field

Note: the tab fetches its reconciliation history on open via the API. If it stays on the loading spinner, the reconciliation-history request failed (network/auth) — re-open Session Details to retry.

What a handover records

A join does not create a discrete, auditable handover record. What it changes on the sales session is the lock:

  • LockedByUserId / LockedTime are updated to the incoming cashier and the moment of the join [code: hubits-api-sales/.../SessionsController.cs:JoinSession @main]
  • OpenedByUserId (the cashier who opened the session) is unchanged

To answer "who was on the till at time T?" there is no per-join history surfaced in the UI. The session shows only who opened it and who currently holds the lock. (The only "audit log" in the product is the generic org AuditLogs viewer, populated by login/CRUD/role-change infrastructure, which JoinSession does not write to [code: hubits-api-sales/.../Controllers/AuditLogsController.cs @main].)

Handover vs close

Cash count required Session status after New session created
Handover (Join) No Still open, new cashier attached No
Close Register Yes — denomination count Pending Close → Closed (after supervisor approval) No (next cashier opens a fresh session)

The key operational difference is the cash count. A handover moves who currently holds the till (the lock) but does not verify how much cash is in the drawer, and does not write a per-handover history. The cash count happens only at session close (or via an optional mid-shift cash reconciliation). This is by design — requiring a count at every shift change would slow service and fragment the cash record across multiple partial sessions. .

Key concepts

  • Handover — the transfer of an open session's lock from one cashier to another. No till count; no session close; the session ID is unchanged.
  • Join — the Counter-screen button that claims an open session by setting its lock to the current cashier. The primary handover mechanism in normal operation. [code: foodops.counter.app/src/pages/CounterPage.tsx:handleJoinSession @main]
  • Locked by — the cashier who currently holds the session lock (LockedByUserId). The Open Sessions picker exposes LockedByUserName and an IsLocked flag.
  • Handover tab — a tab in the Session Details modal titled "Cash Reconciliation & Handover".
  • Audit log — the product's only audit log is the generic org AuditLogs viewer (login/CRUD/role-change events). .

Common questions

Q: Does the outgoing cashier have to do anything to initiate a handover? A: Not for Path 1. They simply step away from the till. The incoming cashier initiates the join from the Counter screen. If both cashiers are present and want a formal handover record, the outgoing cashier can also use Path 2 (Add Shift Handover from Session Details) before stepping away.

Q: Can a handover be undone? A: There is nothing to "undo" beyond the lock — joining just moves the lock to the incoming cashier; no separate handover record is created. If the wrong person joined by mistake, the intended cashier simply taps Join on the same session, which moves the lock back to them. [code: hubits-api-sales/.../SessionsController.cs:JoinSession @main]

Q: What happens if two cashiers try to join the same session at the same moment? A: There is no double-claim protection: both joins succeed, and whichever join writes last is the cashier the session ends up locked to (last-write-wins). The server does NOT reject a join just because another cashier already holds the lock. [code: hubits-api-sales/.../SessionsController.cs:JoinSession @main — the "session is already being used by..." conflict check is present but fully commented out; JoinSession overwrites LockedByUserId unconditionally]

Q: Does a handover affect cash reconciliation at close? A: No — joining only changes the session's lock holder; it does not touch the session's Opening Balance, balances, or counts. All orders taken before and after the handover contribute to the same session's totals, so the variance at close reflects the whole shift regardless of how many cashiers held the register. [code: hubits-api-sales/.../SessionsController.cs:JoinSession @main — modifies only LockedByUserId/LockedTime, no balance/count writes]

Q: How do I see every cashier who touched a session? A: You can't get a full chain of custody from the UI. The session record shows who opened it (OpenedByUserName) and who currently holds the lock (LockedByUserName); it does not retain a history of every cashier who joined. A reconciliation shift card does record who performed each cash reconciliation (reconciledBy), which is the closest thing to a mid-shift custody marker.

Edge cases and known issues

Previous cashier's name persists on the Counter screen after the incoming cashier joins

  • Symptom: the session card on another device still shows the outgoing cashier's name after a successful join.
  • Cause: a join is NOT broadcast to other devices. The SignalR hub only emits SessionOpened and SessionClosed events — there is no SessionJoined/lock-changed event — so a device that already loaded the Open Sessions list will not be told the lock moved until it refetches. [code: foodops.counter.app/src/services/signalr/signalRService.ts @main — session events handled are SessionOpened and SessionClosed only]
  • Workaround: refresh the Counter page (forces a refetch). The session card then reflects the current lock holder.

A join silently overrides another cashier still on the session

  • Symptom: a cashier is taking orders on a register when another staff member taps Join on the same session; the first cashier's lock is taken over with no warning.
  • Cause: by design (currently) — JoinSession has no double-claim guard; the conflict-rejection check is commented out, so the latest join wins. [code: hubits-api-sales/.../SessionsController.cs:JoinSession @main]
  • Workaround: operationally, only the incoming cashier should tap Join, and only after the outgoing cashier has stepped away. The original cashier can re-join to take the lock back.

Related

  • Sales Registers — registers are what sessions (and handovers) are tied to

  • Sales Sessions — the full session open-to-close lifecycle; handover is one event within it

  • Cross-device session sync — SignalR broadcasts SessionOpened and SessionClosed to keep other devices in sync. Note: a join (lock transfer) is NOT broadcast, so a join can be invisible to other devices until they refetch. [code: foodops.counter.app/src/services/signalr/signalRService.ts @main]

  • Cash Handling — the cash events that bookend a session; handover sits between open and close

  • Users, Roles & Permissions — Counter role required to join a session

  • 2026-08-22 — foodops.counter.app 8c411fc..83f0da4 adds a 'Quick Switch' dropdown to SessionDetailsModal.tsx: gated per-session by a new enableQuickSessionSwitch flag (read from unifiedOfflineDB.sessionState, populated offline like the previously-flagged posDefaultViewMode/softEnforcePaymentReference settings), it lists the user's other open sessions (via the new selectOpenSessions selector) in a QuickSwitchDropdown, and selecting one calls handleSelectSwitch → dispatch(joinSession(id)).unwrap() → navigate to /sessions/{id}/pos on success. This is NOT orthogonal to this entry the way prior diffs were: it invokes the exact same joinSession action/lock-transfer mechanic this entry documents (JoinSession sets LockedByUserId/LockedTime, last-write-wins, no double-claim guard), just from a third UI location — the Session Details modal — rather than only the Counter landing screen's Open Sessions cards (Path 1) or the Add Shift Handover cash-reconciliation flow (Path 2). The only extra gate before the join is confirmExitDespiteUnsynced() (an unsynced-offline-data check, unrelated to session locking) — there is still no 'confirm before joining' modal of the kind this entry already ruled out for Path 1. If/when documented, this belongs in this entry as a third join entry-point (e.g. under 'How a handover happens'), noting it is flag-gated (enableQuickSessionSwitch) and behaves identically to Path 1's Join once triggered — same lock semantics, same absence of an audit-log row or confirmation modal. No existing claim is contradicted; sessionSlice.ts's own diff hunk was PATCH-UNAVAILABLE, so the joinSession/selectOpenSessions implementations themselves haven't been independently re-read at this HEAD — worth a follow-up diff read before writing the entry text. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-21 — hubits-api-sales 98c202d..c7e4894 (fix(sales-orders): net unit rate on order docs; feat(counter payments): UTC date/transfer-reference enforcement/receipt PDF; feat: session discard log — server source, export, register-aware reprint; feat: void bin approval mode + acknowledge review path) touches the session close-summary surface but does NOT intersect this entry's session-handover (lock-transfer/Join/audit-log/SignalR) claims — no hunk touches JoinSession, LeaveSession, SessionHandoverTab-backing endpoints, CounterPage's Open Sessions cards, or signalRService. Two genuinely new user-facing pieces, both orthogonal to handover: (1) GetSessionCloseSummary/GetSessionCloseSummaryPdf now populate a new SessionCloseSummaryDto.DiscardLog (via SessionDiscardLogService.BuildAsync) plus SalesRegisterId — a session-scoped discard/void-bin log (order count, total qty/amount, and per-row OrderReference/VoidedDate/OrderType/Reason/RequestedBy/ReviewedBy/Status) now surfaced on the session close summary; (2) the void-bin pre-close validation comment changed from 'reviewed (approved/rejected)' to 'reviewed (approved/rejected/acknowledged)', consistent with the commit's 'void bin approval mode + acknowledge review path' — a third review outcome alongside approve/reject. Neither belongs on session-handover.md, which documents the cashier lock-transfer (Join) mechanic, not the close-summary/void-bin surface; if documented, both belong on close-register.md or session-details.md alongside the existing void-bin-gate and blind-cash-counting notes. No edit required to session-handover.md. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-17 — foodops.counter.app 8c411fc..f30dbeb (chore: hide Take Out/Delivery on dine-in page; fix: real create permission in session order details; fix(receipts/kot): print manual receipt/KOT in org timezone not terminal's; fix(session): anchor Running badge's start instant in UTC) touches SessionDetailsModal.tsx, RegisterSessionDetailsPage.tsx, and sessionSlice.ts but does NOT touch this entry's session-handover surface — no hunk hits JoinSession, LeaveSession, SessionHandoverTab, CounterPage's Open Sessions cards, or signalRService, so none of this entry's lock-transfer/audit-log/join claims are affected. New user-facing pieces, all orthogonal to handover: (1) a new 'Discard Log' tab on RegisterSessionDetailsPage (DiscardLogTab component) alongside the existing Transactions/Payments/Pending Actions/Closing Count/Reconciliation/Handover tabs; (2) a Customer column added to the session's invoice/transactions table; (3) the 'Running' duration badge now parses its start instant via parseServerDate instead of new Date(iso), fixing a bug where a naive-but-UTC openedTime was read as the terminal's local zone, inflating the displayed running time by the terminal's UTC offset — a duration fix, not a zone-conversion (duration is zone-independent); (4) sessionSlice's offline snapshot/hydrate/detect paths now also persist/restore enforceUniqueBlazeNumber (a Transfer-receipt-reuse warning flag) alongside the previously-flagged posDefaultViewMode/softEnforcePaymentReference. If documented, the Discard Log tab and Customer column belong on session-details.md (RegisterSessionDetailsPage's other tabs), and the Running-badge timezone fix wherever session duration/timeline display is documented. No edit required to session-handover.md. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-09 — hubits-api-sales bb69312..7594413 is the same commit batch already logged 2026-08-02 (df4179d) and 2026-08-05 (620d686) — tenancy pin, cache/signalr perf, dependency bump, org-wide customer statements, PosDefaultViewMode/SoftEnforcePaymentReference on SessionDto, and the OpenedTime UTC ISO-8601 fix are all superseded/duplicate coverage, still orthogonal to this entry (no hunk touches JoinSession, LeaveSession, SessionHandoverTab-backing endpoints, or AuditLogsController). One hunk not previously surfaced: OpenRegister's balance-seeding loop now also sets the new Expected field to the opening float amount (OpeningAmount = openingAmount plus an Expected seed, per the added comment 'Seed Expected to the float... "what should be in the drawer" IS the float') when a register is opened. This is about the OpenRegister cash-count seed (relevant to close-register variance display), not about the session lock-transfer/Join mechanic this entry documents, and requires no edit here — if documented, it belongs on close-register.md or session-details.md alongside the existing blind-cash-counting/variance-summary notes. No edit required to session-handover.md. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-05 — hubits-api-sales bb69312..620d686 (tenancy pin; cache/signalr perf; dependency bump; org-wide customer statements) is a superset of diffs already logged 2026-08-01 and 2026-08-02 above (GetSessionCloseSummary/PDF + SessionCloseSummaryDto; SessionDto's PosDefaultViewMode/SoftEnforcePaymentReference) — still orthogonal to this entry, no re-analysis needed there. One genuinely new hunk beyond those: GetMySessionsAndRegisters's OpenSessions projection now emits OpenedTime as an explicit UTC ISO-8601 string (DateTime.SpecifyKind(s.OpenedTime, DateTimeKind.Utc).ToString("O")) instead of a naive yyyy-MM-dd HH:mm:ss, fixing a timezone display bug (client new Date() was previously reading the UTC clock as local). This does not affect this entry: the citation to CounterPage.tsx's Open Sessions cards is about the openedTime field being rendered at all, not its wire format, and no hunk in this diff touches JoinSession, LeaveSession, SessionHandoverTab-backing endpoints, or AuditLogsController. No edit required to session-handover.md; if documented, the timezone fix and the two additive settings belong on session-details.md / a Counter POS-layout entry, not here. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-04 — foodops.counter.app a00083c..37ad39c adds three unrelated user-facing changes, none touching this entry's session-handover (lock-transfer/Join/audit-log/SignalR) surface: (1) a new 'void bin' close-register gate — SessionDetailsModal's handleCloseRegister now detects a 'void bin' message via isVoidBinGate() on a close-register failure, closes both dialogs, and shows a toast with a 'Review' action that stashes the session id and navigates to /sessions/{id}/dinein so the cashier can clear the void bin before closing; (2) RegisterSessionDetailsPage's invoice reference cell is now a clickable link to the new admin app invoice detail (adminAppUrl(/sales/invoices/{transactionId})) when the invoice carries a transactionId; (3) sessionSlice's detectCurrentSession offline snapshot/rehydrate now also persists/restores posDefaultViewMode and softEnforcePaymentReference — the frontend counterpart of the org-level settings already flagged as additive/unrelated to this entry in the 2026-08-02 note above. No hunk touches JoinSession, LeaveSession, SessionHandoverTab, CounterPage's Open Sessions cards, or signalRService, so none of this entry's lock-transfer/audit-log/join claims are affected. If documented, the void-bin close gate belongs on close-register.md or session-details.md, and the invoice link on session-details.md. No edit required to session-handover.md. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-02 — hubits-api-sales bb69312..df4179d (chore(tenancy): pin haafai.domain 1.0.1027; perf(cache): explicit invalidation seam for ExecuteUpdate writes + order micro-cache; perf(signalr): routing envelope for order broadcasts; fix(dependencies): haafai.domain 1.0.1029; feat(customers): org-wide paged statements endpoint) is a superset of the diff already covered by the 2026-08-01 entry above — same GetSessionCloseSummary/GetSessionCloseSummaryPdf/SessionCloseSummaryDto addition, still unrelated to this entry. Two genuinely new pieces beyond that prior entry: SessionDto/GetMySessionsAndRegisters now also stamps every session with PosDefaultViewMode (string "tile"|"compact", from the org-level Outlets 'Default to Tile View' setting, defaults "compact") and SoftEnforcePaymentReference (bool, org-level 'Soft Enforce Payment Reference Number' gating Transfer-payment references, defaults false). Both are additive, org-scoped settings read once per GetMySessionsAndRegisters call and have nothing to do with cashier handover — no hunk touches JoinSession, LeaveSession, SessionHandoverTab-backing endpoints, or AuditLogsController, so none of this entry's lock-transfer/audit-log claims are affected. If documented, PosDefaultViewMode/SoftEnforcePaymentReference belong on a Counter POS-layout or payment-settings entry, not session-handover.md. No edit required. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-01 — hubits-api-sales bb69312..0d94f42 adds the backend implementation behind the 'Session Summary' print/PDF feature already noted in the 2026-07-31 entry above (which observed it from the foodops.counter.app frontend side). Two new SessionsController endpoints — GET /sessions/{id}/close-summary (GetSessionCloseSummary) and GET /sessions/{id}/close-summary/pdf (GetSessionCloseSummaryPdf) — both build a SessionCloseSummaryDto by calling the existing DaySummaryReportService.GenerateReport(sessionId) and mapping via a new SessionCloseSummaryMapper; the PDF path additionally renders HTML via SessionCloseSummaryHtmlBuilder and converts it through a new IHtmlToPdfService. This is a genuine, previously-only-frontend-inferred capability now confirmed server-side, but it is entirely orthogonal to this entry's subject: no hunk touches JoinSession, LeaveSession, SessionHandoverTab-backing endpoints (ShowCashReconciliation/SaveCashReconciliation/GetReconciliationHistory), AuditLogsController, or any lock-transfer/audit-log logic this entry documents. None of session-handover.md's claims are contradicted or require edits. If documented, it belongs on session-details.md or close-register.md (the session close/summary surface), not here. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-31 — foodops.counter.app 5468530..5ac2982 adds a 'Session Summary' print/export feature to RegisterSessionDetailsPage: for users with the ViewExpectedAmount permission (canViewExpected, the same blind-cash-counting gate noted in the 2026-07-30 entry above), two new buttons appear — 'Print Receipt' (fetches a live, force-refreshed close-summary DTO via the new sessionApiService.getCloseSummary() and routes it to a configured thermal print broker via brokerService.submitSessionSummary) and 'Save PDF' (downloads a server-rendered PDF via the new sessionApiService.getCloseSummaryPdf()GET /sessions/{id}/close-summary/pdf). This is a genuine new user-facing capability but is entirely orthogonal to this entry's subject: no hunk touches JoinSession, LeaveSession, SessionHandoverTab, the SignalR session events, or any lock-transfer/audit-log logic this entry documents. None of session-handover.md's claims are contradicted or require edits. If documented, it belongs on session-details.md or close-register.md (the session close/summary surface), not here. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-30 — foodops.counter.app c4cf5dc..5468530 ('hide expected implementation'; 'feat: blind cash counting'; 'feat(counter): blind-close variance summary modal') adds a permission-gated 'blind cash counting' feature that touches the same surfaces this entry documents (Session Details close flow, the 'Cash Reconciliation & Handover' tab, RegisterSessionDetailsPage balances table) but does NOT contradict any of this entry's Session Handover (lock-transfer/Join/audit-log/SignalR) claims — no hunk touches JoinSession, LeaveSession, or SessionHandoverTab's join/lock logic. New behavior: for users lacking the ViewExpectedAmount permission (gated via canViewExpectedCash/canSeeExpectedForSession in src/hooks/useUserPermissions), the Expected and Difference columns are now hidden in both the historical reconciliation ShiftCard table (SessionHandoverTab.tsx) and the RegisterSessionDetailsPage balances table; and the Close Register flow for such users skips the variance-review step entirely — handleCloseRegister detects !canViewExpected, immediately calls a new approveBlindClose() (commits the close via approveClose, refetches session details, sets showVarianceSummary), then presents a new read-only VarianceSummaryModal recap instead of the normal balance-differences dialog. This is a real UI/permissions feature worth a follow-up doc pass (likely on session-details.md / close-register.md rather than this entry, since it's about the cash-count/close flow, not the cashier-lock handover this entry is about) but requires no edit to session-handover.md's existing claims. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-06 — hubits-api-sales@e31810a (feat: 'add operation-log audit coverage + history endpoint') adds a real operation-log audit trail to SalesRegisterSession: OpenRegister now calls newSession.TouchCreate("Register opened."), and CloseRegister, the cancel-pending-close path, and AdjustRegister each now call session.Touch(...) to write an operation-log entry. A new GET /sessions/{sessionId}/history endpoint (GetSessionAuditHistory) was added that returns the session's full operation-log timeline (newest first, with parsed field diffs), and its doc comment explicitly lists 'handovers acknowledged' among the entry types the timeline is meant to surface. This is a genuine, previously-undocumented audit capability for sessions and is directly relevant to this entry's central claim that 'no separate transfer audit event is recorded' for handovers/joins — that claim is NOT contradicted by this diff (no hunk touches JoinSession or LeaveSession themselves; only OpenRegister/CloseRegister/CancelClose/AdjustRegister got Touch/TouchCreate calls), but the 'handovers acknowledged' wording in the new endpoint's doc comment suggests join/leave audit coverage may be planned or added elsewhere. Recommend a targeted follow-up diff/read of JoinSession and LeaveSession at this new HEAD, and of the frontend consumer of /sessions/{id}/history, before deciding whether this entry's 'no audit trail for handovers' claims need an edit. (auto-applied by the truth pipeline; adversarially gated)

Warning: Unverified — status: draft Verified_by: claude-code (internal-docs read, 2026-05-03) · Code access: not available (private repos) Claims derive from internal-docs/foodops/counter/registers-list.md (verified 2026-04-23, line 31: "Joining unlocks it from the previous cashier and writes the handover to the audit log"), internal-docs/foodops/counter/join-session.md (verified 2026-04-23), internal-docs/foodops/counter/session-details.md (verified 2026-04-23, Handover tab and Add Shift Handover button confirmed). Handover form field list marked <!-- UI-TODO --> pending live re-capture.

  • 2026-06-03 — codebase: VERIFIED-NO-CHANGE for the handover flow. Re-verified the join/lock/leave logic against hubits-api-sales@5274891 [SessionsController.cs:479-570]; the lock-on-join + audit-log handover behaviour is unchanged. The new PendingClose status (commit 47912c7 and the 2026-05-08 status-machine work) does not touch handover — it operates entirely within the Open status. Only one minor cross-reference touch: the Handover-vs-Close table now shows the close target as "Pending Close → Closed (after supervisor approval)" to match the new close path [SessionsController.cs:1745-1753, 1894-1916].
  • 2026-06-14 — TRUTH AUDIT (major corrections). The 2026-06-03 note above is itself partly WRONG: there is NO "audit-log handover behaviour" — and there never was in code. Verified against hubits-api-sales@main SessionsController.cs:JoinSession/LeaveSession and Dto/Counter/Sessions/SessionDetailsDto.cs, plus foodops.counter.app@main CounterPage.tsx, SessionHandoverTab.tsx, SessionDetailsModal.tsx, signalRService.ts. Findings: (1) JoinSession only sets LockedByUserId/LockedTime — no AuditLogs row, no handover record. (2) The in-code conflict-rejection check is COMMENTED OUT, so joins are last-write-wins with no double-claim error (prior Q&A claimed the opposite). (3) The "Handover" tab is a cash-reconciliation view, not a join-event log. (4) The Join button calls the API directly — there is no "Confirm before joining" modal, and "Details" does not claim the session. (5) The View-Details-404 and "jQuery Loading handover..." issues describe the retired MVC UI; current app is a React SPA. (6) SignalR has SessionOpened/SessionClosed only — no join broadcast. ROOT CAUSE of the fabrications: the internal-docs source registers-list.md line 31 ("Joining unlocks it from the previous cashier and writes the handover to the audit log") was itself inaccurate and the KB inherited it without code verification. The lock-transfer mechanic and OpenedByUserId preservation ARE real and now carry citations.
  • 2026-06-29 — LAUNCH RE-VERIFY (new-stack only). Re-pinned to foodops.counter.app@32669a9, hubits-api-sales@19190b9. Verdict ACCURATE — re-confirmed the core mechanic UNCHANGED at current HEAD: JoinSession only sets LockedByUserId/LockedTime (SessionsController.cs:547-548), the "session already being used by…" conflict-rejection block is still commented out (L533-544 → last-write-wins, no double-claim error), and OpenedByUserId is set once at OpenRegister and never overwritten. No audit-log row on join; Handover tab is still the "Cash Reconciliation & Handover" view (mid-shift cash counts, not join events). SignalR still broadcasts only SessionOpened/SessionClosed (no join event). No body change needed; no haafai.app.foodops citations present.
  • 2026-06-14 — spot-checked CITED claim: "Close Register → Pending Close → Closed (after supervisor approval)" is CONFIRMED-TRUE. CloseRegister now lands every close in PendingClose regardless of variance [hubits-api-sales/.../SessionsController.cs:1813-1821 @main], and ApproveCloseRegister transitions PendingClose → Closed [SessionsController.cs:1979 @main].

Go beyond POS.
Choose the total solution.

Focus on creating the best experience for your guests while we handle the rest.