Skip to content

feat(reports): batch export daily invoices PDF - #1400

Merged
khaira777 merged 4 commits into
mainfrom
fm/flocafe-1397-daily-invoices-pdf
Oct 29, 2026
Merged

khaira777 merged 4 commits into
mainfrom
fm/flocafe-1397-daily-invoices-pdf

Conversation

@khaira777

@khaira777 khaira777 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1397

Intent

Implement FloCafe issue #1397: batch export all issued daily bills/invoices (paid, partially_refunded, refunded) for a selected business date or range into a single compiled A4 PDF starting with a summary cover page that reconciles with the daily sales .xlsx export, fix the Windows 8.3 short-path file:// URL allowlist in renderHtmlToPdfBuffer, and address PR #1400 review feedback on refund scope reconciliation, locale formatting, active tax pack registration labels, single-page cover bounds, line item totals, numeric bill sequence ordering, and pre-hydration invoice count caps.

What Changed

  • Add GET /api/reports/daily-invoices/export (reports.daily-sales.export) and main/services/daily-invoices-export.ts to compile paid, partially refunded, and refunded bills across 1-366 business days (capped at 5,000 invoices pre-hydration) into print-styled A4 HTML with a bounded fiscal summary cover page reconciled to buildDailySalesExportDataset, active tax-pack registration labels, store regional formatting, refund scope reconciliation, numerically ordered invoice cards with line-item subtotals/totals, and a daily tax and prior-bill refund appendix.
  • Add an Invoices (PDF) action (exportInvoicesPdf across all 24 locale catalogs) to the dashboard daily-sales export menu that exports invoices-YYYY-MM-DD.pdf or invoices-YYYY-MM-DD_to_YYYY-MM-DD.pdf via exportReportAsPdf, and update API and regional-settings documentation.
  • Normalize file:// staging path checks (isAllowedPdfStagingRequestUrl) in main/services/html-to-pdf.ts so renderHtmlToPdfBuffer accepts Windows 8.3 short-path tilde variants (~ vs %7E) for the staged HTML file while blocking other file:// targets, query/hash suffixes, and remote URLs.

Risk Assessment

✅ Low: The compiled daily invoices export and Windows 8.3 short-path PDF staging URL allowlist fix are well-bounded, owner-gated, HTML-escaped, and reconcile directly with buildDailySalesExportDataset while addressing all PR #1400 review invariants.

Testing

Installed workspace dependencies, built the backend and static desktop frontend, ran test:daily-invoices-export, test:reports-export, test:daily-sales-export, test:script-coverage, and i18n:check, fixed a flaky bracketed-bfrange PDF /ToUnicode CMap parser in tests/reports-export.test.ts surfaced during real Electron printToPDF runs, and drove 8 live scenarios end-to-end against an isolated FloCafe server + Playwright Chromium + real Electron renderHtmlToPdfBuffer, capturing 8 visual screenshots, 2 compiled A4 PDFs, 1 reconciling .xlsx workbook, and a structured validation report with all checks passing.

  • Live validation: ✅ go - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Owner clicks 'Invoices (PDF)' on the Dashboard for a single business day and receives a compiled A4 portrait PDF whose cover page reconciles 100% with the Daily Sales .xlsx export ✅ pass live 01-dashboard-export-dropdown.png, 03-compiled-report-cover-page.png, invoices-2026-09-15.pdf, daily-sales-2026-09-15.xlsx
Owner switches Dashboard to a multi-day date range and exports compiled invoices; cover page stays bounded to Page 1 in real A4 PDF pagination while daily tax breakdown moves to the post-invoices appe… ✅ pass live 02-dashboard-range-export.png, 05-compiled-report-appendix.png, 06-browser-print-fallback-popup.png, invoices-2026-09-15_to_2026-09-16.pdf
Compiled export includes paid, partially_refunded, and refunded bills (excluding unpaid/cancelled bills) with status badges, pre-tax Subtotal and after-tax Total line-item columns, and numeric bill se… ✅ pass live 04-compiled-report-invoice-cards.png, invoices-2026-09-15.pdf, live-validation-report.json
Exporting a period with both a refund on a prior-period bill and a post-period refund on an in-period bill reconciles cover-page in-period refunds, lifetime invoice refunds, and the prior-period refun… ✅ pass live 03-compiled-report-cover-page.png, 04-compiled-report-invoice-cards.png, 05-compiled-report-appendix.png, live-validation-report.json
Configuring an active country tax pack with a custom registrationNumberLabel and German regional locale renders the pack's label, HTML lang tag, EUR currency formatting, and localized Dashboard menu i… ✅ pass live 07-dashboard-german-locale-export.png, 08-german-tax-pack-and-locale-card.png, live-validation-report.json
Adversarial requests from unauthenticated callers or non-owner staff roles (manager, cashier, server, chef) are rejected with 401/403, non-owner UI hides Dashboard export, and invalid formats, reverse… ✅ pass live live-validation-report.json, npm run test:daily-invoices-export
Adversarial export over a date window containing 5,001 issued bills is rejected pre-hydration with HTTP 400 before loading order items, and oversize HTML (>15 MB) is rejected by renderHtmlToPdfBuffer… ✅ pass live live-validation-report.json, npm run test:daily-invoices-export
Adversarial HTML passed to real Electron renderHtmlToPdfBuffer blocks external HTTP/file exfiltration requests while allowing inline data:image/ assets and Windows 8.3 short-path file:// URLs (RUNNER~… ✅ pass live live-validation-report.json, npm run test:reports-export

Dashboard single-day Export menu showing Invoices (PDF)
Dashboard multi-day Range Export menu showing Invoices (PDF)
Compiled Daily Invoices Page 1 fiscal summary cover page and refund reconciliation
Compiled Daily Invoices Page 2 issued invoice cards, badges, sequence order, and item totals
Compiled Daily Invoices Appendix showing daily tax breakdown and prior-period refunds
Browser print-fallback popup window rendered from compiled HTML blob
German localized Dashboard Export dropdown showing Rechnungen (PDF)
German locale invoice card showing custom active tax pack registrationNumberLabel and EUR formatting

Evidence: Compiled single-day A4 PDF generated via real Electron printToPDF
%PDF-1.4
%

