Skip to content

feat(reports): PDF export for X, Z, daily sales - #1393

Merged
khaira777 merged 10 commits into
mainfrom
fm/flocafe-1334-pdf-reports
Oct 28, 2026
Merged

khaira777 merged 10 commits into
mainfrom
fm/flocafe-1334-pdf-reports

Conversation

@khaira777

@khaira777 khaira777 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1334

Intent

{"summary": "The developer wanted to align and specify a six-task batch for FloCafe covering routing tests, date-range and PDF sales reports, PIN user switching, payment-method correction, and per-user shifts, and to produce a verified handoff specification covering these decisions and fixes for reproduced defects. For PIN switching, both manual till locking and an optional store-configurable idle auto-lock (disabled by default, with fixed 1, 5, 15, or 30 minute intervals) must preserve the unfinished terminal cart while clearing prior approval and PIN state, rechecking the new user's permissions, and attributing subsequent writes and payment to the newly signed-in user. For payment-method corrections, the developer required supporting cash, card, and active custom tenders while explicitly refusing corrections to or from FloCafe wallet payments. For reporting and accountant exports, daily-sales PDFs must support selected date ranges alongside XLSX and CSV and contain period, daily, payment-method, item, and tax breakdowns (leaving individual payment references to XLSX and CSV), while tax exports must list named taxes separately with labeled shared bases and flag historical records with missing rate or base detail as unknown. Finally, the developer required packaging the work as six separate PRs against main with documented prerequisite order and combined verification, leaving the PRs unmerged after passing tests, review, and CI for later approval."}

What Changed

  • Added format=html export support to /api/reports/x-report/export, /api/reports/z-report/export, and /api/reports/daily-sales/export via serializeXReportHtml, serializeZReportHtml, and serializeDailySalesExportHtml, producing print-styled HTML documents with regional currency/number formatting, tax breakdowns, and a 15 MB size cap on daily-sales HTML exports, and enabled preferCSSPageSize: true in renderHtmlToPdfBuffer.
  • Added exportReportAsPdf (frontend/src/lib/export-pdf.ts) and wired localized PDF (.pdf) export actions (dashboard.exportPdf across all 24 locales) into the dashboard daily-sales export menu and the X/Z report menus in CashCloseModal, using window.electronAPI.saveHtmlAsPdf on desktop and a gesture-reserved browser print window on web.
  • Added unit, integration, translation, and Playwright E2E tests covering X, Z, and daily-sales HTML/PDF exports (including XLSX data parity, date ranges, non-Latin store names, HTML escaping, and native Electron printToPDF rendering) and updated the API, daily-sales export, and regional-settings documentation.

Risk Assessment

✅ Low: The changes cleanly add print-styled HTML serialization (format=html) and PDF export support for X reports, Z reports, and Daily Sales reports across both desktop (saveHtmlAsPdf IPC with offscreen BrowserWindow.webContents.printToPDF and preferCSSPageSize: true) and browser print fallbacks. Existing permission boundaries (reports.view and reports.daily-sales.export), HTML escaping, payload size bounds (MAX_DAILY_SALES_HTML_BYTES matching MAX_PDF_HTML_BYTES), popup gesture reservation/cleanup, and regional formatting contracts are properly enforced and covered by unit, integration, and E2E tests.

Testing

