Foodops
All docs

Counter · Updated 2026-08-24 · v1002fe0

Sales Sessions & Register Management

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

Plain-English summary

A sales session is one shift of a register — from the moment a cashier opens the cash drawer with a starting float to when they count the cash at the end and close the shift. Every order during that window is attached to the session. The Sales Sessions page (/salessessions) is the historical list of every session ever opened, with open/close times, cashier, closing balance, and reconciliation status.

When you'd use this

  • End of shift — close a session and reconcile cash
  • End of day / end of month — audit sessions for variance (expected vs counted cash)
  • Investigating a discrepancy — who opened which register when, and what did they take in
  • Training — show new cashiers the complete open-to-close lifecycle

Summary

Every POS shift starts with opening a register session (entering cash float) and ends with closing (counting cash, reconciling). The Sales Sessions page tracks all session history for auditing.

Access

  • URL: /salessessions
  • Title: "All Register Sessions"

All Register Sessions list — columns: Opened, Session No., Register, Assigned User, Closed, Duration, Closing Balance, Status, Actions

Session History Table

Column Description
Opened Date/time session was started
Session No. Unique identifier (e.g., Session/0001/2026)
Register Which register (e.g., Register 1)
Assigned User The cashier who opened the session
Closed Date/time session was closed (blank if still open)
Duration How long the session lasted
Closing Balance Final cash count at close
Status Open, Pending Close, or Closed
Actions View details, close session

The Status column now reflects a three-state lifecycle: OpenPending CloseClosed. A submitted close lands in Pending Close until a supervisor approves it; only then does it become Closed. The history list can be filtered by status, including a dedicated "pending" filter. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:470] See Session Lifecycle for the full state machine.

Session Lifecycle

Opening a Session

  1. Navigate to POS Counter (/orders)
  2. Click "Open Register" on an available register
  3. Enter cash float by denomination count (MVR 1000, 500, 100, 50, 20, 10, 5, 2, 1)
  4. Click "Open Register"
  5. Session is now active — all transactions logged against it

During a Session

  • All orders processed through this register are tracked
  • Cash in (sales), cash out (refunds), card payments all recorded
  • Multiple sessions can be active across multiple registers

Closing a Session

  1. Click the session management icon on the Counter page
  2. Count physical cash by denomination
  3. Enter actual amounts
  4. System calculates: expected cash vs actual cash = variance
  5. On submit the session moves to Pending Closenot straight to Closed. Every close now routes through this status regardless of variance. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1753]
  6. A supervisor reviews the pending close and approves it to finalise it to Closed, adjusts the count (recount, stays Pending Close), or cancels the close (reverts the session to Open). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1894-1916, 2080-2082, 1988-1990] Verified live 2026-06-11 — the controls surface in two places: at close time, the Balance Difference Warning dialog ("The closing cash count does not match the expected balance.") offers Close Anyway (approve) and Cancel (screenshot 184); for a session already parked in Pending Close, the counter app's Register Sessions page (/register-sessions) → session details shows Approve Close and Adjustment buttons (screenshot 207), and the legacy Session History details page shows Approve Close Register and Adjustment links (screenshot 186). No standalone "cancel close" control was found on the pending-session details surfaces — the revert-to-Open path is the close-time dialog's Cancel.
  7. Once approved → Closed, the shift report is generated for manager review

As of 2026-05-01, the close flow includes a Cancel button so staff can abort mid-close without committing (commit fd248d7). Separately, once a close is submitted, the cancel-close action reverts a Pending Close session back to Open, wiping the closing counts. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1988-2052]

The Session Details modal (accessed via /salessessions/viewsessiondetails?sessionId=<guid>) shows six tabs: Overview, Sales Orders, Receipts, Delivery Cash, Cash Withdrawal, and Handover.

Closing tabs (verified live 2026-06-11): the closing-time tabs only appear once a session has reached Pending Close or Closed (the showClosingTabs gate) [code: foodops.counter.app/src/components/session/SessionDetailsModal.tsx:301-310]. Live observations: the counter app's Session Details modal for an Open session shows Overview, Sales Orders, Receipts, Delivery Cash, Cash Withdrawal, Handover, Online Orders (no closing tabs — screenshot 182); a Pending Close session opened from /register-sessions renders a full details page with Overview, Transactions, Payments, Pending Actions, Closing Count, Reconciliation, Handover (screenshot 207). The legacy details page for a Pending Close session shows Overview, Transactions, Closing Count, Reconciliation (screenshot 186).

Counter app Session Details modal — Open session, no closing tabs

Each invoice row in a session can now carry a pending-action flag — set when a Card / Online / Bank Transfer payment on that invoice has no reference number while the organisation requires one for that method. It marks an individual order/invoice that needs follow-up; it is not a session status. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1202 (IsPendingAction = invoice.InvoicePayments.Any(...)); Dto/Counter/Sessions/SessionDetailsDto.cs:197]

Transfer reference ("blaze") issues + the pre-close gate (NEW 2026-06). When the organisation enables the EnforceUniqueBlazeNumber setting, bank-transfer payments are checked for a blank required reference and for duplicate references across the session. Session Details then shows a new "Issues" tab (rendered only when the setting is on) listing every invoice whose transfer payment has a missing or duplicate reference (a "blaze" is the bank-transfer reference number). [code: foodops.counter.app/src/components/session/SessionDetailsModal.tsx:395,568-570 (Issues tab gated on enforceUniqueBlazeNumber); SessionIssuesTab.tsx; hubits-api-sales/.../Controllers/SessionsController.cs:1206-1245 (HasDuplicateBlaze/DuplicateBlazePayments/MissingBlazePayments); Dto/Counter/Sessions/SessionDetailsDto.cs:197-208 (HasDuplicateBlaze, DuplicateBlazePaymentDto)] Tapping Close Register then runs a pre-close check: if any blaze issues remain it opens a "Resolve transfer reference issues" modal that forces each blank/duplicate reference to be fixed inline before the close can proceed, and the backend enforces the same as a server backstop ("Resolve transfer reference (blaze) issues before closing the register."). [code: foodops.counter.app/src/components/session/SessionDetailsModal.tsx:377-385 (pre-close check opens BlazeIssuesModal); BlazeIssuesModal.tsx; hubits-api-sales/.../Controllers/SessionsController.cs:1657-1672 CloseRegister blaze gate] This is gated on the org setting — outlets without EnforceUniqueBlazeNumber see neither the Issues tab nor the gate.

