Foodops
All docs

Counter · Updated 2026-08-22 · v93fc219

Unclosed Sessions

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

Plain-English summary

An unclosed session is a register session that has been left open across calendar days — typically because a cashier ended their shift without running Close Register. When Foodops detects this, it shows a yellow warning toast on the Counter (POS) screen reading Register '<name>' has an unclosed session from <dd/MM/yyyy>. The session is still operationally fine — orders can still be taken on it — but it should be reconciled and closed promptly so the day's cash position is anchored to a clean shift boundary.

When you'd use this

This concept applies whenever a manager or cashier opens the Counter app and sees a yellow toast referencing an old date, or whenever someone notices on the Sales Sessions list (/salessessions) that yesterday's session never moved to Closed. It also matters when investigating a variance the next morning — the longer a session stays open, the more cash events accumulate against the wrong shift boundary, and the harder it is to attribute drawer movements to a specific cashier or day.

You will also reach this concept from training conversations: new cashiers often leave sessions open by mistake, and supervisors need a clear protocol for how to clean up the next day without erasing yesterday's history.

How Foodops detects it

Detection runs server-side (the check-unclosed endpoint) over every session that is still IsOpen with no ClosedTime. The logic branches on whether the register has a configured CloseTime — it is not three independent triggers applied to every session. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]

