Plain-English summary
A Z-report is the artefact Foodops produces when a register session is closed — a one-page summary of the shift's takings, payment-method totals, the opening and closing cash, and the variance. It is the cashier's signed-off receipt that the day reconciled (or did not), and the manager's primary audit document for end-of-day reviews. The "Z" comes from traditional cash-register terminology where running a Z-reading also resets the register's running totals — in Foodops, the totals are tied to the session, so closing the session is what triggers the Z.
When you'd use this
- End of shift — every cashier produces a Z-report by running Close Register. The printout (or PDF, if printerless) is the close-of-shift handover document.
- Variance investigation — when reconciling discrepancies after the fact, the Z-report shows the snapshot the cashier signed off on at close.
- Tax/audit compliance — many jurisdictions require an end-of-shift register total (Maldives GST among them); the Z-report is what fulfils that requirement.
- Manager handover — the manager closing the day typically reviews each register's Z-report before locking up.
- Training — explaining to new cashiers what the printout means line-by-line.
What appears on a Z-report
A Foodops Z-report covers the closed session in four blocks:
Block 1 — Session header
- Outlet name and address
- Register name (e.g., "Register 1")
- Cashier — the user who held the session at close (or the manager, if a manager closed someone else's)
- Session number (e.g.,
Session/0001/2026) - Opened at — date and time of the original Open Register
- Closed at — date and time of the close
- Duration — derived
Block 2 — Sales summary
- Total order count for the session
- Subtotal across all orders
- Discounts applied (item-level + order-level)
- Tax amount (GST in Maldives outlets)
- Service charge (where configured)
- Delivery charge (where applicable; appears in the Day Summary aggregate, summed from
BaseFreightAmount) [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs:392] - Net total
Block 3 — Payment-method breakdown
For each payment method accepted at the outlet, one row showing:
- Method (Cash, Card, On Account, Loyalty, etc.)
- Number of payments
- Total amount
The cash row is the one that reconciles against the drawer count.
Block 4 — Cash reconciliation
- Opening Balance — counted in at session open, by denomination
- Cash Sales — sum of successful cash payments for the currency [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1700-1729]
- Cash Withdrawals — manager pulls during the shift; deducted from Expected for the base currency only [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1731-1744]
- Expected Cash — at close, the system computes
Expected = (cash payments for this currency) − (cash withdrawals, base currency only); the drawer-level Difference then nets against Opening as well (see below) [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1740-1776] - Counted Cash — what the cashier physically counted at close, by denomination (summed straight from the submitted form's cash counts) [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1775]
- Difference —
Counted − (Opening + Expected)(negative = short, positive = over; see cash variance) [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1776] - Comments — the cashier's narrative on the variance (or "no variance")
In multi-currency outlets, the cash reconciliation block repeats per currency — each SalesRegisterSessionBalance carries its own per-currency Opening/Expected/Counted/Difference. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1695 (foreach balance), DaySummaryReportService.cs:471-489]
How the Z-report is produced
Generation happens server-side from the close data — the denomination counts, Expected formula, and payment-method totals that the Close Register form writes onto the session at close-submit. Note that as of the 2026-05 close-flow rework every cashier-driven close first lands in Pending Close (regardless of whether there is a variance) and is only fully Closed once a supervisor approves it; if the supervisor instead cancels the close, the closing cash counts are wiped, the balances' counted/difference fields reset, and the session reverts to Open, so a Z-report is only a durable record once the close has been approved. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister (PendingClose set @1821), ApproveCloseRegister (Closed set @1979), CancelCloseRegister (wipe+revert-to-Open @2089-2117)] The Day Summary aggregate report (the cross-register roll-up) is itself generated on demand as a report query, not pinned to the close event. [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs]
Output paths:
- Back-office print (X-report / Z-report) — CONFIRMED in the new stack. The FoodOps back-office (foodops-admin-app) register-session detail page has an explicit print action: a dropdown item labelled "Print X-report" while the session is still open/pending and "Print Z-report" once it is closed (
mode = detail.isClosed ? 'z' : 'x'). It builds a thermal-width HTML document viabuildSessionXZReportHtml({ detail, mode, printedAt })and prints it through the browser (window.open→document.write) — it does not go through the thermal print-job broker. The document header reads "Z-REPORT (FINAL)" (closed) or "X-REPORT (READING)" (open), followed by a Period block (opened/closed times), a Payments block (per method: expected; for the Z it adds counted + variance), and a Cash Drawer block (Opening float, Cash sales, withdrawals, Expected close, and — Z only — Counted close and Variance). [code: foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx:309-323,422-424 (handlePrintXZReport, "Print X-report"/"Print Z-report"); apps/admin/src/utils/sessionXZReport.ts:72,114-173 (Z-REPORT (FINAL)/X-REPORT (READING), Payments + Cash Drawer sections)] - On-screen (primary) — the close data is viewable as part of the session detail record. From the Sales Sessions list, opening a closed session's details surfaces the same data on the Overview, Closing Count, and Reconciliation tabs (the closing tabs appear once
showClosingTabsis set). This on-screen view is the confirmed delivery path for Z-report data. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:369-460 (Overview payment-method table Opening/Expected/Counted/Difference); SessionsController.cs:1410 (ShowClosingTabs)]
- Printer — outlets that print thermal documents do so via the print-job pipeline, but the print-job type enum has only three members — Bill (receipt), Kot, and Packaging — there is no dedicated Z-report print-job type in the printing service. [code: foodops.app.printing/Haafai.Printing/Services/PrintReceiptService.cs:669-672
enum PrintJobType { Bill, Kot, Packaging }]
- PDF / Email — the counter app's Session Details page now has a Save PDF action (
handleSavePdf→sessionApiService.getCloseSummaryPdf(sessionId)) that downloads a genuine server-generated PDF of the session close summary directly, in addition to the back-office's browser-print-to-PDF path for the X/Z report HTML. There is still no automated email distribution of Z-reports shown in the supplied evidence — the new PDF action is a manual, on-demand download triggered from the counter app, not a push/email. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:handleSavePdf,sessionApiService.getCloseSummaryPdf]
Z-report vs X-report
Traditional POS systems distinguish:
- X-report ("X-reading") — a peek at session totals mid-shift without closing. Non-destructive.
- Z-report ("Z-reading") — the final totals taken at session close. Destructive in legacy registers (resets running totals).
Foodops does now expose a separate X-report as a printable document: the same back-office print action produces an "X-REPORT (READING)" while a session is still open or pending (vs "Z-REPORT (FINAL)" once closed). Being an open-session reading, the X-report shows per-method expected totals and the cash-drawer expected, without the counted/variance figures (those only exist after a close). [code: foodops-admin-app/apps/admin/src/utils/sessionXZReport.ts:72,120-141 (X vs Z header; counted/variance rows emitted only when isZ); foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx:310 (mode = detail.isClosed ? 'z' : 'x')] The on-screen equivalent is the counter Session Details Overview tab, which shows the running totals live, with Counted rendered as — while the session is still open (fills in only at close). Expected and Difference are now gated by a permission check — canViewExpected = canSeeExpectedForSession(userPermissions, session?.isClosed) — added 2026-07 as part of a 'blind cash counting' change; users without that permission may not see the Expected/Difference columns at all while the session is open (not merely a — placeholder), rather than them being universally visible. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:393-448 (Overview table; session.isOpen ? '—' : formatMoney(...) for Counted; canViewExpected && gate for Expected/Difference columns)]
The Z-report itself is non-destructive in Foodops — closing the session does not delete data. The "reset" effect is just that the next session opens fresh with its own Opening Balance.
What the Z-report is NOT
- Not a tax invoice. Customer-facing receipts are produced per order at Pay & Close.
- Not a real-time view. By definition the Z-report exists only after close — it is a snapshot of the closed state.
- Not editable once fully
Closed. After supervisor approval the Z-report data is immutable; out-of-band corrections live in management reports, not on a re-issued Z. (While a close is still inPending Close— before approval — a supervisor can still adjust the counts viaadjust-registeror cancel the close entirely; immutability applies once the session reaches theClosedterminal state.) [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:AdjustRegister @2153, CancelCloseRegister @2061] - Not aggregated across registers. One Z-report per session per register. The Day Summary report is the cross-register aggregate.
Key concepts
- Z-report — the close-of-session artefact summarising takings, payments, cash reconciliation, and variance.
- X-report — a mid-shift reading of an open/pending session. Available both as a printable document (the back-office "Print X-report" action, header "X-REPORT (READING)") and on-screen via the counter Overview tab's running totals.
- Closing reconciliation block — the cash-reconciliation section of the Z-report, derived from the same Expected formula used in till reconciliation.
- Per-currency block — the reconciliation block repeats per currency in multi-currency outlets. Each currency has its own opening, expected, counted, and variance.
- Day Summary — separately, the report that aggregates Z-reports across registers and dates. Not the same as a Z-report.
Common questions
Q: Is the Z-report the same as the "End of Day" report?
A: No — they're different things that both get called "the end-of-day report". The Z-report is the artefact one register produces when its session is closed: the cashier's signed-off close-of-shift document for that till. The End of Day report (listed as End of Day in the back-office Reports rail, headed Day Summary, at /reports/day-summary) is a report you run over a date range across the business — receipts, sales, category mix, payment methods, credit sales and a per-session cash block. Cashier closing a till → Z-report. Manager closing the day → End of Day Report.
Q: When does the Z-report get generated?
A: From the close data written when Close Register submits successfully — there is no separate "generate Z-report" action. Note that every close first enters Pending Close (regardless of variance); the Z only becomes a durable record once a supervisor approves the close (a cancelled close wipes the counts and reverts the session to Open). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1821, ApproveCloseRegister @1979, CancelCloseRegister @2089-2117]
Q: Can I view (or re-print) a Z-report again after closing? A: Yes to both. On-screen: from the Sales Sessions list, drill into the closed session; the close data is re-displayed on the Overview / Closing Count / Reconciliation tabs, and it is immutable, so it always shows the same figures. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:369-460] Re-print: the back-office (admin app) register-session detail page has a "Print Z-report" action available on a closed session — it re-renders the same HTML document on demand, as many times as needed. [code: foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx:309-323,422-424] (Note: this prints via the browser, not the thermal print-job broker — the broker still has no ZReport job type.)
Q: Is the Z-report different from the receipt the customer gets? A: Yes — completely different. Customer receipts are per-order, produced at Pay & Close. The Z-report is per-session, produced at session close, and contains aggregate shift data not relevant to any single customer.
Q: Does Foodops produce an X-report?
A: Yes — the back-office register-session detail page offers "Print X-report" while a session is still open/pending (it switches to "Print Z-report" once closed). [code: foodops-admin-app/apps/admin/src/pages/sales/RegisterSessionDetailsPage.tsx:310,422-424] The on-screen equivalent is the Overview tab in Session Details, which shows running session totals while the session is still open; Counted/Difference render as — while open and fill in only at close. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:393-448]
Q: What if the printer is offline at close — does the session still close?
A: Yes — the close is independent of any print job. CloseRegister only persists the session state and broadcasts a SessionClosed event; it triggers no print, and a SignalR/notification failure is caught and never rolls back the persisted close. The session moves to Pending Close (and on to Closed at approval) regardless of print status. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1817-1882 (persist then best-effort broadcast)] The close data is always re-viewable on-screen from the Sales Sessions list afterwards.
Q: Does the Z-report show order-level detail? A: Generally no — the Z-report aggregates. For order-level detail (per-invoice reference, date, amount, balance, status, and paid methods), drill into the Transactions tab in Session Details (per-payment detail is on the Payments tab). The Z-report is a summary; the tabs hold the detail. [code: foodops.counter.app/src/pages/RegisterSessionDetailsPage.tsx:372-374 (Transactions/Payments tabs), 502-548 (per-invoice detail table)]
Q: How does the Z-report handle a closed session with zero orders (zero-sale close)? A: The report still generates. Sales totals are zero across the board; the cash reconciliation block shows Opening = Closing (assuming the float is intact). The Comments narrative explains the empty session.
Q: Are Z-reports legally required? A: Jurisdiction-dependent. In the Maldives, GST regulations require end-of-day register totals; the Z-report is the standard fulfilment. Verify with local accounting requirements; the system supports the artefact regardless of whether it is mandatory.
Edge cases and known issues
Z-report shows wrong cashier name
- Symptom: the Z-report's Cashier field names someone other than the user who actually held the session at close.
- Cause: the Cashier field is the closing user —
ClosedByUser.FullName, set to whoever submitted the close. If a handover happened mid-shift, an earlier cashier ran transactions but a different user closed. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1819 (ClosedByUserId = current user), GetSessionDetails @1361 (ClosedByUserName = ClosedByUser.FullName)] - Workaround: consult the Handover tab in Session Details, which lists each reconciliation shift with its
reconciledByuser, so you can see who held/reconciled the session across the shift. The Z-report names only the closing cashier, by design. [code: foodops.counter.app/src/components/session/SessionHandoverTab.tsx (reconciliation-shift history); src/services/session/reconciliationApiService.ts:ReconciliationShift.reconciledBy @87]
Z-report missing a payment method totally
- Symptom: a payment method that took transactions during the shift does not appear in Block 3.
- Cause: the report's payment-method matrix is built by iterating over currently-active payment methods (
PaymentMethods.Where(pm => pm.IsActive)) and emitting a row only where that method's total is > 0. If a method was deactivated in Settings after the shift, it is excluded from the iteration, so its row drops out even though its payment rows still exist in the data. [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Reports/DaySummaryReportService.cs:346-351 (IsActive filter), 416-447 (per-method matrix, row only if totalSales > 0)] - Workaround: re-enable the method in outlet settings before re-running the report. If transactions are missing entirely (not just the row label), escalate — the underlying payment data still exists on the session even when the report layout omits the row.
Variance figure on Z-report does not match drawer reality
- Symptom: the Z's Difference figure looks wrong vs. what the cashier physically observed.
- Cause: typically the Counted column was filled wrong on the Close Register form.
CountedAmountis summed directly from the cash counts submitted on the form, andDifference = Counted − (Opening + Expected), so the Z reflects exactly what was submitted, not what was meant to be submitted. [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:CloseRegister @1775-1776] - Workaround: out-of-band manager correction — the original Z is the record of what the cashier signed off on. A separate management note documents the actual reality. (A pending-close session can also be corrected before approval via the
adjust-registerendpoint, which replaces the closing cash counts and recalculates variance; either way the session stays inPendingClose—AdjustRegisteralways leaves the statusPendingCloseand a supervisor must still run approve-close to reachClosed(the recount does NOT auto-close even when variance is resolved). [code: hubits-api-sales/Hubits.Sales.Api/Controllers/SessionsController.cs:AdjustRegister @2153 (status set @2320, unconditional PendingClose)])
Related
End of Day Report (Day Summary) — the business-wide daily report the Z-report is often confused with
Sales Sessions — the session whose close generates the Z-report
Sales Registers — the register identified on the report
Session Lifecycle — Z-report generation is the artefact of the Closed terminal state
Till Reconciliation — the count flow that drives the cash-reconciliation block
Cash Variance — the over/short reading that headlines Block 4
Cash Handling — the cash events feeding the Expected formula
Zero-sale Close — Z-report on a zero-sale session
Session Reconciliation — the broader close-shift accounting; Z-report is its primary output
Reports — the broader reporting suite Z-report sits alongside
2026-08-21 — UPSTREAM DIFF (foodops.counter.app 8c411fc..a3fe6a1; same five commit subjects already assessed piecemeal in the 2026-08-13 and 2026-08-17 notes — hide Take Out/Delivery buttons, real create-permission fix, org-tz receipt/KOT printing, 'anchor the Running badge's start instant in UTC' — plus a fuller diff of RegisterSessionDetailsPage.tsx). Two items are unchanged from those prior assessments and not re-flagged: the
DiscardLogTabimport (still no implementation shown; follow-up from 08-17 stands) and theformatDurationSince→parseServerDateUTC-anchor fix (still confirmed scoped to the live 'Running' badge on an OPEN session, not the closed-session Block 1 'Duration — derived' figure this entry documents — 08-13's conclusion holds). Genuinely new in this wider diff: (1)handlePrintThermal's print-summary request now preferssummary.salesRegisterIdover the current-session snapshot'sregisterIdwhen buildingtoPrintSessionSummaryRequest(...)— this is the frontend landing point for the newdto.SalesRegisterIdfield the hubits-api-sales close-summary/PDF endpoints started populating per today's separate hubits-api-sales note, giving more reliable register attribution on the printed thermal summary/receipt when the viewed session differs from the device's current session. This is a correctness fix to an already-documented print path (2026-07-31's Print Receipt/handlePrintThermalaction), not a new user-facing feature, and touches no Expected/Counted/Difference or payment-matrix logic, so no entry claim is contradicted. (2) A cosmetic overhaul of the page's loading state, 'Session Not Found' state, and header ('hero band') layout — pure CSS/markup restructuring with no data or business-logic change; the diff is truncated mid-header-restructure, and a code comment mentions the new header accommodating 'up to four buttons on a pending-close session' without the diff showing which buttons — flagged for a follow-up pass (not yet documented as an entry edit) to confirm whether that action row introduces any NEW pending-close action beyond the already-documented Print Receipt/Save PDF/adjust/cancel/approve set, or is just a re-layout of the existing ones. No entry claim is contradicted by anything in this diff. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-21 — UPSTREAM DIFF (hubits-api-sales 98c202d..c7e4894; commits 'print net unit rate on order document', 'counter payments: UTC date, transfer-reference enforcement, receipt PDF', 'session discard log — server source, export, register-aware reprint', 'void bin approval mode + acknowledge review path'). Both close-summary endpoints (GetSessionCloseSummary / GetSessionCloseSummaryPdf) — the same code path already documented as the source of the counter app's 'Save PDF'/session-summary artefact that reconciles with the Day Summary report — now populate
dto.DiscardLogvia a newSessionDiscardLogService.BuildAsync(...)and a newdto.SalesRegisterId, in both the JSON response and, for the PDF path, immediately beforeSessionCloseSummaryHtmlBuilder.Build(dto)runs. This is the likely backend landing for the 'Discard Log' counter-app tab flagged as an open question in the 2026-08-17 verification-history note (whether discarded/voided items are already netted server-side into Block 2 sales totals, or are a distinct reconciliation-relevant figure worth its own citation) — attaching the discard log to the SAME dto the PDF is built from suggests a Discard Log section may now render in the printed/PDF session summary, but this diff does not include SessionDiscardLogService.cs or SessionCloseSummaryHtmlBuilder.cs themselves, so what the discard log contains and whether/how it's rendered remains UNCONFIRMED. Separately, CloseRegister's void-bin-review precondition comment changed from 'approved/rejected' to 'approved/rejected/acknowledged' (matching the 'acknowledge review path' commit), but the actual reviewed-status filter query isn't shown in this diff, so it's unconfirmed whether this changes which sessions are eligible to close. No entry claim is contradicted: none of the cited CloseRegister Expected/Counted/Difference lines (@1700-1776), the PendingClose/ApproveCloseRegister/CancelCloseRegister transitions, or the DaySummaryReportService payment-matrix/IsActive logic are touched by this diff. Follow-up needed: read SessionDiscardLogService.cs and SessionCloseSummaryHtmlBuilder.cs to determine whether Discard Log becomes a documented section of the Z-report/session-summary PDF (and whether it duplicates or nets against Block 2 sales totals), and read the VoidBinEntry status/filter definition to confirm the acknowledge-path's actual effect on close eligibility. (auto-applied by the truth pipeline; adversarially gated)2026-08-17 — UPSTREAM DIFF (foodops.counter.app 8c411fc..f30dbeb; commit subjects: hide Take Out/Delivery dine-in buttons, real create-permission fix for session order details, org-timezone fixes for manual receipt/KOT printing, 'anchor the Running badge's start instant in UTC'). Two hunks in this range (the Transactions-tab Customer column and the formatDurationSince→parseServerDate UTC-anchoring fix) were already assessed in the 2026-08-13 history note as additive/out-of-scope respectively — no new action there. The genuinely new element in this wider diff: RegisterSessionDetailsPage.tsx (counter app) gains an unconditional new tab, 'Discard Log' (
value: 'discard-log', show: true, positioned between Pending Actions and the closing-tabs group), rendering a newDiscardLogTabcomponent scoped tosessionId/currencyCode. Unlike Closing Count/Reconciliation/Handover, this tab is NOT gated bysession.showClosingTabs— it's available on open sessions too. The diff supplied does not include DiscardLogTab's own implementation, so it's unconfirmed whether it surfaces discarded/voided order or line-item data that would affect Block 2 sales totals, the cash-reconciliation Expected formula, or the 'Z-report shows order-level detail' Common Question (which currently only cites the Transactions/Payments tabs for per-invoice/per-payment detail). No entry claim is contradicted by what's shown — the tab's mere existence doesn't alter any cited figure or formula — so no edit is applied. Flagged for a follow-up pass: read DiscardLogTab.tsx to determine whether discarded items are (a) already netted out of the sales totals server-side (in which case this tab is purely an audit/visibility surface, parallel to the existing Handover tab) or (b) a reconciliation-relevant figure worth citing alongside Block 2/Block 4. (auto-applied by the truth pipeline; adversarially gated)2026-08-14 — UPSTREAM DIFF (foodops-admin-app 4a70096..8f9feb5; commits 'stale-session surfacing and the design's control row', 'adjust table footer display for multipage documents', 'add SessionOverviewPanels and SessionStaleNotice components with tests'). RegisterSessionDetailsPage.tsx (admin app) is being restructured above this entry's cited surface: the Overview tab's rendering swaps from a shared
DaySummaryReportBodycomponent to new dedicatedSessionOverviewPanelsandSessionDrawerReconciliationcomponents, and the page gains aSessionStaleNotice/SessionActionChecklistpair driven by a newfacts = summarise(detail)(using newsessionAgehelpers — daysSince/isStale/msSince/formatSpan/ranFor) plus a case-insensitive search filter (tabSearch/matches()) across the Orders/Invoices/Payments tab tables. None of the diff hunks supplied touch this entry's cited surface (handlePrintXZReport@309-323/422-424, themode = detail.isClosed ? 'z' : 'x'ternary @310, or sessionXZReport.ts) — no Z-report print, Expected/Counted/Difference, or payment-matrix logic is shown as changed, so no entry claim is contradicted and no edit is applied. Flagged for a follow-up pass:SessionDrawerReconciliationis a newly-named component landing exactly where cash-reconciliation UI would sit in the admin app's own Overview tab (distinct from the counter app's Overview/Closing Count/Reconciliation tabs already cited by this entry) — the diff is truncated before its contents are shown, so it's unconfirmed whether this becomes a second on-screen cash-reconciliation surface worth documenting alongside the counter app's. A 'session is stale' notice is also new and could eventually warrant a mention in the Z-report entry's session-lifecycle-adjacent edge cases, but is not itself Z-report content. (auto-applied by the truth pipeline; adversarially gated)2026-08-13 — UPSTREAM DIFF (foodops.counter.app 8c411fc..d35a1ba; commit subjects mostly unrelated — hide Take Out/Delivery dine-in buttons, real create-permission fix for session order details, org-timezone fixes for manual receipt/KOT printing, and 'fix(session): anchor the Running badge's start instant in UTC'). Two hunks intersect this entry's cited RegisterSessionDetailsPage.tsx surface. (1) The per-invoice detail table on the Transactions tab — already cited by this entry at 502-548 as the source for order-level 'reference, date, amount, balance, status, and paid methods' — gains a new Customer column, rendering
invoice.customerName || 'Walk-in Customer'beside the Reference cell. This is a genuine new user-facing field on the table the entry documents as the order-level-detail surface; the entry's field list doesn't currently mention Customer. Purely additive, not contradictory, so no edit is applied — but the 'Does the Z-report show order-level detail?' Common Question could fold in 'customer' on a future pass. (2)formatDurationSince(drives the page's 'Running' badge for an OPEN session) now parsesopenedTimeviaparseServerDate(explicit UTC) instead ofnew Date(iso), fixing a bug where a naked-but-UTC timestamp was read in the terminal's local zone, inflating the displayed running time by that terminal's UTC offset. This governs the live pre-close 'Running for Xh Ym' indicator, not the closed-session Block 1 'Duration — derived' figure this entry documents (Closed-minus-Opened) — the entry has no citation pointing at this function, so nothing here is contradicted or edited; flagged only so a future pass can distinguish the two duration surfaces explicitly if it ever cites this code. (auto-applied by the truth pipeline; adversarially gated)2026-08-08 — UPSTREAM DIFF (hubits-api-sales bb69312..cdb3b32; commit subjects unrelated — haafai.domain version pins, ExecuteUpdate cache-invalidation seam, SignalR order-broadcast routing, org-wide customer statements endpoint — this hunk is incidental to that batch). DaySummaryReportService.BuildReportAsync's per-session Balances mapping (the code this entry already cites for the per-currency Opening/Expected/Counted/Difference structure) no longer re-subtracts a report-period-wide
cashWithdrawalTotalfrom the base-currency cash ExpectedAmount when building the Day Summary aggregate. Per the new ADR-0011 comment, that recomputation was a double-subtraction bug: the storedSalesRegisterSessionBalance.ExpectedAmountalready nets THAT session's own withdrawals (the CloseRegister formula this entry documents at @1740-1776), so on a multi-session day the aggregate report was subtracting other registers' withdrawal totals into every row, making the Day Summary's Expected Cash disagree with the session details screen.BuildReportAsyncis now a pure reader of the stored ExpectedAmount. This CONFIRMS rather than contradicts the entry's CloseRegister Expected formula — the fix explicitly relies on that per-session computation being the single source of truth — so no edit is applied to the entry's Block-4 claims. Logged because the bug being fixed was real and user-facing (a wrong Expected Cash figure on the Day Summary report for any outlet running multiple sessions in one day); future passes touching the Day Summary/Expected relationship should treat BuildReportAsync as a pure passthrough, not a recomputation. Separately, the same commit range touches SessionsController.cs's OpenRegister to seed an Expected-related field on the cash balance at session OPEN time (new comment: 'Seed Expected to the float... with no sales and no withdrawals yet, "what should be in the drawer" IS the float') and addsDaySummaryReportService/IHtmlToPdfServiceas new SessionsController dependencies — consistent with, and likely the controller-wiring landing for, the close-summary/PDF endpoints already documented in the 2026-08-01 history note. The OpenRegister hunk is truncated in the supplied diff, so its full effect on pre-close Expected visibility is UNCONFIRMED; needs a follow-up pass reading the untruncated OpenRegister method before any entry claim about pre-close/X-report Expected values is tightened or edited. (auto-applied by the truth pipeline; adversarially gated)2026-08-03 — UPSTREAM DIFF (foodops.counter.app a00083c..2e88679; commit subjects are unrelated sync/test fixes — 'Fix alcyon issues', 'Fix failed test', 'fix(sync): stop the poisoned-customer wedge losing orders and payments', 'fix(sync): re-attach a detached customer via the create body, not a temp-keyed update' — this hunk is incidental to that batch). RegisterSessionDetailsPage.tsx's per-invoice detail table (the same Transactions-tab table this entry already cites at 502-548 for order-level detail) now renders the invoice reference as a clickable link to the admin app's invoice detail page (
adminAppUrl('/sales/invoices/${invoice.transactionId}'), opens in a new tab) whenever the invoice carries atransactionId; invoices without one keep the plain-text reference as before, via a newadminAppUrlimport from@/config/brands. This is a pure UI convenience — no totals, aggregation, or Expected/Counted/Difference logic in the table changed, so it does not touch any Z-report figure and does not affect Block 2-4 or the cash-reconciliation claims. No entry claim is contradicted — 'per-invoice detail is on the Transactions tab' still holds; the entry never asserted whether the reference is plain text or a link — so no edit is applied. Logged only to keep the Transactions-tab citation current in case a future pass wants to mention the cross-link out to the admin invoice detail page. (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, unrelated commit subjects — this hunk is incidental to a tenancy/cache/SignalR perf batch). This is the backend landing for the counter-app
handleSavePdf/getCloseSummaryPdfaction already documented in the 2026-07-31 note: two new SessionsController endpoints,GET /{sessionId}/close-summary(returns SessionCloseSummaryDto via SessionCloseSummaryMapper.Map, built fromDaySummaryReportService.GenerateReport(...)scoped to the session) andGET /{sessionId}/close-summary/pdf(renders the same DTO to HTML via SessionCloseSummaryHtmlBuilder, then converts to a real PDF via a newIHtmlToPdfService.ConvertHtmlToPdfAsync, filenamesession-summary-<reference>.pdf,?download=truetoggles attachment vs inline). CONFIRMS two things the entry already states from frontend evidence: (1) the counter app's 'Save PDF' action is genuine server-side PDF generation, not browser print-to-PDF; (2) the close-summary figures are explicitly built to reconcile with the Day Summary report (doc comment: 'Reuses DaySummaryReportService so the figures reconcile with the day-summary report'), same premise as the thermalhandlePrintThermalfetch already noted. RESOLVES part of the 07-31 open question about open-vs-closed availability: the close-summary/PDF endpoints carry NO server-side closed-only check — the controller doc explicitly says 'Read-only; gating (closed-only) is a UI concern' — so if this action is ever surfaced on an open/pending session it would work at the API level; this is not yet reflected as an entry edit because the entry's claims about this action's availability are UI-scoped and no frontend hunk in this diff shows whether the counter app actually offers Save PDF/Print Receipt pre-close. Still unresolved from 07-31: whetherbrokerService.submitSessionSummaryintroduces a new PrintJobType in foodops.app.printing (not touched here). No entry claim is contradicted; no edit applied — evidence purely reinforces/extends the existing PDF/Email documentation. (auto-applied by the truth pipeline; adversarially gated)2026-07-31 — UPSTREAM DIFF (foodops.counter.app 5468530..5ac2982, commit 'feat: session close-summary print plumbing — types, offline cache, broker mapper + submitSessionSummary'). RegisterSessionDetailsPage.tsx (the COUNTER app's own session-details page, distinct from the already-documented foodops-admin-app back-office X/Z print) gained two new session-header actions gated behind
canViewExpected(the same blind-cash-count permission that gates Expected/Difference visibility documented elsewhere in this entry): Print Receipt —handlePrintThermallive-fetchessessionApiService.getCloseSummary(sessionId, true)('the same aggregation as the Day Summary report, so the printed slip can never show different amounts than the Day Summary' per the code comment) and submits it to the local thermal broker via a newbrokerService.submitSessionSummarycall, routed to the device's current register printer; and Save PDF —handleSavePdfcalls a newsessionApiService.getCloseSummaryPdf(sessionId)endpoint and downloads the returned blob assession-summary-<reference>.pdf, i.e. genuine server-side PDF generation (not browser print-to-PDF). Applied a conservative edit only to the entry's 'no automated server-side PDF/email distribution' claim, since getCloseSummaryPdf directly falsifies the PDF-generation half of it (email distribution remains unconfirmed). NOT yet resolved by this diff, needs follow-up: (1) whether this 'session summary' artefact is the same object the entry calls the Z-report, or a related-but-distinct counter-side receipt — the shared canViewExpected gate and Day-Summary-matching totals strongly suggest it's the same underlying close data, just a new delivery surface; (2) whetherbrokerService.submitSessionSummarymaps onto the existing three-member PrintJobType enum {Bill, Kot, Packaging} in foodops.app.printing or introduces a new job type there — that repo isn't touched by this diff, so the entry's 'no dedicated Z-report print-job type' claim is left untouched pending a look at foodops.app.printing's PrintReceiptService.cs; (3) whether this thermal/PDF action is available on OPEN (X-report-equivalent) sessions too, or only once closed — the canViewExpected gate text ('closed → everyone; open/pending → only with the view expected right') implies it's offered in both states, which would make this the first X-report-equivalent thermal/PDF path as well, but the entry's X-report section is not edited here pending confirmation of the open-session behavior fromSessionCloseSummary/toPrintSessionSummaryRequest. (auto-applied by the truth pipeline; adversarially gated)2026-07-30 — UPSTREAM DIFF (foodops.counter.app c4cf5dc..5468530; commits 'hide expected implementation', 'feat: blind cash counting — hide expected/variance until register session close', 'feat(counter): blind-close variance summary modal (neutral-black figures, per-currency cards)'). RegisterSessionDetailsPage.tsx's Overview payment-method table now gates the Expected and Difference columns (header cells, per-row cells, and total-row cells) behind a new
canViewExpected = canSeeExpectedForSession(userPermissions, session?.isClosed)check; Opening and Counted stay unconditional (Counted still shows—while open, unchanged). Net effect: for users without the permission, Expected/Difference are no longer shown as a—placeholder while the session is open — the columns don't render at all, consistent with a deliberate 'blind' cash-count workflow (cashier counts without seeing the expected total) with variance apparently surfaced via a companion 'blind-close variance summary modal' referenced in the third commit subject but not present in this diff hunk. The exact role semantics ofcanSeeExpectedForSession(which roles see Expected/Difference, and whether visibility after close is universal or still gated) are NOT shown in the supplied diff — needs a follow-up pass readinguseUserPermissions.tsand the new variance-summary modal component before tightening the entry's Block-4/X-report claims further, and before revisiting the Output-paths claim that the Overview/Closing/Reconciliation tabs are 'the confirmed delivery path for Z-report data' (that claim may also need a permission caveat once the modal is examined). Applied conservative edits only to the two claims that asserted Expected/Difference are unconditionally visible (as—) while a session is open. (auto-applied by the truth pipeline; adversarially gated)2026-07-07 — UPSTREAM DIFF (hubits-api-sales 6f242ad..e31810a, 'feat(sessions): add operation-log audit coverage + history endpoint'). CloseRegister, CancelCloseRegister, AdjustRegister, and OpenRegister now each write a Touch()/TouchCreate() operation-log entry (Session closed/adjusted/cancelled/opened, with the request Comments appended) alongside their existing status-transition logic — the transitions themselves (PendingClose @1821, wipe+revert-to-Open on cancel, unconditional PendingClose on adjust @2320) are untouched by this diff, so none of this entry's cash-reconciliation or lifecycle claims are contradicted. New: a
GET /{sessionId}/historyendpoint (GetSessionAuditHistory) returns the session's full audit timeline — opened/updated/status-changed entries newest-first, each with actor, timestamp, remarks, and per-field before/after diffs parsed from OperationLog.ExtraDetails — explicitly said to 'mirror the catalog/finance History tabs'. This is backend-only in the supplied diff; no frontend hunk shows RegisterSessionDetailsPage.tsx or foodops-admin-app consuming it yet, so it is NOT added as a documented Z-report delivery/edge-case path here. Worth a follow-up pass once a History tab/UI surfaces this endpoint — it would directly strengthen the 'wrong cashier name' and close/cancel/adjust edge cases (Handover tab today only covers reconciliation shifts, not the full open/close/cancel/adjust audit trail). (auto-applied by the truth pipeline; adversarially gated)2026-07-04 — UPSTREAM DIFF (hubits-api-sales e4f8b14..aa4a8d5, commit 'feat(sessions): session details totals from sales orders, not invoices'). GetSessionDetails' sales-totals calculation — which feeds the Session Details page's Sales Summary block (Block 2: order totals, subtotal, tax, service charge) — changed basis from realized invoices to the session's linked SalesOrders: totalSales/taxTotal/serviceChargeTotal are now summed directly from session.SalesOrders (BaseGrossTotal/BaseTaxTotal/BaseServiceFeeTotal) across ALL non-cancelled orders, including open/unpaid tabs, with the taxable/nontaxable subtotal split recomputed from a SalesOrderLineItems query; sessions whose orders were never linked to the SalesRegisterSession (legacy data) fall back to the old invoice-based calculation. This entry's Block 2 has no prior code citations for these fields (they were never pinned to specific line numbers), so nothing here is contradicted, but the change is a real behavioral shift worth a follow-up verification pass: confirm whether including unpaid/open orders can make a closed session's Sales Summary total diverge from what invoices show, and add citations to Block 2 if confirmed stable. The diff's other hunk (GetSessionHistory/GetSessionHistoryStats switching from raw-UTC .Date filtering to org-local-timezone-resolved date windows for the session list's Opened/Closed columns) is an unrelated bug fix not covered by this entry and needs no edit. Neither hunk touches CloseRegister/ApproveCloseRegister/CancelCloseRegister/AdjustRegister, so all of this entry's cash-reconciliation (Block 4) claims and citations remain accurate and unaffected. (auto-applied by the truth pipeline; adversarially gated)
2026-05-04 — codebase: haafai/foodops.counter.app:b9677b0, haafai/haafai.app.foodops:46a80a3 (Views/SalesSessions/_ClosingCashDetails.cshtml, _Reconciliation.cshtml confirmed; Printing/ tree exists confirming printable artefact path); UI not yet verified
2026-06-04 — codebase: hubits-api-sales@5274891 (DaySummaryReportService.cs, SessionsController.cs). Content re-verified accurate: cash-reconciliation block (Opening/Expected/Counted/Difference per payment method) and payment-method matrix confirmed; cash withdrawals deducted from base-currency expected (DaySummaryReportService.cs:466). Minor additions noted: a Delivery Charge line in the aggregate (DaySummaryReportService.cs:370). REAL CHANGE applied: clarified that a close now lands in
PendingCloseand the Z is a durable record only after supervisor approval (cancel-close wipes counts); the Day Summary aggregate is an on-demand report query. UI not yet screenshotted.2026-06-14 — TRUTH AUDIT (codebase, prod branches: hubits-api-sales@main, foodops.counter.app@main, foodops.app.printing@master). Re-grounded every uncited claim against SessionsController.cs (CloseRegister/ApproveCloseRegister/CancelCloseRegister/AdjustRegister), DaySummaryReportService.cs, RegisterSessionDetailsPage.tsx, SessionHandoverTab.tsx, reconciliationApiService.ts, PrintJobService.cs. CONFIRMED + cited: every-close-→-PendingClose (regardless of variance, @1821), approve-→-Closed (@1979), cancel-→-wipe+revert-to-Open (@2089-2117), per-currency reconciliation, Difference = Counted−(Opening+Expected) (@1775), Counted summed from submitted form (@1774), withdrawals deducted base-currency-only (@1731-1744), Overview-tab X-report equivalent w/ Counted/Difference '—' until close, missing-payment-method cause = IsActive filter in report matrix (@346-447), wrong-cashier cause = ClosedByUser closing-user. CORRECTED: stale line cites 1743/1992→real symbols; "Sales Orders tab" → actual tab is Transactions (no Sales Orders tab exists); Block-4 Expected formula (no separate Refunds/Acked-Delivery term in close path). FLAGGED UNVERIFIED (no prod code path found): automatic Z-report print-on-close, "Print Z-Report" / thermal re-print action (PrintJobType has only Kot/Bill — no ZReport type), and Z-report PDF/email distribution. UI not yet screenshotted.
2026-06-29 — LAUNCH RE-VERIFY (new-stack only; legacy
haafai.app.foodopsfully retired). MAJOR NEW_INFO that overturns prior UNVERIFIED flags: the new back-office foodops-admin-app register-session detail page DOES have an explicit, named X-report / Z-report print action —handlePrintXZReportbuilds an HTML document viabuildSessionXZReportHtml(header "Z-REPORT (FINAL)" when closed / "X-REPORT (READING)" when open), printed via the browser; dropdown label "Print Z-report" (closed) / "Print X-report" (open). Prior audits (06-14/06-15) searched only foodops.counter.app + foodops.app.printing and concluded "no Z-report/X-report print action" — that conclusion was scoped to the wrong repo. Corrected: the "Foodops does not expose a separate X-report" claim (now WRONG), the re-print-after-close Q (re-print IS available), the X/Z Common-Qs, the X-report Key concept, and the Output-paths section (added the back-office print path). STILL TRUE: close does not auto-print; the thermal print-job broker still has only{ Bill, Kot, Packaging }(no ZReport type) — the X/Z report is a browser HTML print, not a thermal job; no automated server-side PDF/email distribution. Repinned to foodops.counter.app@32669a9, hubits-api-sales@19190b9, foodops-admin-app@f6ffd0b, foodops.app.printing@f6eb32a; dropped retired haafai.app.foodops + haafai.app.common from source_repos.2026-06-15 — ADVERSARIAL RE-VERIFY (second pass; same prod branches, read actual code). Upheld most of the 06-14 corrections (every-close→PendingClose @1821 holds: the
hasDifferences ? PendingClose : Closedternaries at 1834/1873 only label the SignalR/notification messages — the persisted status @1821 is unconditional PendingClose; per-currency @1695/471-489, Transactions tab @373-379/502-548, ClosedByUser @1361/1819, reconciledBy reconciliationApiService.ts:87, Delivery Charge BaseFreightAmount @392, IsActive matrix @346-447 all confirmed). REFUTED-AND-FIXED: prior text claimedadjust-register"resolving all variance transitions it to Closed" — code @2320 sets PendingClose UNCONDITIONALLY (the method docstring @2150 is stale/contradicted by its own body; log @2337 says "status stays PendingClose"); corrected to "always stays PendingClose, approve-close still required". OVERCORRECTED-AND-TIGHTENED: print-enum cite was wrong location + count — actualenum PrintJobTypeis at PrintReceiptService.cs:669 (a nested enum, NOT PrintJobService.cs:220/234 which are call-sites) and has THREE members{ Bill, Kot, Packaging }, not two; "no ZReport type" conclusion still holds. TIGHTENED off-by-one cites: Counted @1774→@1775, Difference @1775→@1776.