Ran focused backend, IPC, translation, and real Electron printToPDF suites (npm run test:reports-export, npm run test:daily-sales-export, tests/printer-ipc.test.ts, npm run test:translations) plus live Chromium Playwright E2E checks (e2e/cash-close-phase1-ui.spec.ts and an interactive browser run covering date-range Daily Sales PDF export and popup-blocked error handling), capturing generated PDFs, rendered report page PNGs, and UI screenshots in the evidence directory; all checks passed.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Owner exports single-day and multi-day date-range Daily Sales reports as PDF/HTML with Summary, Payment Methods, Daily Breakdown, Tax Summary, and Item Sales reconciling with XLSX/CSV without row trun… ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-2026-09-25_to_2026-09-27.pdf, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-page…
Operator exports X Report and Z Report as A4 portrait PDFs via save-html-as-pdf with Summary, Payment Methods, Tax Breakdown, and Staff Sales matching XLSX/CSV ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-2026-09-25.pdf, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/z-report-Z1-2026-09-25.pdf, /User…
Owner triggers PDF (.pdf) export from the Dashboard (single-day browser print fallback and date-range desktop IPC) and from the Close Day modal for X Report and Z Report ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/ui-dashboard-export-sales-menu.png, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/ui-cash-close-x-report…
Adversarial: unauthorized roles, unclosed Z-report dates, invalid formats, HTML/script injection payloads, oversize HTML (>15 MB), save-dialog cancellation, and blocked browser popups are safely rejec… ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-page-1.png, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/verification-summary.json, `npm run t…

Dashboard Export Sales menu with PDF (.pdf) option
Close Day modal Step 1 (X Report) Export Sales menu with PDF (.pdf) option
Close Day modal Step 3 (Z Report) Export Sales menu with PDF (.pdf) option
Rendered Daily Sales date-range report page 1 (Summary & Incomplete Tax Detail warning)
Rendered Daily Sales date-range report page 2 (Payment Methods, Daily Breakdown, Tax Summary, Item Sales)
Rendered X Report PDF page 1 (A4 portrait)
Rendered Z Report PDF page 1 (A4 portrait)

Evidence: Generated Daily Sales date-range PDF via Electron printToPDF
%PDF-1.4
%

... [326950 bytes truncated] ...

 
0000089126 00000 n 
0000089819 00000 n 
0000090555 00000 n 
0000090946 00000 n 
0000091932 00000 n 
0000092310 00000 n 
0000092724 00000 n 
0000094697 00000 n 
0000094926 00000 n 
0000095523 00000 n 
0000096172 00000 n 
0000096978 00000 n 
0000098090 00000 n 
0000098266 00000 n 
0000099379 00000 n 
0000099502 00000 n 
0000099631 00000 n 
0000101279 00000 n 
0000101485 00000 n 
0000101552 00000 n 
0000102118 00000 n 
0000102686 00000 n 
0000103061 00000 n 
0000103160 00000 n 
0000103504 00000 n 
0000104294 00000 n 
0000104630 00000 n 
0000106751 00000 n 
0000106957 00000 n 
0000107589 00000 n 
0000108155 00000 n 
0000108703 00000 n 
0000109264 00000 n 
0000109868 00000 n 
0000110199 00000 n 
0000110472 00000 n 
0000110670 00000 n 
0000113000 00000 n 
0000113206 00000 n 
0000113305 00000 n 
0000113873 00000 n 
0000114192 00000 n 
0000114676 00000 n 
0000115246 00000 n 
0000115559 00000 n 
0000116276 00000 n 
0000116574 00000 n 
0000116942 00000 n 
0000117124 00000 n 
0000117303 00000 n 
0000117625 00000 n 
0000117789 00000 n 
0000119943 00000 n 
0000120149 00000 n 
0000120647 00000 n 
0000121143 00000 n 
0000121904 00000 n 
0000122501 00000 n 
0000123340 00000 n 
0000123493 00000 n 
0000124331 00000 n 
0000124711 00000 n 
0000125095 00000 n 
0000125194 00000 n 
0000125326 00000 n 
0000125567 00000 n 
0000125906 00000 n 
0000126174 00000 n 
0000126420 00000 n 
0000126515 00000 n 
0000128771 00000 n 
0000128984 00000 n 
0000129480 00000 n 
0000130019 00000 n 
0000130679 00000 n 
0000131572 00000 n 
0000131729 00000 n 
0000132632 00000 n 
0000132772 00000 n 
0000132873 00000 n 
0000133120 00000 n 
0000133472 00000 n 
0000135183 00000 n 
0000135389 00000 n 
0000135538 00000 n 
0000136203 00000 n 
0000136416 00000 n 
0000136611 00000 n 
0000136710 00000 n 
0000136912 00000 n 
0000137094 00000 n 
0000137482 00000 n 
0000139642 00000 n 
0000139855 00000 n 
0000139954 00000 n 
0000140539 00000 n 
0000141334 00000 n 
0000141645 00000 n 
0000143132 00000 n 
0000143345 00000 n 
0000144007 00000 n 
0000144280 00000 n 
0000144484 00000 n 
0000146695 00000 n 
0000146908 00000 n 
0000147245 00000 n 
0000149363 00000 n 
0000149576 00000 n 
0000149643 00000 n 
0000149838 00000 n 
0000150439 00000 n 
0000150845 00000 n 
0000151010 00000 n 
0000151169 00000 n 
0000151268 00000 n 
0000151470 00000 n 
0000151613 00000 n 
0000151809 00000 n 
0000151991 00000 n 
0000152561 00000 n 
0000152926 00000 n 
0000153314 00000 n 
0000154162 00000 n 
0000154539 00000 n 
0000156807 00000 n 
0000157020 00000 n 
0000157169 00000 n 
0000157542 00000 n 
0000157731 00000 n 
0000157953 00000 n 
0000158117 00000 n 
0000159202 00000 n 
0000159431 00000 n 
0000159504 00000 n 
0000159718 00000 n 
0000160447 00000 n 
0000161194 00000 n 
0000161665 00000 n 
0000162479 00000 n 
0000162589 00000 n 
0000162813 00000 n 
0000163022 00000 n 
0000163225 00000 n 
0000163887 00000 n 
0000164295 00000 n 
0000164992 00000 n 
0000165435 00000 n 
0000166493 00000 n 
0000166878 00000 n 
0000169255 00000 n 
0000169484 00000 n 
0000170286 00000 n 
0000171036 00000 n 
0000171752 00000 n 
0000172494 00000 n 
0000173259 00000 n 
0000173643 00000 n 
0000174610 00000 n 
0000175028 00000 n 
0000175336 00000 n 
0000175560 00000 n 
0000177998 00000 n 
0000178227 00000 n 
0000178357 00000 n 
0000179111 00000 n 
0000179507 00000 n 
0000180200 00000 n 
0000180936 00000 n 
0000181327 00000 n 
0000182313 00000 n 
0000182691 00000 n 
0000183158 00000 n 
0000183333 00000 n 
0000183539 00000 n 
0000183743 00000 n 
0000184157 00000 n 
0000186310 00000 n 
0000186539 00000 n 
0000186707 00000 n 
0000187135 00000 n 
0000187307 00000 n 
0000187528 00000 n 
0000188551 00000 n 
0000188787 00000 n 
0000188860 00000 n 
0000189621 00000 n 
0000190393 00000 n 
0000190877 00000 n 
0000191047 00000 n 
0000191899 00000 n 
0000192009 00000 n 
0000192238 00000 n 
0000192460 00000 n 
0000192664 00000 n 
0000193337 00000 n 
0000193766 00000 n 
0000194230 00000 n 
0000195308 00000 n 
0000195681 00000 n 
0000198034 00000 n 
0000198270 00000 n 
0000198400 00000 n 
0000199166 00000 n 
0000199568 00000 n 
0000200277 00000 n 
0000201054 00000 n 
0000201466 00000 n 
0000202504 00000 n 
0000202891 00000 n 
0000203377 00000 n 
0000203553 00000 n 
0000203764 00000 n 
0000203975 00000 n 
0000204404 00000 n 
0000204601 00000 n 
0000206862 00000 n 
0000207098 00000 n 
0000207966 00000 n 
0000208716 00000 n 
0000209494 00000 n 
0000210314 00000 n 
0000210724 00000 n 
0000211736 00000 n 
0000212158 00000 n 
0000212474 00000 n 
0000212705 00000 n 
0000215112 00000 n 
0000215318 00000 n 
0000215816 00000 n 
0000215970 00000 n 
0000216482 00000 n 
0000217253 00000 n 
0000217846 00000 n 
0000218676 00000 n 
0000218829 00000 n 
0000219719 00000 n 
0000220552 00000 n 
0000220932 00000 n 
0000221316 00000 n 
0000221411 00000 n 
0000221773 00000 n 
0000223777 00000 n 
0000223983 00000 n 
0000224615 00000 n 
0000225181 00000 n 
0000225729 00000 n 
0000226290 00000 n 
0000226894 00000 n 
0000227209 00000 n 
0000227949 00000 n 
0000228280 00000 n 
0000228553 00000 n 
0000228751 00000 n 
0000231129 00000 n 
0000231335 00000 n 
0000231434 00000 n 
0000232002 00000 n 
0000232321 00000 n 
0000232805 00000 n 
0000233375 00000 n 
0000233688 00000 n 
0000234405 00000 n 
0000234703 00000 n 
0000235071 00000 n 
0000235224 00000 n 
0000235406 00000 n 
0000235585 00000 n 
0000235907 00000 n 
0000236071 00000 n 
0000238249 00000 n 
0000238455 00000 n 
0000238953 00000 n 
0000239106 00000 n 
0000239602 00000 n 
0000240363 00000 n 
0000240744 00000 n 
0000241341 00000 n 
0000242180 00000 n 
0000242333 00000 n 
0000243216 00000 n 
0000244054 00000 n 
0000244552 00000 n 
0000244706 00000 n 
0000245218 00000 n 
0000245989 00000 n 
0000246363 00000 n 
0000246956 00000 n 
0000247786 00000 n 
0000247939 00000 n 
0000248829 00000 n 
0000249662 00000 n 
0000249796 00000 n 
0000249895 00000 n 
0000249995 00000 n 
0000250127 00000 n 
0000250368 00000 n 
0000250707 00000 n 
0000250975 00000 n 
0000251130 00000 n 
0000251284 00000 n 
0000251379 00000 n 
0000251812 00000 n 
0000254153 00000 n 
0000254389 00000 n 
0000254556 00000 n 
0000254778 00000 n 
0000255738 00000 n 
0000255944 00000 n 
0000256011 00000 n 
0000256200 00000 n 
0000256766 00000 n 
0000257334 00000 n 
0000257496 00000 n 
0000257683 00000 n 
0000258308 00000 n 
0000258465 00000 n 
0000258564 00000 n 
0000258760 00000 n 
0000258901 00000 n 
0000259090 00000 n 
0000259267 00000 n 
0000259611 00000 n 
0000259974 00000 n 
0000260764 00000 n 
0000263219 00000 n 
0000263425 00000 n 
0000263524 00000 n 
0000264092 00000 n 
0000264411 00000 n 
0000264895 00000 n 
0000265465 00000 n 
0000265778 00000 n 
0000266495 00000 n 
0000266793 00000 n 
0000267161 00000 n 
0000267570 00000 n 
0000267752 00000 n 
0000267931 00000 n 
0000268253 00000 n 
0000270332 00000 n 
0000270538 00000 n 
0000271170 00000 n 
0000271736 00000 n 
0000272284 00000 n 
0000272845 00000 n 
0000273449 00000 n 
0000274079 00000 n 
0000274710 00000 n 
0000275025 00000 n 
0000275356 00000 n 
0000275629 00000 n 
0000275827 00000 n 
0000278227 00000 n 
0000278433 00000 n 
0000279203 00000 n 
0000279548 00000 n 
0000281696 00000 n 
0000281902 00000 n 
0000282287 00000 n 
0000284382 00000 n 
0000284618 00000 n 
0000284743 00000 n 
0000284856 00000 n 
0000286790 00000 n 
0000287026 00000 n 
0000287958 00000 n 
0000289512 00000 n 
0000289741 00000 n 
0000290229 00000 n 
0000290710 00000 n 
0000290833 00000 n 
0000292307 00000 n 
0000292536 00000 n 
0000293453 00000 n 
0000295007 00000 n 
0000295213 00000 n 
0000295362 00000 n 
0000295715 00000 n 
0000295868 00000 n 
0000296826 00000 n 
0000297530 00000 n 
0000298899 00000 n 
0000299198 00000 n 
0000301094 00000 n 
0000301798 00000 n 
0000303295 00000 n 
0000304825 00000 n 
0000304893 00000 n 
0000305742 00000 n 
0000306041 00000 n 
0000306672 00000 n 
0000306740 00000 n 
0000307615 00000 n 
0000309646 00000 n 
0000318475 00000 n 
0000318713 00000 n 
0000319055 00000 n 
0000319551 00000 n 
0000320173 00000 n 
0000320438 00000 n 
0000320670 00000 n 
0000320947 00000 n 
0000321252 00000 n 
0000321521 00000 n 
0000321676 00000 n 
0000322012 00000 n 
trailer
<</Size 551
/Root 68 0 R
/Info 1 0 R>>
startxref
324051
%%EOF
Evidence: Generated X Report PDF via Electron printToPDF
%PDF-1.4
%

... [201920 bytes truncated] ...

pX
endstream
endobj
46 0 obj
<</Type /Font
/Subtype /Type3
/FontMatrix [.00048828125 0 0 -.00048828125 0 0]
/FirstChar 0
/LastChar 46
/FontBBox [68 495 1949 -1951]
/CIDToGIDMap /Identity
/ToUnicode 353 0 R
/FontDescriptor 304 0 R
/Widths [2048 0 0 0 0 0 1298 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1380]
/Encoding <</Type /Encoding
/Differences [0 /g0 /g0 /g0 /g0 /g0 /g0 /g105 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g12D]>>
/CharProcs <</g0 350 0 R
/g105 351 0 R
/g12D 352 0 R>>>>
endobj
xref
0 354
0000000000 65535 f 
0000000015 00000 n 
0000004685 00000 n 
0000000553 00000 n 
0000008381 00000 n 
0000012653 00000 n 
0000016763 00000 n 
0000021796 00000 n 
0000025286 00000 n 
0000028007 00000 n 
0000031658 00000 n 
0000035458 00000 n 
0000041809 00000 n 
0000047523 00000 n 
0000060194 00000 n 
0000061342 00000 n 
0000063065 00000 n 
0000066148 00000 n 
0000070227 00000 n 
0000076516 00000 n 
0000080442 00000 n 
0000085611 00000 n 
0000090356 00000 n 
0000093235 00000 n 
0000101505 00000 n 
0000104053 00000 n 
0000106713 00000 n 
0000113965 00000 n 
0000117674 00000 n 
0000121783 00000 n 
0000127998 00000 n 
0000134992 00000 n 
0000137560 00000 n 
0000145166 00000 n 
0000153029 00000 n 
0000160718 00000 n 
0000170882 00000 n 
0000174356 00000 n 
0000177859 00000 n 
0000180587 00000 n 
0000183042 00000 n 
0000184855 00000 n 
0000188753 00000 n 
0000194355 00000 n 
0000197415 00000 n 
0000199881 00000 n 
0000201950 00000 n 
0000000590 00000 n 
0000005393 00000 n 
0000005449 00000 n 
0000005568 00000 n 
0000005795 00000 n 
0000005977 00000 n 
0000006476 00000 n 
0000006979 00000 n 
0000007136 00000 n 
0000007327 00000 n 
0000007518 00000 n 
0000007810 00000 n 
0000008151 00000 n 
0000010084 00000 n 
0000010794 00000 n 
0000012139 00000 n 
0000012437 00000 n 
0000014380 00000 n 
0000015090 00000 n 
0000016466 00000 n 
0000018252 00000 n 
0000018319 00000 n 
0000018926 00000 n 
0000020256 00000 n 
0000021264 00000 n 
0000021578 00000 n 
0000023647 00000 n 
0000023874 00000 n 
0000023946 00000 n 
0000024781 00000 n 
0000024973 00000 n 
0000026107 00000 n 
0000026728 00000 n 
0000027476 00000 n 
0000027773 00000 n 
0000029729 00000 n 
0000030350 00000 n 
0000030619 00000 n 
0000031018 00000 n 
0000031346 00000 n 
0000033446 00000 n 
0000034067 00000 n 
0000034644 00000 n 
0000035151 00000 n 
0000036165 00000 n 
0000036392 00000 n 
0000037310 00000 n 
0000038092 00000 n 
0000038983 00000 n 
0000039611 00000 n 
0000040051 00000 n 
0000041158 00000 n 
0000041474 00000 n 
0000043659 00000 n 
0000043887 00000 n 
0000044011 00000 n 
0000044431 00000 n 
0000045262 00000 n 
0000045676 00000 n 
0000046796 00000 n 
0000047191 00000 n 
0000048761 00000 n 
0000059228 00000 n 
0000059472 00000 n 
0000059837 00000 n 
0000060341 00000 n 
0000060570 00000 n 
0000060811 00000 n 
0000061111 00000 n 
0000062014 00000 n 
0000062243 00000 n 
0000062316 00000 n 
0000062759 00000 n 
0000064855 00000 n 
0000065084 00000 n 
0000065849 00000 n 
0000067482 00000 n 
0000067711 00000 n 
0000068404 00000 n 
0000069140 00000 n 
0000069531 00000 n 
0000069909 00000 n 
0000071418 00000 n 
0000071647 00000 n 
0000072244 00000 n 
0000072893 00000 n 
0000073699 00000 n 
0000074811 00000 n 
0000075924 00000 n 
0000076047 00000 n 
0000076176 00000 n 
0000077793 00000 n 
0000077999 00000 n 
0000078066 00000 n 
0000078632 00000 n 
0000079200 00000 n 
0000079575 00000 n 
0000079919 00000 n 
0000080243 00000 n 
0000082247 00000 n 
0000082453 00000 n 
0000082772 00000 n 
0000083256 00000 n 
0000083569 00000 n 
0000084286 00000 n 
0000084584 00000 n 
0000084952 00000 n 
0000085274 00000 n 
0000087207 00000 n 
0000087413 00000 n 
0000088045 00000 n 
0000088593 00000 n 
0000089154 00000 n 
0000089758 00000 n 
0000090031 00000 n 
0000092137 00000 n 
0000092343 00000 n 
0000092682 00000 n 
0000092928 00000 n 
0000094814 00000 n 
0000095027 00000 n 
0000095523 00000 n 
0000095680 00000 n 
0000096219 00000 n 
0000097036 00000 n 
0000097441 00000 n 
0000098101 00000 n 
0000098994 00000 n 
0000099962 00000 n 
0000100865 00000 n 
0000100966 00000 n 
0000101309 00000 n 
0000102783 00000 n 
0000102996 00000 n 
0000103063 00000 n 
0000103162 00000 n 
0000103344 00000 n 
0000103732 00000 n 
0000105864 00000 n 
0000106077 00000 n 
0000106414 00000 n 
0000108532 00000 n 
0000108745 00000 n 
0000108812 00000 n 
0000109007 00000 n 
0000109608 00000 n 
0000110014 00000 n 
0000110179 00000 n 
0000110338 00000 n 
0000110497 00000 n 
0000110699 00000 n 
0000110842 00000 n 
0000111038 00000 n 
0000111220 00000 n 
0000111790 00000 n 
0000112155 00000 n 
0000112543 00000 n 
0000113391 00000 n 
0000113769 00000 n 
0000116040 00000 n 
0000116253 00000 n 
0000116402 00000 n 
0000116775 00000 n 
0000116964 00000 n 
0000117186 00000 n 
0000117350 00000 n 
0000118435 00000 n 
0000118664 00000 n 
0000118737 00000 n 
0000118951 00000 n 
0000119698 00000 n 
0000119907 00000 n 
0000120110 00000 n 
0000120772 00000 n 
0000121215 00000 n 
0000121552 00000 n 
0000123687 00000 n 
0000123916 00000 n 
0000124718 00000 n 
0000125434 00000 n 
0000126176 00000 n 
0000126941 00000 n 
0000127359 00000 n 
0000127667 00000 n 
0000129834 00000 n 
0000130063 00000 n 
0000130193 00000 n 
0000130947 00000 n 
0000131343 00000 n 
0000132036 00000 n 
0000132427 00000 n 
0000133413 00000 n 
0000133791 00000 n 
0000134258 00000 n 
0000134433 00000 n 
0000134637 00000 n 
0000136685 00000 n 
0000136914 00000 n 
0000137082 00000 n 
0000137254 00000 n 
0000138197 00000 n 
0000138433 00000 n 
0000138506 00000 n 
0000139267 00000 n 
0000140039 00000 n 
0000140523 00000 n 
0000140702 00000 n 
0000140872 00000 n 
0000141724 00000 n 
0000141928 00000 n 
0000142601 00000 n 
0000143030 00000 n 
0000143494 00000 n 
0000144572 00000 n 
0000144935 00000 n 
0000147245 00000 n 
0000147481 00000 n 
0000147611 00000 n 
0000148013 00000 n 
0000148722 00000 n 
0000149499 00000 n 
0000149911 00000 n 
0000150949 00000 n 
0000151336 00000 n 
0000151822 00000 n 
0000152033 00000 n 
0000152244 00000 n 
0000152673 00000 n 
0000154766 00000 n 
0000155002 00000 n 
0000155870 00000 n 
0000156620 00000 n 
0000157398 00000 n 
0000158218 00000 n 
0000158628 00000 n 
0000159640 00000 n 
0000160062 00000 n 
0000160378 00000 n 
0000162603 00000 n 
0000162809 00000 n 
0000163307 00000 n 
0000163460 00000 n 
0000163956 00000 n 
0000164717 00000 n 
0000165098 00000 n 
0000165695 00000 n 
0000166534 00000 n 
0000167417 00000 n 
0000168255 00000 n 
0000168753 00000 n 
0000168907 00000 n 
0000169500 00000 n 
0000169599 00000 n 
0000169840 00000 n 
0000169995 00000 n 
0000170149 00000 n 
0000170302 00000 n 
0000170683 00000 n 
0000172631 00000 n 
0000172837 00000 n 
0000172904 00000 n 
0000173093 00000 n 
0000173247 00000 n 
0000174037 00000 n 
0000176228 00000 n 
0000176434 00000 n 
0000177204 00000 n 
0000177549 00000 n 
0000179697 00000 n 
0000179903 00000 n 
0000180288 00000 n 
0000182383 00000 n 
0000182619 00000 n 
0000182744 00000 n 
0000184153 00000 n 
0000184389 00000 n 
0000184556 00000 n 
0000185226 00000 n 
0000185432 00000 n 
0000186064 00000 n 
0000186612 00000 n 
0000187173 00000 n 
0000187777 00000 n 
0000188092 00000 n 
0000188423 00000 n 
0000190451 00000 n 
0000190657 00000 n 
0000190756 00000 n 
0000191324 00000 n 
0000191643 00000 n 
0000192127 00000 n 
0000192440 00000 n 
0000193157 00000 n 
0000193455 00000 n 
0000193823 00000 n 
0000194005 00000 n 
0000195916 00000 n 
0000196145 00000 n 
0000196633 00000 n 
0000197114 00000 n 
0000198436 00000 n 
0000198665 00000 n 
0000199582 00000 n 
0000201136 00000 n 
0000201342 00000 n 
0000201491 00000 n 
0000201644 00000 n 
trailer
<</Size 354
/Root 49 0 R
/Info 1 0 R>>
startxref
202576
%%EOF
Evidence: Generated Z Report PDF via Electron printToPDF
%PDF-1.4
%

... [212392 bytes truncated] ...

/g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g0 /g693]>>
/CharProcs <</g0 372 0 R
/g693 373 0 R>>>>
endobj
xref
0 375
0000000000 65535 f 
0000000015 00000 n 
0000005308 00000 n 
0000000565 00000 n 
0000009016 00000 n 
0000013288 00000 n 
0000017398 00000 n 
0000022431 00000 n 
0000025921 00000 n 
0000028642 00000 n 
0000032293 00000 n 
0000036093 00000 n 
0000042445 00000 n 
0000048160 00000 n 
0000060831 00000 n 
0000061925 00000 n 
0000063739 00000 n 
0000066822 00000 n 
0000070901 00000 n 
0000072935 00000 n 
0000079941 00000 n 
0000083892 00000 n 
0000089061 00000 n 
0000093806 00000 n 
0000097628 00000 n 
0000105953 00000 n 
0000108501 00000 n 
0000111161 00000 n 
0000118413 00000 n 
0000122122 00000 n 
0000126231 00000 n 
0000132446 00000 n 
0000139440 00000 n 
0000142008 00000 n 
0000149834 00000 n 
0000158493 00000 n 
0000166989 00000 n 
0000178155 00000 n 
0000181835 00000 n 
0000185355 00000 n 
0000188351 00000 n 
0000190825 00000 n 
0000193025 00000 n 
0000194824 00000 n 
0000199850 00000 n 
0000205805 00000 n 
0000209292 00000 n 
0000211758 00000 n 
0000000602 00000 n 
0000006028 00000 n 
0000006084 00000 n 
0000006203 00000 n 
0000006430 00000 n 
0000006612 00000 n 
0000007111 00000 n 
0000007614 00000 n 
0000007771 00000 n 
0000007962 00000 n 
0000008153 00000 n 
0000008445 00000 n 
0000008786 00000 n 
0000010719 00000 n 
0000011429 00000 n 
0000012774 00000 n 
0000013072 00000 n 
0000015015 00000 n 
0000015725 00000 n 
0000017101 00000 n 
0000018887 00000 n 
0000018954 00000 n 
0000019561 00000 n 
0000020891 00000 n 
0000021899 00000 n 
0000022213 00000 n 
0000024282 00000 n 
0000024509 00000 n 
0000024581 00000 n 
0000025416 00000 n 
0000025608 00000 n 
0000026742 00000 n 
0000027363 00000 n 
0000028111 00000 n 
0000028408 00000 n 
0000030364 00000 n 
0000030985 00000 n 
0000031254 00000 n 
0000031653 00000 n 
0000031981 00000 n 
0000034081 00000 n 
0000034702 00000 n 
0000035279 00000 n 
0000035786 00000 n 
0000036800 00000 n 
0000037027 00000 n 
0000037945 00000 n 
0000038727 00000 n 
0000039618 00000 n 
0000040246 00000 n 
0000040686 00000 n 
0000041793 00000 n 
0000042109 00000 n 
0000044296 00000 n 
0000044524 00000 n 
0000044648 00000 n 
0000045068 00000 n 
0000045899 00000 n 
0000046313 00000 n 
0000047433 00000 n 
0000047828 00000 n 
0000049398 00000 n 
0000059865 00000 n 
0000060109 00000 n 
0000060474 00000 n 
0000060978 00000 n 
0000061207 00000 n 
0000061395 00000 n 
0000061694 00000 n 
0000062688 00000 n 
0000062917 00000 n 
0000062990 00000 n 
0000063433 00000 n 
0000065529 00000 n 
0000065758 00000 n 
0000066523 00000 n 
0000068156 00000 n 
0000068385 00000 n 
0000069078 00000 n 
0000069814 00000 n 
0000070205 00000 n 
0000070583 00000 n 
0000072092 00000 n 
0000072321 00000 n 
0000072636 00000 n 
0000074665 00000 n 
0000074894 00000 n 
0000075491 00000 n 
0000075663 00000 n 
0000076312 00000 n 
0000077118 00000 n 
0000078230 00000 n 
0000079343 00000 n 
0000079466 00000 n 
0000079595 00000 n 
0000081243 00000 n 
0000081449 00000 n 
0000081516 00000 n 
0000082082 00000 n 
0000082650 00000 n 
0000083025 00000 n 
0000083369 00000 n 
0000083693 00000 n 
0000085697 00000 n 
0000085903 00000 n 
0000086222 00000 n 
0000086706 00000 n 
0000087019 00000 n 
0000087736 00000 n 
0000088034 00000 n 
0000088402 00000 n 
0000088724 00000 n 
0000090657 00000 n 
0000090863 00000 n 
0000091495 00000 n 
0000092043 00000 n 
0000092604 00000 n 
0000093208 00000 n 
0000093481 00000 n 
0000095587 00000 n 
0000095793 00000 n 
0000095947 00000 n 
0000096327 00000 n 
0000096711 00000 n 
0000097050 00000 n 
0000097296 00000 n 
0000099262 00000 n 
0000099475 00000 n 
0000099971 00000 n 
0000100128 00000 n 
0000100667 00000 n 
0000101484 00000 n 
0000101889 00000 n 
0000102549 00000 n 
0000103442 00000 n 
0000104410 00000 n 
0000105313 00000 n 
0000105414 00000 n 
0000105757 00000 n 
0000107231 00000 n 
0000107444 00000 n 
0000107511 00000 n 
0000107610 00000 n 
0000107792 00000 n 
0000108180 00000 n 
0000110312 00000 n 
0000110525 00000 n 
0000110862 00000 n 
0000112980 00000 n 
0000113193 00000 n 
0000113260 00000 n 
0000113455 00000 n 
0000114056 00000 n 
0000114462 00000 n 
0000114627 00000 n 
0000114786 00000 n 
0000114945 00000 n 
0000115147 00000 n 
0000115290 00000 n 
0000115486 00000 n 
0000115668 00000 n 
0000116238 00000 n 
0000116603 00000 n 
0000116991 00000 n 
0000117839 00000 n 
0000118217 00000 n 
0000120488 00000 n 
0000120701 00000 n 
0000120850 00000 n 
0000121223 00000 n 
0000121412 00000 n 
0000121634 00000 n 
0000121798 00000 n 
0000122883 00000 n 
0000123112 00000 n 
0000123185 00000 n 
0000123399 00000 n 
0000124146 00000 n 
0000124355 00000 n 
0000124558 00000 n 
0000125220 00000 n 
0000125663 00000 n 
0000126000 00000 n 
0000128135 00000 n 
0000128364 00000 n 
0000129166 00000 n 
0000129882 00000 n 
0000130624 00000 n 
0000131389 00000 n 
0000131807 00000 n 
0000132115 00000 n 
0000134282 00000 n 
0000134511 00000 n 
0000134641 00000 n 
0000135395 00000 n 
0000135791 00000 n 
0000136484 00000 n 
0000136875 00000 n 
0000137861 00000 n 
0000138239 00000 n 
0000138706 00000 n 
0000138881 00000 n 
0000139085 00000 n 
0000141133 00000 n 
0000141362 00000 n 
0000141530 00000 n 
0000141702 00000 n 
0000142645 00000 n 
0000142881 00000 n 
0000142954 00000 n 
0000143170 00000 n 
0000143931 00000 n 
0000144703 00000 n 
0000145187 00000 n 
0000145366 00000 n 
0000145536 00000 n 
0000146388 00000 n 
0000146592 00000 n 
0000147265 00000 n 
0000147694 00000 n 
0000148158 00000 n 
0000149236 00000 n 
0000149603 00000 n 
0000151936 00000 n 
0000152172 00000 n 
0000152302 00000 n 
0000153068 00000 n 
0000153470 00000 n 
0000154179 00000 n 
0000154956 00000 n 
0000155368 00000 n 
0000156406 00000 n 
0000156793 00000 n 
0000157279 00000 n 
0000157490 00000 n 
0000157701 00000 n 
0000158130 00000 n 
0000160254 00000 n 
0000160490 00000 n 
0000161358 00000 n 
0000162135 00000 n 
0000162885 00000 n 
0000163663 00000 n 
0000164483 00000 n 
0000164893 00000 n 
0000165905 00000 n 
0000166327 00000 n 
0000166643 00000 n 
0000168899 00000 n 
0000169105 00000 n 
0000169603 00000 n 
0000169756 00000 n 
0000170252 00000 n 
0000171013 00000 n 
0000171394 00000 n 
0000171991 00000 n 
0000172830 00000 n 
0000173713 00000 n 
0000174551 00000 n 
0000175049 00000 n 
0000175203 00000 n 
0000175796 00000 n 
0000176626 00000 n 
0000176760 00000 n 
0000176859 00000 n 
0000177100 00000 n 
0000177255 00000 n 
0000177409 00000 n 
0000177562 00000 n 
0000177956 00000 n 
0000179941 00000 n 
0000180147 00000 n 
0000180214 00000 n 
0000180403 00000 n 
0000180565 00000 n 
0000180719 00000 n 
0000181509 00000 n 
0000183724 00000 n 
0000183930 00000 n 
0000184700 00000 n 
0000185045 00000 n 
0000187193 00000 n 
0000187399 00000 n 
0000187655 00000 n 
0000188040 00000 n 
0000190166 00000 n 
0000190402 00000 n 
0000190527 00000 n 
0000191936 00000 n 
0000192172 00000 n 
0000192339 00000 n 
0000192519 00000 n 
0000192711 00000 n 
0000193837 00000 n 
0000194043 00000 n 
0000194192 00000 n 
0000194345 00000 n 
0000194510 00000 n 
0000195619 00000 n 
0000195825 00000 n 
0000195924 00000 n 
0000196492 00000 n 
0000196811 00000 n 
0000197295 00000 n 
0000197608 00000 n 
0000198325 00000 n 
0000198623 00000 n 
0000198991 00000 n 
0000199173 00000 n 
0000199495 00000 n 
0000201502 00000 n 
0000201708 00000 n 
0000202340 00000 n 
0000202906 00000 n 
0000203454 00000 n 
0000204015 00000 n 
0000204619 00000 n 
0000204934 00000 n 
0000205265 00000 n 
0000205463 00000 n 
0000207793 00000 n 
0000208022 00000 n 
0000208510 00000 n 
0000208991 00000 n 
0000210313 00000 n 
0000210542 00000 n 
0000211459 00000 n 
trailer
<</Size 375
/Root 50 0 R
/Info 1 0 R>>
startxref
213013
%%EOF
Evidence: Real Electron printToPDF and layout verification summary
{
  "verifiedAt": "2026-10-28T21:58:31.928Z",
  "electronVersion": "44.9.1",
  "chromeVersion": "152.0.7977.167",
  "reports": {
    "xReport": {
      "pdfPath": "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-2026-09-25.pdf",
      "byteLength": 209737,
      "header": "%PDF-1.4",
      "pageCount": 1,
      "mediaBoxes": [
        [
          0,
          0,
          594.95996,
          841.91998
        ]
      ],
      "layout": {
        "scrollWidth": 794,
        "clientWidth": 794,
        "scrollHeight": 918,
        "clientHeight": 918,
        "overflowingCells": 0,
        "screenshots": [
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-page-1.png"
        ]
      }
    },
    "zReport": {
      "pdfPath": "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/z-report-Z1-2026-09-25.pdf",
      "byteLength": 220594,
      "header": "%PDF-1.4",
      "pageCount": 1,
      "mediaBoxes": [
        [
          0,
          0,
          594.95996,
          841.91998
        ]
      ],
      "layout": {
        "scrollWidth": 794,
        "clientWidth": 794,
        "scrollHeight": 926,
        "clientHeight": 918,
        "overflowingCells": 0,
        "screenshots": [
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/z-report-page-1.png",
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/z-report-page-2.png"
        ]
      }
    },
    "dailySalesRange": {
      "pdfPath": "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-2026-09-25_to_2026-09-27.pdf",
      "byteLength": 335152,
      "header": "%PDF-1.4",
      "pageCount": 4,
      "mediaBoxes": [
        [
          0,
          0,
          841.91998,
          594.95996
        ],
        [
          0,
          0,
          841.91998,
          594.95996
        ],
        [
          0,
          0,
          841.91998,
          594.95996
        ],
        [
          0,
          0,
          841.91998,
          594.95996
        ]
      ],
      "layout": {
        "scrollWidth": 1123,
        "clientWidth": 1123,
        "scrollHeight": 2106,
        "clientHeight": 762,
        "overflowingCells": 0,
        "screenshots": [
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-page-1.png",
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-page-2.png",
          "~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-page-3.png"
        ]
      }
    }
  }
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Owner exports single-day and multi-day date-range Daily Sales reports as PDF/HTML with Summary, Payment Methods, Daily Breakdown, Tax Summary, and Item Sales reconciling with XLSX/CSV without row trun… ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-2026-09-25_to_2026-09-27.pdf, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/daily-sales-page…
Operator exports X Report and Z Report as A4 portrait PDFs via save-html-as-pdf with Summary, Payment Methods, Tax Breakdown, and Staff Sales matching XLSX/CSV ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-2026-09-25.pdf, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/z-report-Z1-2026-09-25.pdf, /User…
Owner triggers PDF (.pdf) export from the Dashboard (single-day browser print fallback and date-range desktop IPC) and from the Close Day modal for X Report and Z Report ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/ui-dashboard-export-sales-menu.png, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/ui-cash-close-x-report…
Adversarial: unauthorized roles, unclosed Z-report dates, invalid formats, HTML/script injection payloads, oversize HTML (>15 MB), save-dialog cancellation, and blocked browser popups are safely rejec… ✅ pass live ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/x-report-page-1.png, ~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X/verification-summary.json, `npm run t…
  • FLO_PDF_ARTIFACTS_DIR=~/.no-mistakes/evidence/01M627TEMQZRV29688GA0TM76X npm run test:reports-export
  • npm run test:daily-sales-export
  • node tests/run-electron-node-test.cjs tests/printer-ipc.test.ts
  • npm run test:translations
  • cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/cash-close-phase1-ui.spec.ts
  • Live Chromium + E2E server verification of Dashboard single-day/date-range PDF export, popup-blocked error toast, and Close Day X/Z Report PDF export with screenshot capture
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add PDF and date-range exports for X, Z, and daily sales reports

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add PDF export for X, Z, and daily sales reports using print-ready HTML.
• Support daily sales ranges up to 93 days, with daily and historical tax breakdowns.
• Extend dashboard controls, translations, tests, and export documentation.
Diagram