If the register HAS a scheduled close time (CloseTime):

  1. Created on a previous day, with its scheduled close already due — the session was created before today and the computed close datetime falls on today or earlier. Message: Register '<name>' has an unclosed session from <dd/MM/yyyy> (the date shown is the session's created date). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]
  2. Past the register's scheduled close time — a still-open session whose configured close instant has passed (even on the same day) is flagged. Message: … has an unclosed session that should have closed at <hh:mm tt>. (The 12-hour rule below does not apply to registers that have a close time.)

If the register has NO configured close time:

  1. Previous calendar day — the session was created before today and is still open. Message: Register '<name>' has an unclosed session from <dd/MM/yyyy>.
  2. Open more than 12 hours — a session open for over 12 hours (measured from OpenedTime) is flagged on elapsed-time alone. Message: … has an unclosed session open for more than 12 hours.

The check is run by the POS (Counter) screen: on page-load and then on a 10-minute poll. When any condition fires, the unclosed-session warning is shown as a persistent yellow toast (Sonner, configured top-center), listing the register name and the relevant date/time. [code: foodops.counter.app/src/pages/POSPage.tsx:checkUnclosedSessions @main] [code: foodops.counter.app/src/components/session/SessionUnclosedBanner.tsx @main] [code: foodops.counter.app/src/components/ui/sonner.tsx:Toaster @main]

The toast is informational only — clicking its Dismiss button does not close the session. Dismissal is persisted (in browser localStorage, keyed by that session's id), so that specific unclosed session will not re-toast on later page-loads or polls on the same device; a genuinely new unclosed session (different id) will still surface. The session itself remains open until someone explicitly runs the Close Register flow. The toast's other action button is labelled View Details, which opens the Session Details modal for the unclosed session. [code: foodops.counter.app/src/pages/POSPage.tsx:persistDismissedUnclosedId,handleBannerClick @main]

There is no auto-close. Foodops will not silently close a session for you, even after many days — there is no background/scheduled job that closes sessions; check-unclosed only reports the condition, and a session leaves IsOpen only via the cashier-driven close-register action. This is by design: an unsupervised auto-close would generate a cash count of "as found" and sweep any variance under the rug, which is the opposite of what reconciliation is for. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @main]

Why this matters operationally

A long-running open session blurs the boundary between two shifts. Concretely:

  • Cash sales taken yesterday and today both contribute to the same Expected figure on close, so the variance you compute reflects a multi-day drawer state — not yesterday's shift in isolation.
  • The Day Summary report selects sessions by their open date (OpenedTime, never ClosedTime) and then books each invoice and sales order by its own date within an org-local-day window (with a business-day overlap, default Business date-type); the session's payment totals, however, are scoped by session membership rather than by each payment's own date. So a long-running session keeps accumulating against the day it was opened on; the day's totals stay provisional and lack a closing balance until the session is closed. [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs:BuildReportAsync @main]
  • If the cash drawer was physically swept overnight (e.g., a manager pulled the cash for safekeeping but did not record a cash withdrawal), the unrecorded movement will appear as a phantom shortfall when someone eventually counts.
  • Audit reports for the day are incomplete — there is no closing balance and no Z-report for the shift.

How to resolve it

Two paths, depending on whether the session has been worked since.

Path A — close it cold (no orders since the boundary)

If the session has been idle since yesterday and no further orders have been booked:

  1. From the Counter (POS) screen, click View Details on the yellow unclosed-session toast (or scroll to the Open Sessions list).
  2. This opens the Session Details modal for the unclosed session.
  3. Tap Close Register at the top-right of the modal → Close Register form.
  4. Count the cash physically present in the drawer right now and enter the denomination counts.
  5. Add a Comments note explaining the gap — for example: "Left open overnight 2026-05-03 → 2026-05-04. No transactions overnight; counted as found."
  6. Submit. The session moves to Pending Close and a supervisor approves it to finalise (see Till Reconciliation → Pending Close); the variance figure then becomes the official record for the shift.

Path B — close it after further activity

If orders were taken on the session today before anyone noticed it should have been closed yesterday, the session legitimately spans two days. Close it now using the same flow, and use the Comments field to record that the shift overran the day boundary. The Day Summary attributes the session to the day it was opened on, and each invoice/sales order is booked by its own date (within the org-local business day, with the business-day overlap), so cross-midnight takings land on the appropriate service day rather than all collapsing onto one date. (Payment-method totals, by contrast, follow the session, not each payment's own date.) [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs:BuildReportAsync @main]

Key concepts

  • Unclosed session — a still-IsOpen session (no ClosedTime) that trips detection. The check branches on the register's CloseTime: registers with a close time are flagged when created on a previous day past close, or simply once that close time has passed; registers without a close time are flagged when created on a previous calendar day, or open more than 12 hours. The classification is therefore not purely date-based — a register with a configured close time or a long-running same-day session can be flagged too. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]
  • Yellow toast — the persistent top-center toast (Sonner) shown on the Counter (POS) screen when an unclosed session is detected. Has a Dismiss button (suppresses that session's toast permanently on this device, via localStorage) and a View Details button (opens Session Details); neither closes the session. [code: foodops.counter.app/src/components/session/SessionUnclosedBanner.tsx @main]
  • Session boundary — the date/time pair that anchors a session's contribution to daily reports. An unclosed session has no boundary until it is closed.
  • Cold close — closing a session with no recent activity, simply to reset the boundary. Variance reflects whatever the drawer holds at the moment of count.
  • Auto-close — explicitly NOT a Foodops feature. Sessions never close themselves; a cashier or manager must always run Close Register.

Common questions

Q: Does the system close my session for me overnight? A: No. Foodops never auto-closes sessions. The yellow warning is a prompt for a human to act, not a notice that the system handled it. Until someone runs Close Register, the session remains open and continues to accept orders.

Q: I dismissed the yellow toast — did that close the session? A: No. Dismiss only hides the warning; the session is still open. Note dismissal sticks: the toast for that specific unclosed session will not reappear on later page-loads or polls on the same browser/device (it is remembered in localStorage). To actually close the session, click View Details to open Session Details and run Close Register. [code: foodops.counter.app/src/pages/POSPage.tsx:persistDismissedUnclosedId @main]

Q: Can I take orders on a session that's been left open from yesterday? A: Yes — the session is fully functional. Any orders you take become part of that session's totals. Best practice, though, is to close it first, run a fresh open with today's float, and start the new shift on a clean session boundary.

Q: What date does a cross-midnight session count against in the Day Summary report? A: The Day Summary selects sessions by their open date (OpenedTimeClosedTime is only ever displayed, never used to bucket), so a session opened on 2026-05-03 and closed on 2026-05-04 is listed under 2026-05-03. Within that, each invoice and sales order is booked by its own date inside the org-local business day (there is a small business-day overlap so just-after-midnight sales still fall on the prior service day); payment-method totals follow the session rather than each payment's own date. So the session does not "move" to its close date — it stays anchored to when it opened. [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs:BuildReportAsync @main]

Q: What if I do not know how much cash should be in the drawer? A: Count what is physically there, enter the denominations honestly, and use the Comments field to explain that the session was left unsupervised. Managers triage variances on the strength of the Comments narrative — a clear "left open overnight, drawer untouched, counted as found" is far better than an unexplained variance.

Q: Can a manager force-close someone else's overnight session? A: Yes — and on the new stack there is no role gate on this at all. Any authenticated user in the organisation can close a left-open session: the close endpoint enforces only that the session belongs to your organisation and is still open (no SalesRegisters.Edit or other role check). The session records who closed it (ClosedByUserId) and when (ClosedTime). The back-office cleanup surface is the FoodOps admin app's Register Sessions list (/sales/register-sessions) → session detail, whose Close / Approve / Adjustment actions also carry no visible role gate. This is the standard cleanup path when the cashier who opened the session is not on shift today. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @main (class-level [ApiKeyOrSessionAuth], org-scope only, no [Authorize(Roles=...)]); foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx:382-410 (Close/Approve/Adjustment buttons)]

Q: How long can a session stay open before it becomes a problem? A: It depends on the register. If the register has a configured close time, the warning trips once that close time passes (or on a previous-day rollover past close). If the register has no close time, it trips at a previous-calendar-day rollover, or at 12 hours of elapsed open time — whichever comes first. Reconciliation accuracy degrades the longer a session stays open because drawer events compound. Most outlets close every shift; beyond the warning threshold, escalate to management. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]

Q: Does the warning appear on the Dashboard, the Counter, or both? A: Only on the Counter (POS) screen. The unclosed-session check (checkUnclosedSessions) and its yellow toast are wired into the POS page alone — the Dashboard page does not run this check or render a stale-session widget. To act on the warning, click View Details on the toast to open Session Details and run Close Register. [code: foodops.counter.app/src/pages/POSPage.tsx:checkUnclosedSessions @main]

Edge cases and known issues

Toast still showing after the session has been closed

  • Symptom: yellow toast persists on a Counter page-load even though the session was closed minutes ago.
  • Cause: stale client-side state; the SignalR SessionClosed notification may not have propagated to all open browser tabs. See cross-device-session-sync.md. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @main]
  • Workaround: refresh the Counter (POS) page once — the check re-runs against the latest server state. (It also re-runs on its own 10-minute poll, so the toast self-clears within ten minutes once the session is actually closed.) [code: foodops.counter.app/src/pages/POSPage.tsx:checkUnclosedSessions @main]