... [343000 bytes truncated] ...

 
0000087041 00000 n 
0000087360 00000 n 
0000087844 00000 n 
0000088414 00000 n 
0000088727 00000 n 
0000089444 00000 n 
0000089742 00000 n 
0000090110 00000 n 
0000090263 00000 n 
0000090445 00000 n 
0000090624 00000 n 
0000090946 00000 n 
0000091110 00000 n 
0000093288 00000 n 
0000093494 00000 n 
0000094126 00000 n 
0000094674 00000 n 
0000095235 00000 n 
0000095839 00000 n 
0000096154 00000 n 
0000096485 00000 n 
0000096758 00000 n 
0000097138 00000 n 
0000097336 00000 n 
0000099688 00000 n 
0000099894 00000 n 
0000100392 00000 n 
0000100545 00000 n 
0000101142 00000 n 
0000102025 00000 n 
0000102405 00000 n 
0000102789 00000 n 
0000102888 00000 n 
0000103227 00000 n 
0000103473 00000 n 
0000105532 00000 n 
0000105745 00000 n 
0000106241 00000 n 
0000106398 00000 n 
0000106937 00000 n 
0000107597 00000 n 
0000108490 00000 n 
0000109393 00000 n 
0000109494 00000 n 
0000109832 00000 n 
0000111249 00000 n 
0000111455 00000 n 
0000111604 00000 n 
0000111957 00000 n 
0000112139 00000 n 
0000113115 00000 n 
0000113321 00000 n 
0000114091 00000 n 
0000114436 00000 n 
0000116584 00000 n 
0000116790 00000 n 
0000116939 00000 n 
0000117292 00000 n 
0000117445 00000 n 
0000117650 00000 n 
0000117969 00000 n 
0000118892 00000 n 
0000119098 00000 n 
0000119165 00000 n 
0000119354 00000 n 
0000119920 00000 n 
0000120488 00000 n 
0000120863 00000 n 
0000121025 00000 n 
0000121179 00000 n 
0000121804 00000 n 
0000121903 00000 n 
0000122044 00000 n 
0000122221 00000 n 
0000122740 00000 n 
0000123084 00000 n 
0000123447 00000 n 
0000124237 00000 n 
0000126674 00000 n 
0000130225 00000 n 
0000130472 00000 n 
0000130702 00000 n 
0000131174 00000 n 
0000131380 00000 n 
0000131760 00000 n 
0000132144 00000 n 
0000132276 00000 n 
0000133985 00000 n 
0000134198 00000 n 
0000134603 00000 n 
0000134902 00000 n 
0000136894 00000 n 
0000137107 00000 n 
0000137256 00000 n 
0000137415 00000 n 
0000137721 00000 n 
0000138543 00000 n 
0000138756 00000 n 
0000138823 00000 n 
0000139018 00000 n 
0000139619 00000 n 
0000140261 00000 n 
0000140667 00000 n 
0000140826 00000 n 
0000140925 00000 n 
0000141121 00000 n 
0000141486 00000 n 
0000141874 00000 n 
0000142722 00000 n 
0000145080 00000 n 
0000145293 00000 n 
0000146110 00000 n 
0000146512 00000 n 
0000146926 00000 n 
0000147066 00000 n 
0000147167 00000 n 
0000148675 00000 n 
0000148888 00000 n 
0000149550 00000 n 
0000150145 00000 n 
0000150736 00000 n 
0000151338 00000 n 
0000151996 00000 n 
0000152324 00000 n 
0000153102 00000 n 
0000153375 00000 n 
0000153579 00000 n 
0000155942 00000 n 
0000156155 00000 n 
0000156254 00000 n 
0000156869 00000 n 
0000157206 00000 n 
0000157791 00000 n 
0000158406 00000 n 
0000158727 00000 n 
0000159522 00000 n 
0000159833 00000 n 
0000160224 00000 n 
0000160380 00000 n 
0000160564 00000 n 
0000160752 00000 n 
0000161086 00000 n 
0000163169 00000 n 
0000163398 00000 n 
0000163566 00000 n 
0000163865 00000 n 
0000164467 00000 n 
0000164696 00000 n 
0000165498 00000 n 
0000166214 00000 n 
0000166956 00000 n 
0000167721 00000 n 
0000168105 00000 n 
0000169072 00000 n 
0000169490 00000 n 
0000169798 00000 n 
0000172023 00000 n 
0000172252 00000 n 
0000172382 00000 n 
0000173136 00000 n 
0000173532 00000 n 
0000174225 00000 n 
0000174961 00000 n 
0000175352 00000 n 
0000176338 00000 n 
0000176716 00000 n 
0000177183 00000 n 
0000177358 00000 n 
0000177562 00000 n 
0000179638 00000 n 
0000179867 00000 n 
0000179940 00000 n 
0000180154 00000 n 
0000180625 00000 n 
0000180735 00000 n 
0000180944 00000 n 
0000181147 00000 n 
0000181809 00000 n 
0000182217 00000 n 
0000182660 00000 n 
0000183718 00000 n 
0000186104 00000 n 
0000191889 00000 n 
0000192136 00000 n 
0000192366 00000 n 
0000192900 00000 n 
0000202152 00000 n 
0000202402 00000 n 
0000202635 00000 n 
0000203175 00000 n 
0000203381 00000 n 
0000203766 00000 n 
0000205861 00000 n 
0000206067 00000 n 
0000206134 00000 n 
0000206323 00000 n 
0000206889 00000 n 
0000207457 00000 n 
0000207832 00000 n 
0000207994 00000 n 
0000208148 00000 n 
0000208773 00000 n 
0000208872 00000 n 
0000209049 00000 n 
0000209568 00000 n 
0000209912 00000 n 
0000210275 00000 n 
0000211065 00000 n 
0000211438 00000 n 
0000213678 00000 n 
0000213884 00000 n 
0000214033 00000 n 
0000214215 00000 n 
0000214380 00000 n 
0000215488 00000 n 
0000215694 00000 n 
0000216192 00000 n 
0000216345 00000 n 
0000216841 00000 n 
0000217602 00000 n 
0000217983 00000 n 
0000218580 00000 n 
0000219419 00000 n 
0000219572 00000 n 
0000220455 00000 n 
0000221293 00000 n 
0000221673 00000 n 
0000222057 00000 n 
0000222156 00000 n 
0000222256 00000 n 
0000222388 00000 n 
0000222629 00000 n 
0000222968 00000 n 
0000223125 00000 n 
0000223371 00000 n 
0000225653 00000 n 
0000225859 00000 n 
0000226491 00000 n 
0000227057 00000 n 
0000227605 00000 n 
0000228166 00000 n 
0000228770 00000 n 
0000229085 00000 n 
0000229825 00000 n 
0000230156 00000 n 
0000230429 00000 n 
0000230627 00000 n 
0000233005 00000 n 
0000233211 00000 n 
0000233310 00000 n 
0000233878 00000 n 
0000234197 00000 n 
0000234681 00000 n 
0000235251 00000 n 
0000235812 00000 n 
0000236125 00000 n 
0000236842 00000 n 
0000237140 00000 n 
0000237508 00000 n 
0000237661 00000 n 
0000237843 00000 n 
0000238022 00000 n 
0000238344 00000 n 
0000240444 00000 n 
0000246018 00000 n 
0000246265 00000 n 
0000246495 00000 n 
0000247045 00000 n 
0000251577 00000 n 
0000251824 00000 n 
0000252054 00000 n 
0000252586 00000 n 
0000252799 00000 n 
0000252866 00000 n 
0000253061 00000 n 
0000253703 00000 n 
0000254109 00000 n 
0000254274 00000 n 
0000254433 00000 n 
0000254532 00000 n 
0000254675 00000 n 
0000254857 00000 n 
0000255427 00000 n 
0000255792 00000 n 
0000256180 00000 n 
0000257028 00000 n 
0000257399 00000 n 
0000259631 00000 n 
0000259844 00000 n 
0000259943 00000 n 
0000260558 00000 n 
0000260895 00000 n 
0000261480 00000 n 
0000261801 00000 n 
0000262596 00000 n 
0000262907 00000 n 
0000263298 00000 n 
0000263632 00000 n 
0000265618 00000 n 
0000265831 00000 n 
0000266493 00000 n 
0000267095 00000 n 
0000267753 00000 n 
0000268081 00000 n 
0000268354 00000 n 
0000270464 00000 n 
0000270677 00000 n 
0000271173 00000 n 
0000271330 00000 n 
0000271869 00000 n 
0000272274 00000 n 
0000272934 00000 n 
0000273837 00000 n 
0000274239 00000 n 
0000274653 00000 n 
0000274806 00000 n 
0000274958 00000 n 
0000275205 00000 n 
0000275546 00000 n 
0000277493 00000 n 
0000286061 00000 n 
0000286311 00000 n 
0000286545 00000 n 
0000287123 00000 n 
0000287352 00000 n 
0000287462 00000 n 
0000287761 00000 n 
0000289052 00000 n 
0000289281 00000 n 
0000290035 00000 n 
0000290413 00000 n 
0000291862 00000 n 
0000292091 00000 n 
0000292856 00000 n 
0000294489 00000 n 
0000294695 00000 n 
0000294762 00000 n 
0000294903 00000 n 
0000295092 00000 n 
0000295611 00000 n 
0000296401 00000 n 
0000296727 00000 n 
0000298815 00000 n 
0000299021 00000 n 
0000299653 00000 n 
0000300257 00000 n 
0000300997 00000 n 
0000301270 00000 n 
0000301468 00000 n 
0000303725 00000 n 
0000303931 00000 n 
0000304030 00000 n 
0000304343 00000 n 
0000304641 00000 n 
0000304805 00000 n 
0000306740 00000 n 
0000306946 00000 n 
0000307047 00000 n 
0000307386 00000 n 
0000307543 00000 n 
0000309360 00000 n 
0000309566 00000 n 
0000309951 00000 n 
0000312046 00000 n 
0000312252 00000 n 
0000313022 00000 n 
0000314571 00000 n 
0000314784 00000 n 
0000314933 00000 n 
0000315232 00000 n 
0000315794 00000 n 
0000316007 00000 n 
0000316106 00000 n 
0000316691 00000 n 
0000317002 00000 n 
0000318464 00000 n 
0000318677 00000 n 
0000319339 00000 n 
0000320439 00000 n 
0000320652 00000 n 
0000320719 00000 n 
0000320818 00000 n 
0000321000 00000 n 
0000321388 00000 n 
0000323520 00000 n 
0000323733 00000 n 
0000324135 00000 n 
0000324549 00000 n 
0000325859 00000 n 
0000326072 00000 n 
0000326477 00000 n 
0000328572 00000 n 
0000331692 00000 n 
0000331939 00000 n 
0000332169 00000 n 
0000332642 00000 n 
0000332855 00000 n 
0000333004 00000 n 
0000333377 00000 n 
0000333541 00000 n 
0000334578 00000 n 
0000334791 00000 n 
0000335196 00000 n 
0000337291 00000 n 
0000337520 00000 n 
0000337670 00000 n 
trailer
<</Size 606
/Root 78 0 R
/Info 1 0 R>>
startxref
339001
%%EOF
Evidence: Compiled multi-day range A4 PDF generated via real Electron printToPDF
%PDF-1.4
%