graph TD
  UI["Dashboard controls"] --> API["Report routes"] --> Data["Report datasets"] --> Output["Export serializers"] --> Helper["PDF export helper"] --> Mode{"PDF environment"}
  Mode --> Desktop["Electron PDF IPC"]
  Mode --> Web["Browser print"]
Loading
High-Level Assessment

Keep the shared-dataset HTML approach and reuse the existing Electron PDF bridge and browser print flow. Generating PDFs on the server would introduce a separate rendering path or dependency without improving consistency with the existing XLSX and CSV accounting data.

Files changed (39) +6170 / -797

Enhancement (8) +3035 / -319
page.tsxAdd range controls and daily sales PDF export +145/-30

Add range controls and daily sales PDF export

• Introduces a bounded date-range dashboard view and enables XLSX, five-part CSV, and PDF exports for day, month, or selected range.

frontend/src/app/(dashboard)/dashboard/page.tsx

CashCloseModal.tsxOffer PDF in X and Z export menus +8/-0

Offer PDF in X and Z export menus

• Adds a PDF action alongside the existing cash-close report export choices.

frontend/src/components/dashboard/CashCloseModal.tsx

useCashClose.tsRoute X and Z PDF actions through shared export helper +17/-7

Route X and Z PDF actions through shared export helper

