Plain-English summary
The Reports area lives in the back-office admin app (admin.foodops.io). It's a persistent left-rail of pre-built reports — around 28 for the foodops product — each its own route (/reports/<name>). Each report is a self-contained view with filters (date range, outlet, register, order type, category) and a results table. A handful of reports also render live in the counter app (e.g. the end-of-day Day Summary at /reports/day-summary).
New-stack note (July-2026 launch): the legacy
app.foodops.io/reportshub (one page, 34 cards across 9 categories,/reports/view/{slug}links) is RETIRED. Reports are now individual React routes in the admin app. Most of the catalogue carried over; the route convention changed from/reports/view/{slug}to/reports/<name>(see the route map below). Brand-Contribution and Order-Quantity reports, Invoice/Sales-Order/Quotes Details, and Purchase-Receive/Expense-Record Details were not ported into the new admin report set; four new reports were added — Sales By Daypart, Sales By Service Type, Voids & Comps, Covers.
When you'd use this
End of day: run Day Summary to tally receipts vs cash for register reconciliation. End of month: Monthly Brand Contribution, Revenue Contribution (%), Output Tax Statement for accounting and GST filing. Weekly operations: Sales By Products (what's selling), Inventory Summary (what's in stock), A/R Aging Summary (who owes us). Ad-hoc: any report can be date-filtered to analyse a specific event, shift, or campaign.
How to access
- URL: each report is its own route —
/reports/<name>on admin.foodops.io (e.g./reports/day-summary,/reports/sales-by-products,/reports/payments). See the full route map below. (Legacy/reports/view/{slug}is retired.) [code: foodops-admin-app/apps/admin/src/App.tsx:1266-1549] - Navigation: the admin app keeps a persistent Reports rail (
ReportsSidebar, eagerly imported so it survives across report navigations) — jump between reports without leaving the area. [code: foodops-admin-app/apps/admin/src/pages/reports/ReportsSidebar.tsx] - Counter app: the end-of-day Day Summary report also renders inside the counter app at
/reports/day-summaryfor on-the-floor close-out. [code: foodops.counter.app/src/pages/DaySummaryReportPage.tsx]
KPI tiles at the top of the hub
Unverified on the new stack. The four-KPI strip described below was the legacy
/reportshub header. The new admin reports area is per-route with a left rail; whether a KPI strip persists on the admin/reportslanding (ReportsPage) is not yet confirmed in code. The live KPI snapshot for foodops is now the admin dashboard stat band (see Dashboard).
| Tile | Formula | Example |
|---|---|---|
| Total Sales This Month | Sum of closed order totals for current month; % vs previous month | MVR 64.80 · 0.00% vs last month |
| Total Orders | Count of closed orders this month | 1 |
| Average Order Value | Total Sales ÷ Total Orders | MVR 64.80 |
| Conversion Rate | (Closed orders ÷ initiated orders) × 100 | 0.00% |
The tiles refresh on page load — no manual submit needed.
Category 1 — Sales Reports
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Day Summary — listed in the rail as End of Day | /reports/day-summary |
End-of-day register close-out: receipts, payments, voids, cash drawer tally per session. See End of Day Report (Day Summary). [code: foodops-admin-app/apps/admin/src/config/reportsConfig.ts:154-166 — 'Daily Operations' → item title 'End of Day' @ main] |
| Sales Details | /reports/sales-details |
Real-time transaction-level view with filters for outlet, register, payment method |
| Sales By Customer | /reports/sales-by-customer |
Top customers by spend; spotting regulars vs one-timers |
| Sales By Products | /reports/sales-by-products |
Best-sellers: quantity sold, sub-total, tax, total, average price per product. "Top N%" filters built in |
| Sales By Daypart | /reports/sales-by-daypart |
NEW — sales broken down by part of day (breakfast/lunch/dinner peaks) |
| Sales By Service Type | /reports/sales-by-service-type |
NEW — sales split by dine-in / takeout / delivery |
| Voids & Comps | /reports/voids-and-comps |
NEW — voided and complimentary (FOC) items/orders audit |
| Covers | /reports/covers |
NEW — covers (guests served) per period |
Example — Day Summary sections

Day Summary is organised into four blocks:
- Receipt Summary — No. of customers, payments, receipts, sales per customer, sales per receipt, no. of void receipts
- Sales Summary — Discounts, FOC, Refunds, Tax, Service Charge, Delivery Charge, Total Amount, Total Sales, Outstanding, Received Amount
- Category Summary — Per menu category: sales count and total
- Cash Summary — Register-level cash accounting
Filters: Date Range, Outlet, Register, Report By (Register Session / Order Time).
Example — Sales By Products