... [345936 bytes truncated] ...

 
0000089634 00000 n 
0000090204 00000 n 
0000090517 00000 n 
0000091234 00000 n 
0000091532 00000 n 
0000091900 00000 n 
0000092053 00000 n 
0000092235 00000 n 
0000092414 00000 n 
0000092736 00000 n 
0000092900 00000 n 
0000095078 00000 n 
0000095284 00000 n 
0000095916 00000 n 
0000096464 00000 n 
0000097025 00000 n 
0000097629 00000 n 
0000097944 00000 n 
0000098275 00000 n 
0000098548 00000 n 
0000098928 00000 n 
0000099126 00000 n 
0000101478 00000 n 
0000101684 00000 n 
0000102182 00000 n 
0000102335 00000 n 
0000102831 00000 n 
0000103428 00000 n 
0000104311 00000 n 
0000104691 00000 n 
0000105075 00000 n 
0000105174 00000 n 
0000105513 00000 n 
0000105759 00000 n 
0000107842 00000 n 
0000108055 00000 n 
0000108551 00000 n 
0000108708 00000 n 
0000109247 00000 n 
0000109907 00000 n 
0000110800 00000 n 
0000111703 00000 n 
0000111804 00000 n 
0000112051 00000 n 
0000112396 00000 n 
0000114089 00000 n 
0000114295 00000 n 
0000114444 00000 n 
0000114797 00000 n 
0000114979 00000 n 
0000115955 00000 n 
0000116161 00000 n 
0000116931 00000 n 
0000117276 00000 n 
0000119424 00000 n 
0000119630 00000 n 
0000119779 00000 n 
0000120132 00000 n 
0000120285 00000 n 
0000120490 00000 n 
0000120809 00000 n 
0000121732 00000 n 
0000121938 00000 n 
0000122005 00000 n 
0000122194 00000 n 
0000122760 00000 n 
0000123328 00000 n 
0000123703 00000 n 
0000123865 00000 n 
0000124019 00000 n 
0000124644 00000 n 
0000124743 00000 n 
0000124884 00000 n 
0000125061 00000 n 
0000125580 00000 n 
0000125924 00000 n 
0000126287 00000 n 
0000127077 00000 n 
0000129514 00000 n 
0000132973 00000 n 
0000133220 00000 n 
0000133450 00000 n 
0000133927 00000 n 
0000134133 00000 n 
0000134513 00000 n 
0000134897 00000 n 
0000135029 00000 n 
0000136738 00000 n 
0000136951 00000 n 
0000137356 00000 n 
0000137655 00000 n 
0000139647 00000 n 
0000139860 00000 n 
0000140009 00000 n 
0000140168 00000 n 
0000140474 00000 n 
0000141296 00000 n 
0000141509 00000 n 
0000141576 00000 n 
0000141771 00000 n 
0000142372 00000 n 
0000143014 00000 n 
0000143420 00000 n 
0000143579 00000 n 
0000143678 00000 n 
0000143874 00000 n 
0000144239 00000 n 
0000144627 00000 n 
0000145475 00000 n 
0000147833 00000 n 
0000148046 00000 n 
0000148451 00000 n 
0000148853 00000 n 
0000149267 00000 n 
0000149407 00000 n 
0000149508 00000 n 
0000151017 00000 n 
0000151230 00000 n 
0000151892 00000 n 
0000152487 00000 n 
0000153078 00000 n 
0000153680 00000 n 
0000154338 00000 n 
0000154666 00000 n 
0000155444 00000 n 
0000155717 00000 n 
0000155921 00000 n 
0000158284 00000 n 
0000158497 00000 n 
0000158596 00000 n 
0000159211 00000 n 
0000159548 00000 n 
0000160133 00000 n 
0000160748 00000 n 
0000161069 00000 n 
0000161864 00000 n 
0000162175 00000 n 
0000162566 00000 n 
0000162722 00000 n 
0000162906 00000 n 
0000163094 00000 n 
0000163428 00000 n 
0000165511 00000 n 
0000165740 00000 n 
0000165908 00000 n 
0000166207 00000 n 
0000166809 00000 n 
0000167038 00000 n 
0000167840 00000 n 
0000168556 00000 n 
0000169298 00000 n 
0000170063 00000 n 
0000170447 00000 n 
0000171414 00000 n 
0000171832 00000 n 
0000172140 00000 n 
0000174365 00000 n 
0000174594 00000 n 
0000174724 00000 n 
0000175478 00000 n 
0000175874 00000 n 
0000176567 00000 n 
0000177303 00000 n 
0000177694 00000 n 
0000178680 00000 n 
0000179058 00000 n 
0000179525 00000 n 
0000179700 00000 n 
0000179904 00000 n 
0000181980 00000 n 
0000182209 00000 n 
0000182282 00000 n 
0000182496 00000 n 
0000182967 00000 n 
0000183077 00000 n 
0000183286 00000 n 
0000183489 00000 n 
0000184151 00000 n 
0000184559 00000 n 
0000185002 00000 n 
0000186060 00000 n 
0000188446 00000 n 
0000194231 00000 n 
0000194478 00000 n 
0000194708 00000 n 
0000195242 00000 n 
0000204494 00000 n 
0000204744 00000 n 
0000204977 00000 n 
0000205517 00000 n 
0000205723 00000 n 
0000206108 00000 n 
0000208203 00000 n 
0000208409 00000 n 
0000208476 00000 n 
0000208665 00000 n 
0000209231 00000 n 
0000209799 00000 n 
0000210174 00000 n 
0000210336 00000 n 
0000210490 00000 n 
0000211115 00000 n 
0000211214 00000 n 
0000211391 00000 n 
0000211910 00000 n 
0000212254 00000 n 
0000212617 00000 n 
0000213407 00000 n 
0000213780 00000 n 
0000216020 00000 n 
0000216226 00000 n 
0000216375 00000 n 
0000216557 00000 n 
0000216722 00000 n 
0000217830 00000 n 
0000218036 00000 n 
0000218534 00000 n 
0000218687 00000 n 
0000219183 00000 n 
0000219944 00000 n 
0000220325 00000 n 
0000220922 00000 n 
0000221761 00000 n 
0000221914 00000 n 
0000222797 00000 n 
0000223635 00000 n 
0000224015 00000 n 
0000224399 00000 n 
0000224498 00000 n 
0000224598 00000 n 
0000224730 00000 n 
0000224971 00000 n 
0000225310 00000 n 
0000225467 00000 n 
0000225713 00000 n 
0000227995 00000 n 
0000228201 00000 n 
0000228833 00000 n 
0000229399 00000 n 
0000229947 00000 n 
0000230508 00000 n 
0000231112 00000 n 
0000231427 00000 n 
0000232167 00000 n 
0000232498 00000 n 
0000232771 00000 n 
0000232969 00000 n 
0000235347 00000 n 
0000235553 00000 n 
0000235652 00000 n 
0000236220 00000 n 
0000236539 00000 n 
0000237023 00000 n 
0000237593 00000 n 
0000238154 00000 n 
0000238467 00000 n 
0000239184 00000 n 
0000239482 00000 n 
0000239850 00000 n 
0000240003 00000 n 
0000240185 00000 n 
0000240364 00000 n 
0000240686 00000 n 
0000242786 00000 n 
0000248360 00000 n 
0000248607 00000 n 
0000248837 00000 n 
0000249387 00000 n 
0000254107 00000 n 
0000254354 00000 n 
0000254584 00000 n 
0000255116 00000 n 
0000255329 00000 n 
0000255396 00000 n 
0000255591 00000 n 
0000256233 00000 n 
0000256639 00000 n 
0000256804 00000 n 
0000256963 00000 n 
0000257062 00000 n 
0000257205 00000 n 
0000257387 00000 n 
0000257957 00000 n 
0000258322 00000 n 
0000258710 00000 n 
0000259558 00000 n 
0000259929 00000 n 
0000262161 00000 n 
0000262374 00000 n 
0000262473 00000 n 
0000263088 00000 n 
0000263425 00000 n 
0000264010 00000 n 
0000264331 00000 n 
0000265126 00000 n 
0000265437 00000 n 
0000265828 00000 n 
0000266162 00000 n 
0000268148 00000 n 
0000268361 00000 n 
0000269023 00000 n 
0000269625 00000 n 
0000270283 00000 n 
0000270611 00000 n 
0000270884 00000 n 
0000272994 00000 n 
0000273207 00000 n 
0000273703 00000 n 
0000273860 00000 n 
0000274399 00000 n 
0000274804 00000 n 
0000275464 00000 n 
0000276367 00000 n 
0000276769 00000 n 
0000277183 00000 n 
0000277336 00000 n 
0000277488 00000 n 
0000277735 00000 n 
0000278076 00000 n 
0000280023 00000 n 
0000288591 00000 n 
0000288841 00000 n 
0000289075 00000 n 
0000289653 00000 n 
0000289882 00000 n 
0000289992 00000 n 
0000290291 00000 n 
0000291582 00000 n 
0000291811 00000 n 
0000292565 00000 n 
0000292943 00000 n 
0000294392 00000 n 
0000294621 00000 n 
0000295386 00000 n 
0000297019 00000 n 
0000297225 00000 n 
0000297292 00000 n 
0000297433 00000 n 
0000297622 00000 n 
0000298141 00000 n 
0000298931 00000 n 
0000299257 00000 n 
0000301345 00000 n 
0000301551 00000 n 
0000302183 00000 n 
0000302787 00000 n 
0000303527 00000 n 
0000303800 00000 n 
0000303998 00000 n 
0000306255 00000 n 
0000306461 00000 n 
0000306560 00000 n 
0000306873 00000 n 
0000307171 00000 n 
0000307335 00000 n 
0000309270 00000 n 
0000309476 00000 n 
0000309577 00000 n 
0000309916 00000 n 
0000310073 00000 n 
0000311890 00000 n 
0000312096 00000 n 
0000312481 00000 n 
0000314576 00000 n 
0000314782 00000 n 
0000315552 00000 n 
0000317101 00000 n 
0000317314 00000 n 
0000317463 00000 n 
0000317762 00000 n 
0000318324 00000 n 
0000318537 00000 n 
0000318636 00000 n 
0000319221 00000 n 
0000319532 00000 n 
0000320994 00000 n 
0000321207 00000 n 
0000321869 00000 n 
0000322969 00000 n 
0000323182 00000 n 
0000323249 00000 n 
0000323348 00000 n 
0000323530 00000 n 
0000323918 00000 n 
0000326050 00000 n 
0000326263 00000 n 
0000326665 00000 n 
0000327079 00000 n 
0000328389 00000 n 
0000328602 00000 n 
0000329007 00000 n 
0000331102 00000 n 
0000334583 00000 n 
0000334830 00000 n 
0000335060 00000 n 
0000335538 00000 n 
0000335751 00000 n 
0000335900 00000 n 
0000336273 00000 n 
0000336437 00000 n 
0000337474 00000 n 
0000337687 00000 n 
0000338092 00000 n 
0000340187 00000 n 
0000340416 00000 n 
0000340566 00000 n 
trailer
<</Size 608
/Root 78 0 R
/Info 1 0 R>>
startxref
341897
%%EOF
  • Evidence: Reconciling Daily Sales XLSX workbook exported from live Dashboard (local file: ~/.no-mistakes/evidence/01M62XNJ4EGH5029F18KDE5HN5/daily-sales-2026-09-15.xlsx)