• Extends cash-close exports to request report HTML for PDF saving or printing while retaining XLSX and CSV downloads.

frontend/src/hooks/useCashClose.ts

export-pdf.tsShare desktop PDF saving and browser print fallback +59/-0

Share desktop PDF saving and browser print fallback

• Fetches a report’s HTML export, passes it to the existing Electron PDF API when available, and otherwise uses the reserved-popup browser print flow.

frontend/src/lib/export-pdf.ts

reports.tsExpose permission-gated HTML and range exports +277/-73

Expose permission-gated HTML and range exports

• Adds HTML responses for X, Z, and daily sales exports, validates daily sales date ranges and CSV parts, and accepts bounded date parameters for insights.

main/routes/reports.ts

cash-close-export.tsSerialize X and Z datasets as print-ready HTML +380/-3

Serialize X and Z datasets as print-ready HTML

• Adds escaped, locale-aware HTML reports with summary, payment, tax, and staff sections sourced from cash-close export data.

main/services/cash-close-export.ts

daily-sales-export-files.tsAdd daily and tax sheets, CSV parts, and range filenames +183/-9

Add daily and tax sheets, CSV parts, and range filenames

• Extends XLSX and CSV serialization with daily and per-tax-rate rows, adds range-aware filenames, and exposes the HTML serializer.