Filters combine: Date Range + Order Type (multi) + Category (multi) + Outlet + Register. Results show each product's quantity sold, sub-total, tax total, total, average price.
Category 2 — Brand Reports (NOT in the new admin report set)
These two Brand-Contribution reports were in the legacy monolith catalogue but have not been ported into the new admin reports area (no
brand-contributionroute exists atfoodops-admin-app@4173811). There is no dedicated brand report at launch; multi-brand orgs should filter Sales By Products / Sales Details by category in the meantime.
| Report (legacy) | Legacy slug | Status |
|---|---|---|
| Brand Contribution By Numbers | brand-contribution-totals |
Not ported to new admin |
| Monthly Brand Contribution | brand-contribution-by-product-category |
Not ported to new admin |
Category 3 — Revenue Reports
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Monthly Revenue by Product Category | /reports/monthly-revenue-by-product-category |
Matrix: rows = categories, columns = months |
| Revenue by Product Category | /reports/revenue-by-product-category |
Single-period breakdown by category |
| Revenue breakdown by Month | /reports/revenue-by-month |
Year-over-year monthly trend |
| Revenue Contribution (%) | /reports/revenue-contribution |
% share of each category/product in total revenue |
Category 4 — Order Reports (NOT in the new admin report set)
The two Order-Quantity-by-category reports were not ported into the new admin reports area (no matching route at
foodops-admin-app@4173811). The closest replacements in the new set are Sales By Products and the new Covers / Sales By Service Type reports.
| Report (legacy) | Legacy slug | Status |
|---|---|---|
| Order Quantity breakdown by Product Category | order-quantity-breakdown-by-product-category |
Not ported to new admin |
| Order Quantity totals by Product Category | order-quantity-total-by-product-category |
Not ported to new admin |
Category 5 — Accounts Receivable
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| A/R Aging Summary | /reports/aging-summary |
Who owes you, bucketed by age (0-30/31-60/61-90/90+ days) |
| A/R Aging Details | /reports/aging-details |
Same, but with per-invoice detail rows |
| Customer Balances | /reports/customer-balances |
Current outstanding per customer |
| Receivable Summary | /reports/receivable-summary |
High-level A/R metrics |
| Receivable Details | /reports/receivable-details |
Transaction-level receivable records |
| Invoice Details | — (not ported) | Legacy report; in the new app, browse invoices at /sales/invoices |
| Sales Order Details | — (not ported) | Legacy report; orders live under /sales/orders |
| Quotes Details | — (not ported) | Legacy report; quotes live under /quotes |
Category 6 — Payments Received
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Payments Report (Payments Received) | /reports/payments |
All received payments with method, date, amount |
| Refund History | /reports/refund-history |
All refunds issued |
| Credit Note Details | /reports/credit-note-details |
Credit notes issued to customers |
Category 7 — Tax Reports
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Input Tax Statement | /reports/input-tax-statement |
Taxes paid on purchases/expenses — GST-claim-back prep |
| Output Tax Statement | /reports/output-tax-statement |
Taxes collected on sales — monthly GST filing |
Note: a filing-grade VAT / GST Return report (
/reports/vat-return) also exists, but it sits in the finance module — it is tree-shaken out of the foodops build and only appears on finance-v2. The two tax statements above are in the foodops report set.
Category 8 — Inventory Reports
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Inventory Summary | /reports/inventory-summary |
Current stock levels + availability status |
| Committed Stock Details | /reports/committed-stock-details |
Stock allocated to open orders or production |
| Inventory Valuation Summary | /reports/inventory-valuation-summary |
Balance-sheet view: stock × cost = asset value |
| Inventory Aging Summary | /reports/inventory-aging-summary |
How long each batch has been in stock — flags slow movers |
| Product Sales Report | /reports/product-sales |
Sales performance with inventory turnover metrics |
| Stock Summary Report | /reports/stock-summary |
Consolidated stock levels, locations, availability |
Category 10 — Kitchen & Food Cost (restaurant brand)
These are the costing/kitchen reports, grouped under Kitchen & Food Cost in the reports hub. They're restaurant-oriented and read from your recipes, ingredient costs and wastage logs. [code: foodops-admin-app/apps/admin/src/config/reportsConfig.ts — group 'Kitchen & Food Cost' @ main]
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Food Cost % | /costing/food-cost |
Food cost as a share of sales — the single most important kitchen KPI |
| Cost Variance | /costing/variance |
Theoretical vs actual ingredient usage — where money is leaking |
| Recipe Variance | /restaurant/recipe-variance |
Theoretical vs actual cost per dish — spots kitchen drift and over-portioning |
| Recipe Costing | /costing/recipes |
Plate cost per item, gross margin and target food-cost analysis |
| Wastage Log | /costing/wastage-logs |
Logged spoilage, breakage and prep loss, broken down by reason |
| Yield Analysis | /costing/yield |
Trim and prep yield variance against expected recipe yields |
If these numbers look wrong, the usual cause is upstream data, not the report: Recipe Costing / Food Cost / Recipe Variance depend on each item having a recipe with ingredient quantities and ingredient costs — an item with no recipe, or ingredients with no cost, reads as zero or missing. Wastage Log only shows what was actually logged as wastage. Yield Analysis compares against the expected yield set on the recipe, so a missing or wrong expected-yield makes the variance meaningless. Check the item's recipe and ingredient costs first.
Category 11 — Restaurant Analytics (restaurant brand)
| Report | Route (admin.foodops.io) | Primary use |
|---|---|---|
| Outlet Performance | /restaurant/outlet-performance |
Compare net sales, COGS, gross profit and margin across outlets |
| Supplier Price Alerts | /restaurant/supplier-price-alerts |
Suppliers raising prices on items you buy — catches quiet hikes |
Outlet Performance numbers off? It compares outlets on net sales, COGS, gross profit and margin — the COGS/margin columns depend on the same recipe-and-cost data as the Kitchen reports above, so an outlet whose items lack costed recipes will show a distorted margin.
[code: reportsConfig.ts — 'Outlet Performance' description: "Compare net sales, COGS, gross profit and margin across outlets" @ main]
Category 9 — Purchase (NOT in the new admin report set)
The two Purchase reports were not ported into the new admin reports area as dedicated report routes. In the new admin app, browse this data operationally under Expenses —
/expenses/purchase-receipts(stock receiving) and/bills//expenses/suppliers.
| Report (legacy) | Legacy slug | Status |
|---|---|---|
| Purchase Receive Details | purchase-receive-details |
Not ported; see /expenses/purchase-receipts |
| Expense Record Details | expense-record-details |
Not ported; see /bills |
The standard report view
Every report has the same three-part structure:
- Top bar — Report title, Reports nav, filter controls (date range, outlet, register, etc.), Submit button to re-run. (Day Summary's dedicated History button/modal was removed 2026-07 in favor of a shared list-view export dialog + bottom-right ExportProgressTray —
foodops-admin-app@6642e57; whether other reports still expose a per-report History nav is unverified.) - Header metadata — Organization name ("KB Test Restaurant Pvt Ltd"), report name, date range
- Body — Metric blocks or tabular data, with totals row
Common filters across reports
| Filter | Appears on | Type |
|---|---|---|
| Date Range | All reports | Date picker with presets (Today, Yesterday, This Month, Last Month, Custom) |
| Outlet | Multi-outlet orgs | Dropdown |
| Register | Sales reports | Dropdown |
| Report By | Day Summary | Register Session / Order Time |
| Order Type | Sales By Products, Sales Details | Dine-In / Takeout / Delivery multi-select |
| Category | Sales-by-product family | Multi-select from product categories |
Key concepts
- Route — each report has a stable route (e.g.,
/reports/day-summary) on admin.foodops.io. Link to reports by route, not by ephemeral ID. (The legacy/reports/view/{slug}pattern is retired with the monolith.) - Report By (Day Summary) — switches the bucketing axis. "Register Session" groups cash/totals by each open-close cycle of a register; "Order Time" groups by calendar day regardless of session.
- FOC — "Free Of Charge" — comped items/orders. Tracked separately from discounts because the tax treatment differs.
- Committed Stock — stock reserved for orders that haven't shipped yet. Unlike sold stock, it's not deducted from on-hand until the order closes.
- Brand — an organizational layer above categories, used when one Foodops org runs multiple concepts. Brand Reports only show content for orgs that have configured brands.
- Top N% — the Sales By Products filter lets you show only the top-performing products (by revenue). Useful for 80/20-type analysis.
Common questions
Q: Which report should I run at end of day? A: Day Summary. Filter by today's date, pick the register(s) you closed, set Report By = Register Session. Reconcile the Cash Summary against the physical cash in the drawer.
Q: How do I see each day's sales? / Where is the daily sales report? / How do I know the sales of the day?
A: Run the Day Summary report (/reports/day-summary). Set the Date Range to the day (or a preset like Today / Yesterday) and Submit — it tallies that day's sales (Total Sales, Total Amount, Received, Outstanding), payments by method, category breakdown, and per-register cash. To compare several days side by side, use Sales Details (/reports/sales-details) with a multi-day date range, or Revenue breakdown by Month (/reports/revenue-by-month) for a longer daily/monthly trend. The counter app also shows the same Day Summary at /reports/day-summary for an on-the-floor read without leaving the till.
Q: How do I add / record expenses? Where do daily costs go?
A: Expenses are recorded in the admin back-office, not on the till. Use Expenses → Records (/expenses/records/new) to log a one-off or reimbursable expense, or Expenses → Bills (/expenses/bills) for a supplier invoice you'll pay later. See Recording Expenses for the full flow. (Cash physically taken out of the register drawer mid-shift is a different thing — that's a cash withdrawal / pickup on the register session, not a business expense; see the cash-handling entries.)
Q: How do I view the sales order report?
A: There is no report literally titled "Sales Order" in the Reports rail — the legacy monolith's Sales Order Details report was not ported to the new admin report set. In the new admin app, sales orders are a document type you view and manage operationally at Sales → Sales Orders (/sales/orders): a filterable, searchable list where you open each order (/sales/orders/:id), create/edit/duplicate them, and export the list to CSV or Excel from the Export dialog. [code: foodops-admin-app/apps/admin/src/pages/sales/SalesOrdersPage.tsx:168,417 (CSV/XLSX export); App.tsx:782-852 (routes)] For order-level sales analytics (what sold, by product/customer/day), use Sales Details, Sales By Products, or Day Summary in the Reports rail instead.
Q: Where's the monthly revenue-vs-target comparison? A: No built-in target-vs-actual report. Use Monthly Brand Contribution or Revenue breakdown by Month and overlay target externally. The top-of-hub KPI tile shows the % change vs last month, which covers the common case.
Q: How do I export a report to Excel? A: Every report view has an Export option (usually top-right of the results area — the three-dot or download icon). Exports CSV for tabular reports and PDF for formatted summaries. Large exports may take a moment.
Q: Can I schedule a report to email me every morning? A: Not from the Reports UI. Scheduled reports are on the roadmap — for now, use the History link on each report to revisit a previously-run version.
Q: The numbers on Day Summary don't match Sales Details — why? A: Day Summary uses register-session buckets by default. Sales Details uses order time. If a register session spans midnight (opened 10pm, closed 2am), Sales Details splits the sales across two calendar days while Day Summary keeps them together. Switch Day Summary's "Report By" to "Order Time" to align.
Q: What's the difference between "Receivable Summary" and "A/R Aging Summary"? A: Receivable Summary is a flat list of outstanding amounts. A/R Aging bucketizes them by age (0-30, 31-60, 61-90, 90+). Use aging to decide who to chase first.
Q: Inventory Valuation Summary — is that current cost or historical cost? A: Current cost, multiplied by current stock. For historical valuation (e.g., "what was this worth on 31 Dec?"), you need to pair with the Inventory Aging Summary or use a date-specific export.
Q: Can non-admin users see reports? A: Per role. By default only admin and manager roles have report access. Configurable in Settings → Users & Roles → Permissions → Reports.
Q: The Conversion Rate KPI shows 0.00% — what conversion is this measuring? A: Initiated orders that reached Closed status vs orders that went through cancellation / abandonment. Low conversion often means a flow issue (payment failed, customer walked out) rather than an inventory problem.
Q: Where are staff/payroll reports? A: Not in this catalogue — staff/payroll reports are either handled in a separate Accounting module (if enabled on plan) or via integration with external payroll software. Users & Roles screen has a basic activity feed per user.
Edge cases and known issues
Report shows 0.00 across the board
- Symptom: Every cell is 0.00 even though orders exist
- Cause: Date range defaults to today; running before any orders for the day
- Workaround: Click the Date Range filter, pick a range you know has sales, click Submit
"No records found" on a report that should have data
- Symptom: Blank result despite matching orders existing
- Cause: Filter combination is too narrow — e.g., Outlet + Register + Order Type excludes everything
- Workaround: Start with just Date Range, confirm you get results, then layer filters one at a time
Slow load on large date ranges
- Symptom: Spinner for 30+ seconds on a year-wide Sales Details
- Cause: Report joins transaction tables without pagination
- Workaround: Narrow the date range; use a monthly summary report (Monthly Revenue by Product Category) for broad trends, Sales Details only for specific investigations
KPI tiles show "0.00% vs last month" even with data
- Symptom: Percentage always reads 0.00%
- Cause: No data in the comparison month (newly onboarded org, or a skipped month)
- Workaround: Once you have two full months of data, the comparison starts working. Before that, ignore the % delta and use absolute numbers
Tax statements don't match accounting software
- Symptom: Output Tax Statement total differs from what your external accountant calculated
- Cause: Tax is computed per-line at order close, so mid-period tax rate changes or after-the-fact discount adjustments can cause drift
- Workaround: Reconcile against Sales Details with tax columns enabled; investigate any order where the line tax doesn't match rate × sub-total
API and integration notes
- Report pages are React routes in the admin app —
foodops-admin-app/apps/admin/src/pages/reports/*ReportPage.tsx, registered inapps/admin/src/App.tsx(paths/reports/<name>, lines 1266-1549 for the foodops set), with the persistent rail inapps/admin/src/pages/reports/ReportsSidebar.tsx. - Report data is served by the sales API —
hubits-api-sales/Hubits.Sales.Api/Controllers/ReportsController.cs(+CounterReportsController.cs,Services/Counter/Reports/**); inventory-flavoured reports pull from the inventory API. The by-daypart / by-service-type / voids-comps endpoints added in 2026-05 are now surfaced as the four new admin report pages. - Finance reports (P&L, Balance Sheet, Trial Balance, VAT/GST Return, Cash Flow, A/R & A/P Aging, Customer/Supplier statements, period-close) are gated behind
INCLUDE_FINANCEand tree-shaken out of the foodops build — they render only on finance-v2. [code: foodops-admin-app/apps/admin/src/App.tsx:201-217] - Day Summary also renders inside the counter app (
foodops.counter.app/src/pages/DaySummaryReportPage.tsx, components insrc/components/reports/*). - Filter state and CSV/PDF export are handled per-report in React; the legacy Razor
?format=csv|pdfexport endpoints are retired with the monolith.
X-Report & Z-Report (register close-out)
The X-Report and Z-Report are printable back-office artifacts generated from a register session, not from the /reports rail:
- Open a session at Sales → Register Sessions → (a session) —
/sales/register-sessions/:id(RegisterSessionDetailsPage) — and print the X or Z report from there. - The report HTML is built by
apps/admin/src/utils/sessionXZReport.tsbuildSessionXZReportHtml({ mode }):mode: 'x'renders "X-REPORT (READING)" (a mid-shift read that does NOT close the session),mode: 'z'renders "Z-REPORT (FINAL)" (the close-out, which adds the counted-cash close + variance). [code: foodops-admin-app/apps/admin/src/utils/sessionXZReport.ts:72 —mode === 'z' ? 'Z-REPORT (FINAL)' : 'X-REPORT (READING)'] - Sections: Period, Sales (Subtotal, Tax, Nontaxable, Total), Payments (by method), Cash drawer (counted close + variance on Z), Footer (printed-at / printed-by). It auto-opens the browser print dialog (
window.print()). [code: sessionXZReport.ts:103-111,281]
What changed since 2026-04-15 (NEW 2026-05)
Reports activity in this window was concentrated on the Payments Report + Day Summary.
Legacy monolith — Payments Report layout fix (NEW 2026-05-03)
46a80a32026-05-03 fix(PaymentsReport): update layout to use_CounterLayout— the Payments Report Razor view (Haafai.Sales/Views/Reports/PaymentsReport.cshtml) was migrated to the counter-style layout (_CounterLayout), aligning its appearance with the rest of the POS-facing reports.7b60f3b2026-05-03 update payments report to match pos app — paired companion commit; touchedHaafai.Sales/Views/Reports/PaymentsReport.cshtml,_PaymentsReport.cshtml,_PaymentsReportPrint.cshtml, plusHaafai.Sales/ViewModels/SalesReport/PaymentReceivedReport.cs. View-model + view + print-view all moved to the new layout.11b19952026-04-20 cashwithdrawal/session amount fix — Day Summary view models updated to surface cash withdrawal details and reconcile session-amount inconsistencies on the report (also touched the Restaurant.App SalesSessions views).e9abffb2026-04-18 day-summary calc fix — Day Summary "counted" + "difference" amounts now correctly account for payment method visibility (some payment methods were being silently filtered, throwing off totals).621cc022026-04-21 report download fix in notifications —haafai.app.commonpackage version bumped to 1.0.57 to fix a "report download failed from notifications" issue.
Counter app — Day Summary report polish
ca3029f2026-04-29 fix(DaySummaryReportPage): removereportDatadependency from export-button disabled state — export buttons no longer disable when reportData is empty (was a UX regression).9f56b2e2026-04-28 — fix to "unable to download day summary report".ee4f5322026-04-28 fix(DaySummaryReportPage): hide export and history buttons for future re-enablement — temporary toggle while the export pipeline is rebuilt; the buttons are present in code but hidden in UI.a19c7462026-04-24 refactor: remove 'Report By' selection — the "Report By" axis (Register Session vs Order Time) was simplified out of the React DaySummaryReportPage. The Q&A aboutReport Byin this entry references the legacy monolith report; counter.app's React view has dropped this filter. Note this discrepancy when running playwright re-verification.b2283962026-04-24 layout enhancement — DaySummaryReportPage layout polish.
Verified 2026-05-04 (Playwright):
Payments Report layout: the Payments Report at
/reports/paymentsreportis now a React page (counter-style layout) with columns: REFERENCE / PAYMENT DATE / PAYMENT REFERENCE / REMARKS / CUSTOMER / PAYMENT METHOD / INVOICES / DEPOSIT TO / TOTAL / UNUSED. Filters: Date Range (start + end) only — no Outlet or Register filter. Export (PDF), export (CSV), and History buttons are all visible at top-right.
Day Summary export + History buttons: commit
ee4f532noted them as temporarily hidden, but as of 2026-05-04 they are visible — PDF export, CSV export, and History buttons all appear at the top-right of the Day Summary Report page. The export-button re-enable may have been completed via commitca3029f(2026-04-29) which removed the disabled-state dependency. The buttons are functional in the current build.
Report By selector (Day Summary): the React
DaySummaryReportPage(counter.app) shows "Report By" with "Register Session" as the default option — thea19c746removal noted in codebase was evidently reverted or re-added. The Q&A aboutReport By = Register Sessionfor end-of-day reporting applies to both surfaces as of 2026-05-04.
What changed since 2026-05-04 (NEW 2026-06)
The user-facing report catalogue (34 reports / 9 categories) is unchanged. Activity this window was backend + display polish:
- New sales-analysis API endpoints — by daypart, service type, and voids/comps (
bcaadac62026-05-06) — added toHubits.Sales.Api/Controllers/ReportsController.cs, but these are API-only: no corresponding report cards exist in the hub (legacyViews/Reports/) or in the counter app, so they don't appear in the catalogue above. Likely groundwork for future report cards.[code: hubits-api-sales/Hubits.Sales.Api/Controllers/ReportsController.cs — bcaadac6] - Organization timezone integrated into report date/time formatting (
b7b92257,cdd45cee2026-05-17) — report components (register sessions, day summary) now format dates/times in the org's timezone (persisted to localStorage, routed viaparseServerDate). Cosmetic — fixes off-by-timezone display on session timestamps, doesn't change report figures.[code: foodops.counter.app/src/components/reports/RegisterSessions.tsx — cdd45cee] - Currency-handling + cash-difference fixes in
PaymentMethodSummary/RegisterSessions(c1dd098c,24d27baf2026-05-25) — better multi-currency rendering and a corrected cash-difference calculation on the Day Summary cash blocks.[code: foodops.counter.app/src/components/reports/PaymentMethodSummary.tsx — c1dd098c]
Note: the brief's "reports driver" 0198d25 is actually a counter.app commit adding address/contact/customer sub-resource services (not report content) — no report-catalogue impact.
Audit log — who changed what (and change notifications)
This moved to its own entry so it retrieves on its own: Change Notifications & the Audit Log. Short version — there are no push alerts on edits (the notification bell carries export/background-task items only); instead you review changes on demand via Settings → Audit Log and per-record History tabs (pull, not push).
Related
Inventory — data source for Inventory Reports
Purchasing & Suppliers — data source for Purchase reports
Recipes & Ingredients — feeds food-cost side of reports
Settings Overview — report permissions are role-based
User Stories: Reporting — operational scenarios
2026-08-18:
hubits-api-sales(144add7..98c202d) — most of this range's diff hunks against the entry's cited surface duplicate what's already captured verbatim in the 2026-08-03 through 2026-08-11 verification notes above (the export-columns endpoint, the Aging Summary/DetailsReportPredicates.SoldInvoicefix, the Payments ReceivedDateFieldmodified/payment filter,PosDefaultViewMode/SoftEnforcePaymentReferencesession fields, theOpenedTimeUTC ISO-8601 fix, theOpenRegisterExpectedAmount-seed fix, and the Day Summary cash-withdrawal ADR-0011 fix) — no new information from those parts. What IS new:GetPaymentsReceived(backs the Payments Report, Category 6,/reports/payments) now excludes voided payments from its summary/totals block (.Where(p => p.PaymentStatusId != ModelConstants.PaymentStatus.PaymentVoid)) even when the caller'sIncludeVoidedflag put voided rows into the result set — per the inline comment, 'a void moved no money, so summing it reports funds that do not exist,' while the individual rows still display (each carrying its own status/badge, per the 2026-07-23 note about the red 'Void' badge). This doesn't contradict any documented claim — the entry's Category 6 description only says 'all received payments with method, date, amount', with no claim about how totals handle voided rows — but it's a real behavior a user could stumble on: ticking 'Include voided payments' will surface voided rows inline without moving the summary totals. Separately and unrelated to Reports: this same diff range adds anEnforceUniqueBlazeNumberfield to the open-sessions payload (SessionsController.GetMySessionsAndRegisters) — per comment, already present on the session-details payload for the Issues tab/close gate, now also stamped on the open-sessions list so the counter can gate a Transfer-Receipt reference-reuse warning at the payment surface — this is POS/session settings plumbing, not report content, no catalogue impact. Worth a Playwright re-check next pass on/reports/paymentswith a voided payment present and 'Include voided payments' ticked, to confirm the summary/total row excludes it while the row itself still appears with its Void badge. (auto-applied by the truth pipeline; adversarially gated)2026-08-14:
foodops-admin-app(47ff627..f9cc71d) changes the production deploy workflow's health-check target for the finance-brand deploy job fromhttps://finance-v2.hubits.io/healthtohttps://finance.hubits.io/health(.github/workflows/deploy-production.yml, thename: finance-v2/brand: finance/host_secret: EC2_HOST_ADMIN_FINANCE_V2entry). This is the same deploy target this entry's VAT/GST-Return note refers to when it says the report 'is tree-shaken out of the foodops build and only appears on finance-v2.' It's unclear from this diff alone whether this reflects a subdomain rename (finance-v2.hubits.io → finance.hubits.io) or a fix to a health check that was already pointed at the wrong URL for a build that was always served at finance.hubits.io — either reading means 'finance-v2' can no longer be treated as a confirmed-current hostname for the finance build. This doesn't strictly contradict the entry's text (which names the deploy target, not a specific URL, and asserts nothing about routing/tree-shaking that this diff touches), so no forced edit — but it's worth a Playwright check next pass on whetherhttps://finance.hubits.io(notfinance-v2.hubits.io) is now the correct address to send a user looking for the VAT/GST Return report. The rest of this diff range (the 'Quick range' date-preset chip-row series, and the newApp.tsxErrorBoundary/RouteErrorBoundary/lazyWithReloadchunk-resilience layer wrapping the Dashboard/Finance/CustomerStatements/Salesmade/Assistant pages) duplicates what the 2026-08-03, 2026-08-04, and 2026-08-11 verification notes above already captured — no new information there, same open question about whether report pages get the same chunk-404 self-heal treatment. Thepurchase-receipts.spec.tstest change (Import-from-Document now routes to the Documents inbox instead of opening a modal) sits outside this entry's cited surface (Expenses/Purchase Receipts, not Reports) — no impact here. (auto-applied by the truth pipeline; adversarially gated)2026-08-11:
foodops-admin-app(47ff627..4e51283, quick-date-filter series culminating in the 'Quick range' chip row) also lands a chunk-load resilience layer touchingApp.tsx— the file this entry cites for both the report-route registration (App.tsx:1266-1549) and theINCLUDE_FINANCEtree-shake gate (App.tsx:201-217). NewErrorBoundary/RouteErrorBoundarycomponents (with tests) catch render-time errors so a crash degrades to a per-route 'Reload page' recovery panel instead of React 18 blanking the entire<div id="root">;RouteErrorBoundarykeys on pathname so navigating to a different report self-heals the error state, and per its own comment the surrounding chrome (i.e. the reports rail) stays mounted through a crash. Paired with this,lazy(...)is replaced by a newlazyWithReload(...)wrapper forDashboardPage,FinanceDashboardPage,CustomerStatementsPage,SalesmadeDashboardPage, andAssistantHomePage— per the added comment, this exists because a deploy replaces the container outright, so a tab left open beforehand requests hashed chunks that 404;lazyWithReloadturns that into one self-healing reload instead of an uncaught rejection. The visible diff hunks do NOT show the individual/reports/<name>report-page constants (day-summary, sales-by-products, etc.) being converted fromlazytolazyWithReload, nor showRouteErrorBoundarybeing wired around the report route tree specifically — only the five dashboard-family pages above are confirmed touched here, so it's unconfirmed whether report pages get the same chunk-404 self-heal or crash-recovery treatment yet. Nothing in this diff contradicts any documented claim: theINCLUDE_FINANCE ? … : ExcludedRoutetree-shake pattern this entry cites is structurally unchanged (just wrapslazyWithReloadinstead oflazy), and report routes/paths are untouched. Two things worth a follow-up pass: (1) confirm via Playwright whether report pages (not just Dashboard/Finance/Salesmade/Assistant) getlazyWithReload+RouteErrorBoundarycoverage, since a stale-tab-after-deploy 404 on a report chunk is a plausible real-world failure mode for this entry's audience; (2)App.tsxpicked up ~10 more lines (two new imports plus an expanded comment block) on top of the ~7-line drift already flagged in the 2026-07-21 verification note, so theApp.tsx:1266-1549(report routes) andApp.tsx:201-217(INCLUDE_FINANCEgate) line citations in this entry are now further stale and should be re-confirmed next time that section is touched. (auto-applied by the truth pipeline; adversarially gated)2026-08-11:
hubits-api-sales(144add7..0d4d26c, export column registries + typed export pipeline series) adds a genuine correctness fix to A/R Aging Summary and A/R Aging Details (Category 5 —/reports/aging-summary,/reports/aging-details), both backed byReportsController.GetAgingSummary/GetAgingDetails. Both queries previously filtered outstanding invoices only onRecordStatusId == Active(the soft-delete flag) plusOutstanding > 0andInvoiceDate < asOfUtc; per the inline comment,RecordStatusIddoesn't reflect document status, so a voided invoice or one still in Draft — as long as itsOutstandingcolumn wasn't zeroed — used to age into the buckets and overstate the receivable. Both endpoints now add.Where(ReportPredicates.SoldInvoice), explicitly described as 'the same predicate the sibling receivable reports use so they cannot disagree' (i.e. aligning with whatever screen Customer Balances/Receivable Summary already use). This doesn't contradict any documented claim in this entry — the Category 5 table only describes the two reports at the 'who owes you, bucketed by age' / 'per-invoice detail' level, with no claim about voided/draft-invoice handling — but it's a real change to what numbers a user sees on these two reports (any org with voided or draft invoices carrying a nonzero Outstanding value should see their aging totals drop). The rest of this diff range (theexport-columnsendpoint, the Payments ReceivedDateField/modifiedDatesort wiring, theSessionsControllerPosDefaultViewMode/SoftEnforcePaymentReference/OpenedTime-UTC/ExpectedAmount-seed changes, and theDaySummaryReportServiceADR-0011 cash-withdrawal fix) is identical to what's already captured in the 2026-08-03, 2026-08-04, and 2026-08-08 verification notes above — no new information there. Worth a Playwright re-check next pass on/reports/aging-summaryand/reports/aging-detailsfor an org with a voided or draft invoice that still shows nonzero Outstanding, to confirm it now drops out of the aging buckets. (auto-applied by the truth pipeline; adversarially gated)2026-08-08:
hubits-api-sales(144add7..cdb3b32) ships a genuine calculation fix to the Cash Summary this entry documents under Day Summary and 'X-Report & Z-Report'.DaySummaryReportService.BuildReportAsync(ADR-0011, 'single-source Expected Cash: pure reader') stops subtractingcashWithdrawalTotalfrom each register session's cashExpectedAmount— per the inline comment, that subtraction was wrong twice over: the storedExpectedAmountalready nets that session's own withdrawals, andcashWithdrawalTotalwas the sum across EVERY session in the report period, so on a multi-session day one register's cash drops leaked into every other register's Expected figure, and per the comment 'that is what made the Session Summary's Expected Cash disagree with the session details screen' (the Session Summary artifact added 2026-08-01, previously flagged). This directly firms up the accuracy of the per-session Cash Summary block documented in this entry's Day Summary section and the 2026-07-30 'blind cash counting' note (which gates Expected/Difference visibility but doesn't touch the underlying math) — no visibility/permission behavior changes, just a correctness fix to the number shown once revealed. Companion fix in the same diff:SessionsController.OpenRegisternow seeds the cash balance'sExpectedAmountto the opening float at register-open time (both the multi-currency and single-currency branches) instead of leaving it 0 — per comment, previously 'a just-opened register show[ed] Opening correctly and Expected as 0.00 until the first payment settled,' which would have shown a misleadingly-zero Expected on a freshly-opened, no-sales-yet register in Day Summary / Session Summary. Neither fix contradicts any entry text (no documented claim asserted a specific Expected-Cash formula or a 0.00-at-open behavior), so no edit is needed — but worth a Playwright re-check on Day Summary's Cash Summary for a multi-session day and a just-opened register to confirm the corrected figures render. Separately in this same diff range,SessionsController.GetMySessionsAndRegistersfixes an unrelated timezone-display bug —OpenedTimenow emits an explicit UTC ISO-8601 string (DateTime.SpecifyKind(...,Utc).ToString("O")) instead of a zone-lessyyyy-MM-dd HH:mm:ss, so the client no longer misreads a UTC timestamp as local (was showing a just-opened register hours off) — this is open-session-list plumbing, not report content, no catalogue impact. TheGetExportColumnsendpoint, the Payments ReportDateField/modified-date filter, and thePosDefaultViewMode/SoftEnforcePaymentReferencesession fields also present in this diff range are identical to what's already covered in the 2026-08-03 and 2026-08-04 verification notes above — no new information there. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-04:
foodops-admin-app(47ff627..b288f36, quick-filter design/spec docs + 'add shared org-timezone-aware date presets' + 'add the quick date-range chip row') is largely the same 'Quick range' chip-row rollout already captured in the 2026-08-03 verification note (Covers, Credit Note Details, Day Summary, Input Tax Statement, Output Tax Statement, Payments Received gain the chip; Aging Summary and Customer Balances gain only areportKeyprop +columnsexport arg) — no new edit needed for that part. What's NOT yet captured:PaymentsReceivedReportPage.tsxin this diff also adds a newDateField = 'payment' | 'modified'state (dateField, default'payment'), explicitly commented as backing a 'Filter by' dropdown, threaded intoappliedfilters, the report query (dateField: applied.dateField), and the export payload (dateField, columnsalongside a newly addedreportKey="payments-received"). The same commit also renames the payments-table sort key fromcreatedDatetomodifiedDate. This is a genuinely new filter dimension for the Payments Report (Category 6) beyond the already-documented Date Range + Voided checkbox, but the diff hunks shown don't include the actual<Select>/dropdown JSX, so the UI control's existence/label/options aren't directly proven here — only the state, query-wiring, and export-wiring are. Doesn't contradict any existing entry text (Category 6 table and the 2026-05-04/2026-07-23 Payments Report filter notes only describe Date Range + the Voided checkbox as filters, neither of which is falsified). Worth a Playwright re-check next pass on/reports/paymentsto confirm the 'Filter by: Payment Date / Modified Date' control renders, capture its default and label text, and note whether the export column-picker (reportKey+columns) has a visible dialog yet — mirroring the same open question already flagged for Aging Summary/Customer Balances. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-04:
hubits-api-sales(144add7..5889642, export column registries + typed export pipeline series) adds a new server-side gate onSessionsController.CloseRegister— a register can no longer be closed while it has an unreviewed 'void bin' entry (VoidBinEntrieswithReviewStatusId == Pending); the close request now fails withSuccess = false, Message = "Review and clear the void bin before closing the register."This is directly relevant to the register close-out flow this entry documents under 'X-Report & Z-Report (register close-out)' and the Day Summary end-of-day reconciliation guidance — closing a session can now be blocked mid-flow by an unreviewed void, which isn't mentioned anywhere in this entry today. The diff doesn't touchfoodops-admin-apporfoodops.counter.app, so it's unverified whether the counter UI currently surfaces a void-bin review screen/modal in response to this error, or whether it silently fails — worth a Playwright re-check on the register-close flow next pass, ideally provoking a void first. Separately,GetPaymentsReceived(backs Payments Report,/reports/payments) gains aDateFieldquery option: results now filter byUpdatedDateinstead ofPaymentDatewhen the caller passesDateField=modified(default unchanged — stillPaymentDate), a newModifiedDatefield is exposed per row, andmodifiedDateis added as a sort key; per the inline comment this backs a 'Filter by' dropdown in the UI, but no frontend hunk is included so the dropdown's existence is unverified. This doesn't contradict the entry's existing filter/column description for Payments Report, just extends it once confirmed live. Also unrelated to Reports:SessionsController.GetMySessionsAndRegistersnow stampsPosDefaultViewMode(org-level tile/compact default) andSoftEnforcePaymentReference(org-level Transfer-payment reference enforcement) onto open sessions — POS/counter settings plumbing, no intersection with any claim in this entry. The export-columns endpoint hunk in this diff range is identical to the one already covered in the 2026-08-03 verification note (df4179d); no new information there. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-03:
foodops-admin-app(47ff627..76df568, 'design for quick date filters on report pages' → 'add shared org-timezone-aware date presets' → 'add the quick date-range chip row') adds a new 'Quick range' preset-chip control (ReportDatePresets, backed by a shareduseReportDatePresetshook andpresetRange()/ymdToStartOfDay/ymdToEndOfDayhelpers inutils/datePresets) next to the From/To date pickers on Covers, Credit Note Details, Day Summary, Input Tax Statement, Output Tax Statement, and Payments Received. (Aging Summary and Customer Balances only gained areportKeyprop and acolumnsargument on their export handlers in this diff, not the chip row itself — hinting at a column-selection export capability with no export-dialog UI visible in these hunks, so unverified.) Selecting a chip both updates the draft From/To fields and immediately commits/re-fetches (onCommitcallsfetchData/updatesappliedstate and resets pagination), unlike the plain date pickers which require a separate Apply click. Presets are computed timezone-aware viauseOrgTimeZone, consistent with the org-timezone treatment already documented elsewhere in this entry for report timestamps. Default date range on first load also changed on the retrofitted operational pages: Covers (previously a rolling 7-day window ending today), Credit Note Details and Payments Received (previously 'first of current month' through today) all now default to the 'today' preset; Input Tax Statement and Output Tax Statement — filing/tax-category reports — keep a 'thisMonth' default, matching their existing accounting-cadence framing. This doesn't contradict the entry's 'Common filters across reports' claim that Date Range appears on 'All reports' with presets (Today/Yesterday/This Month/Last Month/Custom) — if anything this diff is a concrete step toward that claim landing — but it's currently a separate 'Quick range' chip control rather than presets built into the date picker, and confirmed live so far only on the six pages above, not the full ~28-report catalogue. It also reinforces this entry's own Edge Cases note ('Report shows 0.00 across the board... Cause: Date range defaults to today') for the pages whose default just changed to 'today', since that note previously assumed a default that wasn't true for Covers/Credit Note Details/Payments Received before this diff. Worth a Playwright re-check next pass to screenshot the new chip row, confirm its exact preset label set, and check whether the remaining report pages (Sales family, A/R, Inventory, etc.) get the same control before updating the Common Filters table with specifics. (auto-applied by the truth pipeline; adversarially gated)2026-08-03:
hubits-api-sales(144add7..df4179d, feat(reports): typed export pipeline with column selection, sales-details first + convert remaining 7 wave-1 report branches) adds a newGET /reports/export-columns?reportKey=...endpoint (ReportsController.cs) returning the available export columns, in default order, for a report — per the inline doc-comment, intended to back a column-picker in the admin app's export dialog. The same comment states the endpoint 404s for reports without column selection ('day-summary, revenue matrices, non-wave-1 keys') and the client treats that as 'export everything' — so Day Summary's export behavior documented above is explicitly unaffected, and this is confirmed to be a rolling 'wave-1' subset (sales-details first, plus 7 more report branches per the commit series), not a catalogue-wide change. This diff is API-only — nofoodops-admin-appfrontend hunks are included, so it's unverified whether the export dialog currently calls this endpoint or shows a column picker in the UI yet. Doesn't contradict the entry's existing generic export description ('Every report view has an Export option... usually top-right'); worth a Playwright re-check next pass, starting with Sales Details (/reports/sales-details), to see if a column-picker has appeared in report export dialogs. Separately and unrelated to Reports: this diff also touchesSessionsController.cs, addingPosDefaultViewMode(org-level POS tile/compact default from Outlets settings) andSoftEnforcePaymentReference(org-level Transfer-payment reference enforcement) fields to the open-sessions response — this is counter/POS session-settings plumbing, not report content, and doesn't intersect any claim documented in this entry. (auto-applied by the truth pipeline; adversarially gated)2026-08-01:
hubits-api-sales(bb69312..0d94f42, tenancy pin / cache+signalr perf / customer-statements series) adds two new endpoints onSessionsController—GET /{sessionId}/close-summaryandGET /{sessionId}/close-summary/pdf— that are NOT the documented X-Report/Z-Report (sessionXZReport.ts/buildSessionXZReportHtml). This is a separate 'Session Summary' artifact: a newServices/Counter/Reports/SessionCloseSummaryMapper.cscallsDaySummaryReportService.GenerateReport(...)scoped to one session (per an inline comment, the date-window args are ignored when asessionIdis supplied — session membership alone defines inclusion) and maps the result 'verbatim' — one tender row per payment-method×currency with Opening/Expected/Counted/Difference, plus the full Day SummarySalesTotalsset unchanged — so the new artifact is guaranteed to reconcile numerically with the existing Day Summary report this entry documents. A newSessionCloseSummaryHtmlBuilder.csrenders this DTO as an 80mm monospace thermal-receipt layout (titled 'SESSION SUMMARY', citing Figma frame 2850-20643) for both a?download=truePDF and (per comment) a client-built thermal print. This diff is API-only — nofoodops-admin-apporfoodops.counter.appfrontend hunks are included, so it's unverified whether/where a UI button calls these endpoints yet (likely a register-session close action on the counter, given the thermal-receipt framing, but not proven here). Does not contradict any existing claim about X-Report/Z-Report or Day Summary — it's an additive, reconciling artifact. Worth a Playwright re-check next pass to see if a 'Session Summary' print/download action has appeared on the register-session close flow, and if so add it as a new subsection alongside the existing X-Report & Z-Report section. (auto-applied by the truth pipeline; adversarially gated)2026-07-30:
foodops.counter.app(c4cf5dc..5468530, 'blind cash counting') changes the Cash Summary / Register Sessions block on the counter-app Day Summary (DaySummaryReportBody.tsx) so the Expected and Difference columns are hidden by default for an open register session — they only appear once every session for the day has closed (allSessionsClosed), unless the viewer holds a newcanViewExpectedCashpermission, in which case they're always shown. While hidden, a note reads 'Expected cash & variance are available after all sessions for the day are closed.' This is a permission-gated UX change to the reconciliation workflow this entry documents under 'End of day: run Day Summary to tally receipts vs cash for register reconciliation' and the Cash Summary block description — most users doing a mid-shift Day Summary check will now see only Opening/Counted (no Expected/Difference) until close-out. Companion commit subjects ('blind-close variance summary modal, neutral-black figures, per-currency cards') suggest a related close-out variance modal shipped too, but its code isn't in this diff's hunks, so its details are unverified. Worth a Playwright re-check to confirm which roles getcanViewExpectedCashby default and to capture the hidden-state note + the variance modal on session close; no existing entry text is contradicted since the entry never specified that Expected/Difference are unconditionally visible. (auto-applied by the truth pipeline; adversarially gated)2026-07-27:
hubits-api-sales(c3662fc..bf7ac10, tenancy/scale series) adds server-side paging, free-text search, and multi-column sort toGetSalesDetails(backs Sales Details,/reports/sales-details, documented in Category 1 as 'Real-time transaction-level view with filters for outlet, register, payment method'). The endpoint keeps its original behavior whenpage/pageSizearen't supplied (Paging.IsRequested(...)false → same unpagedOrderBy(CreatedDate)query as before, now routed through a new sharedShapeSalesDetailsRowsAsynchelper that preserves the existing org-local date conversion and outlet-name resolution logic verbatim). When paging is requested, the endpoint additionally accepts asearchterm (matches invoice reference or customer name) and asortBy/sortDirpair (createdDate, invoiceDate, netTotal, taxTotal, serviceCharge, amount, outstanding, reference, customerName, registerSession, statusName), and returns aReportPagedApiResponsewith a summary block (totalAmount/netTotal/taxTotal/outstanding). This is API-only groundwork — the diff doesn't touchfoodops-admin-app, so it's unverified whether the Sales Details report page currently sendspage/pageSize/search/sortByparams or still calls the legacy unpaged path; the documented filter set (Date Range, Outlet, Register, Order Type) and result columns are unchanged either way. No entry text needs to change now — worth a Playwright re-check next pass to see whether Sales Details has gained a search box, sortable columns, or paginated results in the UI, similar to how the 2026-05-06 daypart/service-type endpoints and 2026-07-13 Sales Details outlet filter shipped API-only before any frontend confirmation. (auto-applied by the truth pipeline; adversarially gated)2026-07-23:
hubits-api-sales(aca3395..34ca3ff, Refine invoice query to filter by ApplicationOrganisationId for improved accuracy) adds an explicitTransaction.ApplicationOrganisationId == orgIdpredicate to both invoice queries insideDaySummaryReportService.cs— the one inBuildReportAsyncthat feeds the documented Day Summary Sales/Receipt/Category/Cash Summary blocks, and the one inGenerateInvoiceBreakdownAsync(invoice-level drill-down data). Previously these queries relied solely onSalesRegisterSessionIdmatching a pre-scopedsessionIdsset to exclude other orgs' invoices; the new explicit org filter is a correctness/accuracy hardening (per the commit subject) suggesting the prior filter could, in some edge case, admit invoices from a differentApplicationOrganisationIdinto a Day Summary run — which would silently inflate the documented Sales Summary / Cash Summary totals for the affected org. This is not a contradiction of any documented Day Summary behavior (sections, filters, routes are all unchanged) and there's no UI signal to verify — it's backend query scoping only. Worth a note in case any org previously saw inexplicably-high Day Summary totals; no entry text needs to change. The companion commit in this same diff range (chore: bump haafai.domain to 1.0.1016, modifier recipe deduction) does not intersect this entry's cited surface (no hunks shown against Reports/Sales files) — unrelated to reports. (auto-applied by the truth pipeline; adversarially gated)2026-07-23:
foodops-admin-app(985570b..35146e3, Fix alcyon issues) wires the frontend for theIncludeVoidedAPI flag flagged as API-only/unverified in the 2026-07-22 verification note.PaymentsReceivedReportPage.tsx(backs Payments Report,/reports/payments) adds a new 'Voided' filter field — a checkbox labeled 'Include voided payments', defaulting to unchecked (includeVoided: false) — so voided payments are now excluded from the results by default, and re-queries the API withincludeVoided: truewhen ticked. Rows whererow.isVoidedis true get a red 'Void' badge inline next to the REFERENCE cell. This resolves the prior pass's open question: the checkbox does exist, and the entry's Category-6 description ('All received payments...') and the 2026-05-04 Playwright-verified filter list ('Date Range (start + end) only — no Outlet or Register filter') are both now incomplete — the Voided checkbox is a new filter (not Outlet/Register, so not literally contradicted, but the filter list is stale) and by default the table no longer shows literally all received payments. Recommend a Playwright re-check next pass to confirm the live default view and capture a screenshot of the new checkbox + Void badge, then update the Category 6 table and the 2026-05-04 verification note's filter description. Separately and unrelated:SalesOrdersPage.tsx's reference cell now renders an explicithref={/sales/orders/:id}link — this only reinforces the entry's already-documented claim that sales orders open at/sales/orders/:id; no edit needed there. (auto-applied by the truth pipeline; adversarially gated)2026-07-22:
hubits-api-sales(407e704..355de62, Fix alcyon isusues) changesGetPaymentsReceived(backs Payments Report,/reports/payments) to hide voided payments by default: a newIncludeVoidedquery flag now filters out rows wherePaymentStatusId == PaymentVoidunless the caller opts in, and each row gainsStatusName/IsVoidedfields so a UI 'can badge a voided row when IncludeVoided is on' (per the inline comment). This is API-only groundwork — the diff doesn't touch the admin frontend, so it's unverified whether an 'Include Voided' checkbox actually exists yet in the Payments Report UI, or whether voided rows were previously visible in the documented 10-column table (REFERENCE/PAYMENT DATE/PAYMENT REFERENCE/REMARKS/CUSTOMER/PAYMENT METHOD/INVOICES/DEPOSIT TO/TOTAL/UNUSED, screenshot 167, 2026-05-04). Not a contradiction of the entry's 'All received payments' description yet, since we can't confirm the frontend currently omits voided rows from the default view — but worth a Playwright re-check next pass to see whether voided payments have silently disappeared from the default table (a user-visible behavior change) or whether this is inert pending frontend wiring, similar to the 2026-05-06 daypart/service-type endpoints that shipped API-only with no UI card. (auto-applied by the truth pipeline; adversarially gated)2026-07-21:
foodops-admin-app(53d3af8..799c61f, feat(admin): TanStack Query client + provider, write-invalidation bridge — wave 1) wraps the admin app in a globalQueryClientProvider(apps/admin/src/App.tsx, 30s staleness contract) and refactorsNotificationBell.tsx(cited in this entry's source_repos and referenced in the 'Audit log' section's notification-bell pointer) to read its unread count from a shared TanStack Query cache (NOTIFICATION_COUNT_KEY) instead of a per-componentusePageVisibility-gated poll. User-visible behavior is unchanged — still a 2-minute poll, still pauses while the tab is hidden, still catches up immediately onNOTIFICATIONS_CHANGED_EVENT/window focus (now viainvalidateQueries) — this entry's claim that 'the notification bell carries export/background-task items only' and the pull-not-push framing both still hold; the only effect is that multiple mounted bells (rail + mobile drawer) now collapse to one network request instead of one each. Also note: App.tsx gained ~7 lines near the top of the file (QueryClientProvider import/wrap + a dev-only ReactQueryDevtools block), so the line-number citations for the foodops report routes (App.tsx:1266-1549) and theINCLUDE_FINANCEgate (App.tsx:201-217) may have drifted by roughly that many lines — the diff does not show the route/gate content itself changing, so no route or gating behavior is affected, but the exact line numbers are worth re-confirming next time that section is touched. Worth watching: the commit series is labeled 'wave 1,' implying report pages themselves may adopt TanStack Query (with its write-invalidation-marks-all-stale behavior) in a future wave — not evidenced by this diff, no action needed yet. (auto-applied by the truth pipeline; adversarially gated)2026-07-21:
hubits-api-sales(99e6847..95e017e, fix(reports): update customer balance comparison to avoid timezone conversion) fixes a double-timezone-conversion bug inGetCustomerBalances(backs/reports/customer-balances, documented in the A/R category as 'Current outstanding per customer'). The as-of journal-date comparison previously took the already org-localtoDateLocaland ran it back throughReportTimeZone.ToUtcFromOrgLocal(toDateLocal, orgTz)before comparing againstJournalEntry.JournalDate; that extra conversion could shift the effective as-of boundary near midnight depending on the org's UTC offset, potentially including or excluding journal entries dated right on the boundary day. The fix comparesJournalEntry.JournalDate <= toDateLocaldirectly, dropping the redundant conversion. This does not contradict any documented claim — the entry only describes Customer Balances at the 'current outstanding per customer' level, not its as-of date-boundary mechanics — but it's a correctness fix worth flagging since it changes which balances near a day boundary get counted, i.e. the actual numbers users see. No route, filter, or column change. (auto-applied by the truth pipeline; adversarially gated)2026-07-17:
hubits-api-sales(acfa332..416ee89) follow-up-corrects the 2026-07-13-flagged 'F8' change onGetPaymentsReceived(backs Payments Report,/reports/payments). The prior commit had collapsed the REFERENCE field top.Reference ?? p.Transaction.Reference; this diff reverts that per an inline comment noting the collapse 'cost the report its sequence number' —Referencenow goes back to the system PAY number (p.Transaction.Reference) and a newBankReferencefield carries the user-entered bank/cheque ref separately, alongside a newly exposedRemarksandUnappliedAmount. This does NOT contradict the entry: the Playwright-verified column list (screenshot 167, 2026-05-04) already documents distinct REFERENCE, PAYMENT REFERENCE, and REMARKS columns, so this backend revert restores consistency with what was already documented rather than breaking it — it just means the 2026-07-13 verification note's description of the collapsed single-Reference behavior is now superseded/stale. Also added: results now order byPaymentDatethenCreatedDate(fixes same-day payment ordering, since PaymentDate is often midnight-only), andChartOfAccount/InvoicePaymentsare now included (likely feeding the deposit-account / linked-invoice columns already in the documented table). Separately,GetSalesDetails(backs/reports/sales-details) gained an outlet filter (ReportPredicates.OutletMatches) and now resolves anOutletNameper row (viaInvoice.OutletId, falling back to the register session's outlet) — the entry already generically lists 'outlet' as a Sales Details filter, so this isn't a new claim, but whether Outlet now appears as a visible results column is unverified and worth a Playwright check next pass. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-07-13:
hubits-api-sales(de33dbc..acfa332) touches two endpoints behind documented reports without contradicting any documented claim.ReportsController.GetPaymentsReceived(backs Payments Report,/reports/payments) now returnsReference = p.Reference ?? p.Transaction.Referenceinstead of alwaysp.Transaction.Reference— per the inline comment ('F8'), the REFERENCE field now shows the user-entered bank/transfer reference when captured, falling back to the system PAY-number only when it wasn't. This changes the actual values users will see in the REFERENCE column of the Playwright-verified Payments Report table (screenshot 167), though the column set itself is unchanged. Separately,ReportsController.GetSalesDetails(likely backing/reports/sales-details) gained a filter restricting results toTransactionTypeId == Invoice, narrowing the transaction-level data returned — worth confirming with Playwright whether this changes visible row counts on Sales Details. Unrelated:CounterReportsController's Day Summary PDF/Excel generation switched date-format strings fromdd/MM/yyyytodd'/'MM'/'yyyy(escaping literal slashes so .NET doesn't substitute a culture-specific date separator) — a display-safety fix, not a content change, and not worth a catalogue edit. (auto-applied by the truth pipeline; adversarially gated)2026-07-05:
foodops.counter.app(9200293..b813f61, feat(reports): adopt admin's unified Day Summary body + date toggle) refactors the counter app's Day Summary into a single sharedDaySummaryReportBody.tsx, replacing four previously separate components (CategorySummary.tsx,PaymentMethodSummary.tsx,RegisterSessions.tsx,SummarySection.tsx— all fully deleted). This is a structural consolidation, not a content change: the documented Receipt/Sales/Category/Cash Summary blocks are preserved, and the new body is written to be reusable in a session-overview context too (itsshowKpiStripprop is suppressed there because 'its hero card already shows those figures' per an inline comment) — confirming Register Sessions and Credit Sales rows already lived on this page pre-refactor, just via separate components. Two genuinely new pieces of UI ship in the same commit and are not yet in the entry: (1)DaySummaryKpiStrip.tsx— a brand-new KPI tile strip (2/3/6-col responsive grid, currency + count-style tiles frombuildKpiTiles(report)) rendered above the Day Summary tables — distinct from the legacy hub-level KPI strip already flagged elsewhere in this entry as unverified/retired; (2)DateTypeToggle.tsx— a brand-new 'Business Date' / 'Transaction Date' toggle component. Given the commit groups this toggle with the Day Summary body work, it may be the next iteration of (or a replacement for) the already-documented 'Report By: Register Session / Order Time' filter — but the diff hunks available don't show whereDateTypeToggleis actually wired intoDaySummaryReportPage, so the relationship to the existing 'Report By' filter is UNCLEAR and needs a follow-up pass (ideally with Playwright) before editing that section. Recommend re-verifying the Day Summary filters and adding a KPI-strip note next pass rather than editing now, since the wiring isn't proven by this diff alone. (auto-applied by the truth pipeline; adversarially gated)2026-07-05:
foodops-admin-app(8ec1ab1..6642e57, feat(reports): report exports through list-view dialog/tray UX, incl. Day Summary) replaces Day Summary's bespoke PDF/Excel dropdown-export + polling History modal with the shared list-view export flow:ReportPageShell'sonExportprop now takes anExportFormatand resolves{ correlationId }, with progress/download surfacing in a shared bottom-rightExportProgressTray(notification bell as fallback) instead of an in-page History modal —DaySummaryReportPage.tsxdrops handleShowHistory/handleExportPdf/handleExportExcel and the 5s-polling effect entirely. The commit subject implies this is a cross-report UX unification rather than Day-Summary-only, though this diff only directly proves it for Day Summary + the shared shell; other report pages' History affordance is unverified pending a fuller pass. Separately and unrelated to History:CommittedStockReportPage.tsxandSalesDetailsReportPage.tsxnow format timestamps viaformatDateTimeOrgTz(value, orgTz)instead of rawparseISO/format, extending the org-timezone treatment already documented for Day Summary/Register Sessions/X-Z reports to two more report pages — cosmetic only, no change to report figures.CoversReportPage.tsxalso now passes adateTypeparam on export, andScheduledReportRunsPage.test.tsxwas refactored to a sharedrenderWithProviderstest helper — both internal/test-only. (auto-applied by the truth pipeline; adversarially gated)2026-07-04:
hubits-api-sales(01cd718..3005390, feat(reports): Day Summary exports through tray pipeline with legacy workbook) adds the concrete plumbing behind Day Summary's PDF/CSV export + History buttons documented above.CounterReportsController.GenerateDaySummaryPdfReportnow writes aFileDownloadLinkNotification (keyed on the report id, message"Day Summary PDF export ready (...)") after the PDF is uploaded to blob storage, so the admin app's export tray — which polls notifications by EntityId — can surface the download; per the code comment this is 'same contract as ReportExportJob's XLSX exports,' i.e. Day Summary PDF exports now join the same async notification-based delivery already used for XLSX. Separately, the ~300-line inline ClosedXML workbook-building code previously inGenerateDaySummaryExcelReportwas extracted verbatim into a new sharedServices/Counter/Reports/DaySummaryWorkbook.cs(DaySummaryWorkbook.Build(...)), so the counter app's history-modal pipeline and the admin app's export-tray pipeline render the identical workbook from one renderer fed byDaySummaryReportService— no change to workbook content/layout (extracted verbatim, not rewritten). No documented claim is contradicted: the Export/History buttons, their visibility, and the 'Large exports may take a moment' note all remain accurate — this diff explains why (async Hangfire job + notification poll) rather than changing the user-visible contract. Worth a line in the API/integration notes about the sharedDaySummaryWorkbookrenderer and the notification-based export-tray delivery mechanism next time that section is touched. (auto-applied by the truth pipeline; adversarially gated)2026-07-04:
foodops-admin-app(88dfde2..f25a41c) extends the org-timezone treatment — previously documented (2026-06-04 entry) as covering Day Summary / Register Sessions display only — to the printed X-Report/Z-Report artifact itself.buildSessionXZReportHtmlinapps/admin/src/utils/sessionXZReport.tsnow takes a requiredtimeZoneparam and renders the Period (Opened/Closed) and Footer (Printed at) timestamps viaformatInTimeZoneagainst the org's IANA timezone instead of raw local-machineformat(parseISO(iso));RegisterSessionDetailsPage.tsxnow passestimeZone: orgTzat the print call site. Structure/sections and the X=READING / Z=FINAL mode labels are unchanged. The rest of this diff (App.tsx additions forai/autonomy,ai/activity,customisation/import-resultSettings routes; finance-report test-harness refactors torenderWithProviders; theinvoiceCount→SessionSalesSummaryCardinternal cleanup;formatDurationSincemerged intoformatDuration) is either outside the Reports feature area or test/internal-only and doesn't affect any documented report claim. (auto-applied by the truth pipeline; adversarially gated)
Warning: 2026-06-29 — new-stack re-verify for July-10 launch · codebase Verified_by:
codebase· Last verified: 2026-06-29 ·foodops-admin-app@4173811(main) +foodops.counter.app@32669a9(main) +hubits-api-sales@663dae0(main) Re-pointed the whole catalogue off the retired legacy monolith onto the new admin app. The report surface is now ~28 per-route React pages underapps/admin/src/pages/reports/*(registeredApp.tsx:1266-1549), with a persistentReportsSidebarrail; route convention changed/reports/view/{slug}→/reports/<name>. Updated every category table's identifier column to the new route and verified each route exists at HEAD. NOT ported to the new admin set (no matching route): Brand-Contribution (2), Order-Quantity-by-category (2), Invoice/Sales-Order/Quotes Details, Purchase-Receive/Expense-Record Details. NEW reports added: Sales By Daypart, Sales By Service Type, Voids & Comps, Covers (/reports/sales-by-daypart|sales-by-service-type|voids-and-comps|covers). Finance reports (P&L/BS/TB/VAT-return/etc.) areINCLUDE_FINANCE-gated → tree-shaken out of the foodops build (only on finance-v2). Documented the X-Report / Z-Report printable register close-out artifact (utils/sessionXZReport.tsbuildSessionXZReportHtml, X=READING / Z=FINAL, autowindow.print()), reached from/sales/register-sessions/:id. Tax is still computed per-line at order close (counterorderCalculationService.ts). Frontmatter droppedhaafai.app.foodops+haafai.app.commonlegacy globs. Open: KPI-strip-on-/reports-landing and live screenshots still UI-TODO (no Playwright this pass).
Note: Code + Playwright verified 2026-05-04 · codebase + live E2E Verified_by:
playwright· Last verified: 2026-05-04 Playwright pass (2026-05-04) confirmed: Payments Report at/reports/paymentsreportis a React counter-style page with date-range-only filter, 10-column table (REFERENCE/PAYMENT DATE/PAYMENT REFERENCE/REMARKS/CUSTOMER/PAYMENT METHOD/INVOICES/DEPOSIT TO/TOTAL/UNUSED), and PDF+CSV export + History buttons (screenshot 167). Day Summary Report shows Date Range + Outlet + Register + "Report By" (Register Session) filters and PDF+CSV export + History buttons at top-right — export buttons were re-enabled (screenshot 166). "Report By" selector reverted — present in both surfaces as of 2026-05-04. Code-verified claims updated accordingly; status downgraded fromstabletoneeds-reviewis partially resolved — the "Report By" discrepancy no longer exists. Status remainsneeds-reviewfor the broader entry that hasn't been playwright-verified beyond these two pages.
Note: 2026-06-04 — Content re-verify (mostly no-change) · codebase Verified_by:
codebase· Last verified: 2026-06-04 Re-verified againstfoodops.counter.app@24d27ba,hubits-api-sales@720407c(main),haafai.app.common@master. The brief's driver0198d25is a counter.app sub-resource-services commit, not report content — no catalogue impact. The report catalogue (34 reports / 9 categories) is unchanged — no user-facing behaviour change to the documented reports, filters, slugs, or KPI tiles. Backend/polish only: new daypart/service-type/voids-comps API endpoints exist but have no UI cards (bcaadac6); org-timezone now drives report date/time display (b7b92257/cdd45cee, cosmetic); currency + cash-difference fixes on PaymentMethodSummary/RegisterSessions (c1dd098c/24d27baf). Added a "since 2026-05-04" subsection capturing these. No Playwright this pass.
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.