Evidence: Structured live validation report across all 8 scenarios
{
  "baseUrl": "http://127.0.0.1:53721",
  "scenarios": {
    "singleDay": {
      "pdfFile": "~/.no-mistakes/evidence/01M62XNJ4EGH5029F18KDE5HN5/invoices-2026-09-15.pdf",
      "xlsxFile": "~/.no-mistakes/evidence/01M62XNJ4EGH5029F18KDE5HN5/daily-sales-2026-09-15.xlsx",
      "pdfPages": 3,
      "pdfBytes": 351202,
      "invoiceOrder": [
        {
          "billNumber": "INV-9999",
          "orderNumber": "ORD-9999",
          "status": "paid"
        },
        {
          "billNumber": "INV-10000",
          "orderNumber": "ORD-10000",
          "status": "partially_refunded"
        },
        {
          "billNumber": "BILL-10001",
          "orderNumber": "ORD-10001",
          "status": "refunded"
        }
      ],
      "refundScopes": {
        "in_window_refunds_total": 354.4,
        "in_window_refunds_on_included_bills": 304.4,
        "in_window_refunds_on_prior_bills": 50,
        "post_window_refunds_on_included_bills": 25,
        "lifetime_refunds_on_included_bills": 329.4
      },
      "reconciledMetricsCount": 26
    },
    "multiDayRange": {
      "pdfFile": "~/.no-mistakes/evidence/01M62XNJ4EGH5029F18KDE5HN5/invoices-2026-09-15_to_2026-09-16.pdf",
      "pdfPages": 3,
      "invoiceOrder": [
        "INV-9999",
        "INV-10000",
        "BILL-10001",
        "BILL-10002"
      ],
      "todayLivePosInvoice": {
        "billNumber": "INV-20261029-0001",
        "orderNumber": "ORD-20261029-0001",
        "status": "paid"
      }
    },
    "browserPrintFallback": {
      "popupPrintTriggered": true
    },
    "taxPackAndLocale": {
      "htmlLang": "de-DE",
      "customRegistrationLabel": "USt-IdNr. (CustomPack): DE998877665",
      "germanMenuLabelVerified": "Rechnungen (PDF)"
    },
    "adversarialAuthAndValidation": {
      "unauthenticated": 401,
      "roleStatuses": {
        "manager": 403,
        "cashier": 403,
        "server": 403,
        "chef": 403
      },
      "managerDashboardExportVisible": false,
      "maxRangeBoundary366Status": 200,
      "overMaxRange367Status": 400
    },
    "adversarialCaps": {
      "billsSeeded": 5001,
      "statusCode": 400,
      "errorMessage": "Export exceeds maximum invoice count (5000); please narrow the date range",
      "rejectionLatencyMs": 2,
      "oversizeHtmlRejected": true
    },
    "pdfAllowlistAndWindows83": {
      "canaryHits": 0,
      "stagingLeftoverFiles": 0,
      "win83RawTildeAllowed": true,
      "win83EncodedTildeAllowed": true,
      "siblingAndQueryAndRemoteBlocked": true
    }
  }
}

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 - 8 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Owner clicks 'Invoices (PDF)' on the Dashboard for a single business day and receives a compiled A4 portrait PDF whose cover page reconciles 100% with the Daily Sales .xlsx export ✅ pass live 01-dashboard-export-dropdown.png, 03-compiled-report-cover-page.png, invoices-2026-09-15.pdf, daily-sales-2026-09-15.xlsx
Owner switches Dashboard to a multi-day date range and exports compiled invoices; cover page stays bounded to Page 1 in real A4 PDF pagination while daily tax breakdown moves to the post-invoices appe… ✅ pass live 02-dashboard-range-export.png, 05-compiled-report-appendix.png, 06-browser-print-fallback-popup.png, invoices-2026-09-15_to_2026-09-16.pdf
Compiled export includes paid, partially_refunded, and refunded bills (excluding unpaid/cancelled bills) with status badges, pre-tax Subtotal and after-tax Total line-item columns, and numeric bill se… ✅ pass live 04-compiled-report-invoice-cards.png, invoices-2026-09-15.pdf, live-validation-report.json
Exporting a period with both a refund on a prior-period bill and a post-period refund on an in-period bill reconciles cover-page in-period refunds, lifetime invoice refunds, and the prior-period refun… ✅ pass live 03-compiled-report-cover-page.png, 04-compiled-report-invoice-cards.png, 05-compiled-report-appendix.png, live-validation-report.json
Configuring an active country tax pack with a custom registrationNumberLabel and German regional locale renders the pack's label, HTML lang tag, EUR currency formatting, and localized Dashboard menu i… ✅ pass live 07-dashboard-german-locale-export.png, 08-german-tax-pack-and-locale-card.png, live-validation-report.json
Adversarial requests from unauthenticated callers or non-owner staff roles (manager, cashier, server, chef) are rejected with 401/403, non-owner UI hides Dashboard export, and invalid formats, reverse… ✅ pass live live-validation-report.json, npm run test:daily-invoices-export
Adversarial export over a date window containing 5,001 issued bills is rejected pre-hydration with HTTP 400 before loading order items, and oversize HTML (>15 MB) is rejected by renderHtmlToPdfBuffer… ✅ pass live live-validation-report.json, npm run test:daily-invoices-export
Adversarial HTML passed to real Electron renderHtmlToPdfBuffer blocks external HTTP/file exfiltration requests while allowing inline data:image/ assets and Windows 8.3 short-path file:// URLs (RUNNER~… ✅ pass live live-validation-report.json, npm run test:reports-export
  • npm run test:daily-invoices-export
  • npm run test:reports-export
  • npm run test:daily-sales-export
  • npm run test:script-coverage
  • npm run i18n:check
  • npm run build && npm run build:frontend
  • Live Electron + Playwright Chromium end-to-end validation across single-day export, multi-day range export, POS sale -> compiled PDF, .xlsx reconciliation, refund scope reconciliation, active tax pack registration label + de-DE locale, browser print popup fallback, RBAC/query bounds, 5,001-bill pre-hydration cap, >15 MB PDF cap, and real Electron onBeforeRequest exfiltration canary + Windows 8.3 allowlist
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@coderabbitai