main/services/daily-sales-export-files.ts

daily-sales-export.tsBuild range-aware daily sales data and printable HTML +1966/-197

Build range-aware daily sales data and printable HTML

• Validates inclusive ranges up to 93 days and aggregates business-day sales, profitability, payments, and historically reconstructed taxes. Flags shared taxable bases and incomplete historical detail, reconciles tax amounts, and renders the dataset as escaped, locale-aware HTML.

main/services/daily-sales-export.ts

Tests (4) +2967 / -383
cash-close-phase1-ui.spec.tsExercise browser printing and X-report PDF handoff +74/-0

Exercise browser printing and X-report PDF handoff

• Adds a browser test for the daily sales print fallback and verifies the X-report PDF payload reaches a mocked Electron bridge.

frontend/e2e/cash-close-phase1-ui.spec.ts

daily-sales-export.test.tsCover ranges, tax reconciliation, and HTML parity +1405/-4

Cover ranges, tax reconciliation, and HTML parity

• Tests validation, business-day cutoffs, range-to-day equivalence, historical tax edge cases, new XLSX and CSV sections, permissions, and single-day and range HTML exports.

tests/daily-sales-export.test.ts

reports-export.test.tsVerify X and Z HTML exports and PDF rendering +266/-24

Verify X and Z HTML exports and PDF rendering