Session Details modal — 6 tabs visible: Overview, Sales Orders, Receipts, Delivery Cash, Cash Withdrawal, Handover

The Overview tab shows a payment method breakdown (Cash, Transfer, Card) with Opening, Expected, Counted, and Difference columns. For the dedicated session detail page, navigate to /salessessions/details/<guid>.

Session detail standalone page — Overview tab with payment method breakdown

Cross-device session sync (NEW 2026-05)

Sessions can now be opened and closed across multiple devices via SignalR real-time messaging (introduced 2026-05-01, commit 73a5a08). When a cashier opens or closes a register on one device, all other connected devices are notified immediately — preventing two devices from operating the same session in conflict. The SessionDetector component also re-validates session state whenever the browser regains focus or visibility (e.g., after switching tabs or unlocking a screen), so a stale session from a disconnected period is caught and corrected on reconnect.

See cross-device-session-sync.md for the full lockout and reconnect-revalidation behavior.

Key concepts

  • Session — one open-to-close cycle on one register. Atomic unit for cash reconciliation.
  • Float — the opening cash (typically MVR 500). Entered by denomination count for traceability.
  • Variance — expected cash (float + cash sales − cash refunds) vs counted cash. Non-zero variance is the signal of shrinkage, miscounts, or uncleared returns.
  • Closing balance — total cash in the drawer at close. Reported alongside variance.
  • Register session vs order time — reports (especially Day Summary) bucket either by session or by order time; a session that spans midnight groups sales differently depending on the choice.
  • Parallel sessions — multiple registers can have open sessions simultaneously (one session per register). A register cannot have two open sessions. Register access across devices is coordinated via SignalR — see cross-device-session-sync.md for lockout details.

Common questions

Q: What's the difference between a session and a shift? A: Overlap in practice but not identical. A shift is a person's work period (could span sessions if they switch registers). A session is a register's open/close cycle. Reports reconcile by session.

Q: Can a session span multiple days? A: Yes — if you open at 10pm and don't close until 2am, the session covers both dates. Use "Report By: Order Time" on Day Summary to split sales across calendar days.

Q: How do I close a session I forgot to close? A: From /salessessions, find the open session, click Actions → View Details → Close. Enter the counted cash as of now; Foodops warns about the age of the session.

Q: What happens to unclosed sessions long-term? A: They stay open indefinitely — nothing auto-closes. The Dashboard flags an old open session with a warning ("Register 'X' has an unclosed session from DATE") to prompt closure.

Q: Who can close a session they didn't open? A: Any user with the Session: Close permission (usually Manager or higher). The closing user's name is recorded alongside the opening user for audit.

Q: Does closing a session with variance fail? A: No — variance is allowed, never blocked. But the close no longer finalises in one step: the session moves to Pending Close, and a supervisor approves it ("Close Anyway"), adjusts the count, or cancels the close. When the count matches expected the same approval step still applies — every close lands in Pending Close first. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1745-1753]

Q: A register shows "Pending Close" — what does that mean and who clears it? A: The cashier submitted the cash count, but the close is awaiting supervisor sign-off. A supervisor opens Session Details and either approves it (finalises to Closed), adjusts the count (recount; stays Pending Close), or cancels the close (the session goes back to Open and the closing counts are wiped). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:1894-1916, 2080-2082, 1988-1990] See Session Lifecycle.

Q: Can two cashiers use the same register at the same time? A: Register access is coordinated across devices via SignalR cross-device sync (introduced 2026-05-01) — one device holds an active lock at a time. See cross-device-session-sync.md for lockout and handover details.

Test Data

Field Value
Session Session/0001/2026
Register Register 1
Status Open (since 14/04/2026 06:11 AM)
Orders processed 1 (SO-0001/2026, MVR 64.80 cash)