coderabbitai Bot commented Oct 29, 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

Export daily invoices as a compiled PDF with a fiscal summary

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add owner-only, date-range invoice exports with a fiscal cover that reuses daily-sales figures.
• Add a localized dashboard PDF action and normalize Windows staging URLs for PDF rendering.
• Document the endpoint and add export, reconciliation, authorization, and URL-allowlist tests.
Diagram

graph TD
  Dashboard["Dashboard export"] --> API["Reports API"] --> Invoice["Invoice dataset"] --> HTML["A4 HTML"] --> PDF["PDF renderer"]
  Invoice --> Sales["Daily sales dataset"] --> DB["SQLite bills"]
  Invoice --> DB
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Chunk invoice hydration for large ranges
  • ➕ Bounds peak memory and reduces the size of individual database reads for busy 366-day ranges.
  • ➖ Complicates ordered assembly, cover reconciliation, and the existing whole-document HTML-to-PDF handoff.

Recommendation: Keep the existing daily-sales dataset as the cover's source of truth and use the bounded HTML export for now. If busy tenants approach the 15 MB limit or experience slow exports, consider chunked invoice hydration before changing the PDF handoff.

Files changed (33) +2540 / -14

Enhancement (28) +1396 / -3
page.tsxAdd dashboard invoice PDF export action +34/-0