• Checks export permissions, escaped non-Latin content, HTML-to-XLSX metric parity, and cleanup of staged HTML after Electron PDF rendering.

tests/reports-export.test.ts

translations.test.tsKeep translation checks aligned with PDF labels +1222/-355

Keep translation checks aligned with PDF labels

• Adds the PDF label to intentional-identical translation allowances and reformats translation validation tests.

tests/translations.test.ts

Documentation (3) +72 / -47
regional-settings.mdDocument regional settings used by report HTML +8/-3

Document regional settings used by report HTML

• Adds X, Z, and daily sales HTML serialization to the regional-settings consumer list and refreshes related consumers.

docs/architecture/regional-settings.md

api.mdDocument range and HTML export API contracts +26/-23

Document range and HTML export API contracts

• Describes HTML support for X, Z, and daily sales exports, daily sales range validation and CSV parts, and date-scoped insights.

docs/reference/api.md

daily-sales-export.mdExplain multi-day accounting and PDF-ready HTML +38/-21

Explain multi-day accounting and PDF-ready HTML

• Documents range aggregation, historical tax reconciliation, new workbook and CSV sections, HTML limits, filenames, and test coverage.

docs/reference/daily-sales-export.md

Other (24) +96 / -48
ar.jsonTranslate Arabic range and export labels +4/-2

Translate Arabic range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ar.json

bn.jsonTranslate Bengali range and export labels +4/-2

Translate Bengali range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/bn.json

de.jsonTranslate German range and export labels +4/-2

Translate German range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/de.json

en.jsonName range and PDF options in English +4/-2

Name range and PDF options in English

• Adds the range and PDF labels and changes CSV guidance from three files to five.

frontend/src/lib/i18n/messages/en.json

es.jsonTranslate Spanish range and export labels +4/-2

Translate Spanish range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/es.json

fa.jsonTranslate Persian range and export labels +4/-2

Translate Persian range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/fa.json

fil.jsonTranslate Filipino range and export labels +4/-2

Translate Filipino range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/fil.json

fr.jsonTranslate French range and export labels +4/-2

Translate French range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/fr.json

hi.jsonTranslate Hindi range and export labels +4/-2

Translate Hindi range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/hi.json

id.jsonTranslate Indonesian range and export labels +4/-2

Translate Indonesian range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/id.json

it.jsonTranslate Italian range and export labels +4/-2

Translate Italian range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/it.json

ja.jsonTranslate Japanese range and export labels +4/-2

Translate Japanese range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ja.json

ko.jsonTranslate Korean range and export labels +4/-2

Translate Korean range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ko.json

ne.jsonTranslate Nepali range and export labels +4/-2

Translate Nepali range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ne.json

nl.jsonTranslate Dutch range and export labels +4/-2

Translate Dutch range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/nl.json

pt.jsonTranslate Portuguese range and export labels +4/-2

Translate Portuguese range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/pt.json

ru.jsonTranslate Russian range and export labels +4/-2

Translate Russian range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ru.json

sq.jsonTranslate Albanian range and export labels +4/-2

Translate Albanian range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/sq.json

th.jsonTranslate Thai range and export labels +4/-2

Translate Thai range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/th.json

tr.jsonTranslate Turkish range and export labels +4/-2

Translate Turkish range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/tr.json

ur.jsonTranslate Urdu range and export labels +4/-2

Translate Urdu range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/ur.json

vi.jsonTranslate Vietnamese range and export labels +4/-2

Translate Vietnamese range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/vi.json

zh-tw.jsonTranslate Traditional Chinese range and export labels +4/-2

Translate Traditional Chinese range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/zh-tw.json

zh.jsonTranslate Simplified Chinese range and export labels +4/-2

Translate Simplified Chinese range and export labels

• Adds the range and PDF labels and revises CSV guidance for five files.

frontend/src/lib/i18n/messages/zh.json

Comment thread tests/daily-sales-export.test.ts Fixed
Comment thread tests/reports-export.test.ts Fixed
Comment thread tests/reports-export.test.ts Fixed
@qodo-code-review

qodo-code-review Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Insights show sales for the wrong date ✓ Resolved
Description
/insights validates start_date and end_date with reportDate(), which checks their shape but
not whether they are real calendar dates. A request for 2026-02-30 passes validation, then
reportDayBounds() normalizes it to March 2 and queries that day's sales.
Code

main/routes/reports.ts[R1003-1006]

+    const startDateParam =
+      req.query.start_date === undefined ? undefined : reportDate(req.query.start_date, '');
+    const endDateParam =
+      req.query.end_date === undefined ? undefined : reportDate(req.query.end_date, '');
Relevance

●●● Strong

Report date and timezone correctness issues are routinely accepted, especially when invalid
boundaries alter financial results.

PR-#783
PR-#537

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
reportDate() accepts any string matching the date pattern. The new handler relies on that result,
while the business-date helper constructs bounds with Date.UTC, which normalizes out-of-range
days; those bounds filter the insights queries.

main/routes/reports.ts[135-137]
main/routes/reports.ts[1003-1026]
shared/business-date.ts[82-102]
main/routes/reports.ts[1050-1058]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new insights range accepts impossible calendar dates and queries normalized dates instead.

## Fix Focus Areas
- main/routes/reports.ts[1003-1012]

## Recommended Fix
Validate each supplied date by parsing its year, month, and day and round-tripping the resulting calendar date before calculating bounds. Return 400 for impossible dates.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. PDF totals remain unverified ✓ Resolved
Description
The PDF test renderer returns a fixed %PDF-1.4 buffer regardless of the HTML supplied, and the
only PDF assertions check that prefix and temporary-file cleanup. A changed report total or missing
report content can therefore pass the PDF parity test without being compared with the XLSX or HTML
data.
Code

tests/reports-export.test.ts[R16-24]

+    class FakePrintWindow {
+      webContents = {
+        on: () => undefined,
+        setWindowOpenHandler: () => undefined,
+        session: {
+          webRequest: { onBeforeRequest: () => undefined },
+        },
+        printToPDF: async () => Buffer.from('%PDF-1.4\n%FloCafe test PDF\n%%EOF\n'),
+      };
Relevance

●●● Strong

Recent test precedents accept stronger regression assertions when mocks can pass without validating
the requested behavior.

PR-#1267
PR-#1129

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires PDF model parity coverage. The added fake printToPDF ignores all input and
the resulting assertions verify only a constant prefix and cleanup, while the separate HTML checks
do not validate PDF content.

Export verification and documentation
tests/reports-export.test.ts[16-24]
tests/reports-export.test.ts[362-373]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PDF parity test uses a renderer mock that ignores its HTML input and validates only a constant file signature, so report content and totals are not verified against XLSX or HTML output.

## Fix Focus Areas
- tests/reports-export.test.ts[16-24]
- tests/reports-export.test.ts[362-373]

## Recommended Fix
Capture the HTML passed to the PDF renderer or use a content-aware test double, then assert that X and Z report totals and key report fields match the canonical XLSX or HTML metrics before checking staging cleanup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. One cash report PDF stays unverified ✓ Resolved
Description
The new end-to-end test claims to cover both cash reports but clicks the export control only once
and asserts only an X Report payload. The Z-report PDF action and its generated HTML are therefore
never exercised by the required UI verification.
Code

frontend/e2e/cash-close-phase1-ui.spec.ts[R186-215]

+    await page.getByRole('button', { name: 'Close day', exact: true }).click();
+    const dialog = page.getByRole('dialog').first();
+    await expect(dialog).toBeVisible({ timeout: 15000 });
+    await dialog.getByRole('button', { name: 'Export sales', exact: true }).click();
+    await page.getByRole('menuitem', { name: 'PDF (.pdf)' }).click();
+
+    await expect
+      .poll(() =>
+        page.evaluate(
+          () =>
+            (
+              window as unknown as {
+                __pdfCalls: Array<{ defaultFileName: string; pageSize: string; html: string }>;
+              }
+            ).__pdfCalls.length,
+        ),
+      )
+      .toBe(1);
+    const xPdfCall = await page.evaluate(
+      () =>
+        (
+          window as unknown as {
+            __pdfCalls: Array<{ defaultFileName: string; pageSize: string; html: string }>;
+          }
+        ).__pdfCalls[0],
+    );
+    expect(xPdfCall.defaultFileName).toMatch(/^x-report-\d{4}-\d{2}-\d{2}\.pdf$/);
+    expect(xPdfCall.pageSize).toBe('A4');
+    expect(xPdfCall.html).toContain('X Report');
+  });
Relevance

