Skip to content

feat(reports): date-range daily sales export - #1391

Merged
khaira777 merged 6 commits into
mainfrom
fm/flocafe-1335-date-range-sales-export
Oct 28, 2026
Merged

khaira777 merged 6 commits into
mainfrom
fm/flocafe-1335-date-range-sales-export

Conversation

@khaira777

@khaira777 khaira777 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1335

Intent

Implement and verify issue #1335 (feat(reports): date-range daily sales export with per-tax-rate summary for accountants) and open a PR targeting main without merging it:

  • Accountants want one file per month or period. Today owners have to download one business day at a time via GET /reports/daily-sales/export (main/routes/reports.ts ~L1410).
  • Decisions & Requirements:
  • Add inclusive from / to business-date parameters (YYYY-MM-DD, store timezone and business-day cutoff respected), capped at 93 calendar days per request so memory stays bounded.
  • Keep the existing single-day call working unchanged (date=).
  • Reject mixed single-date/range arguments, incomplete ranges, invalid dates, reversed ranges (to < from), and oversized ranges (> 93 days) with HTTP 400 and a clear message.
  • Preserve existing single-day accounting rules, columns, CSV parts, card transaction IDs (feat(payments): card transaction id & export #1233), service and packaging charge totals, and ingredient cost/margin (feat: add ingredient cost tracking and profitability reporting #1332). Preserve sale-day and refund-day attribution across [from, to]. Recompute period margins and coverage from underlying amounts rather than averaging percentages.
  • Add daily summaries and a per-tax-rate summary sheet to XLSX, with equivalent CSV parts: taxable base, tax, and gross per rate per day.
  • Preserve separate named tax components and their rates. Shared component bases must be clearly identified as non-additive.
  • Derive historical detail from stored order/item tax evidence, never current product tax configuration. Unknown rates or bases remain blank/unknown; their stored tax amounts remain in reconciliation, with an incomplete-detail warning.
  • Acceptance criteria:
  • A range export over N days equals the sum of N single-day exports (orders, payments, tax).
  • Ranges over 93 days, or with to < from, return 400 with a clear message.
  • The tax summary sheet totals equal the tax totals of the orders sheet.
  • CSV parts and XLSX sheets are consistent.
  • Update the date-range picker in the dashboard export UI and update docs/reference/daily-sales-export.md in the same PR.

What Changed

  • Extended GET /api/reports/daily-sales/export (main/routes/reports.ts, main/services/daily-sales-export.ts, main/services/daily-sales-export-files.ts) to support inclusive from and to business-date ranges (capped at 93 calendar days) in addition to single-day date, returning HTTP 400 on invalid, incomplete, mixed, reversed, or oversized ranges and recomputing period profitability margins and cost coverage from summed underlying amounts.
  • Added Daily (part=daily) and per-tax-rate Taxes (part=taxes) XLSX sheets and CSV parts built from stored bill and order-item tax snapshots and breakdowns, preserving separate named tax components, marking shared non-additive taxable bases (shared_base, base_additive), and flagging incomplete historical tax detail (incomplete_detail, warning, tax_detail_complete, tax_detail_warning) while reconciling tax totals to Summary.tax_total.
  • Updated the dashboard export controls (frontend/src/app/(dashboard)/dashboard/page.tsx), /api/reports/insights (start_date and end_date query support), locale message catalogs, reference documentation (docs/reference/daily-sales-export.md, docs/reference/api.md), and tests/daily-sales-export.test.ts to cover multi-day exports and all five CSV parts (summary, daily, taxes, items, payments).

Risk Assessment

✅ Low: The branch cleanly implements single-day and date-range daily sales exports (XLSX and 5-part CSV) with per-tax-rate breakdowns, refund reconciliation, strict calendar-date and 93-day range validation, range query hoisting, dashboard period controls (Day, Month, Custom Range), translations across all 24 locales, documentation updates, and end-to-end HTTP/workbook tests. Prior review feedback regarding historical tax edge cases, calendar date validation in report routes, range query hoisting, and removal of the unrequested tax_summary rollup has been resolved.

Testing

Ran the focused integration suites (test:daily-sales-export, test:ingredient-cost, test:reports-insights, test:reports-export, and test:customer-sales-export), built the backend and static frontend, and stood up an isolated FloCafe instance on local ports with a disposable SQLite database to drive all six scenarios live via HTTP and Playwright Chromium, capturing dashboard screenshots and downloaded XLSX/CSV export artifacts. All scenarios and assertions passed.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Owner exports a single business day via GET /api/reports/daily-sales/export?date=YYYY-MM-DD in XLSX and CSV formats and receives the 5-sheet workbook (Summary, Daily, Taxes, Items, Payments) and match… ✅ pass live Single-day Summary CSV (2026-10-20), Single-day Taxes CSV (2026-10-20), and npm run test:daily-sales-export
Owner exports a multi-day range (?from=YYYY-MM-DD&to=YYYY-MM-DD) respecting store timezone and business-day cutoff, where the N-day range export equals the exact sum of the N single-day exports across… ✅ pass live Multi-day range Summary CSV downloaded from Dashboard UI, Multi-day range Daily CSV downloaded from Dashboard UI, and Multi-day range reconciliation and adversarial HTTP 400 validation responses
API rejects mixed single-date and range parameters, incomplete ranges, invalid calendar dates, reversed ranges (to < from), and oversized ranges (> 93 days) with HTTP 400 and clear error messages whil… ✅ pass live Multi-day range reconciliation and adversarial HTTP 400 validation responses and npm run test:daily-sales-export
Exported Taxes sheet and part=taxes CSV list taxable base, tax, and gross per tax component and rate per business day, reconcile total tax with Summary.tax_total and Daily.tax_total, keep separate nam… ✅ pass live Multi-day range Taxes CSV downloaded from Dashboard UI and Multi-day range XLSX workbook (Summary, Daily, Taxes, Items, Payments sheets) downloaded from Dashboard UI
When a historical paid bill or item has a stored non-zero tax_amount without rate or taxable-base breakdown, Taxes leaves tax_rate, taxable_base, and gross_amount blank while keeping stored tax_amount… ✅ pass live Multi-day range Taxes CSV downloaded from Dashboard UI and Multi-day range Summary CSV downloaded from Dashboard UI
Owner switches the dashboard period selector between Day, Month, and Range modes, adjusts from/to range dates (clamped to at most 93 days), and downloads both the range XLSX workbook and all five CSV… ✅ pass live Dashboard Day mode with Day/Month/Range period selector, Dashboard Range mode (2026-10-20 to 2026-10-22) with period KPIs and recomputed 64.44% gross margin (Partial cost), Dashboard Range mode with E…

Dashboard Day mode with Day/Month/Range period selector
Dashboard Range mode (2026-10-20 to 2026-10-22) with period KPIs and recomputed 64.44% gross margin (Partial cost)
Dashboard Range mode with Export sales dropdown menu open

  • Evidence: Multi-day range XLSX workbook (Summary, Daily, Taxes, Items, Payments sheets) downloaded from Dashboard UI (local file: ~/.no-mistakes/evidence/01M625JJHCZ90BE3228D5JS8H3/daily-sales-2026-10-20_to_2026-10-22.xlsx)
Evidence: Multi-day range Summary CSV downloaded from Dashboard UI

metric,value business_date,2026-10-20..2026-10-22 from_date,2026-10-20 to_date,2026-10-22 day_count,3 timezone,Asia/Kolkata business_day_start,04:00 currency,INR order_count,4 paid_bill_count,4 gross_collected,462.5 refunds_issued,10 net_collected,452.5 tax_total,32.5 discount_total,0 service_charge_total,4 packaging_charge_total,1 delivery_charge_total,0 known_cost_net_item_sales,405 ingredient_cost_total,144 gross_profit_total,261 gross_margin_percent,64.44 cost_coverage_status,partial tax_detail_complete,false tax_detail_warning,Incomplete historical tax detail: one or more historical records lacked rate or taxable base breakdown; stored tax amounts are included in tax_total. payment_cash,352.5 payment_card,100

metric,value
business_date,2026-10-20..2026-10-22
from_date,2026-10-20
to_date,2026-10-22
day_count,3
timezone,Asia/Kolkata
business_day_start,04:00
currency,INR
order_count,4
paid_bill_count,4
gross_collected,462.5
refunds_issued,10
net_collected,452.5
tax_total,32.5
discount_total,0
service_charge_total,4
packaging_charge_total,1
delivery_charge_total,0
known_cost_net_item_sales,405
ingredient_cost_total,144
gross_profit_total,261
gross_margin_percent,64.44
cost_coverage_status,partial
tax_detail_complete,false
tax_detail_warning,Incomplete historical tax detail: one or more historical records lacked rate or taxable base breakdown; stored tax amounts are included in tax_total.
payment_cash,352.5
payment_card,100
Evidence: Multi-day range Daily CSV downloaded from Dashboard UI

business_date,order_count,paid_bill_count,gross_collected,refunds_issued,net_collected,tax_total,discount_total,service_charge_total,packaging_charge_total,delivery_charge_total,known_cost_net_item_sales,ingredient_cost_total,gross_profit_total,gross_margin_percent,cost_coverage_status 2026-10-20,2,2,320,0,320,20,0,0,0,0,300,90,210,70,known 2026-10-21,1,1,60,10,50,5,0,4,1,0,30,24,6,20,partial 2026-10-22,1,1,82.5,0,82.5,7.5,0,0,0,0,75,30,45,60,known

business_date,order_count,paid_bill_count,gross_collected,refunds_issued,net_collected,tax_total,discount_total,service_charge_total,packaging_charge_total,delivery_charge_total,known_cost_net_item_sales,ingredient_cost_total,gross_profit_total,gross_margin_percent,cost_coverage_status
2026-10-20,2,2,320,0,320,20,0,0,0,0,300,90,210,70,known
2026-10-21,1,1,60,10,50,5,0,4,1,0,30,24,6,20,partial
2026-10-22,1,1,82.5,0,82.5,7.5,0,0,0,0,75,30,45,60,known
Evidence: Multi-day range Taxes CSV downloaded from Dashboard UI

business_date,tax_name,tax_rate,taxable_base,tax_amount,gross_amount,shared_base,base_additive,incomplete_detail,warning 2026-10-20,VAT,10,100,10,110,false,true,false, 2026-10-20,CGST,2.5,200,5,205,true,false,false, 2026-10-20,SGST,2.5,200,5,205,true,false,false, 2026-10-21,VAT,10,50,5,55,false,true,false, 2026-10-22,Unknown,,,7.5,,false,false,true,Incomplete historical tax detail: rate or taxable base was not stored; tax_amount is included in reconciliation.

business_date,tax_name,tax_rate,taxable_base,tax_amount,gross_amount,shared_base,base_additive,incomplete_detail,warning
2026-10-20,VAT,10,100,10,110,false,true,false,
2026-10-20,CGST,2.5,200,5,205,true,false,false,
2026-10-20,SGST,2.5,200,5,205,true,false,false,
2026-10-21,VAT,10,50,5,55,false,true,false,
2026-10-22,Unknown,,,7.5,,false,false,true,Incomplete historical tax detail: rate or taxable base was not stored; tax_amount is included in reconciliation.
Evidence: Multi-day range Items CSV downloaded from Dashboard UI
product_id,product_name,product_sku,quantity,gross_item_sales,item_discounts,net_item_sales,tax_amount,ingredient_cost,profit,margin_percent,cost_status
prod-mocha,Dark Mocha,SKU-MOCHA,5,220,0,220,12,50,150,75,partial
prod-latte,Artisan Latte,SKU-LATTE,4,205,0,205,20.5,94,111,54.15,known
Evidence: Multi-day range Payments CSV downloaded from Dashboard UI
bill_number,order_number,payment_time,method,amount,transaction_id
BILL-LIVE-1A,ORD-LIVE-1A,2026-10-20 15:30:00,card,110,000987
BILL-LIVE-1B,ORD-LIVE-1B,2026-10-20 15:30:00,cash,210,
BILL-LIVE-2C,ORD-LIVE-2C,2026-10-22 01:30:00,cash,60,
BILL-LIVE-3D,ORD-LIVE-3D,2026-10-22 04:00:00,cash,82.5,
Evidence: Single-day Summary CSV (2026-10-20)
metric,value
business_date,2026-10-20
from_date,2026-10-20
to_date,2026-10-20
day_count,1
timezone,Asia/Kolkata
business_day_start,04:00
currency,INR
order_count,2
paid_bill_count,2
gross_collected,320
refunds_issued,0
net_collected,320
tax_total,20
discount_total,0
service_charge_total,0
packaging_charge_total,0
delivery_charge_total,0
known_cost_net_item_sales,300
ingredient_cost_total,90
gross_profit_total,210
gross_margin_percent,70
cost_coverage_status,known
tax_detail_complete,true
tax_detail_warning,
payment_cash,210
payment_card,110
Evidence: Single-day Taxes CSV (2026-10-20)
business_date,tax_name,tax_rate,taxable_base,tax_amount,gross_amount,shared_base,base_additive,incomplete_detail,warning
2026-10-20,VAT,10,100,10,110,false,true,false,
2026-10-20,CGST,2.5,200,5,205,true,false,false,
2026-10-20,SGST,2.5,200,5,205,true,false,false,
Evidence: Multi-day range reconciliation and adversarial HTTP 400 validation responses
{
  "reconciliationComparison": {
    "order_count": {
      "day1": 2,
      "day2": 1,
      "day3": 1,
      "sumOfSingleDays": 4,
      "rangeExportValue": 4
    },
    "paid_bill_count": {
      "day1": 2,
      "day2": 1,
      "day3": 1,
      "sumOfSingleDays": 4,
      "rangeExportValue": 4
    },
    "gross_collected": {
      "day1": 320,
      "day2": 60,
      "day3": 82.5,
      "sumOfSingleDays": 462.5,
      "rangeExportValue": 462.5
    },
    "refunds_issued": {
      "day1": 0,
      "day2": 10,
      "day3": 0,
      "sumOfSingleDays": 10,
      "rangeExportValue": 10
    },
    "net_collected": {
      "day1": 320,
      "day2": 50,
      "day3": 82.5,
      "sumOfSingleDays": 452.5,
      "rangeExportValue": 452.5
    },
    "tax_total": {
      "day1": 20,
      "day2": 5,
      "day3": 7.5,
      "sumOfSingleDays": 32.5,
      "rangeExportValue": 32.5
    },
    "discount_total": {
      "day1": 0,
      "day2": 0,
      "day3": 0,
      "sumOfSingleDays": 0,
      "rangeExportValue": 0
    },
    "service_charge_total": {
      "day1": 0,
      "day2": 4,
      "day3": 0,
      "sumOfSingleDays": 4,
      "rangeExportValue": 4
    },
    "packaging_charge_total": {
      "day1": 0,
      "day2": 1,
      "day3": 0,
      "sumOfSingleDays": 1,
      "rangeExportValue": 1
    },
    "delivery_charge_total": {
      "day1": 0,
      "day2": 0,
      "day3": 0,
      "sumOfSingleDays": 0,
      "rangeExportValue": 0
    },
    "known_cost_net_item_sales": {
      "day1": 300,
      "day2": 30,
      "day3": 75,
      "sumOfSingleDays": 405,
      "rangeExportValue": 405
    },
    "ingredient_cost_total": {
      "day1": 90,
      "day2": 24,
      "day3": 30,
      "sumOfSingleDays": 144,
      "rangeExportValue": 144
    },
    "gross_profit_total": {
      "day1": 210,
      "day2": 6,
      "day3": 45,
      "sumOfSingleDays": 261,
      "rangeExportValue": 261
    },
    "payment_cash": {
      "day1": 210,
      "day2": 60,
      "day3": 82.5,
      "sumOfSingleDays": 352.5,
      "rangeExportValue": 352.5
    },
    "payment_card": {
      "day1": 110,
      "day2": -10,
      "day3": 0,
      "sumOfSingleDays": 100,
      "rangeExportValue": 100
    }
  },
  "adversarialResults": [
    {
      "case": "mixed_date_and_range",
      "query": "date=2026-10-20&from=2026-10-20&to=2026-10-22&format=xlsx",
      "status": 400,
      "response": {
        "error": "Specify either date or from/to date range, not both"
      }
    },
    {
      "case": "from_without_to",
      "query": "from=2026-10-20&format=xlsx",
      "status": 400,
      "response": {
        "error": "Both from and to dates (YYYY-MM-DD) are required for a range export"
      }
    },
    {
      "case": "to_without_from",
      "query": "to=2026-10-22&format=xlsx",
      "status": 400,
      "response": {
        "error": "Both from and to dates (YYYY-MM-DD) are required for a range export"
      }
    },
    {
      "case": "impossible_calendar_date",
      "query": "date=2026-02-30&format=xlsx",
      "status": 400,
      "response": {
        "error": "Invalid date; expected a valid YYYY-MM-DD calendar date"
      }
    },
    {
      "case": "invalid_from_format",
      "query": "from=not-a-date&to=2026-10-22&format=xlsx",
      "status": 400,
      "response": {
        "error": "Invalid from or to date; expected valid YYYY-MM-DD calendar dates"
      }
    },
    {
      "case": "reversed_range",
      "query": "from=2026-10-22&to=2026-10-20&format=xlsx",
      "status": 400,
      "response": {
        "error": "Invalid date range: to date must be on or after from date"
      }
    },
    {
      "case": "oversized_range_94_days",
      "query": "from=2026-01-01&to=2026-04-04&format=xlsx",
      "status": 400,
      "response": {
        "error": "Date range exceeds maximum of 93 days (requested 94 days)"
      }
    },
    {
      "case": "max_allowed_range_93_days",
      "query": "from=2026-01-01&to=2026-04-03&format=csv&part=summary",
      "status": 200,
      "response": {
        "day_count": 93
      }
    }
  ]
}

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 - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Owner exports a single business day via GET /api/reports/daily-sales/export?date=YYYY-MM-DD in XLSX and CSV formats and receives the 5-sheet workbook (Summary, Daily, Taxes, Items, Payments) and match… ✅ pass live Single-day Summary CSV (2026-10-20), Single-day Taxes CSV (2026-10-20), and npm run test:daily-sales-export
Owner exports a multi-day range (?from=YYYY-MM-DD&to=YYYY-MM-DD) respecting store timezone and business-day cutoff, where the N-day range export equals the exact sum of the N single-day exports across… ✅ pass live Multi-day range Summary CSV downloaded from Dashboard UI, Multi-day range Daily CSV downloaded from Dashboard UI, and Multi-day range reconciliation and adversarial HTTP 400 validation responses
API rejects mixed single-date and range parameters, incomplete ranges, invalid calendar dates, reversed ranges (to < from), and oversized ranges (> 93 days) with HTTP 400 and clear error messages whil… ✅ pass live Multi-day range reconciliation and adversarial HTTP 400 validation responses and npm run test:daily-sales-export
Exported Taxes sheet and part=taxes CSV list taxable base, tax, and gross per tax component and rate per business day, reconcile total tax with Summary.tax_total and Daily.tax_total, keep separate nam… ✅ pass live Multi-day range Taxes CSV downloaded from Dashboard UI and Multi-day range XLSX workbook (Summary, Daily, Taxes, Items, Payments sheets) downloaded from Dashboard UI
When a historical paid bill or item has a stored non-zero tax_amount without rate or taxable-base breakdown, Taxes leaves tax_rate, taxable_base, and gross_amount blank while keeping stored tax_amount… ✅ pass live Multi-day range Taxes CSV downloaded from Dashboard UI and Multi-day range Summary CSV downloaded from Dashboard UI
Owner switches the dashboard period selector between Day, Month, and Range modes, adjusts from/to range dates (clamped to at most 93 days), and downloads both the range XLSX workbook and all five CSV… ✅ pass live Dashboard Day mode with Day/Month/Range period selector, Dashboard Range mode (2026-10-20 to 2026-10-22) with period KPIs and recomputed 64.44% gross margin (Partial cost), Dashboard Range mode with E…
  • npm run test:daily-sales-export
  • npm run test:ingredient-cost
  • npm run test:reports-insights
  • npm run test:reports-export
  • npm run test:customer-sales-export
  • npm run build && npm run build:frontend
  • Live isolated FloCafe server (:4301) + Playwright Chromium verification of GET /api/reports/daily-sales/export (single-day, multi-day range, adversarial 400 checks) and /dashboard Range picker + XLSX/CSV downloads
✅ **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 date-range daily sales exports with per-tax-rate summaries

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Export up to 93 inclusive business days while preserving single-day accounting and refund
 attribution.
• Add daily and historical per-tax-rate summaries to XLSX and CSV, with reconciliation warnings for
 incomplete detail.
• Add dashboard range controls, API documentation, and coverage for validation and accounting
 equivalence.
Diagram

graph TD
  UI["Dashboard controls"] --> API["Export endpoint"] --> DATA["Accounting dataset"] --> SERIAL["File serializers"] --> OUT["XLSX and CSV"]
  TAX[("Stored tax evidence")] --> DATA
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Set-based period aggregation
  • ➕ Could reduce repeated database queries for longer ranges.
  • ➖ Would complicate store-local business-day grouping and risk diverging from established single-day accounting rules.

Recommendation: Keep the per-business-day aggregation: it makes range totals traceable to existing single-day exports and preserves cutoff and refund attribution. Consider batching database reads separately if measured performance at the 93-day limit warrants it.

Files changed (31) +2517 / -284

Enhancement (4) +1569 / -241
page.tsxAdd dashboard range selection and five-part downloads +123/-28

Add dashboard range selection and five-part downloads

• Adds bounded custom-range date controls alongside day and month modes. Exports the selected period as XLSX or all five CSV parts and uses the range for dashboard queries.

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

reports.tsValidate and serve single-day or range exports +38/-9

Validate and serve single-day or range exports

• Accepts mutually exclusive date or from/to arguments, validates CSV parts, and maps range errors to HTTP 400. Passes the resolved period to the dataset builder and filenames.

main/routes/reports.ts

daily-sales-export-files.tsSerialize Daily and Taxes sheets and CSV parts +177/-8

Serialize Daily and Taxes sheets and CSV parts

• Adds the two worksheets and matching CSV parts with numeric formatting, blank unknown values, and period-aware filenames.

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

daily-sales-export.tsAggregate bounded periods and reconstruct historical tax detail +1231/-196

Aggregate bounded periods and reconstruct historical tax detail

• Validates inclusive ranges up to 93 days and aggregates sales, payments, items, costs, and profitability by business day and period. Reconstructs named tax components from stored evidence, marks non-additive shared bases, and reconciles incomplete detail without inventing rates or bases.

main/services/daily-sales-export.ts

Tests (1) +869 / -4
daily-sales-export.test.tsVerify range validation, accounting parity, and tax reconciliation +869/-4

Verify range validation, accounting parity, and tax reconciliation

• Adds endpoint and workbook assertions for range limits, cutoff and refund attribution, single-day equivalence, period margins, CSV/XLSX consistency, historical tax changes, split bills, and incomplete-detail warnings.

tests/daily-sales-export.test.ts

Documentation (2) +55 / -39
api.mdDocument the expanded daily sales export API +21/-20

Document the expanded daily sales export API

• Describes inclusive range parameters, validation errors, the five sheets and CSV parts, and range filenames.

docs/reference/api.md

daily-sales-export.mdSpecify range and historical-tax reconciliation contracts +34/-19

Specify range and historical-tax reconciliation contracts

• Documents business-day aggregation, period profitability, shared tax bases, incomplete historical detail, file columns, and test coverage.

docs/reference/daily-sales-export.md

Other (24) +24 / -0
ar.jsonTranslate the range mode into Arabic +1/-0

Translate the range mode into Arabic

• Adds the dashboard range-mode label.

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

bn.jsonTranslate the range mode into Bengali +1/-0

Translate the range mode into Bengali

• Adds the dashboard range-mode label.

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

de.jsonTranslate the range mode into German +1/-0

Translate the range mode into German

• Adds the dashboard range-mode label.

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

en.jsonLabel the range mode in English +1/-0

Label the range mode in English

• Adds the dashboard range-mode label.

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

es.jsonTranslate the range mode into Spanish +1/-0

Translate the range mode into Spanish

• Adds the dashboard range-mode label.

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

fa.jsonTranslate the range mode into Persian +1/-0

Translate the range mode into Persian

• Adds the dashboard range-mode label.

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

fil.jsonTranslate the range mode into Filipino +1/-0

Translate the range mode into Filipino

• Adds the dashboard range-mode label.

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

fr.jsonTranslate the range mode into French +1/-0

Translate the range mode into French

• Adds the dashboard range-mode label.

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

hi.jsonTranslate the range mode into Hindi +1/-0

Translate the range mode into Hindi

• Adds the dashboard range-mode label.

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

id.jsonTranslate the range mode into Indonesian +1/-0

Translate the range mode into Indonesian

• Adds the dashboard range-mode label.

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

it.jsonTranslate the range mode into Italian +1/-0

Translate the range mode into Italian

• Adds the dashboard range-mode label.

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

ja.jsonTranslate the range mode into Japanese +1/-0

Translate the range mode into Japanese

• Adds the dashboard range-mode label.

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

ko.jsonTranslate the range mode into Korean +1/-0

Translate the range mode into Korean

• Adds the dashboard range-mode label.

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

ne.jsonTranslate the range mode into Nepali +1/-0

Translate the range mode into Nepali

• Adds the dashboard range-mode label.

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

nl.jsonTranslate the range mode into Dutch +1/-0

Translate the range mode into Dutch

• Adds the dashboard range-mode label.

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

pt.jsonTranslate the range mode into Portuguese +1/-0

Translate the range mode into Portuguese

• Adds the dashboard range-mode label.

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

ru.jsonTranslate the range mode into Russian +1/-0

Translate the range mode into Russian

• Adds the dashboard range-mode label.

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

sq.jsonTranslate the range mode into Albanian +1/-0

Translate the range mode into Albanian

• Adds the dashboard range-mode label.

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

th.jsonTranslate the range mode into Thai +1/-0

Translate the range mode into Thai

• Adds the dashboard range-mode label.

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

tr.jsonTranslate the range mode into Turkish +1/-0

Translate the range mode into Turkish

• Adds the dashboard range-mode label.

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

ur.jsonTranslate the range mode into Urdu +1/-0

Translate the range mode into Urdu

• Adds the dashboard range-mode label.

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

vi.jsonTranslate the range mode into Vietnamese +1/-0

Translate the range mode into Vietnamese

• Adds the dashboard range-mode label.

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

zh-tw.jsonTranslate the range mode into Traditional Chinese +1/-0

Translate the range mode into Traditional Chinese

• Adds the dashboard range-mode label.

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

zh.jsonTranslate the range mode into Simplified Chinese +1/-0

Translate the range mode into Simplified Chinese

• Adds the dashboard range-mode label.

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

@khaira777 khaira777 changed the title feat(reports): add date-range daily sales export with per-tax-rate summary feat(reports): date-range daily sales export Oct 28, 2026
@qodo-code-review

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Discounted legacy sales show wrong bases ✓ Resolved
Description
extractItemLegacyEntries derives an inclusive taxable base by subtracting the full item tax from
the discounted item subtotal. When an order-level discount scales the tax too, a 110 gross item with
10 tax and a 10% discount yields a reported base of 89 instead of 90.
Code

main/services/daily-sales-export.ts[R533-535]

+      ? item.tax_type === 'inclusive'
+        ? subtotalDec.mul(taxDiscountRatioDec).minus(itemTaxDec)
+        : subtotalDec.mul(taxDiscountRatioDec)
Relevance

●●● Strong

Discount scaling must apply consistently to taxable bases and tax amounts; recent
financial-calculation fixes were accepted.

PR-#757
PR-#1282

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The calculation applies the ratio to subtotal but not item tax; order tax rollup applies the
discount ratio to tax.

main/services/daily-sales-export.ts[529-549]
main/services/orders.ts[243-269]

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 legacy inclusive-base calculation discounts gross but subtracts undiscounted item tax.
## Fix Focus Areas
- main/services/daily-sales-export.ts[529-549]
## Recommended Fix
Derive the inclusive base using tax scaled by the same order discount ratio, respecting the stored bill's rounding, and add a discounted inclusive legacy-record test.

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


2. Discounted sales show tax at wrong rates ✓ Resolved
Description
resolveBillHistoricalTaxEntries uses undiscounted item snapshot components even when the bill
stores discounted tax evidence, then reconciles only the difference against the bill total. On an
order-level discount larger than the rounding tolerance, the original rate retains too much tax and
an Unknown row absorbs the difference; the snapshot taxable base also remains undiscounted.
Code

main/services/daily-sales-export.ts[R762-765]

+      const snap = extractSnapshotEntries(item.tax_snapshot);
+      if (snap.present) {
+        hasSnapshotEvidence = true;
+        itemEntries.push(...snap.entries);
Relevance

●●● Strong

Discounted historical tax evidence must scale amounts and bases consistently; analogous discount-tax
fixes were accepted.

PR-#757
PR-#1282

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Order tax rollup scales item snapshots for the bill, including taxable bases. The export instead
takes unscaled item entries, while its reconciliation changes amounts only.

main/services/orders.ts[243-269]
main/services/tax.ts[900-938]
main/services/daily-sales-export.ts[638-683]
main/services/daily-sales-export.ts[761-809]

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

## Issue description
Item snapshots precede order-level discounts, so their component amounts and taxable bases do not describe the paid bill.
## Fix Focus Areas
- main/services/daily-sales-export.ts[756-810]
- main/services/tax.ts[900-938]
## Recommended Fix
Prefer the bill's discounted component snapshot where it represents the complete bill. For fallback item evidence, apply the stored order discount consistently to component amounts and bases before reconciliation; test per-rate amounts as well as the total.

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



Remediation recommended

3. Export tax can disagree with X/Z report tax ✓ Resolved
Description
resolveBillHistoricalTaxEntries sets effectiveTargetTax to the sum of active-item tax_amount
when bills.tax_amount is 0 and the bill has no snapshot or breakdown. Its active-item filter
(isExcludedFromBillReceipt) also keeps refunded items, which aggregateTaxComponents drops. On
those legacy bills the export's tax_total (and an Unknown incomplete Taxes row) can differ from
the X/Z reports and from the export's own output before this PR, which still use
aggregateTaxComponents on the same bills.
Code

main/services/daily-sales-export.ts[R714-718]

+  const effectiveTargetTax =
+    billTaxDec !== null &&
+    (!billTaxDec.isZero() || billHasOwnEvidence || activeItemsTaxSum.isZero())
+      ? billTaxDec
+      : activeItemsTaxSum;
Relevance

●●● Strong

Different item-status and tax-target rules can diverge from established reports, matching accepted
cross-report consistency fixes.

PR-#537
PR-#757

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The old path in tax-components.ts only reconciles against the stored bill tax and filters items with
isTerminalItemStatus (which excludes refunded). The new path replaces a stored zero with the
item sum and uses a different status filter. Meanwhile cash-closures.ts still runs
aggregateTaxComponents over the same hydrated bills for X/Z.

main/services/daily-sales-export.ts[700-718]
main/services/daily-sales-export.ts[620-640]
main/services/tax-components.ts[120-130]
main/services/tax-components.ts[240-275]
shared/order-item-status.ts[8-24]
main/routes/cash-closures.ts[865-885]

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 export now computes per-bill tax differently from `aggregateTaxComponents`, which X/Z reports use. When `bills.tax_amount` is 0 it falls back to the summed item tax, and its item filter keeps `refunded` items. So the export's `tax_total` can disagree with the closed-day tax.

## Fix Focus Areas
- main/services/daily-sales-export.ts[700-718]

## Recommended Fix
When `bill.tax_amount` is not null, use it as the reconciliation target, as `aggregateTaxComponents` does. Filter items with the same predicate the old resolver uses (`isTerminalItemStatus`), or document why they intentionally differ. Add a test asserting that export `tax_total` equals the X-report tax total for the same day.

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


4. Fixed taxes appear as zero-percent tax ✓ Resolved
Description
extractItemLegacyEntries treats a stored legacy rate: 0 as a known percentage rate and marks the
row complete if it can derive a base. The tax writer also stores fixed-rule components as rate: 0
when no percentage rate exists, so legacy records without snapshots can present a fixed charge as a
complete zero-percent tax.
Code

main/services/daily-sales-export.ts[R539-543]

+  return breakdown.map((comp) => {
+    const rateStr = comp.rate === null ? null : comp.rate.toString();
+    const base = comp.explicitBase ?? (comp.rate !== null ? derivedItemBase : null);
+    const incompleteDetail = rateStr === null || base === null;
+    return {
Relevance

●●● Strong

Treating absent fixed-tax rates as known zero-percent detail violates the historical-tax contract.

PR-#405
PR-#757

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The writer substitutes zero for an absent component rate, while the new legacy reader equates any
parsed zero with a known rate.

main/services/tax.ts[515-527]
main/services/daily-sales-export.ts[508-517]
main/services/daily-sales-export.ts[539-549]

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

## Issue description
Legacy breakdowns encode missing fixed-tax rates as zero, which cannot establish a zero-percent rate.
## Fix Focus Areas
- main/services/daily-sales-export.ts[487-519]
- main/services/daily-sales-export.ts[539-549]
## Recommended Fix
Do not report a nonzero legacy tax amount with rate zero as a verified percentage. Mark its rate and detail unknown unless independent stored evidence establishes the rate; cover fixed-rule legacy records in tests.

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


5. Yen stores get false incomplete-tax warnings ✓ Resolved
Description
MINOR_UNIT_ROUNDING_TOLERANCE is hard-coded at 0.05 currency units, and reconcileBillTaxEntries,
shouldExtractDocumentResiduals and the residual extraction all use it whatever the tenant's
currency fraction digits are. For zero-decimal currencies such as JPY, a normal one-unit rounding
difference becomes an Unknown incomplete row and sets the Summary warning. For three-decimal
currencies such as KWD, gaps of up to 50 minor units are silently folded into a named component.
Code

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

+const MINOR_UNIT_ROUNDING_TOLERANCE = new Decimal('0.05');
Relevance

●●● Strong

Currency-independent rounding tolerance causes concrete tax-detail errors for zero- and
three-decimal currencies.

PR-#695

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tolerance is a fixed decimal constant. Gaps at or under it are added to the last complete
component, and larger gaps become an incomplete Unknown entry that drives
tax_detail_complete=false. The function already has minorFactor/fractionDigits for the
currency but never passes them in.

main/services/daily-sales-export.ts[554-571]
main/services/daily-sales-export.ts[656-684]
main/services/daily-sales-export.ts[1039-1041]
main/services/daily-sales-export.ts[1390-1390]

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 rounding tolerance used to reconcile historical tax components is fixed at 0.05 regardless of currency. Zero-decimal currencies get spurious incomplete rows, and three-decimal currencies hide large gaps.

## Fix Focus Areas
- main/services/daily-sales-export.ts[554-571]
- main/services/daily-sales-export.ts[573-617]
- main/services/daily-sales-export.ts[619-684]
- main/services/daily-sales-export.ts[686-824]

## Recommended Fix
Pass a tolerance from `buildDailySalesExportDataset` into these helpers, derived from `getCurrencyMinorUnitFactor(currency)`, e.g. `new Decimal(k).div(minorFactor)` for a few minor units. Use it in place of the module constant.

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


View medium (5)
6. Mixed tax bases lose their distinction ✓ Resolved
Description
aggregateDailyTaxRows groups by tax name and rate without separating shared-base entries from
independent-base entries, then combines their bases and ORs their sharedBase flags. If the same
tax appears alone on one sale and alongside another component on another sale that day, its one
output row marks the entire combined base non-additive, leaving accountants unable to distinguish
the independently additive portion.
Code

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

+    const key = `${entry.title}\u0000${normalizedRate ?? ''}\u0000${incomplete ? '1' : '0'}`;
Relevance

●●● Strong

Aggregation collapses additive and shared bases, undermining the PR’s explicit non-additive tax
reporting contract.

PR-#405
PR-#757

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Entry extraction distinguishes multi-component bases, but aggregation discards that distinction from
its key and propagates a shared flag to the merged base.

main/services/daily-sales-export.ts[463-479]
main/services/daily-sales-export.ts[842-890]

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

## Issue description
Grouping shared and independent bases into one tax row loses which amount is additive.
## Fix Focus Areas
- main/services/daily-sales-export.ts[842-890]
## Recommended Fix
Include base-sharing status in the grouping key, or emit separate additive and shared-base amounts, and test the same named rate on single- and multi-component sales on one day.

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


7. CSV downloads rebuild the range five times ✓ Resolved
Description
exportDailySales now makes five sequential part requests, and the route builds the entire dataset
before serializing any one part. For a 93-day CSV download, bill hydration, tax reconstruction, item
aggregation, and payment loading all run five times even though each response uses only one part.
Code

frontend/src/app/(dashboard)/dashboard/page.tsx[R487-489]

+      for (const part of ['summary', 'daily', 'taxes', 'items', 'payments'] as const) {
        const res = await api.get('/reports/daily-sales/export', {
-          params: { date: selectedDate, format: 'csv', part },
+          params: { ...dateParams, format: 'csv', part },
Relevance

●●● Strong

The client performs five redundant full dataset builds, creating a concrete performance regression
for large ranges.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The client issues five requests; each invocation of the route independently builds all days and all
parts before selecting one CSV serialization.

frontend/src/app/(dashboard)/dashboard/page.tsx[485-493]
main/routes/reports.ts[1508-1530]
main/services/daily-sales-export.ts[1188-1228]

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

## Issue description
Five CSV part requests each repeat the full range dataset build.
## Fix Focus Areas
- frontend/src/app/(dashboard)/dashboard/page.tsx[485-492]
- main/routes/reports.ts[1508-1530]
## Recommended Fix
Provide a single download that builds the dataset once and returns all CSV parts, or otherwise reuse one completed dataset safely across the five part responses.

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


8. Range insights show unrelated dates ✓ Resolved
Description
The new dashboard range mode scopes financial and product requests to the selected dates but still
requests /reports/insights with days: 30. Selecting a historical range leaves staff, category,
and other insight cards showing the rolling window rather than the selected period; the insights
endpoint does not accept range endpoints.
Code

frontend/src/app/(dashboard)/dashboard/page.tsx[R300-302]

+      : periodMode === 'range'
+        ? { startDate: rangeStartDate, endDate: rangeEndDate }
+        : { startDate: selectedDate, endDate: selectedDate };
Relevance

●●● Strong

Historical dashboard date-scoping defects were accepted when aggregates used inconsistent reporting
windows.

PR-#537

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The range drives financial requests, whereas the insights request remains fixed at 30 days and the
endpoint derives its window from today.

frontend/src/app/(dashboard)/dashboard/page.tsx[389-424]
frontend/src/app/(dashboard)/dashboard/page.tsx[1125-1135]
main/routes/reports.ts[999-1052]

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 newly selectable range changes dashboard dates without changing the insights window.
## Fix Focus Areas
- frontend/src/app/(dashboard)/dashboard/page.tsx[389-424]
- main/routes/reports.ts[999-1052]
## Recommended Fix
Accept and use selected start and end dates for insights, or clearly label and separate the rolling-30-day cards from period-scoped results.

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


9. Some product rows merge across SKUs ✓ Resolved
Description
periodItemMap uses row.product_sku ?? '' in its key, collapsing a missing SKU and an explicitly
empty SKU. When the same product ID and name have both stored forms, the export combines their
amounts under whichever SKU is encountered first, although the item query groups them separately.
Code

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

+      const key = `${row.product_id}\u0000${row.product_name}\u0000${row.product_sku ?? ''}`;
Relevance

●●● Strong

Null and empty SKUs are distinct SQL groups; preserving that distinction prevents incorrect
product-row merging.

PR-#809

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
SQL groups by the raw SKU, but the new map key converts NULL to the same string as an empty SKU and
retains only the first group's SKU.

main/services/daily-sales-export.ts[1115-1121]
main/services/daily-sales-export.ts[1228-1257]

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 period item key collapses SQL's distinct NULL and empty-string SKU groups.
## Fix Focus Areas
- main/services/daily-sales-export.ts[1228-1257]
## Recommended Fix
Encode SKU nullability separately in the map key or use a structured key, preserving the SQL grouping identity. Test both SKU forms for the same product and name.

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


10. Large ranges can stall the server 🐞 Bug ➹ Performance
Description
buildDailySalesExportDataset caps calendar days but loads every paid bill and its hydrated orders
for each day, while retaining payment rows for the entire period. A permitted high-volume range can
therefore consume substantial process memory and synchronous database time; the day cap does not
bound the transaction count or output size.
Code

main/services/daily-sales-export.ts[R1203-1205]

+    const bills = billsStmt.all(start, end) as PaidBillTaxRow[];
+    const orders = getOrdersWithItemsForBills(db, bills);
+    const dayTaxEntries: HistoricalTaxEntry[] = [];
Relevance

●● Moderate

The concern is plausible under high volume, but the 93-day cap makes capacity impact
deployment-dependent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The 93-day check limits dates only. Each day's unrestricted SELECT b.* is fully hydrated, and
payment rows accumulate in the dataset before serialization.

main/services/daily-sales-export.ts[232-240]
main/services/daily-sales-export.ts[1061-1067]
main/services/daily-sales-export.ts[1188-1228]
main/services/daily-sales-export.ts[1337-1355]

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 date cap permits an arbitrarily large number of bills and payment lines to be materialized in one range request.
## Fix Focus Areas
- main/services/daily-sales-export.ts[1188-1228]
- main/services/daily-sales-export.ts[1337-1355]
- main/routes/reports.ts[1508-1530]
## Recommended Fix
Process bills in bounded batches and stream or spool large output where possible. Apply a measured row or output-size guard if the chosen serializer still requires whole-file buffering.

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

Dismiss ↗ | View ↗



Informational

11. CSV toast still promises three files ✓ Resolved
Description
exportDailySales now downloads five CSV parts (summary, daily, taxes, items, payments)
and is now offered in month and range mode as well. It still shows t('exportCsvStarted'), whose
text (along with exportCsvHint) says only summary, items and payments are downloaded. Users get
two more files than the message tells them about, in every locale.
Code

frontend/src/app/(dashboard)/dashboard/page.tsx[487]

+      for (const part of ['summary', 'daily', 'taxes', 'items', 'payments'] as const) {
Relevance

●●● Strong

The toast is observably stale across locales after adding two CSV parts; localization mismatches are
routinely accepted.

PR-#1170
PR-#1315

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The en.json dashboard namespace still describes three files, while the loop now iterates five parts.

frontend/src/lib/i18n/messages/en.json[341-342]

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 CSV export toast and hint still say three files (summary, items, payments), but five are now downloaded.

## Fix Focus Areas
- frontend/src/lib/i18n/messages/en.json[341-342]

## Recommended Fix
Update `exportCsvHint` and `exportCsvStarted` in every locale file so they list summary, daily, taxes, items and payments.

ⓘ 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
Review mode: Auto: 🧠 Deep: Dense accounting logic spans range validation, tax aggregation, exports, UI, and tests.

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 main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
Comment thread main/services/daily-sales-export.ts Resolved
khaira777 added a commit that referenced this pull request Oct 28, 2026
@khaira777
khaira777 force-pushed the fm/flocafe-1335-date-range-sales-export branch from 0311df6 to 0f16cfe Compare October 28, 2026 05:48
@khaira777 khaira777 changed the title feat(reports): date-range daily sales export feat(reports): add date-range daily sales export with per-tax-rate summary Oct 28, 2026
@khaira777 khaira777 changed the title feat(reports): add date-range daily sales export with per-tax-rate summary feat(reports): date-range daily sales export Oct 28, 2026
Add inclusive from/to business-date range support (up to 93 calendar days) to GET /api/reports/daily-sales/export with Daily and per-tax-rate Taxes sheets/CSV parts, historical tax snapshot reconstruction, shared-base non-additive flags, incomplete-detail reconciliation warnings, dashboard range export controls, docs, and tests.

Fixes #1335
@khaira777
khaira777 force-pushed the fm/flocafe-1335-date-range-sales-export branch from 0f16cfe to e80f9f1 Compare October 28, 2026 21:34
@khaira777 khaira777 changed the title feat(reports): date-range daily sales export feat(reports): date-range daily sales export with per-tax-rate summary Oct 28, 2026
@khaira777 khaira777 changed the title feat(reports): date-range daily sales export with per-tax-rate summary feat(reports): date-range daily sales export Oct 28, 2026
@khaira777
khaira777 merged commit 2fa0228 into main Oct 28, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1335-date-range-sales-export branch October 28, 2026 21:42
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): date-range daily sales export with per-tax-rate summary for accountants

1 participant