Add dashboard invoice PDF export action

• Adds an export menu item that uses the selected day or range, names the PDF accordingly, and delegates desktop or browser output to the existing PDF helper.

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

ar.jsonLabel invoice PDF export in Arabic +1/-0

Label invoice PDF export in Arabic

• Adds the localized dashboard menu label.

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

bn.jsonLabel invoice PDF export in Bengali +1/-0

Label invoice PDF export in Bengali

• Adds the localized dashboard menu label.

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

de.jsonLabel invoice PDF export in German +1/-0

Label invoice PDF export in German

• Adds the localized dashboard menu label.

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

en.jsonLabel invoice PDF export in English +1/-0

Label invoice PDF export in English

• Adds the dashboard menu label for the new export.

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

es.jsonLabel invoice PDF export in Spanish +1/-0

Label invoice PDF export in Spanish

• Adds the localized dashboard menu label.

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

fa.jsonLabel invoice PDF export in Persian +1/-0

Label invoice PDF export in Persian

• Adds the localized dashboard menu label.

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

fil.jsonLabel invoice PDF export in Filipino +1/-0

Label invoice PDF export in Filipino

• Adds the localized dashboard menu label.

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

fr.jsonLabel invoice PDF export in French +1/-0

Label invoice PDF export in French

• Adds the localized dashboard menu label.

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

hi.jsonLabel invoice PDF export in Hindi +1/-0

Label invoice PDF export in Hindi

• Adds the localized dashboard menu label.

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

id.jsonLabel invoice PDF export in Indonesian +1/-0

Label invoice PDF export in Indonesian

• Adds the localized dashboard menu label.

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

it.jsonLabel invoice PDF export in Italian +1/-0

Label invoice PDF export in Italian

• Adds the localized dashboard menu label.

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

ja.jsonLabel invoice PDF export in Japanese +1/-0

Label invoice PDF export in Japanese

• Adds the localized dashboard menu label.

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

ko.jsonLabel invoice PDF export in Korean +1/-0

Label invoice PDF export in Korean

• Adds the localized dashboard menu label.

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

ne.jsonLabel invoice PDF export in Nepali +1/-0

Label invoice PDF export in Nepali

• Adds the localized dashboard menu label.

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

nl.jsonLabel invoice PDF export in Dutch +1/-0

Label invoice PDF export in Dutch

• Adds the localized dashboard menu label.

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

pt.jsonLabel invoice PDF export in Portuguese +1/-0

Label invoice PDF export in Portuguese

• Adds the localized dashboard menu label.

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

ru.jsonLabel invoice PDF export in Russian +1/-0

Label invoice PDF export in Russian

• Adds the localized dashboard menu label.

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

sq.jsonLabel invoice PDF export in Albanian +1/-0

Label invoice PDF export in Albanian

• Adds the localized dashboard menu label.

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

th.jsonLabel invoice PDF export in Thai +1/-0

Label invoice PDF export in Thai

• Adds the localized dashboard menu label.

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

tr.jsonLabel invoice PDF export in Turkish +1/-0

Label invoice PDF export in Turkish

• Adds the localized dashboard menu label.

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

ur.jsonLabel invoice PDF export in Urdu +1/-0

Label invoice PDF export in Urdu

• Adds the localized dashboard menu label.

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

vi.jsonLabel invoice PDF export in Vietnamese +1/-0

Label invoice PDF export in Vietnamese

• Adds the localized dashboard menu label.

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

zh-tw.jsonLabel invoice PDF export in Traditional Chinese +1/-0

Label invoice PDF export in Traditional Chinese

• Adds the localized dashboard menu label.

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

zh.jsonLabel invoice PDF export in Simplified Chinese +1/-0

Label invoice PDF export in Simplified Chinese

• Adds the localized dashboard menu label.

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

reports.tsExpose owner-only daily invoices export +60/-0

Expose owner-only daily invoices export

• Adds the HTML report endpoint with format and business-date validation, a 366-day range cap, download headers, and mapped error responses.

main/routes/reports.ts

daily-invoices-export.tsBuild and serialize compiled daily invoices +1273/-0

Build and serialize compiled daily invoices

• Loads issued bills and their items, taxes, payments, and refunds in bill-number order. Reuses daily-sales figures for a fiscal cover and produces size-limited, escaped A4 HTML.

main/services/daily-invoices-export.ts

daily-sales-export.tsAllow callers to specify the sales dataset range cap +5/-3

Allow callers to specify the sales dataset range cap

• Parameterizes range validation while preserving the default daily-sales limit, allowing the invoice report to reuse its dataset over up to 366 days.

main/services/daily-sales-export.ts

Bug fix (1) +58 / -5
html-to-pdf.tsNormalize staged PDF file URLs before allowlisting +58/-5

Normalize staged PDF file URLs before allowlisting

• Compares resolved staging paths, including Windows file URLs with encoded 8.3-path tildes, while retaining the block on other files and remote requests.

main/services/html-to-pdf.ts

Tests (3) +1060 / -2
package.jsonRegister the daily invoices integration test +2/-1

Register the daily invoices integration test

• Adds a dedicated test script and includes it in the main test sequence.

package.json

daily-invoices-export.test.tsCover invoice export behavior and PDF handoff +1007/-0

Cover invoice export behavior and PDF handoff

• Adds integration checks for authorization, validation, invoice ordering, refunds, date boundaries, and reconciliation against daily-sales XLSX. Exercises desktop PDF IPC with a fake renderer and browser print fallback; it does not establish a live PDF-rendering result.

tests/daily-invoices-export.test.ts

reports-export.test.tsTest the PDF staging URL allowlist +51/-1

Test the PDF staging URL allowlist

• Adds Windows short-path and percent-encoded tilde cases, plus checks that other local files, query-bearing URLs, and remote URLs remain blocked.

tests/reports-export.test.ts

Documentation (1) +26 / -4
api.mdDocument the daily invoices export contract +26/-4

Document the daily invoices export contract

• Describes authorization, date and format parameters, cover and invoice contents, response headers, and errors. Updates route inventory counts.

docs/reference/api.md

@qodo-code-review

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Custom tax packs show the wrong tax label ✓ Resolved
Description
The export derives tax_id_label from the static country profile instead of the active tax pack's
registration label. When a tenant activates a custom or community tax pack with a different
registration label, the cover page and invoice cards continue showing the country-default label.
Code