●●● Strong

Accepted E2E precedents consistently require each UI variant and meaningful payload assertion to be
exercised.

PR-#1269
PR-#1121

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires the PDF UI action and verification coverage for all report types. The added
test performs one cash-close export and asserts only an X-report payload, while the changed hook
supports separate X and Z endpoints.

Export verification and documentation
frontend/e2e/cash-close-phase1-ui.spec.ts[186-215]
frontend/src/hooks/useCashClose.ts[396-412]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PDF end-to-end test exercises only the X-report action even though it claims to verify both cash reports, leaving the Z-report UI and payload untested.

## Fix Focus Areas
- frontend/e2e/cash-close-phase1-ui.spec.ts[186-215]

## Recommended Fix
After the X-report assertion, open the Z-report export action for a closed day, click PDF, and assert a second IPC payload whose filename and HTML identify the Z report.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (4)
4. Large sales PDFs omit accounting rows ✓ Resolved
Description
serializeDailySalesExportHtml() takes only the first 2,000 tax rows and first 2,000 item rows,
while the other serializers use the complete dataset. When a range exceeds either threshold, the PDF
contains summary totals for rows its tax or item table does not show, unlike the corresponding
spreadsheet or CSV.
Code

main/services/daily-sales-export.ts[R2013-2014]

+          <td class="num" data-col="net_item_sales" data-value="${escapeHtml(String(item.net_item_sales))}">${fmtMoney(item.net_item_sales)}</td>
+          <td class="num" data-col="tax_amount" data-value="${escapeHtml(String(item.tax_amount))}">${fmtMoney(item.tax_amount)}</td>
Relevance

●●● Strong

The stated PDF parity intent makes truncating accounting rows a concrete correctness mismatch rather
than a cosmetic concern.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The HTML serializer defaults maxTableRows to 2,000 and slices both detail arrays. The export route
supplies no override, whereas the XLSX serializer iterates the dataset's complete tax and item
arrays.

