Repository navigation
feat(reports): WhatsApp sales and auto Z report - #1407
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
PR Summary by QodoSend daily sales and day-close Z reports via WhatsApp
AI Description
Diagram
High-Level Assessment
Files changed (48)
|
Code Review by Qodo
1.
|
: 1. **Core Test Suite (Shard 2/4) - `test:security` (`tests/security-hardening.test.ts:484`)** - **Invariant**: Canary secret suffixes asserted absent across serialized settings responses must use characters disjoint from randomly generated hex, base64url, and timestamp values present in those responses so substring leak assertions are deterministic. - **Cause**: `syntheticPairingCode = 'SYNTH-PAIR-9482'` gave `syntheticPairingSuffix = '9482'`, a 4-character hex/decimal string that collided (~1 in 1,700 runs) with random hex/base64url values in `GET /api/settings` (`cloud_pos_hash`, `jwt_secret` tail, `cloud_device_secret` tail) even while `mobile_pairing_code` itself was masked to `'****'`. - **Fix**: Updated `syntheticPairingCode` in `tests/security-hardening.test.ts` to `'SYNTH-PAIR-ZXQ!'` so `syntheticPairingSuffix` (`'ZXQ!'`) cannot collide with hex, base64url, timestamps, or seeded settings while still failing if `mobile_pairing_code` ever falls back to `****<last4>` masking. 2. **Core Test Suite (Shard 4/4) - `test:backup` (`tests/backup-restore-production.test.ts:974`) and Playwright E2E (Shard 1/4) (`frontend/e2e/database-export-import.spec.ts:289`)** - **Invariant**: Every test and same-version JSON import fixture that targets the live database schema version must match the latest schema version (`122`) registered in `MIGRATIONS` in `main/db.ts`. - **Cause**: Migration `v122` (`add_whatsapp_daily_sales_and_day_close_dispatch`) bumped the live schema version from `121` to `122`. `tests/backup-restore-production.test.ts` still asserted `121` after interrupted-restore recovery, and `frontend/e2e/database-export-import.spec.ts` still supplied `schema_version: '121'`, causing `DatabaseSettingsTab` and `POST /api/db/import` to treat the merge import as a cross-version schema mismatch requiring a Master PIN. - **Fix**: Updated `tests/backup-restore-production.test.ts` lines 976-977 and `frontend/e2e/database-export-import.spec.ts` line 214 from `121` to `122`. 3. **End-to-End Playwright Tests and Required Checks Gate** - **Cause**: Downstream aggregator gates failed solely because `Playwright E2E (Shard 1/4)` failed. **Verification run and passed**: - `npm run test:security` - `npm run test:backup` - `npm run test:schema-health && npm run test:migration-registry && npm run test:upgrade-path` - `npm run test:cash-closures && npm run test:whatsapp-schema && npm run test:whatsapp-service && npm run test:daily-sales-export && npm run test:reports-export && npm run test:rtl-kds-server-whatsapp && npm run test:expenses && npm run test:local-backup && npm run test:recovery-cloud` - `npm run build && npm run build:frontend` - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 e2e/database-export-import.spec.ts e2e/cash-close-whatsapp-recipient.spec.ts` (5/5 passed) - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 --shard=1/4 --grep='@ci-tier1|@ci-tier2' --grep-invert='FINDING:|a paid order offers a refund, and issuing it settles against the recorded sale'` (22/22 passed) - `npm run lint && npm run lint:budget && npm run i18n:check && npm run docs:check && npm run test:script-coverage` - `node scripts/ci/check-changed-format.cjs --base 1d04ad3 && git diff --check`
: 1. **Core Test Suite (Shard 2/4) - `test:security` (`tests/security-hardening.test.ts:484`)** - **Invariant**: Canary secret suffixes asserted absent across serialized settings responses must use characters disjoint from randomly generated hex, base64url, and timestamp values present in those responses so substring leak assertions are deterministic. - **Cause**: `syntheticPairingCode = 'SYNTH-PAIR-9482'` gave `syntheticPairingSuffix = '9482'`, a 4-character hex/decimal string that collided (~1 in 1,700 runs) with random hex/base64url values in `GET /api/settings` (`cloud_pos_hash`, `jwt_secret` tail, `cloud_device_secret` tail) even while `mobile_pairing_code` itself was masked to `'****'`. - **Fix**: Updated `syntheticPairingCode` in `tests/security-hardening.test.ts` to `'SYNTH-PAIR-ZXQ!'` so `syntheticPairingSuffix` (`'ZXQ!'`) cannot collide with hex, base64url, timestamps, or seeded settings while still failing if `mobile_pairing_code` ever falls back to `****<last4>` masking. 2. **Core Test Suite (Shard 4/4) - `test:backup` (`tests/backup-restore-production.test.ts:974`) and Playwright E2E (Shard 1/4) (`frontend/e2e/database-export-import.spec.ts:289`)** - **Invariant**: Every test and same-version JSON import fixture that targets the live database schema version must match the latest schema version (`122`) registered in `MIGRATIONS` in `main/db.ts`. - **Cause**: Migration `v122` (`add_whatsapp_daily_sales_and_day_close_dispatch`) bumped the live schema version from `121` to `122`. `tests/backup-restore-production.test.ts` still asserted `121` after interrupted-restore recovery, and `frontend/e2e/database-export-import.spec.ts` still supplied `schema_version: '121'`, causing `DatabaseSettingsTab` and `POST /api/db/import` to treat the merge import as a cross-version schema mismatch requiring a Master PIN. - **Fix**: Updated `tests/backup-restore-production.test.ts` lines 976-977 and `frontend/e2e/database-export-import.spec.ts` line 214 from `121` to `122`. 3. **End-to-End Playwright Tests and Required Checks Gate** - **Cause**: Downstream aggregator gates failed solely because `Playwright E2E (Shard 1/4)` failed. **Verification run and passed**: - `npm run test:security` - `npm run test:backup` - `npm run test:schema-health && npm run test:migration-registry && npm run test:upgrade-path` - `npm run test:cash-closures && npm run test:whatsapp-schema && npm run test:whatsapp-service && npm run test:daily-sales-export && npm run test:reports-export && npm run test:rtl-kds-server-whatsapp && npm run test:expenses && npm run test:local-backup && npm run test:recovery-cloud` - `npm run build && npm run build:frontend` - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 e2e/database-export-import.spec.ts e2e/cash-close-whatsapp-recipient.spec.ts` (5/5 passed) - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 --shard=1/4 --grep='@ci-tier1|@ci-tier2' --grep-invert='FINDING:|a paid order offers a refund, and issuing it settles against the recorded sale'` (22/22 passed) - `npm run lint && npm run lint:budget && npm run i18n:check && npm run docs:check && npm run test:script-coverage` - `node scripts/ci/check-changed-format.cjs --base 1d04ad3 && git diff --check`
81beaf4 to
6e667fa
Compare
Add Daily Sales WhatsApp summary delivery via POST /api/reports/daily-sales/whatsapp and the dashboard Export sales menu, formatted in the store language and regional currency/number conventions with 93-day range enforcement and progressive truncation that preserves financial totals. Add optional owner-configured z_report_day_close automation rule with E.164 recipient validation, per-closure idempotency in migration v122, Offline mode suppression, non-blocking error handling without auto-retry on disconnect or ack timeout, and report-kind access gating across WhatsApp history and manual retry.
…nd Z-report locale preferences"}
: 1. **Core Test Suite (Shard 2/4) - `test:security` (`tests/security-hardening.test.ts:484`)** - **Invariant**: Canary secret suffixes asserted absent across serialized settings responses must use characters disjoint from randomly generated hex, base64url, and timestamp values present in those responses so substring leak assertions are deterministic. - **Cause**: `syntheticPairingCode = 'SYNTH-PAIR-9482'` gave `syntheticPairingSuffix = '9482'`, a 4-character hex/decimal string that collided (~1 in 1,700 runs) with random hex/base64url values in `GET /api/settings` (`cloud_pos_hash`, `jwt_secret` tail, `cloud_device_secret` tail) even while `mobile_pairing_code` itself was masked to `'****'`. - **Fix**: Updated `syntheticPairingCode` in `tests/security-hardening.test.ts` to `'SYNTH-PAIR-ZXQ!'` so `syntheticPairingSuffix` (`'ZXQ!'`) cannot collide with hex, base64url, timestamps, or seeded settings while still failing if `mobile_pairing_code` ever falls back to `****<last4>` masking. 2. **Core Test Suite (Shard 4/4) - `test:backup` (`tests/backup-restore-production.test.ts:974`) and Playwright E2E (Shard 1/4) (`frontend/e2e/database-export-import.spec.ts:289`)** - **Invariant**: Every test and same-version JSON import fixture that targets the live database schema version must match the latest schema version (`122`) registered in `MIGRATIONS` in `main/db.ts`. - **Cause**: Migration `v122` (`add_whatsapp_daily_sales_and_day_close_dispatch`) bumped the live schema version from `121` to `122`. `tests/backup-restore-production.test.ts` still asserted `121` after interrupted-restore recovery, and `frontend/e2e/database-export-import.spec.ts` still supplied `schema_version: '121'`, causing `DatabaseSettingsTab` and `POST /api/db/import` to treat the merge import as a cross-version schema mismatch requiring a Master PIN. - **Fix**: Updated `tests/backup-restore-production.test.ts` lines 976-977 and `frontend/e2e/database-export-import.spec.ts` line 214 from `121` to `122`. 3. **End-to-End Playwright Tests and Required Checks Gate** - **Cause**: Downstream aggregator gates failed solely because `Playwright E2E (Shard 1/4)` failed. **Verification run and passed**: - `npm run test:security` - `npm run test:backup` - `npm run test:schema-health && npm run test:migration-registry && npm run test:upgrade-path` - `npm run test:cash-closures && npm run test:whatsapp-schema && npm run test:whatsapp-service && npm run test:daily-sales-export && npm run test:reports-export && npm run test:rtl-kds-server-whatsapp && npm run test:expenses && npm run test:local-backup && npm run test:recovery-cloud` - `npm run build && npm run build:frontend` - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 e2e/database-export-import.spec.ts e2e/cash-close-whatsapp-recipient.spec.ts` (5/5 passed) - `cd frontend && CI=true npx playwright test --config=playwright.config.ts --project=chromium --retries=0 --shard=1/4 --grep='@ci-tier1|@ci-tier2' --grep-invert='FINDING:|a paid order offers a refund, and issuing it settles against the recorded sale'` (22/22 passed) - `npm run lint && npm run lint:budget && npm run i18n:check && npm run docs:check && npm run test:script-coverage` - `node scripts/ci/check-changed-format.cjs --base 1d04ad3 && git diff --check`
- Q1: Preserve sent/delivered status against post-ACK errors and shutdown - Q2: Check reports.view permission when configuring z_report_day_close rule - Q3: Restrict 4096-char length cap to report message kinds - Q4: Atomically claim queued Z-report attempt inside day close transaction - Q5: Track deliveryStatus (sent vs submitted) with message ACK waiter - Q6: Format business dates in WhatsApp reports with calendar and digit preferences
6e667fa to
a057a38
Compare
Fixes #1336
Summary & Behavior
POST /api/reports/daily-sales/whatsapp,formatDailySalesWhatsAppMessage, andshareDailySalesViaWhatsAppviaSendWhatsAppReportModal) for a single business date or inclusive range up to 93 business dates frombuildDailySalesExportDataset, formatted in the configured store language, regional currency/number/calendar preferences, store timezone, andday_close_hourboundaries.wa.meshare-window fallbacks.reports.view,reports.daily-sales.export, andwhatsapp.useon daily-sales sends and retries, hidesz_reporthistory/retry from users withoutreports.view, hidesdaily_sales_reporthistory/retry from users lackingreports.vieworreports.daily-sales.export, and exempts owner/report sends from customer-campaign quiet hours and weekly caps while still obeying disabled WhatsApp, recipient blocklists, Offline mode, and transport cooldown/rate limits.z_report_day_close), disabled by default with an explicit E.164 recipient (never inferred frombusiness_phone), triggered post-commit only on store-level day closes (scope === 'day', never per-staff shift closes) without ever failing or rolling back closing, persisted as at most one automatic attempt per closure (cash_closure_id) with manual retry instead of automatic retry on disconnection/timeout, suppressed with no delayed send in Offline mode, and never opening a manual share window.Dependencies
No new runtime or development packages added; reuses existing WhatsApp service/history/retry infrastructure,
SendWhatsAppReportModal, andbuildDailySalesExportDataset.Migration Implications
Adds additive SQLite schema migration
v122(add_whatsapp_daily_sales_and_day_close_dispatch) inmain/db.tsthat rebuildswhatsapp_messagesto permitkind = 'daily_sales_report', adds nullablecash_closure_id INTEGER REFERENCES cash_closures(id) ON DELETE SET NULL, and creates partial unique indexidx_whatsapp_messages_auto_z_closureon(cash_closure_id)wheretrigger_rule = 'z_report_day_close' AND cash_closure_id IS NOT NULL, preserving all existing customer data, attempts, and foreign keys across upgrades.Tests Run
npm run lint,npm run lint:budget,npm run build,npm run build:frontend,npm run i18n:check,npm run docs:check,npm run audit:db,npm run test:script-coverage,git diff --check,node tests/run-electron-node-test.cjs tests/reports-whatsapp.test.ts,npm run test:whatsapp-schema,npm run test:upgrade-path,npm run test:daily-sales-export,npm run test:security,npm run test:backup, Playwright E2E (frontend/e2e/cash-close-whatsapp-recipient.spec.tsandfrontend/e2e/database-export-import.spec.ts), and full GitHub Actions CI.Remaining Limitations
Real end-to-end WhatsApp receipt on a physical linked WhatsApp account was not verified; external WhatsApp/Baileys transport was simulated at the socket boundary in isolated temporary directories without touching any host WhatsApp session.
Intent
Implement FloCafe issue #1336 (
feat(reports): send daily sales via WhatsApp and optional auto-send of Z on day close):reports.view, the existing daily-sales export authority, andwhatsapp.use. Hide all report message kinds from users without report access.What Changed
POST /api/reports/daily-sales/whatsapp,formatDailySalesWhatsAppMessage, and dashboard export-menu WhatsApp sharing (shareDailySalesViaWhatsApp) to send plain-text daily sales summaries across 1-93 business dates in the store language and regional format (truncating top-item detail before financial totals within the 4096-character limit, withwa.meshare fallback only when disconnected).z_report_day_closeautomation rule with explicit E.164 recipient configuration, SQLite migration v122 (daily_sales_reportmessage kind,cash_closure_id, andidx_whatsapp_messages_auto_z_closure), and post-committriggerDayCloseZReportdispatch for store-level day closes that suppresses cleanly in Offline mode or when WhatsApp is disabled and records single-attempt failures for manual retry.reports.viewandreports.daily-sales.exportacross WhatsApp send, pre-pagination message history, and retry paths, exempted report message kinds from customer quiet hours and weekly caps while keeping transport rate/blocklist/length guards, and updated all 24 locale catalogues, generated print labels, tests, and documentation.Risk Assessment
✅ Low: The implementation and follow-up fixes cleanly enforce permission boundaries, migration safety, single-attempt day-close deduplication, Offline-mode suppression, and progressive 4096-character truncation across all 44 changed files with no remaining defects or unrequired components.
Testing
Built the backend and static desktop frontend, passed targeted backend integration, migration, schema, print-label, translation, daily-sales-export, and Playwright E2E suites, and drove seven live end-to-end browser and API scenarios against an isolated FloCafe server covering Dashboard Daily Sales WhatsApp sharing, 93-day range and 4096-character truncation boundaries, store-locale and 04:00 business-day formatting, Owner/Manager/Server history RBAC hiding, owner-only Z-on-day-close configuration, single-attempt day-close persistence and restart ack_timeout handling, and quiet-hour/weekly-cap/blocklist/Offline-mode guards with 11 UI screenshots and 7 JSON transcripts captured as evidence (only live paired Meta WhatsApp Web socket transmission remained untested without a physical mobile WhatsApp account).
Evidence: Live Scenario 1 API payload, formatted plain-text Daily Sales body, manual-share URL, and 93-day range response
Evidence: Live Scenario 2 adversarial 94-day rejection, ignored query-string fallback, and 4096-character truncation transcript
Evidence: Live Scenario 3 business-day 04:00 cutoff and Spanish/Arabic store-language formatting transcript
Evidence: Live Scenario 4 Owner, Manager, and Server RBAC and server-side history filtering transcript
Evidence: Live Scenario 5 Z-Report rule defaults, phone validation, 403 owner_required guard, and rule-preservation transcript
Evidence: Live Scenario 6 shift-close non-trigger, day-close single attempt persistence, unique-index deduplication, and restart ack_timeout transcript
Evidence: Live Scenario 7 quiet-hours and weekly-cap bypass, blocklist/disabled obedience, and Offline mode suppression transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 5 issues found → auto-fixed (4) ✅
main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800),POST /api/whatsapp/send(main/routes/whatsapp.ts:165),POST /api/whatsapp/messages/:id/retry(main/routes/whatsapp.ts:234), and the UI retry action (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a defaultmanager(who holdswhatsapp.use+reports.viewbut is deniedreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/reports.ts:1867-POST /api/reports/daily-sales/whatsappswallowsRegionalNotConfiguredError(statusCode = 409) as HTTP500: when tenantcountry,currency, ortimezoneis missing,reportToday()(main/routes/reports.ts:1823) andbuildDailySalesExportDataset()(main/routes/reports.ts:1828) throwRegionalNotConfiguredErrorwithstatusCode = 409, which FloCafe routes must surface as HTTP409perdocs/reference/product-invariants.md:240,docs/reference/daily-sales-export.md:19, and sibling export handlers (main/routes/reports.ts:1968,main/routes/reports.ts:2022). Thecatchblock atmain/routes/reports.ts:1867handles onlystatusCode === 400and falls through to500on409; sibling docdocs/reference/api.md:870also omits409from the endpoint error list.main/services/whatsapp.ts:3792-formatZReportDateTimeignores tenantcalendarandnumber_digitsregional preferences when formattingclosure.period_startandclosure.period_end(main/services/whatsapp.ts:3792, called atmain/services/whatsapp.ts:3854).formatStoredZReportWhatsAppMessageresolvespreferences(digits,calendar) and applies them to money (fmtMoney, line 3843) and counts (fmtNum, line 3844), butformatZReportDateTimecallsnew Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short', timeZone })withoutnumberingSystemorcalendar. For example, anIR(fa-IR) store configured withcalendar = 'gregorian'andnumber_digits = 'latin'gets Latin digits and Gregorianbusiness_dateeverywhere else in the Z-report message whileperiod_start/period_endrender in the Persian Jalali calendar with Extended Arabic-Indic digits (and a store withcalendar = 'persian'on a non-fa-IRlocale gets Gregorian timestamps). Passpreferences(or delegate toformatDateForTenantinmain/countries.ts:481) so period timestamps honordigitsandcalendar.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2607andmain/services/whatsapp.ts:2638, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1083). Recommend removing the?kind=query filter and filtering returned messages in the test assertions instead.main/routes/reports.ts:1820- Unrequired fallback acceptance path (req.querydate/range fallback onPOST /api/reports/daily-sales/whatsapp):main/routes/reports.ts:1820,main/routes/reports.ts:1821, andmain/routes/reports.ts:1822fall back toreq.query.date,req.query.from, andreq.query.towhen omitted fromreq.body.docs/reference/api.md:870specifies JSON body inputs (phone_e164, optionaldateorfrom+to),phone_e164itself is read only fromreq.body, and no production caller, test, or User Intent requirement uses query-string parameters on thisPOSTroute. Recommend narrowing range parsing topayload.date,payload.from, andpayload.toonly.🔧 Fix applied.
8 warnings still open:
main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800),POST /api/whatsapp/send(main/routes/whatsapp.ts:165),POST /api/whatsapp/messages/:id/retry(main/routes/whatsapp.ts:234), and the UI retry action (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a defaultmanager(who holdswhatsapp.use+reports.viewbut is deniedreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/reports.ts:1867-POST /api/reports/daily-sales/whatsappswallowsRegionalNotConfiguredError(statusCode = 409) as HTTP500: when tenantcountry,currency, ortimezoneis missing,reportToday()(main/routes/reports.ts:1823) andbuildDailySalesExportDataset()(main/routes/reports.ts:1828) throwRegionalNotConfiguredErrorwithstatusCode = 409, which FloCafe routes must surface as HTTP409perdocs/reference/product-invariants.md:240,docs/reference/daily-sales-export.md:19, and sibling export handlers (main/routes/reports.ts:1968,main/routes/reports.ts:2022). Thecatchblock atmain/routes/reports.ts:1867handles onlystatusCode === 400and falls through to500on409; sibling docdocs/reference/api.md:870also omits409from the endpoint error list.main/services/whatsapp.ts:3792-formatZReportDateTimeignores tenantcalendarandnumber_digitsregional preferences when formattingclosure.period_startandclosure.period_end(main/services/whatsapp.ts:3792, called atmain/services/whatsapp.ts:3854).formatStoredZReportWhatsAppMessageresolvespreferences(digits,calendar) and applies them to money (fmtMoney, line 3843) and counts (fmtNum, line 3844), butformatZReportDateTimecallsnew Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short', timeZone })withoutnumberingSystemorcalendar. For example, anIR(fa-IR) store configured withcalendar = 'gregorian'andnumber_digits = 'latin'gets Latin digits and Gregorianbusiness_dateeverywhere else in the Z-report message whileperiod_start/period_endrender in the Persian Jalali calendar with Extended Arabic-Indic digits (and a store withcalendar = 'persian'on a non-fa-IRlocale gets Gregorian timestamps). Passpreferences(or delegate toformatDateForTenantinmain/countries.ts:481) so period timestamps honordigitsandcalendar.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2607andmain/services/whatsapp.ts:2638, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1083). Recommend removing the?kind=query filter and filtering returned messages in the test assertions instead.main/routes/reports.ts:1820- Unrequired fallback acceptance path (req.querydate/range fallback onPOST /api/reports/daily-sales/whatsapp):main/routes/reports.ts:1820,main/routes/reports.ts:1821, andmain/routes/reports.ts:1822fall back toreq.query.date,req.query.from, andreq.query.towhen omitted fromreq.body.docs/reference/api.md:870specifies JSON body inputs (phone_e164, optionaldateorfrom+to),phone_e164itself is read only fromreq.body, and no production caller, test, or User Intent requirement uses query-string parameters on thisPOSTroute. Recommend narrowing range parsing topayload.date,payload.from, andpayload.toonly.main/services/whatsapp.ts:3938-formatStoredZReportWhatsAppMessage(main/services/whatsapp.ts:3938) can truncateDrawer Reconciliationfinancial totals before shortening custom payment method rows.main/routes/payment-methods.ts:11accepts custom payment method names up to 60 characters with no count cap (exercised with 48 60-character custom methods intests/reports-whatsapp.test.ts:579-595). When a day close records 48 60-character payment methods,paymentLines(main/services/whatsapp.ts:3888-3892) renders ~3,648 characters ahead ofDrawer Reconciliation(main/services/whatsapp.ts:3909-3913); even afternotesCap = 0andstoreCap = 48(main/services/whatsapp.ts:3928-3937),buildMessage(0, 48)is ~4,123 characters (> 4096), so line3938tail-slicesbuildMessage(0, 32)at 4,096 characters and dropsCounted cashandDifference(variance) while leaving all 60-character payment method labels unshortened, violating the User Intent requirement to keep financial totals intact before shortening detail. Provenance: Round 1 modifiedformatStoredZReportWhatsAppMessage(main/services/whatsapp.ts:3868-3870) to forwardpreferencesintoformatZReportDateTime, leaving this pre-existing defect from the author's commit atmain/services/whatsapp.ts:3889-3892andmain/services/whatsapp.ts:3928-3938; sibling formatterformatDailySalesWhatsAppMessage(main/services/daily-sales-export.ts:2471-2515) already caps payment method names and preserves all financial totals before fitting payment rows with..., leavingformatStoredZReportWhatsAppMessageas the only remaining site.main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800-1801),POST /api/whatsapp/send(main/routes/whatsapp.ts:165-166),POST /api/whatsapp/messages/:messageId/retry(main/routes/whatsapp.ts:234), and the UI retry action (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240-1241) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:414-416, alongsidereports.financial.viewatshared/permissions.ts:407-409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2626andmain/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a defaultmanager(who holdswhatsapp.use+reports.viewbut lacksreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2621andmain/services/whatsapp.ts:2638-2641, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1103). Recommend removing the?kind=query filter and filtering returned messages in the test assertions instead.🔧 Fix applied.
8 warnings still open:
main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800),POST /api/whatsapp/send(main/routes/whatsapp.ts:165),POST /api/whatsapp/messages/:id/retry(main/routes/whatsapp.ts:234), and the UI retry action (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a defaultmanager(who holdswhatsapp.use+reports.viewbut is deniedreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/reports.ts:1867-POST /api/reports/daily-sales/whatsappswallowsRegionalNotConfiguredError(statusCode = 409) as HTTP500: when tenantcountry,currency, ortimezoneis missing,reportToday()(main/routes/reports.ts:1823) andbuildDailySalesExportDataset()(main/routes/reports.ts:1828) throwRegionalNotConfiguredErrorwithstatusCode = 409, which FloCafe routes must surface as HTTP409perdocs/reference/product-invariants.md:240,docs/reference/daily-sales-export.md:19, and sibling export handlers (main/routes/reports.ts:1968,main/routes/reports.ts:2022). Thecatchblock atmain/routes/reports.ts:1867handles onlystatusCode === 400and falls through to500on409; sibling docdocs/reference/api.md:870also omits409from the endpoint error list.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2607andmain/services/whatsapp.ts:2638, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1083). Recommend removing the?kind=query filter and filtering returned messages in the test assertions instead.main/routes/reports.ts:1820- Unrequired fallback acceptance path (req.querydate/range fallback onPOST /api/reports/daily-sales/whatsapp):main/routes/reports.ts:1820,main/routes/reports.ts:1821, andmain/routes/reports.ts:1822fall back toreq.query.date,req.query.from, andreq.query.towhen omitted fromreq.body.docs/reference/api.md:870specifies JSON body inputs (phone_e164, optionaldateorfrom+to),phone_e164itself is read only fromreq.body, and no production caller, test, or User Intent requirement uses query-string parameters on thisPOSTroute. Recommend narrowing range parsing topayload.date,payload.from, andpayload.toonly.main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800-1801),POST /api/whatsapp/send(main/routes/whatsapp.ts:165-166),POST /api/whatsapp/messages/:messageId/retry(main/routes/whatsapp.ts:234), and the UI retry action (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240-1241) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:414-416, alongsidereports.financial.viewatshared/permissions.ts:407-409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2626andmain/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a defaultmanager(who holdswhatsapp.use+reports.viewbut lacksreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2621andmain/services/whatsapp.ts:2638-2641, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1103). Recommend removing the?kind=query filter and filtering returned messages in the test assertions instead.main/routes/whatsapp.ts:406- Missing access-policy decision on secondary financial disclosure in WhatsApp message history:POST /api/reports/daily-sales/whatsapp(main/routes/reports.ts:1800-1801),POST /api/whatsapp/send(main/routes/whatsapp.ts:165-166),POST /api/whatsapp/messages/:messageId/retry(main/routes/whatsapp.ts:234), and the UI retry button (frontend/src/app/(dashboard)/whatsapp/page.tsx:1240-1241) all require bothreports.viewandreports.daily-sales.export(owner-only by default inshared/permissions.ts:414-416, alongsidereports.financial.viewatshared/permissions.ts:407-409) fordaily_sales_reportmessages. However,GET /api/whatsapp/messages(main/routes/whatsapp.ts:406) andwhatsapp.listMessages(main/services/whatsapp.ts:2626andmain/services/whatsapp.ts:2650, documented atdocs/reference/api.md:775anddocs/architecture/cloud-integrations.md:381) check onlyreports.view, so after an owner sends a daily sales report, a shipped-defaultmanager(who holdswhatsapp.useandreports.viewwhile deniedreports.daily-sales.exportandreports.financial.view) receives the full up-to-93-daydaily_sales_reportbody containing gross/refund/net collections, tax/discount/charge totals, payment mix, and top-5 net item sales. Confirm whetherGET /api/whatsapp/messagesandwhatsapp.listMessagesshould also hidekind = 'daily_sales_report'rows when the caller lacksreports.daily-sales.export.main/routes/whatsapp.ts:399- Unrequired component (?kind=query filter onGET /api/whatsapp/messages):main/routes/whatsapp.ts:399andmain/routes/whatsapp.ts:411,main/services/whatsapp.ts:2621andmain/services/whatsapp.ts:2638-2641, anddocs/reference/api.md:775add a client-suppliedkindfilter to WhatsApp message history. No User Intent requirement needs a?kind=query parameter (intent requires only server-side hiding of report message kinds from users without report access and reusing existing history/retry), and no production caller infrontend/ormain/uses it (onlytests/reports-whatsapp.test.ts:516andtests/reports-whatsapp.test.ts:1103-1105). Recommend removing the?kind=query filter and assertingevery((m) => m.kind !== 'daily_sales_report')on returned messages intests/reports-whatsapp.test.ts:1103-1105instead.🔧 Fix applied.
1 warning still open:
main/services/whatsapp.ts:571- Owner-only authorization bypass onz_report_day_closevia non-string trigger coercion inPUT /api/whatsapp/automation: when the storeownerenablesz_report_day_closewith a configuredrecipientPhone, an authenticated non-owner withwhatsapp.manage(such as a defaultmanager) can sendPUT /api/whatsapp/automationwith{"rules":[{"trigger":["z_report_day_close"]}]}(or{"rules":[{"id":["z_report_day_close"]}]}). Inmain/routes/whatsapp.ts:285-292,incomingZRulechecks strict string equality (rec.trigger === 'z_report_day_close' || rec.id === 'z_report_day_close'), which evaluates tofalsefor the array and skips therole !== 'owner'guard atmain/routes/whatsapp.ts:320-327. Next,saveAutomationConfig(main/services/whatsapp.ts:571) coercesr.trigger ?? r.idwithString(...)into'z_report_day_close', adding it toprovidedTriggersand dropping the owner's storedz_report_day_closerule atmain/services/whatsapp.ts:575, whilenormalizeWhatsAppAutomationConfig(shared/whatsapp-automation.ts:455) keysruleByTriggerby the non-string array reference and resetsz_report_day_closeto{ enabled: false, recipientPhone: '' }with HTTP200. Accept only stringtrigger/idkeys acrossmain/services/whatsapp.ts:571,main/routes/whatsapp.ts:285-292, andshared/whatsapp-automation.ts:455(and preservecurrentZRuleunconditionally whenrole !== 'owner'after rejecting explicit edits).🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
npm run buildHUSKY=0 npm run build:frontendnode tests/run-electron-node-test.cjs tests/reports-whatsapp.test.tsnpm run test:whatsapp-schemanpm run test:upgrade-pathnpm run test:translationsnpm run test:print-labelsnpm run test:daily-sales-exportcd frontend && CI=1 E2E_BASE_URL=http://127.0.0.1:4301 E2E_KDS_BASE_URL=http://127.0.0.1:4302 E2E_SERVER_APP_BASE_URL=http://127.0.0.1:4303 npx playwright test --config=playwright.config.ts --project=chromium --retries=0 e2e/cash-close-whatsapp-recipient.spec.tsLive end-to-end Playwright Chromium + HTTP API + SQLite verification againstnode dev-server.jsserving builtdist/andfrontend/outon isolated ports14301/14302/14303with disposableFLO_DEV_USER_DATA✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.