main/services/daily-invoices-export.ts[R420-424]

+  const storeLegalName = readDbSetting(db, 'business_name') || 'FloCafe Store';
+  const storeAddress = readDbSetting(db, 'business_address');
+  const storeTaxId = readDbSetting(db, 'tax_registration_number');
+  const storeCountry = readDbSetting(db, 'country');
+  const taxIdLabel = getCountryByCode(storeCountry)?.taxIdLabel || 'Tax ID';
Relevance

●●● Strong

Active tax-pack metadata should drive registration labels; similar tax-pack metadata feedback was
accepted.

PR-#405

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new dataset assigns the label from getCountryByCode, while the tax-pack model and active-pack
resolver provide pack-specific registration labels that this export never reads.

Business-day filtering and localized tax presentation
main/services/daily-invoices-export.ts[420-424]
main/services/daily-invoices-export.ts[696-702]
main/tax-packs/types.ts[54-61]
main/services/tax.ts[121-139]

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 export uses the static country tax-ID label and ignores the active tax pack's registration label.

Fix Focus Areas
- main/services/daily-invoices-export.ts[420-424]
- main/services/daily-invoices-export.ts[696-702]
- main/services/daily-invoices-export.ts[1178-1180]
- main/services/tax.ts[121-139]

Recommended Fix
Resolve the active tax pack for the tenant and use its `registrationNumberLabel` when constructing `tax_id_label`, with the country profile only as a fallback when no active pack label is available.

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


2. Invoice amounts lose local formatting ✓ Resolved
Description
The HTML serializer formats monetary values with Number(...).toFixed(...), emits the currency code
directly, and declares the document language as English instead of using the store's locale-aware
money formatter. Exports for stores with localized grouping, currency placement, symbols, numbering
systems, or currency preferences therefore display different financial values and presentation from
the daily sales report.
Code

main/services/daily-invoices-export.ts[R722-727]