Related

  • POS Takeaway Order — includes register opening

  • Cross-device session sync — SignalR lockout and reconnect behavior (NEW 2026-05)

  • Reports Catalogue — Shift/Session reports

  • User Stories: POS — US-POS-014, US-POS-015, US-POS-016

  • 2026-08-21 — codebase re-verify, hubits-api-sales 98c202d..c7e4894 (SessionsController.cs, Dto/Counter/Sessions/SessionCloseSummaryDto.cs, new file Dto/Counter/Sessions/SessionDiscardLogDto.cs — all in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: the session close-summary report (GET /sessions/{id}/close-summary and its /pdf sibling, logged as a backend-only 'Session Summary' artifact on 2026-08-01 and still unconfirmed against any UI trigger) now stamps a DiscardLog onto SessionCloseSummaryDto via a new SessionDiscardLogService.BuildAsync(sessionId, orgId) call, plus a SalesRegisterId looked up from SalesRegisterSessions. SessionDiscardLogDto shapes an overview (OrderCount, TotalQuantity, TotalAmount) plus one SessionDiscardRowDto per discarded order (OrderId/Reference, VoidedDate, OrderType, Reason, RequestedBy, ReviewedBy, TotalAmount, Status) — a server-side, register-and-session-scoped rebuild of the same underlying void-bin data the 2026-08-17/08-15 entries already found being consumed client-side by foodops.counter.app's (unwired-to-a-tab, as of those entries) DiscardLogTab.tsx/DiscardOrderLogModal.tsx. This confirms the backend now treats the discard log as a first-class part of the printable/exportable close-summary artifact — consistent with the commit subject 'session discard log — server source, export, register-aware reprint' — but this diff has no foodops.counter.app hunks, so there is still no confirmed evidence the DiscardLog is rendered anywhere in the counter UI (modal tab, close-summary PDF layout, or otherwise); the DTO's Status/ReviewedBy fields are also new, unconfirmed vocabulary for discard review states. Separately, CloseRegister's pre-close void-bin-review gate comment changes from 'every void bin entry reviewed (approved/rejected) before close' to '...(approved/rejected/acknowledged) before close' — the diff hunk shown is only the comment, not the query logic beneath it, so it's unconfirmed whether 'acknowledged' is already a satisfying status value in the underlying enum/query or just documents an in-flight addition; per the commit subject 'void bin approval mode + acknowledge review path' this looks like a genuine new third review outcome alongside Approved/Rejected, but this entry makes no existing citable claim about the void-bin close-gate's state values to contradict. Needs the full CloseRegister diff (the actual status-check expression) and a live-UI pass on both the close-summary PDF and the counter's void-bin review surface before promoting any of this to an authoritative fact or a new subsection. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-20 — codebase re-verify, foodops.counter.app 8c411fc..fe34047 (SessionDetailsModal.tsx, SessionOrdersTab.tsx, sessionSlice.ts, DiscardLogTab.tsx, DiscardOrderLogModal.tsx — all in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: this diff's tracked-surface hunks substantially reproduce the 2026-08-17 entry's account of the session-scoped Discard Log feature (two-stat header Total No of Orders/Total Amount; per-row three-state review pill Approved/Rejected/'Approval Pending'; empty-state copy 'No discards or rejections this session'; DiscardOrderLogModal merging an order's operation-log history with its Discard Requested → Discard Approved/Rejected timeline into one chronological view), plus the already-logged SessionOrdersTab.tsx hasCreatePermission wiring (2026-08-10), sessionSlice.ts's three-flag offline-snapshot persistence of posDefaultViewMode/softEnforcePaymentReference/enforceUniqueBlazeNumber (2026-08-15), and the unrelated CustomerFormDialog.tsx discountId-hydration fix (customer profile, not session/register — out of scope). One detail beyond the 2026-08-17 account: DiscardLogTab now sources its data via a dedicated getSessionDiscardLog(sessionId) call (src/offline/actions/voidBinActions) returning a shaped {rows, orderCount, totalAmount} object, with an explicit three-way state machine — undefined=loading, null=offline ('Discard log needs a connection'), populated=loaded — where the offline state wasn't previously called out. This diff still contains no hunk confirming DiscardLogTab is wired into SessionDetailsModal's activeTab switch as a selectable tab, so the open question from 2026-08-17 (exact label/position among Overview/Sales Orders/Receipts/Delivery Cash/Cash Withdrawal/Handover/Online Orders/Issues) remains unresolved from source alone. Needs a live-UI pass before promoting any of this to the entry's Session Details tab list or Closing-a-Session walkthrough. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-17 — codebase re-verify, foodops.counter.app 8c411fc..e2fe2fc (src/components/session/, src/components/sales/ — this entry's tracked surface). NEW_INFO, no contradiction to documented claims: two new files, DiscardLogTab.tsx and DiscardOrderLogModal.tsx (with matching unit tests), implement a session-scoped 'Discard Log' surface not currently described in this entry's Session Details tab list (Overview, Sales Orders, Receipts, Delivery Cash, Cash Withdrawal, Handover, plus the documented Online Orders/Issues conditional tabs). DiscardLogTab reads per-session void-bin entries (getSessionVoidBinEntries, reusing the existing voidBinGrouping/constants/voidBin infrastructure), groups them by discard event, and renders a two-stat header (Total No of Orders / Total Amount) plus a table of discarded/rejected order rows with a three-state review pill — Approved, Rejected, or 'Approval Pending' (test-confirmed empty-state copy: 'No discards or rejections this session'). Clicking a row opens DiscardOrderLogModal, which merges the order's operation-log history (orderApiService.getOrderLogs) with the discard-request/review timeline (Discard Requested → Discard Approved/Rejected, with reviewer, reason chip, and review note) into one chronological, expandable timeline titled 'Order Logs - '. This looks like a supervisor-review workflow analogous to the entry's documented Pending Close approve/adjust/cancel flow, but scoped to individual voided/discarded order items within a session rather than the session close itself. Crucially, the diff hunk touching SessionDetailsModal.tsx in this range shows only the unrelated onRefresh={() => fetchSessionDetails(true)} addition to SessionOrdersTab (itself a duplicate of the already-logged 2026-08-15 entry) — there is no hunk in this diff confirming DiscardLogTab is actually registered as a tab in the modal's activeTab switch, so this cannot yet be promoted into the entry's tab list or the Closing-a-Session walkthrough. Also in this diff but out of scope for this entry: CustomerFormDialog.tsx (src/components/sales/**) fixes discountId being hardcoded to '' on every edit (now hydrates from detail.discountId) — a customer-profile discount-field bug fix unrelated to session/register management, not a session-tracked claim. All other hunks in this range (SessionOrdersTab.tsx real hasCreatePermission wiring, sessionSlice.ts offline-snapshot persistence of posDefaultViewMode/softEnforcePaymentReference/enforceUniqueBlazeNumber) duplicate content already logged verbatim in the 2026-08-10 and 2026-08-15 verification entries — no new behavior there. Needs a live-UI pass (open Session Details on a session with voided/discarded items) to confirm the Discard Log tab is reachable, its exact label/position among the other tabs, and the review-pill wording, before promoting any of this to an authoritative, citable fact or a new Session Details subsection. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-15 — codebase re-verify, foodops.counter.app 8c411fc..5cbfe1a (SessionDetailsModal.tsx, SessionOrdersTab.tsx, sessionSlice.ts — all in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: the SessionDetailsModal.tsx (onRefresh wiring into SessionOrdersTab) and SessionOrdersTab.tsx (getOrderButtonConfig → real hasCreatePermission, replacing hardcoded false) hunks in this diff duplicate content already logged in the 2026-08-10 verification entry for the 8c411fc..7c23e6d sub-range — no new behavior there. New in this wider range: sessionSlice.ts's joinSession and hydrateSessionFromOffline thunks now persist/restore posDefaultViewMode, softEnforcePaymentReference, AND enforceUniqueBlazeNumber into/from the offline session snapshot (previously none of the three were carried through these two paths, per the diff showing all three as new additions), and detectCurrentSession's persist/restore paths — which already carried posDefaultViewMode/softEnforcePaymentReference from an earlier, unlogged change — now also carry enforceUniqueBlazeNumber. Per the added comments, this closes a gap where an offline-hydrated or just-joined session could silently drop enforceUniqueBlazeNumber back to false/undefined after a hard refresh, causing this entry's documented transfer-reference Issues tab and pre-close 'Resolve transfer reference issues' gate (EnforceUniqueBlazeNumber-gated, per the existing 'Transfer reference ("blaze") issues' section) to disappear for that device/session even though the org setting is on, until the next full server re-detect. This is a bug fix for an offline/hard-refresh edge case this entry makes no specific claim about — not a contradiction of the documented blaze-issues behavior itself, which remains accurate for the normal online path. No live-UI check needed to confirm intent — the added comments make the before/after behavior unambiguous from code alone, consistent with how the 2026-08-05 OpenedTime serialization fix was handled. The other three commits in this range (hide Take Out/Delivery on dine-in, receipt/KOT org-timezone print fixes, Running badge UTC-anchor fix) produced no further diff hunks against this entry's tracked surface beyond what's already covered by prior entries. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-12 — codebase re-verify, hubits-api-sales bb69312..ecfea28 (SessionsController.cs GetMySessionsAndRegisters, Dto/Counter/Sessions/SessionDto.cs, SessionDetailsDto.cs — all in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: (1) SessionDto gains an EnforceUniqueBlazeNumber bool (default false), repeating the same org setting this entry already documents driving the session-close-time Issues tab + pre-close 'Resolve transfer reference issues' gate (Dto/Counter/Sessions/SessionDetailsDto.cs, cited in this entry's Transfer reference/blaze paragraph). Per the added comment, this copy is for the counter's payment-entry surface, to decide whether a Transfer Receipt whose reference was already used this session should raise a warning at the point of entry — a different moment in the flow (payment-taking) than the documented close-time Issues tab/gate. The controller diff supplied is truncated before the line assigning enforceUniqueBlazeNumberForSessions onto the response DTO, so the wiring is inferred from the new SessionDto property + its comment, not a confirmed end-to-end assignment. This diff has no foodops.counter.app changes, so there's no confirmed frontend consumer yet — no evidence the counter actually reads or displays this warning. (2) SessionInvoiceDto (used in GetSessionDetails, e.g. the Receipts tab) gains a CustomerName field — additive, no consumer confirmed in this diff. Everything else in this range duplicates content already logged: the DaySummaryReportService/IHtmlToPdfService constructor injection and SessionCloseSummaryDto (2026-08-01), PosDefaultViewMode/SoftEnforcePaymentReference on SessionDto (2026-08-04), and the OpenedTime UTC ISO-8601 serialization fix (2026-08-05) are unchanged here. RegisterBalanceScheduler.cs only gains an unrelated using-directive. Unrelated commits in this range (haafai.domain pin bumps, ExecuteUpdate cache-invalidation seam, order micro-cache, SignalR routing envelope, new customer-statements endpoint) do not intersect this entry's tracked surface. Needs a live-UI pass (a Transfer payment whose reference repeats one already used in the session, on an outlet with EnforceUniqueBlazeNumber on) before promoting the payment-time warning to an authoritative, citable fact or adding it alongside the existing blaze-issues paragraph. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-10 — codebase re-verify, foodops.counter.app 8c411fc..7c23e6d (SessionDetailsModal.tsx, SessionOrdersTab.tsx — both in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: two small fixes to the session's Sales Orders tab (fix commit 'use real create permission in session order details'). (1) SessionOrdersTab now fetches sessionApiService.getOrderButtonConfig(sessionId) on mount and passes the result's hasCreatePermission into the order-details panel it opens (OrderDetailsCard), replacing a previously hardcoded hasCreatePermission={false}. This diff doesn't include OrderDetailsCard, so the exact UI/action this permission gates (e.g. a create/duplicate/reorder control) isn't confirmed from this evidence alone — only that opening an order from the session's Sales Orders tab is no longer unconditionally denied that permission for users who actually hold it. (2) SessionDetailsModal now passes onRefresh={() => fetchSessionDetails(true)} into SessionOrdersTab, and the tab's order-details close handler calls onRefresh?.() after an order update — so actions taken inside that order-details panel (e.g. Close Order, payment) now refresh the parent session snapshot instead of leaving the Sales Orders tab's row showing a stale status until the modal was reopened. Neither change touches the documented open/close/pending-close/approve/blaze-issue lifecycle or the Sales Orders tab's existence/columns as described in this entry. The other three commits in this range (hide Take Out/Delivery buttons on dine-in, receipt/KOT org-timezone print fixes, Running badge UTC-anchor fix) produced no diff hunks against this entry's tracked surface and are not addressed here. Needs a live-UI pass (open an order from Session Details → Sales Orders tab as a user with create permission) before promoting the hasCreatePermission behavior to an authoritative, citable fact. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-09 — codebase re-verify, hubits-api-sales bb69312..7594413 (SessionsController.cs OpenRegister, in this entry's tracked surface; plus SessionCloseSummaryDto.cs, SessionDto.cs, RegisterBalanceScheduler.cs already logged). NEW_INFO, no contradiction to documented claims: OpenRegister's cash-balance-seeding loop now also captures the per-currency opening float total in a local openingAmount variable and, per the added comment ('Seed Expected to the float. With no sales and no withdrawals yet, "what should be in the drawer" IS the float...'), appears to seed the new balance row's Expected figure to that same float amount at open time — the supplied diff hunk is cut off before the actual field assignment line, so the exact target field name isn't confirmed from this evidence alone. If confirmed, this is a bug fix consistent with — not a change to — this entry's documented Variance formula ('expected cash (float + cash sales − cash refunds) vs counted cash'): previously a freshly-opened session's Expected may have read as 0 (or been unset) until the first sale/withdrawal touched it; now it reads as the float immediately at open. Everything else in this diff range duplicates content already logged against this entry's tracked surface: the OpenedTime UTC/ISO-8601 serialization fix (logged 2026-08-05), SessionDto's PosDefaultViewMode/SoftEnforcePaymentReference fields (logged 2026-08-04), the DaySummaryReportService/IHtmlToPdfService constructor injection plus SessionCloseSummaryDto.cs and the close-summary endpoints (logged 2026-08-01), and RegisterBalanceScheduler's debounce mechanism (logged 2026-07-19, this diff only adds an unrelated using-directive to that file) — no behavioral changes to any of these since. Unrelated commits in this range (haafai.domain pin bumps, ExecuteUpdate cache-invalidation seam, order micro-cache, SignalR routing envelope, new customer-statements endpoint) do not intersect this entry's tracked surface. Needs the full (untruncated) OpenRegister diff to confirm the exact field/name before promoting the float-seeding behavior to a citable, documented fact. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-05 — codebase re-verify, hubits-api-sales bb69312..620d686 (SessionsController.cs, in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: GetMySessionsAndRegisters' OpenSessions.OpenedTime serialization changes from a bare "yyyy-MM-dd HH:mm:ss" string to an explicit UTC ISO-8601 string (DateTime.SpecifyKind(s.OpenedTime, DateTimeKind.Utc).ToString("O")). Per the added inline comment, the old format had no timezone marker, so the client's new Date(...) parsed it as local time and rendered the raw UTC clock instead of converting — e.g. showing 03:02 instead of 08:02 for an outlet in UTC+5 — making a just-opened register's displayed open time look hours off. This is a bug fix to a call this entry doesn't cite an exact format for (the Session History Table's 'Opened' column description is generic — 'Date/time session was started' — with no documented timezone-handling claim to contradict). Everything else in this diff range duplicates content already logged in prior verification entries against the same tracked surface: the DaySummaryReportService/IHtmlToPdfService constructor injection and the close-summary/close-summary-pdf endpoint + SessionCloseSummaryDto were already covered by the 2026-08-01 entry, and the SessionDto PosDefaultViewMode/SoftEnforcePaymentReference additions were already covered verbatim by the 2026-08-04 entry — no changes to either since. Unrelated commits in this range (haafai.domain pin bumps, ExecuteUpdate cache-invalidation seam, order micro-cache, SignalR routing envelope, new customer-statements endpoint) do not intersect this entry's tracked surface. No live-UI check needed — the added comment makes the before/after serialization behavior unambiguous from code alone; the fix only affects the raw open-session hydration payload, not a claim this entry makes about displayed format. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-04 — codebase re-verify, hubits-api-sales bb69312..0c16730 (SessionsController.cs GetMySessionsAndRegisters, Dto/Counter/Sessions/SessionDto.cs — both in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: SessionDto (returned by the sessions/registers hydration call) gains two new org-scoped fields, each read once per request and stamped on every session. (1) SoftEnforcePaymentReference (bool, default false) — from a new 'Soft Enforce Payment Reference Number' Outlets General setting (Services/Settings/PaymentReferenceSettings.IsSoftEnforcedAsync) — when true, the counter is meant to require a reference number for Transfer payments at payment-entry time. This is a distinct mechanism from the documented EnforceUniqueBlazeNumber gate: that setting drives the session-close-time Issues tab + pre-close 'Resolve transfer reference issues' modal for missing/duplicate blaze numbers on invoices already in the session, whereas SoftEnforcePaymentReference appears to gate reference entry earlier, at the point a Transfer payment is taken, independent of session close. (2) PosDefaultViewMode (string 'tile'|'compact', default 'compact') — from an Outlets General 'Default to Tile View' setting — seeds the counter's per-device POS grid layout; this is a POS-ordering-screen concern, not session/register management, and out of scope for this entry. Both fields are additive and this diff includes no foodops.counter.app changes, so there is no confirmed frontend consumer yet for either — no evidence the counter reads or enforces SoftEnforcePaymentReference, or that PosDefaultViewMode is wired into the grid. This diff also re-shows the close-summary/PDF endpoints and DaySummaryReportService/IHtmlToPdfService constructor wiring already logged in the 2026-08-01 verification entry — no change there. Needs a live-UI pass (a Transfer payment on an outlet with the new setting enabled) before promoting SoftEnforcePaymentReference to an authoritative claim or adding it to the Common questions/blaze-issues subsection. Unrelated changes in this same commit range (haafai.domain pin bumps, ExecuteUpdate cache-invalidation seam, order micro-cache, SignalR order-broadcast routing envelope, a new customer-statements endpoint) do not intersect this entry's tracked surface. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-02 — codebase re-verify, foodops.counter.app 5468530..f52b6dc (SalesRegisterSessionsView.tsx, SessionPrintMenu.tsx [new], sessionApiService.ts — all in this entry's tracked surface). This resolves the open question from the 2026-08-01 note: the backend-only GET /sessions/{id}/close-summary and /close-summary/pdf endpoints now have a confirmed frontend trigger. On the Sales Sessions history page (/salessessions, rendered by SalesRegisterSessionsView — the same component behind the documented 'All Register Sessions' list/screenshot 118), every row (both the card and table layouts) gets a new printer-icon menu (SessionPrintMenu, Figma ref 2912-22622) with two actions: 'Save PDF' (saveSessionSummaryPdf, wraps the new getCloseSummaryPdf blob fetch) and 'Print Receipt' (printSessionSummaryThermal, thermal broker mapper per the commit subject). Both actions are disabled while offline (tracked via navigator.onLine/online-offline listeners) or when the viewing user isn't allowed to see the session's figures: allowed is computed as canSeeExpectedForSession(permissions, isClosed) where isClosed = !session.isOpen && session.status !== 'Pending Close' — i.e. a Closed session's summary is visible to everyone, but an Open or Pending Close session's summary is gated behind the same view-expected-cash permission introduced for blind cash counting on 2026-07-30. sessionApiService also gained getCloseSummary (cached/offline-first, mirroring getSessionDetails) and getCachedCloseSummary for the offline path. Not yet promotable to the Closing-a-Session walkthrough or the Session History Table's Actions-column description as an authoritative fact — needs a live-UI pass to screenshot the printer icon, the dropdown copy, and the disabled states (offline / no view-expected permission) before citing exact wording. Unrelated in this same diff: CustomerActivityTimeline.tsx and CustomerTransactionsTab.tsx (also under src/components/sales/**) add a 'Refund' filter/label that distinguishes money-out Payment rows (isRefund=true) from received payments in the customer profile's activity feed and transactions tab, plus an admin-app-redirect confirmation dialog for external links — this is customer-profile/AR-facing behavior, not session/register management, and does not intersect any claim in this entry. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-01 — codebase re-verify, hubits-api-sales bb69312..0d94f42 (SessionsController.cs + new Dto/Counter/Sessions/SessionCloseSummaryDto.cs). NEW_INFO, no contradiction to documented claims: two new read-only endpoints add a 'Session Summary' report for a register session — GET /sessions/{sessionId}/close-summary returns a SessionCloseSummaryDto, and GET /sessions/{sessionId}/close-summary/pdf server-renders the same data to PDF (Figma ref 2850-20643 thermal layout per the doc comment; ?download=true forces attachment vs inline). Both handlers call the newly-injected DaySummaryReportService.GenerateReport(..., sessionId) and map via SessionCloseSummaryMapper, so the slip's figures are explicitly designed to reconcile with the existing Day Summary report rather than being a separate calculation — consistent with this entry's Overview-tab payment-method breakdown (Opening/Expected/Counted/Difference per tender) but is a new artifact, not a rename of anything documented. DTO shape: session/outlet/register identifiers, OpenedByUserName/ClosedByUserName, OpenedTime/ClosedTime (UTC) and an IsOpen flag, a per-payment-method×currency Tenders list (Opening/Expected/Counted/Difference), curated SalesCounts (Sales/Void), and the full Day Summary SalesTotals line set. The controller doc-comment explicitly states 'gating (closed-only) is a UI concern' — the backend does not itself restrict this to closed sessions despite the IsOpen field suggesting it's meant for post-close use (thermal 'session summary' receipt printed at close). This diff is backend-only (no foodops.counter.app changes) — there is no confirmed UI trigger point (e.g. a 'Print Summary' or 'Download PDF' button on the Close Register / Session Details flow) yet, so this should not be promoted into the Closing-a-Session walkthrough or a new subsection until a live-UI pass locates the entry point and confirms the rendered thermal/PDF content and gating behavior. Unrelated changes in this same commit range (haafai.domain pin bump, ExecuteUpdate cache-invalidation seam, order micro-cache, SignalR order-broadcast routing envelope, a new customer-statements endpoint) do not intersect this entry's tracked surface. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-30 — codebase re-verify, foodops.counter.app c4cf5dc..5468530 (CashReconciliationModal.tsx, CloseRegisterModal.tsx, SessionBalancesTab.tsx, SessionDetailsModal.tsx, SessionHandoverTab.tsx, SessionOrdersTab.tsx, SessionOverviewTab.tsx, SessionReconciliationTab.tsx — all in this entry's tracked surface). NEW_INFO: a 'blind cash counting' feature (commit subjects 'hide expected implementation'; 'feat: blind cash counting — hide expected/variance until register session close'; 'feat(counter): blind-close variance summary modal'). Two new permission helpers from @/hooks/useUserPermissions — canViewExpectedCash(permissions) and the session-aware canSeeExpectedForSession(permissions, isClosed) — now gate every Expected Amount/Difference/Variance figure across the session surfaces this entry documents: the Overview tab's payment-method table, both Totals rows, and the standalone CashDrawerExpectedCard; the Balances tab's summary cards and table columns; the Reconciliation tab's Expected/Variance summary rows; the Handover tab's per-shift Expected/Difference columns; the Cash Reconciliation modal's Expected Amount/Difference lines; and, only while a session is OPEN, the Orders tab's Total Amount/Average Order cards (order count and per-order line amounts stay visible, revealed once closed or with permission per the component's own comment). The implementation of the two permission functions is not in this diff, so the exact right/setting name and whether canSeeExpectedForSession truly auto-reveals post-close (as the commit subject 'until register session close' implies) are unconfirmed — read useUserPermissions.ts before citing a specific permission name. More significant for the documented Closing-a-Session flow: CloseRegisterModal's 'Expected cash in drawer' card is now itself gated on canViewExpectedCash (a user without the right counts blind, no reference figure while entering denominations), and the submit button's label changed from 'Close Register'/'Closing...' to 'Submit Count'/'Submitting...'. In SessionDetailsModal's handleCloseRegister, when the close response reports hasDifferences=true AND the user lacks canViewExpectedCash, the code no longer opens the documented Balance Difference Warning dialog (Close Anyway/Cancel) — it calls a new approveBlindClose(sid), which calls sessionApiService.approveClose(sid) directly, then (on success) re-fetches the session and opens a new read-only VarianceSummaryModal; on failure it stashes a blindCloseRetry state and toasts rather than stranding the user. This is a real change to the flow documented in this entry's 'Closing a Session' steps 5-6 and the 'Does closing a session with variance fail?' FAQ: for a user without the view-expected right, a variance-carrying close now commits automatically — no Close-Anyway/Cancel choice is ever presented to them — rather than always landing in Pending Close for a distinct, visible supervisor decision. The diff doesn't include SessionsController.cs, so whether the backend independently re-checks a permission on this approveClose call (i.e., whether this path would reject a genuinely low-privilege cashier) is unclear from front-end evidence alone — the 'supervisor approves the pending close' framing should be treated as still accurate for privileged/default users, but a blind-count cohort whose closes self-approve now appears to exist. Not editing the documented default flow yet — needs a live-UI pass (once as a user with the view-expected right, once without) to confirm the exact gate, the VarianceSummaryModal's copy, and whether approveClose genuinely fails for an unprivileged account, before promoting any of this to an edit of the Closing-a-Session walkthrough or the variance FAQ. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-19 — codebase re-verify, foodops.counter.app b43110b..054a5b3 (SessionDetailsModal.tsx, in this entry's tracked surface). NEW_INFO: bug fix, not a lifecycle change. Commit 'fix(sessions): close stuck on "pending close" until another window opens' — the close-register and cancel-close success handlers previously invalidated only ['/sessions/my', /sessions/${id}/details] on completion; the Counter Sessions history list (/register-sessions and its paged/filtered variants, backed by /sessions/history + /history/stats) sat behind its own 2-minute cache that neither call cleared, so a session that had just been closed (or whose close had just been cancelled) could keep rendering its stale Pending Close row until something unrelated (e.g. opening another window) happened to bust that cache. Both the close and cancel-close success paths now also clear '/sessions/history' (a prefix delete covering the paged/filtered/stats variants). This does not contradict any documented claim in this entry — the three-state Open/Pending Close/Closed lifecycle, the approve/adjust/cancel actions, and the Status column filtering are all unchanged; this only fixes a staleness bug in when the history list's cached rows caught up to the real status. Two other commits in this range ('auto-join the register you just opened', 'open-register still bounced to the picker when the sync won the race') touch SessionDetector.tsx's temp-session-id-to-real-id resolution for a just-opened register — this is about the open-register/offline-queue flow, not the pending-close/approval/reconciliation behavior this entry documents, and doesn't intersect any claim here (closer to a companion note for cross-device-session-sync.md if documented at all, per the same reasoning used in the 2026-07-06 entry for a similar SessionDetector change). No live-UI reproduction needed to confirm the fix's intent — the added comment and the paired unit-test scaffolding changes make the before/after behavior unambiguous from code alone. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-19 — codebase re-verify, hubits-api-sales e5d8572..e0517e4 (SessionsController.cs, in this entry's tracked surface). NEW_INFO, no contradiction to documented claims: (1) fix(sessions) — GetSessionDetails now Includes Payment.OriginalCurrency and computes each receipt's Amount/CurrencyCode from the payment's native OriginalAmount/OriginalCurrency when UseDifferentCurrency is true, instead of always showing the transaction's base currency; per the inline ADR-0011 comment this fixes foreign-tender receipts in the session's Receipts tab to display the currency actually collected rather than the org's base currency — a bug fix to previously-undocumented behavior, not a contradiction of anything in this entry. (2) GetMySessionsAndRegisters now returns OutletId on RegisterDto even for a not-yet-opened register (previously only present once a session was already open), closing the same offline-hydration gap as the 2026-07-06 SessionDetector self-heal note — the counter can scope salesmade's catalog sync to an outlet as soon as the open-register call is queued offline, before any server session exists. Recommend cross-linking as a companion note in cross-device-session-sync.md rather than folding into this entry, consistent with that prior precedent. (3) Unrelated backend scale fix in the same file: a new IRegisterBalanceScheduler/RegisterBalanceScheduler debounces the per-payment register-balance recompute (previously enqueued on every settled payment, O(P^2) per shift) to ~one recompute per 5s window per session; the recompute itself is unchanged (a full idempotent snapshot), so the documented 'expected cash vs counted cash' variance formula is unaffected. This is an internal saturation fix, not a user-facing behavior change, and the diff doesn't show it wired into the close-register path, so no live-UI check is warranted for it. Needs a live-UI pass to screenshot a foreign-currency receipt before promoting (1) to an authoritative, citable fact. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-09 — codebase re-verify, foodops.counter.app 0545649..e4e7aaa (supersedes the 6885e57 pin cited in the 07-08 entry; same tracked surface: SessionDetailsModal.tsx, sessionApiService.ts, salesPostingService.ts, FailedPostingsDialog.tsx, SessionDetector.tsx). No contradiction of documented claims. Two updates to the 07-08 NEW_INFO on the dead-lettered-financial-postings close gate: (1) new unit tests in sessionApiService.test.ts and salesPostingService.test.ts confirm the exact behaviour and pin concrete strings — the structured-400 gate message is verbatim e.g. '1 sale(s) failed financial posting and need back-office attention before this register can close.'; when a { success:false } body carries no message, closeRegister falls back to the client-side default 'Failed to close register'; and a non-structured error (e.g. bare 502/gateway with no parseable body) still rethrows rather than being swallowed as a gate — so only recognised { success:false } bodies route to the FailedPostingsDialog, not arbitrary failures. This still needs a live-UI pass to screenshot the dialog and confirm the gate copy actually shown to a cashier before promoting from NEW_INFO to an authoritative, citable fact in the Closing a Session section, per the 07-08 note's own recommendation. (2) A separate, unrelated change in this same range: SessionDetector's one-time self-heal re-detect (previously triggered only by a missing outletId, per the 2026-07-06 entry) now also fires when isQuoteModeEnabled is undefined on a session hydrated from an offline snapshot, to backfill the field that gates the salesmade 'Quote' button; guarded so it never fires for temp_ sessions (which have no server record to enrich from and would otherwise loop). This is a Quote-button/offline-hydration concern, not a change to the documented open/close/pending-close/reconciliation lifecycle in this entry — better logged as a companion note in cross-device-session-sync.md (which already tracks SessionDetector's self-heal behavior) than folded into this entry. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-08 — codebase re-verify, foodops.counter.app 0545649..6885e57 (SessionDetailsModal.tsx, sessionApiService.ts, and new file salesPostingService.ts/FailedPostingsDialog.tsx — all in this entry's tracked surface). NEW_INFO: a new close-register gate for async financial postings. A sale whose async financial posting (invoice + payments + journals + stock) failed every retry on the backoff ladder is 'dead-lettered' server-side; if a register session has any dead-lettered postings, attempting to close it returns a structured HTTP 400 whose message matches /financial posting|still posting/i (e.g. '1 sale(s) failed financial posting and need back-office attention before this register can close.'). sessionApiService.closeRegister now catches this structured 400 and resolves {success:false, message} instead of throwing — confirmed by the updated test suite, which previously only covered a bare-error rethrow. SessionDetailsModal's close handler tests the message via the new isFinancialPostingGate() regex and, on match, opens a new FailedPostingsDialog scoped to the session (backed by salesPostingService, hitting GET /v1/sales/posting/dead-letter and POST /v1/sales/posting/{id}/retry) that lists each stuck sale with its attempt count and last error and lets the cashier retry one or all; a successful 'retry all' closes the dialog so the cashier can attempt the close again. This is a distinct failure mode from the documented Balance Difference Warning (cash variance) and the blaze/transfer-reference Issues gate — it fires on backend posting failures, not variance or missing transfer references — and, unlike the blaze gate, there is no client-side pre-close check in this diff; it surfaces only when the close attempt itself is rejected. No contradiction to existing lifecycle claims (variance never blocks close, Pending Close routing, blaze gate) — this is an additional gate on the same 'Close Register' action. Needs a live-UI pass to screenshot the dialog and confirm exact gate copy before promoting to an authoritative, citable fact; recommend documenting as a new subsection in Closing a Session alongside the existing Blaze Issues paragraph once verified live. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-06 — codebase re-verify (hubits-api-sales 6f242ad..e31810a). Session open/close/cancel-close/adjust now write to an operation-log audit trail (SalesRegisterSession.TouchCreate/Touch calls added at each transition), and a new GET /sessions/{sessionId}/history endpoint returns the parsed audit timeline (action label, actor, timestamp, remarks, per-field before/after diffs) for a session — explicitly described as mirroring the catalog/finance entities' existing History tabs. This does not change any documented open/close/pending-close/cancel/adjust behavior; it adds an auditability layer. No frontend (foodops.counter.app) diff in this change confirms a session-side History tab consuming the new endpoint yet — needs a live-UI pass on /register-sessions session details to check for a History tab before documenting it as a user-facing feature. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-06 — codebase re-verify, foodops.counter.app b813f61..047ad31 (SessionDetector.tsx, sessionSlice.ts — both in this entry's tracked surface). New self-heal: an offline-hydrated session that lacks outletId (older offline snapshots didn't persist it) previously left the salesmade catalog sync unable to scope itself, leaving the POS grid empty after a hard refresh with no way to recover short of a manual re-login — SessionDetector's 'we hold this session' branch never re-ran the server detect that supplies outletId. Now, on holding a session with a missing outletId (and while online), SessionDetector dispatches detectCurrentSession(sessionId) once per session (tracked via a ref) to backfill it; joinSession, hydrateSessionFromOffline, and detectCurrentSession were also updated to persist outletId into the offline snapshot going forward so the gap self-corrects. This is an offline/hydration edge-case fix, not a change to the documented open/close/reconciliation lifecycle or the three-state Pending Close flow — no contradiction to existing claims. It's more naturally a companion note in cross-device-session-sync.md (which already covers SessionDetector's reconnect-revalidation behavior) than a rewrite of this entry; recommend adding it there and cross-linking, with a live-UI pass to confirm the empty-grid symptom is actually resolved before treating this as authoritative. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-04 (frontend follow-up) — codebase re-verify, foodops.counter.app be4fe21..d439ab4. SessionSalesSummaryCard.tsx (src/components/session/**, in this entry's tracked surface) drops its invoiceCount prop entirely and switches the session Overview 'Total Sales' sub-label from an invoice count to detail.liveStats.orderCount, with the copy itself changing from 'X invoice(s)' / 'No invoices' to 'X order(s)' / 'No orders'. This is the UI-visible confirmation of the backend change already logged on 2026-07-04 (GetSessionDetails totals computed from linked SalesOrders rather than realized invoices) — the card's own label now says 'orders' instead of 'invoices'. Still recommend a live-UI pass on the Session Details Overview tab to see the rendered label in context before treating the sub-label wording as a citable, screenshot-backed fact. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-04 — codebase re-verify (hubits-api-sales e4f8b14..aa4a8d5, GetSessionDetails). Session Details Overview totals (sub/tax/total-sales/service-charge/nontaxable/outstanding) are now computed from the session's linked SalesOrders — including still-open/unpaid tabs — rather than realized invoices, matching the single-day dashboard's OrderSummaryAsync basis; sessions whose orders were never session-linked (legacy data) still fall back to the invoice-based calculation so totals don't collapse to zero. Practical effect: the Overview tab's Expected/Total figures can now reflect revenue from orders that haven't been invoiced yet. Separately, GetSessionHistory/GetSessionHistoryStats date-range filtering and the Opened/Closed display strings now resolve the org's local timezone (ReportTimeZone.ToOrgLocal/ToUtcFromOrgLocal) instead of comparing/rendering raw UTC .Date — fixes cross-midnight/timezone mismatches in the Sales Sessions history list and its date-range filter. Both changes are backend-only in this diff; needs a live-UI pass to confirm the Overview tab's visible numbers before promoting from NEW_INFO to a firm edit. (auto-applied by the truth pipeline; adversarially gated)

Warning: Code-verified 2026-05-03 · codebase only (auth.json stale) Verified_by: codebase · Last verified: 2026-05-03 Cross-device sync section derived from commit messages 73a5a08, 2fb2737, fd248d7 (haafai/foodops.counter.app). UI-level details (toast banners, lockout screen wording) marked with <!-- UI-TODO --> for re-verification after auth.json refresh. Remaining prose unchanged from prior playwright pass; still indicative rather than authoritative for exact labels.

  • 2026-06-03 — codebase: CHANGED. Re-verified against hubits-api-sales@5274891 and foodops.counter.app@e002374. Close is now a two-step Pending-Close → approve flow (status machine Open/PendingClose/Closed); updated the Status column, the Closing-a-Session walkthrough, and two Common-questions. Added an isPendingAction per-invoice flag note (commit 47912c7, 2026-05-31 — invoice missing a required payment reference; NOT a session status). Flagged the React modal's extra Closing Count + Reconciliation tabs as <!-- UI-TODO -->.

  • 2026-06-11 — LIVE UI: walked the full close flow on counter.foodops.io (Register 1, Session/0003/2026). Confirmed: Close Register dialog with denomination count + Comments (screenshot 183); Balance Difference Warning dialog with exact copy "The closing cash count does not match the expected balance.", per-method delta line, Close Anyway / Cancel buttons (screenshot 184); Close Anyway took the session straight to Closed (approve fast-path). For deferred approval: counter /register-sessions page lists sessions with Open / Pending Close / Closed pills (screenshot 206) and a Pending Close session's details page carries Approve Close + Adjustment buttons and Closing Count + Reconciliation tabs (screenshot 207). Legacy surfaces match (screenshots 185, 186). SignalR lockout toast still unverified (needs a second device).

  • 2026-06-29 — LAUNCH RE-VERIFY (new-stack only; legacy haafai.app.foodops fully retired). Re-pinned to prod HEADs foodops.counter.app@32669a9, hubits-api-sales@19190b9. NEW_INFO: added the transfer-reference ("blaze") Issues tab + pre-close "Resolve transfer reference issues" gate, both gated on the org EnforceUniqueBlazeNumber setting (counter SessionDetailsModal.tsx:377-385,395,568-570; SessionIssuesTab.tsx/BlazeIssuesModal.tsx; backend CloseRegister blaze gate @1657-1672; new DTO fields HasDuplicateBlaze/DuplicateBlazePayments/MissingBlazePayments). Repinned IsPendingAction to SessionsController.cs:1202. SessionsController.cs grew ~210 lines since ee6b65f (blaze feature + edit-times); method/behaviour citations remain symbol-anchored and behaviourally valid, individual line numbers in older sentences may read low by ~50-200 lines.

Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.

Go beyond POS.
Choose the total solution.

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