Session crosses the date boundary mid-shift (e.g., late-night shift past midnight)

  • Symptom: a shift that started before midnight and runs past it triggers the warning at the date rollover; a long same-day shift can also trip the 12-hour / scheduled-close-time triggers.
  • Cause: detection branches on the register's CloseTime — registers with a close time are flagged once that close time passes (or a previous-day-past-close rollover); registers with no close time are flagged at a previous-calendar-day rollover or open >12h. A 23:30 → 02:00 shift is flagged at the date rollover; a same-day no-close-time shift open >12h is flagged on elapsed time. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]
  • Workaround: ignore the toast for genuinely long single shifts. Close at end of shift normally; the Comments field can note the extended runtime if needed.

Multiple unclosed sessions on different registers in the same outlet

  • Symptom: the warning surfaces one unclosed session at a time — not one stacked toast per register.
  • Cause: the check-unclosed endpoint returns on the first unclosed session it finds (it does not aggregate), so the POS toast reports a single register/session; once you handle (or dismiss) that one, the next page-load/poll surfaces the next still-open session. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CheckUnclosedSessions @main]
  • Workaround: close each in turn from the Open Sessions list. There is no bulk-close action — the Sales API exposes only single-session close-register/approve-close/cancel-close endpoints (each takes one SessionId), and the back-office admin app likewise closes one session at a time from its detail page. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @main; foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx (per-session Close action)]
  • Note: which register appears first is not guaranteed to be a particular outlet/register order — the endpoint iterates the org's open sessions and returns the first that trips a condition.