main/services/daily-sales-export.ts[1787-1787]
main/services/daily-sales-export.ts[1867-1870]
main/services/daily-sales-export.ts[2013-2014]
main/services/daily-sales-export.ts[2040-2041]
main/routes/reports.ts[1696-1702]
main/services/daily-sales-export-files.ts[170-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The HTML/PDF serializer silently limits tax and item detail to 2,000 rows per table, so its detail does not reconcile with its own summary for larger exports.

## Fix Focus Areas
- main/services/daily-sales-export.ts[2013-2014]
- main/services/daily-sales-export.ts[2040-2041]

## Recommended Fix
Render all tax and item rows for the exported range, or explicitly decline PDF generation when the complete report cannot be rendered. Keep the summary and detail for a generated PDF reconcilable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Cutoff edge cases remain untested ✓ Resolved
Description
The new cutoff test inserts one transaction at 01:30 local time while the configured business-day
cutoff is 04:00 and asserts its day assignment. It does not exercise timestamps immediately before,
exactly at, or immediately after the cutoff, so boundary regressions can pass the required range and
cutoff verification.
Code

tests/daily-sales-export.test.ts[R910-913]

+        const day1At = dbTimestamp(new Date('2026-10-01T10:00:00Z')); // 15:30 IST on 2026-10-01
+        // 2026-10-03 01:30 IST = 2026-10-02 20:00 UTC -> with 04:00 cutoff, belongs to business day 2026-10-02!
+        const day2CutoffAt = dbTimestamp(new Date('2026-10-02T20:00:00Z'));
+        const day3At = dbTimestamp(new Date('2026-10-03T10:00:00Z')); // 15:30 IST on 2026-10-03
Relevance

●●● Strong

Cutoff-boundary coverage is explicitly prioritized in recent cash-session testing precedents.

PR-#1129
PR-#695

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist explicitly requires cutoff-boundary coverage. The test configures a 04:00 cutoff but
inserts only a 01:30 local-time record and asserts only that interior case.

Export verification and documentation
tests/daily-sales-export.test.ts[903-913]
tests/daily-sales-export.test.ts[1153-1161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The cutoff coverage tests only a timestamp well inside the prior business day and never verifies behavior at the configured 04:00 boundary.

## Fix Focus Areas
- tests/daily-sales-export.test.ts[910-913]
- tests/daily-sales-export.test.ts[1153-1161]

## Recommended Fix
Add records immediately before, exactly at, and immediately after the store-local cutoff, then assert each record is attributed to the expected business date in both single-day and range exports.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Large exports repeatedly scan refunds ✓ Resolved
Description
buildDailySalesExportDataset() executes the refund aggregate and paymentMethodBreakdown()
separately for every business date in a range. A 93-day export can therefore rescan the refunds
table at least 93 times through a created_at predicate with no matching index, in addition to
repeated refund scans in the payment breakdown.
Code

main/services/daily-sales-export.ts[1432]

+    const refundRow = refundAggStmt.get(minorFactor, start, end) as { refunds_issued: number };
Relevance

●● Moderate

Repeated refund scans are plausible performance debt, but historical evidence does not establish
team acceptance of this optimization.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both calls are inside the newly added range loop. The direct aggregate filters refunds.created_at
without an index on that column, while the payment breakdown's refund branch has no date predicate
before its final grouping filter.

main/services/daily-sales-export.ts[1275-1279]
main/services/daily-sales-export.ts[1419-1434]
main/services/daily-sales-export.ts[1559-1565]
main/routes/cash-closures.ts[414-443]
main/db.ts[6532-6533]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The 93-day export repeats unindexed refund scans for each day, including scans inside payment-method aggregation.

## Fix Focus Areas
- main/services/daily-sales-export.ts[1419-1434]
- main/services/daily-sales-export.ts[1559-1572]

## Recommended Fix
Fetch and group refunds for the requested period in one pass, then use those grouped totals for each daily row. Avoid calling a full-table refund breakdown for every date; add a suitable date index if a bounded date query remains.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Long insights ranges can stall reporting ✓ Resolved
Description
The new explicit-date branch of /insights bypasses the existing 365-day limit without imposing
another range cap. An authorized request spanning years runs every insights query over that period
and loads all matching order timestamps into memory for bucketing.
Code

main/routes/reports.ts[R1018-1021]

+    if (startDateParam || endDateParam) {
+      const effectiveStart = startDateParam || endDateParam!;
+      const effectiveEnd = endDateParam || startDateParam!;
+      if (effectiveStart > effectiveEnd) {
Relevance

●● Moderate

Potentially serious range-performance issue, but no close precedent confirms rejecting the
explicit-date range design.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Only the fallback days branch clamps its lookback to 365. The explicit-date branch has no
equivalent bound, and its timestamp query materializes every matching order before bucketing.

main/routes/reports.ts[1018-1045]
main/routes/reports.ts[1162-1175]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Explicit insights dates bypass the existing bounded lookback and permit arbitrarily large queries and timestamp arrays.

## Fix Focus Areas
- main/routes/reports.ts[1018-1035]
- main/routes/reports.ts[1162-1173]

## Recommended Fix
Enforce a documented maximum span for explicit date ranges before running the metrics queries, returning 400 when the span exceeds it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

8. Blocked pop-ups show a misleading download error ✓ Resolved
Description
In web mode, exportReportAsPdf hands off to printMenuInBrowser, which throws
MenuPopupBlockedError('Allow pop-ups to use browser printing') when the reserved window is null or
closed. The dashboard's exportDailySales and useCashClose.exportReport catch every error and
show only tCommon('downloadFailed'), so a user whose browser blocked the pop-up never learns that
allowing pop-ups would fix it.
Code

frontend/src/lib/export-pdf.ts[R52-53]

+    printMenuInBrowser(html, reservedWindow);
+    handedOff = true;
Relevance

●●● Strong

Popup-blocking behavior is actionable and historically accepted when browser-print failures obscure
recovery steps.

PR-#949

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
printMenuInBrowser throws a specific, actionable error when the pop-up is blocked. Both callers
swallow it with a bare catch and show the generic download-failed toast.

frontend/src/lib/printer/menu-web-print.ts[117-121]
frontend/src/hooks/useCashClose.ts[397-421]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When the browser blocks the print pop-up, PDF export shows a generic "download failed" toast, and `MenuPopupBlockedError` is never shown to the user.

## Fix Focus Areas
- frontend/src/hooks/useCashClose.ts[397-421]
- frontend/src/app/(dashboard)/dashboard/page.tsx[472-512]

## Recommended Fix
In both catch blocks, check `error instanceof MenuPopupBlockedError` (exported from `@/lib/printer/menu-web-print`) and show a localized "Allow pop-ups to print/save as PDF" toast. Keep the generic `downloadFailed` toast for every other error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Wide daily-sales tables may not fit portrait PDF pages ✓ Resolved
Description
serializeDailySalesExportHtml puts a 16-column Daily Breakdown table and a 12-column Item Sales
table on @page { size: A4 portrait }. Each money cell is white-space: nowrap and also carries a
second raw number such as (1234.5). On desktop, renderHtmlToPdfBuffer calls printToPDF with
portrait A4/Letter and no landscape or scale option, so the right-most columns (Profit, Margin %,
Cost Status) can be shrunk until unreadable or cut off in the saved PDF.
Code

main/services/daily-sales-export.ts[2034]

+    @page { size: A4 portrait; margin: 12mm; }
Relevance

●● Moderate

PDF layout risk is plausible but depends on renderer behavior and actual widths; no close repository
precedent confirms it.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The page is fixed to portrait, numeric cells cannot wrap, and the PDF renderer passes only
pageSize with no landscape or scale option. Together these make it likely that 16 nowrap money
columns, each also showing a raw number, are wider than the roughly 186mm printable width.

main/services/daily-sales-export.ts[2084-2088]
main/services/html-to-pdf.ts[53-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The daily sales HTML/PDF uses A4 portrait with 16 nowrap money columns, so columns may be shrunk or clipped in the PDF.

## Fix Focus Areas
- main/services/daily-sales-export.ts[2034-2088]
- main/services/html-to-pdf.ts[53-55]

## Recommended Fix
Switch the daily sales report to `@page { size: A4 landscape }` and pass `preferCSSPageSize: true` (or a `landscape` option) to `printToPDF`. Alternatively, drop the duplicated raw `(exact)` spans from visible cells and keep exact values only in `data-value` attributes so columns can fit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
⚠️ Tickets: not configured — No jira, linear integration is connected for the linked tickets
✅ Compliance rules (platform): 5 rules
✅ Cross-repo context — repo relationships
  Explored: repo: FreeOpenSourcePOS/FloAdmin (sha: e5d6830b) — View relationship
  Explored: repo: FreeOpenSourcePOS/RevFlo (sha: 37d47cb2) — View relationship
Review mode: Auto: 🧠 Deep: Dense cross-cutting report, date-range, PDF, permissions, and localization changes.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread frontend/e2e/cash-close-phase1-ui.spec.ts
Comment thread tests/reports-export.test.ts
Comment thread tests/daily-sales-export.test.ts Outdated
Comment thread main/routes/reports.ts
Comment thread main/routes/reports.ts
Comment thread main/services/daily-sales-export.ts Outdated
Comment thread main/services/daily-sales-export.ts
Comment thread frontend/src/lib/export-pdf.ts Outdated
Comment thread main/services/daily-sales-export.ts Outdated
@khaira777 khaira777 changed the title feat(reports): add PDF and date-range exports for X, Z, and daily sales reports feat(reports): PDF export for X, Z, daily sales Oct 28, 2026
khaira777 added a commit that referenced this pull request Oct 28, 2026
@khaira777 khaira777 changed the title feat(reports): PDF export for X, Z, daily sales feat(reports): add PDF and date-range exports for X, Z, and daily sales reports Oct 28, 2026
@khaira777 khaira777 changed the title feat(reports): add PDF and date-range exports for X, Z, and daily sales reports feat(reports): PDF export for X, Z, daily sales Oct 28, 2026
…ests/daily-sales-export.test.ts` and `tests/reports-export.test.ts` by unescaping `&amp;` last (after `&lt;`, `&gt;`, `&quot;`, and `&#39;`) in `extractHtmlSummaryMap` and `parseHtmlSummaryMetrics`. Verified with `npx tsx tests/reports-export.test.ts`, `npx tsx tests/daily-sales-export.test.ts`, `npm run lint`, `npm run build`, and `git diff --check`
@khaira777
khaira777 force-pushed the fm/flocafe-1334-pdf-reports branch from 81234a2 to 9344106 Compare October 28, 2026 22:09
@khaira777 khaira777 changed the title feat(reports): PDF export for X, Z, daily sales feat(reports): PDF export for X, Z, and daily sales reports Oct 28, 2026
@khaira777 khaira777 changed the title feat(reports): PDF export for X, Z, and daily sales reports feat(reports): PDF export for X, Z, daily sales Oct 28, 2026
…` failure (`ci-1`, `github-check-run:123131589299`) in `npm run test:reports-export` (`tests/reports-export.test.ts:1265` and `:1489`). ### Invariant & Root Cause - **Invariant**: PDF report generation (`buildCashCloseReportHtml`, `buildDailySalesReportHtml`) and its real Electron `printToPDF` verification (`extractPdfInfo` / `pdfTextIncludesToken`) must preserve the full multi-script document metadata and verify rendered report tokens across X, Z, and daily-sales exports on every supported platform (macOS, Windows, and headless Linux CI runners with or without optional CJK/Devanagari system fonts). - **Root Cause**: 1. Both PDF HTML templates (`main/services/cash-close-export.ts` and `main/services/daily-sales-export.ts`) only specified generic Latin sans-serif fallbacks (`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`) instead of the multi-script system font stack used in `frontend/src/app/globals.css`. 2. On `ubuntu-24.04` GitHub Actions runners (`linux-tests`), `fonts-dejavu-core` and `fonts-liberation` are pre-installed (covering Latin, accented French, Arabic, and currency `₹`), whereas optional CJK and Devanagari system font packages are not pre-installed. When HarfBuzz/FontConfig has no installed font for a character, Chromium's Skia PDF backend maps the character to glyph ID `0` (`.notdef`): the first `.notdef` character/cluster in a font subset is emitted as bare `<0000> Tj` and omitted from `/ToUnicode` (`SkPDFMakeToUnicodeCmap` starts at glyph index `1`), while all subsequent `.notdef` characters in that font subset are emitted via `/Span<</ActualText <FEFF...> >> BDC` (e.g. `'京カフェ'`, `'茶ラテ'`, `'कैफे'`, `'साला चाय'`), and the PDF document metadata dictionary (`/Title <FEFF...>`) always preserves the complete UTF-16BE string (`كافيه النور · 東京カフェ · फ्लो कैफे · Café Élégant <script>alert(1)</script>`). ### Changes 1. Updated the CSS `font-family` fallback stack in `main/services/cash-close-export.ts` and `main/services/daily-sales-export.ts` to include the cross-platform multi-script system fonts (Noto/PingFang/Hiragino/Yu Gothic/Meiryo/Microsoft YaHei/Nirmala UI/Kohinoor). 2. Updated `extractPdfInfo` and added `pdfTextIncludesToken` in `tests/reports-export.test.ts` across all three real Electron `printToPDF` verification blocks (`xPdfInfo`, `zPdfInfo`, `dsPdfInfo`) to decode and assert the full UTF-16BE `/Title` metadata on all three PDFs while matching `/ToUnicode` + `/ActualText` page stream text across both full-font environments and headless Linux runners without CJK/Devanagari system fonts. ### Verification - `npm run test:reports-export` (passed, including real Electron `printToPDF` child-process suite) - `npm run test:daily-sales-export` (248/248 passed) - `npm run lint` (passed, 0 errors) - `npm run build` (passed) - `git diff --check` (clean)
…` failure (`ci-1`, `github-check-run:123144964054`) in `npm run test:reports-export` (`tests/reports-export.test.ts`). ### Invariant & Root Cause - **Invariant**: Headless Electron PDF and layout verification in `tests/reports-export.test.ts` must rely only on deterministic offscreen-safe DOM layout metrics (`executeJavaScript`) and `printToPDF` output, never invoking Viz compositor surface capture (`webContents.capturePage()`) on hidden (`show: false`) windows where X11/Xvfb does not allocate a composited surface. - **Root Cause**: After all X, Z, and daily-sales `printToPDF` assertions succeeded on Linux CI (`ubuntu-24.04` under `xvfb-run`), `verifyHtmlLayoutAndCapturePages` opened a hidden (`show: false`) `BrowserWindow` and called `win.webContents.capturePage()` in a loop to write unused PNG screenshots. On Linux X11/Xvfb, an unmapped (`show: false`) window does not submit frames to the Viz surface, so Electron's `WebContents::CapturePage` callback (`OnCapturePageDone`) receives an empty `SkBitmap` (`bitmap.drawsNothing() === true`) after the ~3.5s Viz copy timeout and rejects with `[Error: UnknownVizError]`. ### Changes - Renamed `verifyHtmlLayoutAndCapturePages` to `verifyHtmlLayout` in `tests/reports-export.test.ts` and removed the `webContents.capturePage()` loop and `FLO_PDF_ARTIFACTS_DIR` artifact-dumping path across all three report layout checks (`xLayout`, `zLayout`, and `dsLayout`), retaining the `executeJavaScript` DOM layout and overflow assertions (`scrollWidth <= clientWidth && overflowingCells === 0`). ### Verification - `npm run test:reports-export` (passed, including real Electron `printToPDF` child-process suite) - `npm run lint` (passed, 0 errors) - `node scripts/ci/check-changed-format.cjs --base 2fa0228` (passed) - `npm run build` (passed) - `git diff --check` (clean)
@khaira777
khaira777 merged commit 855a1ed into main Oct 28, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1334-pdf-reports branch October 28, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(reports): PDF export for X, Z and daily sales reports

2 participants