Repository navigation
fix(reports): scope shift X exports and split tax - #1403
Conversation
- Allocate cross-shift paid bill historical tax components proportionally across contributing cash sessions with deterministic minor-unit remainder reconciliation in computeSessionAggregates without recomputing from live tax settings. - Pass selectedSessionId from useCashClose.exportReport and scope GET /api/reports/x-report/export (CSV, XLSX, HTML/PDF) with matching shift validation, authorization, shift labels, and shift filenames. - Add cross-shift tax and shift-scoped export integration/hook regression tests and update API/permissions docs.
…and shift exports
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
PR Summary by QodoFix cross-shift tax attribution and shift-scoped X-report exports
AI Description
Diagram
High-Level Assessment
Files changed (12)
|
Code Review by Qodo
1.
|
Include partially paid bills in session X/Z tax aggregation and allocate historical tax components sequentially across payment lines against the remaining bill balance in minor units so shifts closed before settlement retain their collected tax share. Resolve untagged legacy payment lines deterministically across overlapping shifts via cash_sessions independent of the caller session, and reuse mergeDisplayTaxComponents from tax-components.ts.
…hip and export test"}
… in closure tax queries"}
…hift tax attribution
|
Fixed the partial-close-before-settlement tax conservation finding in 99eba49 (commits 5540eaa, 8978cc6, and 98f7770): session-scoped X/Z tax aggregation in |
Fixes #1402
Intent
Fix the two confirmed per-staff shift reporting defects identified in the secondary review of merged PRs #1395 and #1393:
cash_session_idvalues), attribute the bill's stored historical tax components across the contributing sessions proportionally to each session's collected payment share (preserving the bill's exact total tax across all contributing sessions via deterministic minor-unit remainder allocation) rather than attributing 100% of the bill's tax to every session that touched the bill. Never recompute historical tax from current live tax settings.CSV,XLSX, andPDF) ignoring selected shift: When an operator selects a specific staff shift (session_id) in the cash-close / X-report UI and exports (CSV,XLSX, orPDF), passsession_idfrom the frontend and makeGET /api/reports/x-report/exporthonorsession_idwith the same session aggregation and authorization rules asGET /api/reports/x-report, including the shift identity in the exported document and filename rather than silently exporting whole-day figures.What Changed
computeSessionAggregates(),computePeriodAggregates(),mergeDisplayTaxComponents(),reconcileSnapshotAllocations()), excluding cancelled unsettled orders and anchoring pre-settlement timestamp-less payment lines tobills.created_atso partial and cross-shift payments conserve exact bill tax without recomputing from live tax settings.GET /api/reports/x-report/export,cash-close-export.ts, anduseCashCloseto pass and enforcesession_idwith the same validation, shift-ownership checks, and session aggregation asGET /api/reports/x-report, emittingShift #<id> - <staff>metadata andx-report-<date>-shift-<id>.<ext>filenames across CSV, XLSX, and HTML/PDF exports.tests/cash-sessions.test.tsandtests/reports-export.test.tsand updateddocs/architecture and API references for cross-shift tax attribution and shift-scoped X-report exports.Risk Assessment
✅ Low: Cross-shift historical tax allocation and shift-scoped X-report exports are well-bounded, conserve minor-unit tax totals without recomputing from live settings, enforce shared shift ownership rules, and cleanly resolve both Round 1 findings on cancelled unsettled orders and legacy payment-line window bounds.
Testing
Built the backend and static desktop frontend, ran targeted suites (
test:cash-sessions,test:reports-exportincluding ElectronprintToPDF,test:cash-closures, andtest:tax-components), and drove all five derived scenarios (including live tax-pack mutation, cancelled unsettled partial-payment order, and cross-shift/malformedsession_idexport guards) against a real isolated FloCafe server via Playwright Chromium and Electron PDF rendering, capturing UI screenshots, shift-scoped CSV/XLSX/HTML/PDF exports, and SQLite closure reconciliation; everything passed.npm run test:cash-sessionsnpm run test:cash-sessionsx-report-<date>-shift-<id>.<ext>f…reports.view, and malformed or nonexistentsession_idvalues are rejected byGET /api/reports/x-report/exportnpm run test:cash-sessionsEvidence: Desktop Electron printToPDF output for Shift #2 X-report (x-report-2026-10-29-shift-2.pdf)
Evidence: UI-downloaded shift-scoped CSV export (x-report-2026-10-29-shift-2.csv)
Evidence: UI-downloaded all-shifts baseline CSV export (x-report-2026-10-29-all-shifts.csv)
~/.no-mistakes/evidence/01M640PZ3VDN1AMED0J1STNB0R/x-report-2026-10-29-shift-2.xlsx)Evidence: Shift-scoped HTML export document for Shift #2 (x-report-2026-10-29-shift-2.html)
~/.no-mistakes/evidence/01M640PZ3VDN1AMED0J1STNB0R/live-validation-summary.json)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
main/routes/cash-closures.ts:1018- Session tax aggregation incomputePeriodAggregatesbroadenedWHERE b.paid_at IS NOT NULLtoWHERE b.payment_details IS NOT NULLwithout excluding unsettled bills whose parent order was cancelled (o.status = 'cancelled'). When an operator records a partial payment on a bill during a shift and a manager subsequently cancels the order viaPATCH /api/orders/:id/status(status: 'cancelled',main/routes/orders.ts:2981-2988),orders.statusand allorder_items.statusbecome'cancelled'whilebills.paid_atstaysNULLandbills.tax_snapshot/bills.tax_amountremain populated.resolveTaxComponents(doc)(main/services/tax-components.ts:252-319) then sees zero non-terminal items and falls back todocument.tax_snapshot, causingGET /api/reports/x-report?session_id=...,GET /api/reports/x-report/export?session_id=..., andPOST /api/cash-sessions/:id/close(immutablecash_closures.tax_components_json) to attribute proportional tax for the cancelled order while whole-day X/Z (main/routes/cash-closures.ts:1060,WHERE b.paid_at >= ? AND b.paid_at < ?),GET /api/reports/tax-components(main/routes/reports.ts:477,AND o.status != 'cancelled'), andshiftUnpaidBillsInWindow(main/services/cash-day-close-guards.ts:91,AND o.status != 'cancelled') all report zero. Fixmain/routes/cash-closures.ts:1016-1018by joiningorders o ON o.id = b.order_idand requiring(b.paid_at IS NOT NULL OR o.status != 'cancelled').main/routes/cash-closures.ts:1030- IncomputePeriodAggregates, the sessionTaxBillRowEXISTSpredicate removed theline_time >= datetime(session.opened_at)lower-bound check on untagged legacy payment lines (cash_session_id IS NULL) and placed the correlated(SELECT cs.id FROM cash_sessions cs WHERE ... ORDER BY datetime(cs.opened_at) DESC, cs.id DESC LIMIT 1)subquery ahead of any window filter. Becausecash_sessionshas no index onopened_atorclosed_atandline_time < datetime(end)is true for every historical bill from the past, upgraded databases execute a full scan and sort ofcash_sessionsfor every pre-v91 payment line inbillssynchronously on the main thread (O(N_legacy_lines * N_sessions)). Because thecash_sessionssubquery can matchsession.idonly whenline_time >= datetime(session.opened_at), guard the untagged branch inmain/routes/cash-closures.ts:1027-1049withNULLIF(json_extract(je.value, '$.cash_session_id'), '') IS NULL AND ${LEGACY_STABLE_PAYMENT_LINE_TIME_SQL} >= datetime(?) AND ${LEGACY_STABLE_PAYMENT_LINE_TIME_SQL} < datetime(?)before evaluating thecash_sessionsoverlap tie-breaker.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
npm run test:cash-sessionsnpm run test:cash-sessionsx-report-<date>-shift-<id>.<ext>f…reports.view, and malformed or nonexistentsession_idvalues are rejected byGET /api/reports/x-report/exportnpm run test:cash-sessionsnpm run build && npm run build:frontendnpm run test:cash-sessionsnpm run test:reports-exportnpm run test:cash-closuresnpm run test:tax-componentsELECTRON_RUN_AS_NODE=1 FLO_DEV_USER_DATA=/tmp/flo-live-shift-8c33b41f PORT=43101 KDS_PORT=43102 SERVER_APP_PORT=43103 "$(node -p "require('electron')")" dev-server.js(isolated live FloCafe instance driving multi-shift billing, live tax mutation, cancelled partial order, export authz/validation, Playwright Chromium Close Day shift-scoped CSV/XLSX/PDF exports, Day Z close, ElectronwebContents.printToPDF, and SQLitecash_closuresreconciliation)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.