Related

  • Sales Sessions — the lifecycle the unclosed warning is signalling about

  • Sales Registers — the register on which the session is anchored

  • Session Lifecycle — the formal state machine that explains why "open" persists until closed

  • Cash Handling — the cash count that closes the session and produces the variance

  • Till Reconciliation — the count-vs-expected flow run when closing

  • Z-Report — generated at session close

  • Cross-device session sync — why the warning may temporarily lag behind a recent close

  • 2026-08-21 — upstream diff (hubits-api-sales 98c202d..c7e4894, SessionsController.cs): GetSessionCloseSummary and GetSessionCloseSummaryPdf now stamp dto.DiscardLog (via a new SessionDiscardLogService.BuildAsync, per the commit's 'session discard log — server source, export, register-aware reprint' feature) and dto.SalesRegisterId onto the close-summary DTO before returning/rendering it. This extends the close-summary surface first noted 2026-08-01/2026-07-31 (still not yet documented in this entry's body pending a UI screenshot) but does not touch CheckUnclosedSessions, the toast, Dismiss/View Details, CloseRegister's no-auto-close/no-role-gate behavior, or DaySummaryReportService bucketing — no contradiction to any existing claim. Separately, the CloseRegister void-bin-review gate's comment was updated from '(approved/rejected)' to '(approved/rejected/acknowledged)', but only the comment line is shown in this diff — the underlying unreviewedVoids query/enforcement logic is not shown to have changed, so per the trust-method-bodies-over-comments rule this is not treated as a confirmed behavior change and is not folded into any claim. Worth a follow-up once the discard-log UI surfaces and once a diff shows the actual void-bin acknowledgment logic, not just its comment. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-12 — upstream diff (hubits-api-sales bb69312..ecfea28, SessionsController.cs + DaySummaryReportService.cs): the hunks intersecting this entry's cited surface are a re-show of what's already logged on 2026-08-08 — ADR-0011's pure-passthrough fix to RegisterSessionInfo.Balances.ExpectedAmount in DaySummaryReportService.BuildReportAsync (no longer subtracts cashWithdrawalTotal), and GetMySessionsAndRegisters' DTO additions (PosDefaultViewMode, SoftEnforcePaymentReference, OpenedTime switched to UTC ISO-8601 'O' format for correct client-side local-time rendering). One addition not previously called out: GetMySessionsAndRegisters now also reads the org's 'Enforce Unique Blaze Number' setting and stamps it on session DTOs, feeding a Transfer-Receipt payment-reference-reuse warning at the counter payment surface — this is unrelated to unclosed-session detection (CheckUnclosedSessions), the toast, Dismiss/View Details, or Day Summary session/invoice bucketing, so no claim in this entry is affected. The remaining new commits in this range (haafai.domain version pin, an ExecuteUpdate cache-invalidation seam plus order micro-cache, a SignalR order-broadcast routing envelope, and a new org-wide customer-statements endpoint) do not touch CheckUnclosedSessions, CloseRegister, or DaySummaryReportService.BuildReportAsync. No contradiction to any existing claim; nothing new to fold into this entry's body beyond this traceability note. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-08 — upstream diff (hubits-api-sales bb69312..cdb3b32, DaySummaryReportService.cs + SessionsController.cs): ADR-0011 ('single-source Expected Cash') fixes a bug in BuildReportAsync's per-session RegisterSessionInfo.Balances shaping — it previously subtracted cashWithdrawalTotal (the SUM of cash withdrawals across every session in the whole report period) from the ExpectedAmount of each session's base-currency cash balance, which double-counted (the stored ExpectedAmount already nets that session's own withdrawals) and leaked other registers' drops into each row on multi-session days. The code comment states this is exactly what made the Session Summary's Expected Cash (the close-summary endpoint added 2026-08-01, still pending a UI follow-up in this entry) disagree with the session details screen; ExpectedAmount is now a pure passthrough of the stored per-session value. This does NOT touch the session-selection-by-OpenedTime or invoice/order own-date bucketing logic this entry documents — no contradiction to any existing claim, but worth folding into the Day Summary Q&A / 'How to resolve it' once the Session Summary UI is screenshotted, since it explains a specific Expected-Cash discrepancy a manager might otherwise flag as a bug. SessionsController.cs also gained DaySummaryReportService/IHtmlToPdfService constructor deps (continued close-summary plumbing, already noted 2026-08-01) and, in the unrelated GetMySessionsAndRegisters endpoint, a PosDefaultViewMode/SoftEnforcePaymentReference stamp on session DTOs plus an OpenedTime UTC-ISO8601 serialization fix (a timezone-display bug fix, not CheckUnclosedSessions) — neither touches any claim in this entry. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-01 — upstream diff (hubits-api-sales bb69312..0d94f42, SessionsController.cs): confirms the server-side implementation behind the 'Session Summary' feature first spotted client-side on 2026-07-31 (foodops.counter.app sessionApiService.ts). Two new endpoints land on SessionsController: GET /sessions/{sessionId}/close-summary and GET /sessions/{sessionId}/close-summary/pdf?download=. Both reuse DaySummaryReportService.GenerateReport(..., sessionId) scoped to the single session (window args are ignored when sessionId is supplied — membership defines inclusion), so the summary figures reconcile with the Day Summary report this entry already documents. The PDF path additionally renders via IHtmlToPdfService and is explicitly commented 'Online-only'; the code comment on GetSessionCloseSummary notes gating (e.g. closed-only) is a UI concern, not enforced server-side. Neither endpoint is a close action — no contradiction to this entry's 'no bulk-close action' or no-auto-close claims. Still worth a follow-up note in 'How to resolve it' (Path A/B Submit step) once a UI screenshot confirms where/when the summary surfaces (auto-shown after Submit vs. accessible later from Session Details). (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-31 — upstream diff (foodops.counter.app 5468530..5ac2982, sessionApiService.ts): new session close-summary plumbing adds SessionApiService.getCloseSummary()/getCachedCloseSummary()/getCloseSummaryPdf(), backing a 'Session Summary' thermal print + server-rendered PDF, fetched offline-first from GET /sessions/{sessionId}/close-summary (and .../close-summary/pdf) with results cached to bootstrapCache. This is adjacent to, not a change to, this entry's close flow (Path A/B Submit step in 'How to resolve it') — once a session, including a cold/overnight-unclosed one, is closed, a printable/PDF summary may now be available; worth a follow-up note once a UI screenshot confirms where/when it surfaces (auto after Submit vs. from Session Details). The remainder of this diff (POSPage.tsx: sessionId-scoped SignalR order events and a scope:'status' local-only repaint instead of a forced network reload) is a perf change to kitchen/order-status handling and does not touch CheckUnclosedSessions, checkUnclosedSessions, the toast, dismiss/View Details, or Day Summary bucketing — no existing claim in this entry is affected. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-29 — upstream diff (foodops-admin-app 70e66bc..9856e01, ProtectedRoute.tsx): a new client-side, map-driven RBAC gate was added to ProtectedRoute — requiredRolesForPath(location.pathname) (backed by a new @/config/routePermissions module not included in this diff) redirects unauthorized users to /dashboard once their org-scoped roles array is loaded and doesn't contain a role required for the matched path. This is a general frontend route-gating mechanism directly relevant to this entry's 'no role gate' framing for the back-office Register Sessions surface, but this diff only demonstrates/tests it for /catalog paths (ProtectedRoute.rbac.test.tsx mocks requiredRolesForPath to return roles only for paths starting with /catalog) — whether /sales/register-sessions (admin) or /register-sessions (counter) is present in the real routePermissions.ts map is NOT shown here. Neither RegisterSessionsPage.tsx nor RegisterSessionDetailsPage.tsx nor SessionsController.cs is touched by this diff, so the existing 'no visible role gate on Close/Approve/Adjustment' and 'API enforces org-scope only, no [Authorize(Roles=...)]' claims are not contradicted — but this new mechanism means the back-office route itself could now be gated even if the in-page buttons and API remain ungated. Flagged for a follow-up check of apps/admin/src/config/routePermissions.ts to confirm whether register-sessions routes were added to the map. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-07 — upstream diff (foodops.counter.app 047ad31..6885e57, sessionApiService.ts closeRegister): the client now catches structured 400 responses from close-register and returns a typed {success:false, message, hasDifferences:false} instead of throwing a generic error, so a Submit attempt blocked by a server-side close gate (the code comment lists dead-lettered financial postings, unclosed orders within the session, blaze issues, and held sales) now surfaces the specific reason in the UI rather than failing generically/silently. This refines the Submit step in this entry's 'How to resolve it' flow (Path A/B) for the case where closing an overnight-unclosed session is itself blocked by an unrelated gate — worth a short note there once a screenshot of the resulting message is available. Nothing else in this diff (a POSPage.tsx effect-dependency/loading-spinner fix for payment-panel flashing) touches the CheckUnclosedSessions detection logic, the toast, dismiss/View Details, or Day Summary bucketing — those claims are unaffected. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-07-06 — upstream diff (hubits-api-sales 6f242ad..e31810a, SessionsController.cs): OpenRegister/CloseRegister/CancelClose/AdjustRegister now each write an operation-log entry via session.Touch()/TouchCreate() ('Register opened.', 'Session closed. ', 'Session close cancelled. ', 'Session adjusted. '), and a new GET /api/sessions/{sessionId}/history endpoint returns the session's full audit timeline (newest first: Opened/Updated/Status changed/Archived/Deleted) with per-field before/after diffs parsed from OperationLog.ExtraDetails. This directly substantiates and extends this entry's 'without losing the audit trail' framing — the audit trail behind Close/Cancel/Adjust is now an explicit, queryable timeline rather than just implicit ClosedByUserId/ClosedTime fields. CheckUnclosedSessions (detection logic) and the close endpoint's org-scope-only/no-role-gate authorization are unchanged by this diff — no contradiction to existing claims. No frontend consumer of the new /history endpoint is visible in this diff (foodops-admin-app not touched), so it isn't yet surfaced in any UI this entry documents (e.g. Session Details modal, Register Sessions detail page) — worth a follow-up KB note once a UI timeline ships. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-05-04 — codebase: haafai/foodops.counter.app:b9677b0, haafai/haafai.app.foodops:46a80a3; UI not yet verified

  • 2026-06-04 — codebase: hubits-api-sales@5274891 (SessionsController.cs check-unclosed). REAL CHANGES applied: detection is NOT purely date-based — check-unclosed has three triggers: previous-calendar-day, past the register's configured CloseTime, and (no close time set) open >12 hours (SessionsController.cs:2434,2445,2469). Corrected the detection section, Key concept, the cross-midnight edge case, and the "how long" Q&A. Also noted close now lands in PendingClose (see till-reconciliation). UI not yet screenshotted.

  • 2026-06-29 — LAUNCH RE-VERIFY (new-stack only; legacy haafai.app.foodops fully retired). Re-pinned to foodops.counter.app@32669a9, hubits-api-sales@19190b9, foodops-admin-app@f6ffd0b; dropped retired haafai.app.foodops from source_repos. Re-confirmed CheckUnclosedSessions UNCHANGED: same CloseTime-branched logic (previous-day-past-close / "should have closed at hh:mm tt" for registers with a CloseTime; previous-calendar-day / ">12 hours" for those without), returns the FIRST unclosed session only, dd/MM/yyyy date format (SessionsController.cs:2609-2690). DaySummary session-by-OpenedTime / no-status-filter re-confirmed (DaySummaryReportService.cs:126-127). CORRECTED the "manager force-close" Q: removed the retired-monolith SalesRegisters.Edit back-office gate claim — the new stack (foodops-admin-app + API) closes with no role gate; route corrected to /sales/register-sessions. Repointed the bulk-close/back-office citations off haafai.app.foodops onto foodops-admin-app. No-auto-close still holds.

  • 2026-06-14 — TRUTH AUDIT (codebase, prod branches): foodops.counter.app@df086e4 (main), hubits-api-sales@f79eb7b (main), haafai.app.foodops@40624ca (main). CORRECTIONS: (1) toast position is top-center, not bottom-right (sonner.tsx Toaster position="top-center"); (2) the warning lives ONLY on the POS/Counter page (POSPage.tsx) — the "Dashboard surfaces stale-session indicators" claim was fabricated and was corrected (DashboardPage does not run the check); (3) dismiss is a labelled Dismiss button and is persistent per-session-id via localStorage, NOT "until next page-load" — also action label is View Details not "Details"/"×"; (4) Day Summary buckets sessions by OpenedTime, invoices by their own date with a business-day overlap — the repeated "buckets by session-close-date / books closing balance under close date" claim was wrong (DaySummaryReportService.BuildReportAsync); (5) detection is a CloseTime-branched check (not three uniform triggers) and 12h applies only when no CloseTime — restructured; (6) "Manager-role can close any session" softened to SalesRegisters.Edit (MVC) / org-membership-only on the Counter API; (7) "one stacked toast per register" was wrongcheck-unclosed returns the FIRST unclosed session only; (8) message date format is dd/MM/yyyy (was yyyy-mm-dd in summary). No-auto-close CONFIRMED (no hosted/background close job; only cashier-driven CloseRegister leaves IsOpen). Citations added throughout. UI not yet screenshotted.

Go beyond POS.
Choose the total solution.

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