+export function serializeDailyInvoicesExportHtml(dataset: DailyInvoicesExportDataset): string {
+  const digits = getCurrencyFractionDigits(dataset.currency);
+  const fmtMoney = (n: number | null | undefined) =>
+    n === null || n === undefined || !Number.isFinite(Number(n)) ? '' : Number(n).toFixed(digits);
+  const fmtRate = (n: number | null | undefined) =>
+    n === null || n === undefined || !Number.isFinite(Number(n)) ? '-' : `${Number(n).toFixed(2)}%`;
Relevance

●●● Strong

The Total column must use stored item totals, including tax, matching receipt rendering and bill
projections.

PR-#537

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new serializer only gets fraction digits and calls toFixed, while the existing daily sales
serializer resolves locale and preferences and calls formatMoney; the new document also hardcodes
lang="en".

Business-day filtering and localized tax presentation
main/services/daily-invoices-export.ts[722-727]
main/services/daily-invoices-export.ts[932-935]
main/services/daily-sales-export.ts[1852-1870]

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 invoice export uses fixed decimal formatting and hardcoded English presentation rather than the store's locale and currency preferences.

Fix Focus Areas
- main/services/daily-invoices-export.ts[722-727]
- main/services/daily-invoices-export.ts[932-935]
- main/services/daily-sales-export.ts[1852-1870]

Recommended Fix
Resolve the same regional snapshot used by daily sales exports, including locale and currency preferences, and format every displayed amount with the existing locale-aware `formatMoney` helper. Set the document language from the resolved locale instead of hardcoding `en`.

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


3. Refund totals do not match invoice details ✓ Resolved
Description
The invoice query selects bills by paid_at, but refundStmt loads every refund for those bills
regardless of refund date, while the cover totals refunds by created_at within the selected
business-day window. A later refund of a selected bill can appear on its card but not in the cover
total, and an in-window refund against an older, excluded bill can enter the cover total without a
corresponding card.
Code

main/services/daily-invoices-export.ts[R623-645]

+    const rawRefunds = refundStmt.all(bill.id) as Array<{
+      id: number;
+      bill_id: number;
+      order_item_id: number | null;
+      amount_cents: number;
+      method: string | null;
+      reason: string | null;
+      created_at: string;
+      item_name: string | null;
+      item_quantity: number | null;
+      item_variant: string | null;
+    }>;
+
+    let refundTotalDec = new Decimal(0);
+    const refunds: DailyInvoiceRefundLine[] = rawRefunds.map((r) => {
+      const amountDec = new Decimal(Number(r.amount_cents || 0)).div(minorFactor);
+      refundTotalDec = refundTotalDec.plus(amountDec);
+      return {
+        id: Number(r.id),
+        amount: amountDec.toDecimalPlaces(6, Decimal.ROUND_HALF_UP).toNumber(),
+        method: String(r.method || 'cash'),
+        reason: String(r.reason || ''),
+        created_at_local: storeLocalTimestamp(r.created_at, timezone),
Relevance

●●● Strong

Export formatting should match the existing locale-aware report serializer, especially for localized
currencies and numbering.

PR-#1393

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The invoice query filters bills on b.paid_at, then the card refund query filters refunds only by
r.bill_id. The reused daily sales metric instead filters refunds on their own created_at date,
so the card and cover queries can count different refund populations at either edge of the selected
window.

Tax summary cover page
main/services/daily-invoices-export.ts[493-504]
main/services/daily-invoices-export.ts[623-645]
main/services/daily-sales-export.ts[1274-1280]
main/services/daily-invoices-export.ts[494-504]
main/services/daily-invoices-export.ts[519-535]
main/services/daily-invoices-export.ts[636-650]
main/services/daily-sales-export.ts[1430-1449]
main/services/daily-invoices-export.ts[710-715]

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

## Issue description
Invoice cards show refunds attached to bills paid in the selected period regardless of refund date, while the cover counts refunds created in that period, including refunds against older bills. Make the two scopes explicit and ensure readers can reconcile the cover total with the invoice details.

## Fix Focus Areas
- main/services/daily-invoices-export.ts[494-500]
- main/services/daily-invoices-export.ts[519-535]
- main/services/daily-invoices-export.ts[623-645]
- main/services/daily-invoices-export.ts[636-650]
- main/services/daily-invoices-export.ts[710-715]
- main/services/daily-sales-export.ts[1274-1280]

## Recommended Fix
Apply a consistent reconciliation policy: preserve complete refund history on each invoice if needed, but separately present refunds created within the selected business-day window, including those against older bills. Label the lifetime and in-window scopes explicitly, use the daily sales dataset’s window for the in-window figures, and test refunds crossing both boundaries of the selected period.

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



Remediation recommended

4. Invoices fall out of sequence after number 9999 ✓ Resolved
Description
The daily invoices export uses ORDER BY b.bill_number ASC on a TEXT value, while
generateBillNumber pads the counter to a minimum of four digits. When a numbering series passes
9,999, INV-10000 sorts before INV-9999; if the prefix changes mid-range, bills also group by
prefix rather than issue order, scrambling the sequence in the audit PDF.
Code

main/services/daily-invoices-export.ts[R499-501]

+      WHERE b.paid_at >= ? AND b.paid_at < ?
+        AND b.payment_status IN ('paid', 'partially_refunded', 'refunded')
+      ORDER BY b.bill_number ASC, b.id ASC
Relevance

●●● Strong

This violates the stated strict invoice ordering and fails once padded counters exceed four digits.

PR-#1105

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
allocateUniqueNumber constructs bill numbers using head + String(seq).padStart(4, '0'), so the
counter grows to five digits after 9999 rather than retaining a fixed width. Because bill_number
is stored as TEXT and the export orders by that column, SQLite compares the formatted values
character by character. With a reset period of never, the counter can reach 10000 without a period
change, and a changed prefix can likewise alter the exported order.

main/db.ts[8868-8899]
main/db.ts[8856-8858]
main/db.ts[8922-8949]
main/services/daily-invoices-export.ts[499-504]
main/db.ts[8229-8232]
main/db.ts[8867-8889]

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 invoices export sorts formatted bill numbers as text, which does not reliably preserve issue order when a counter exceeds four digits or a prefix changes.

## Fix Focus Areas
- main/services/daily-invoices-export.ts[463-504]
- main/db.ts[8867-8889]

## Recommended Fix
Replace lexical ordering by `b.bill_number` with an order that follows bill allocation, such as `b.id ASC` if bill rows are inserted in allocation order. Alternatively, order by the underlying numeric sequence within its numbering period with a deterministic tie-breaker; do not rely on lexical comparison of the formatted number. Add a test covering `INV-9999` followed by `INV-10000`, and cover a mid-range prefix change.

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


5. Year-long invoice export freezes the app, then fails ✓ Resolved
Description
The daily-invoices export permits 366-day ranges and synchronously hydrates every bill, its items,
the full daily-sales dataset, and refunds via refundStmt.all(bill.id) once per bill before
checking the finished HTML against the 15 MB limit. On a multi-month range with thousands of
invoices, that work blocks the Electron main process and POS/API traffic before the route returns
400 for oversized output.
Code

main/services/daily-invoices-export.ts[R1267-1271]

+  if (Buffer.byteLength(html, 'utf8') > MAX_DAILY_SALES_HTML_BYTES) {
+    throw new DailySalesExportValidationError(
+      'Export exceeds maximum printable PDF size; please narrow the date range',
+    );
+  }
Relevance

●● Moderate

Recommendation generated with fallback mode.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The route allows up to 366 days, while buildDailyInvoicesExportDataset loads all selected bills
and items and runs a synchronous refund query inside its per-bill map;
serializeDailyInvoicesExportHtml checks the byte limit only after constructing the full document.
At roughly 100 bills per day, a year approaches 36,000 invoice cards of several KB each, far beyond
15 MB; the existing daily-sales export caps ranges at 93 days.

main/services/daily-invoices-export.ts[463-537]
main/services/daily-invoices-export.ts[623-634]
main/routes/reports.ts[1906-1920]
main/routes/reports.ts[1906-1927]
main/services/daily-invoices-export.ts[519-537]
main/services/daily-invoices-export.ts[623-650]
main/services/daily-invoices-export.ts[1267-1272]

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 invoices export hydrates every bill, item and refund for permitted ranges of up to 366 days before discovering that the HTML exceeds the 15 MB limit. Its per-invoice synchronous refund queries compound the main-process blocking even when the export ultimately fails.

## Fix Focus Areas
- main/services/daily-invoices-export.ts[463-537]
- main/services/daily-invoices-export.ts[519-537]
- main/services/daily-invoices-export.ts[623-650]
- main/services/daily-invoices-export.ts[1267-1272]

## Recommended Fix
Run a cheap `SELECT COUNT(*)` using the same window and status filter before hydration, and reject with `DailySalesExportValidationError` above a conservative per-card count cap, or lower `DAILY_INVOICES_EXPORT_MAX_RANGE_DAYS`. Replace the per-bill refund query with a batched `WHERE r.bill_id IN (...)` query or window join, group the results by bill ID in JS, and use each group when constructing cards.

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


6. Invoice item totals differ from receipts ✓ Resolved
Description
The card’s Total item column renders item.subtotal rather than the separately stored
item.total used on receipts. For an item whose subtotal is 100 and total is 105 after tax, the
compiled invoice labels 100 as its line total while the customer receipt shows 105.
Code

main/services/daily-invoices-export.ts[R811-813]

+                  <td class="num">${escapeHtml(fmtMoney(item.unit_price))}</td>
+                  <td class="num">${escapeHtml(fmtMoney(item.subtotal))}</td>
+                </tr>`;
Relevance

●● Moderate

Likely print-layout defect, but pagination behavior is subjective and lacks a closely matching
precedent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new item row uses subtotal under a Total heading; the bill projection keeps subtotal and
total distinct, and the receipt renderer uses total.

main/services/daily-invoices-export.ts[805-813]
main/services/daily-invoices-export.ts[895-902]
main/routes/bills.ts[654-675]
shared/print/document.ts[664-672]

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 compiled invoice labels an item's subtotal as its total, producing a different line amount from the receipt when those values differ.
## Fix Focus Areas
- main/services/daily-invoices-export.ts[805-813]
- main/services/daily-invoices-export.ts[895-907]
## Recommended Fix
Render `item.total` in the Total column to match the receipt, or explicitly label the column as a pre-tax subtotal if that is the intended accounting presentation. Test an item with different subtotal and total values.

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


View medium (1)
7. Long ranges push invoices past page two ✓ Resolved
Description
The cover section prints every daily tax row and applies a page break only after the entire section.
A range with tax activity across many of the permitted 366 days makes the cover span multiple pages
before the first invoice appears.
Code

main/services/daily-invoices-export.ts[R952-955]

+  .cover-page.has-invoices {
+    page-break-after: always;
+    break-after: page;
+  }
Relevance

●● Moderate

The workload risk is plausible, but historical evidence does not establish this team’s preferred
range or batching design.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The sales builder appends tax rows for each business day, the template puts all of them inside the
cover, and the CSS does not restrict that cover to one page.

main/services/daily-invoices-export.ts[30-30]
main/services/daily-sales-export.ts[1429-1454]
main/services/daily-invoices-export.ts[758-774]
main/services/daily-invoices-export.ts[1239-1262]

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 tax breakdown can make the cover occupy multiple pages, so invoices no longer begin on page two.
## Fix Focus Areas
- main/services/daily-invoices-export.ts[758-774]
- main/services/daily-invoices-export.ts[949-955]
- main/services/daily-invoices-export.ts[1233-1262]
## Recommended Fix
Keep a bounded summary on the cover and move the complete per-day tax breakdown into a subsequent appendix or otherwise paginate it after the first invoice page. Test a long, tax-active range with actual print pagination.

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


Grey Divider

Context sources
✅ Linked PRs:
  🌐 PR #39
⚠️ 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/docs.flopos.com (sha: abe7cff3) — View relationship
Review mode: Auto: 🧠 Deep: Large, bug-dense financial export spanning backend, frontend, PDF security, and tests.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
Comment thread main/services/daily-invoices-export.ts Resolved
@khaira777
khaira777 merged commit b3c71f9 into main Oct 29, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1397-daily-invoices-pdf branch October 29, 2026 04:57
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): batch export daily invoices into single compiled PDF

1 participant