TC-RPT-004: Export Integrity & Total Parity

🆕 New on stage — 2026-06-19 → 2026-07-01. This TC covers the scoped real-.xlsx export (#228), the platform-wide CSV formula-injection guard (#336/#340), tenant-local export dates on invoices only (#450), the canonical projectInvoiceTotals parity with the known attendance-report drift (#443/#450), and NO_SHOW inclusion/exclusion across the attendance stats vs. export surfaces (#397/#416). See the Test Plan banners and the Jun 25 release.

Module: Reporting & Analytics — Exports / Money Parity / Attendance Reconciliation

Primary Test User: treasurer@demo.membervu.com / Treasurer123! (Finance Manager); events@demo.membervu.com / Events123! (Event Manager) for the attendance scenarios

Priority: P1 (money-parity + injection-safety are core trust assertions)

Scope correction — xlsx is NOT universal. Only these 4 exports accept ?format=xlsx: invoices (GET /api/billing/admin/invoices/export), payments (GET /api/billing/admin/payments/export), treasury (GET /api/treasury/export), event registrations (GET /api/admin/events/:eventId/registrations/export). The attendance report/audit exports are CSV-only; the members report (GET /api/reporting/reports/members) and donations report are JSON-only with no export at all. Do not test xlsx against attendance, members, or donations — there is nothing to find, it's not implemented there.

Objectives

  1. Confirm ?format=xlsx on the invoice (or payments) export opens as a real Excel workbook with correct headers — and confirm the attendance and members exports do NOT offer xlsx (CSV-only / no-export respectively).
  2. Confirm a formula-injection payload (=cmd|...) in a free-text field is neutralized to '=cmd|... on export, while a normal negative-looking value in the middle of a string (e.g. a date) is left untouched.
  3. Confirm invoice-export dates render in the tenant's local timezone, while the attendance export's dates are raw UTC.
  4. Confirm projectInvoiceTotals parity across invoice detail / listing / finance summary / invoice export for the SAME invoice — and confirm the known attendance totalCollected divergence.
  5. Confirm NO_SHOW registrations are excluded from the attendance stats checked-in/total ratio but present in the attendance export rows.

Scenario summary

IDScenarioKey data assertion
RPT4-01Real xlsx on invoice/payments exportGET /api/billing/admin/invoices/export?format=xlsx returns a genuine .xlsx (opens in Excel/Sheets, correct column headers) — not a CSV renamed to .xlsx.
RPT4-02No xlsx on attendance/membersGET /admin/events/:eventId/attendance?format=xlsx does NOT produce an xlsx workbook (CSV-only surface); GET /api/reporting/reports/members has no export parameter at all — no button, no ?format= support.
RPT4-03Formula-injection neutralizedA member/registrant name of =cmd|'/c calc'!A1 appears in the exported cell as '=cmd|'/c calc'!A1 (leading ' prepended) — opening in Excel shows literal text, not a formula/warning dialog.
RPT4-04Mid-string dash/plus untouchedA value like 2024-01-01 or +63 917 123 4567 is exported byte-for-byte unchanged (no leading ') — sanitizer only anchors on the FIRST character.
RPT4-05Tenant-local invoice-export datesInvoice export's issuedDate/dueDate/paidDate match the org's configured timezone (default Asia/Manila), not raw UTC.
RPT4-06UTC attendance-export datesAttendance export date/timestamp columns are raw UTC (.toISOString()) — do NOT expect tenant-local conversion here.
RPT4-07projectInvoiceTotals parityThe same invoice's amountCents/balanceCents match exactly across detail, listing, finance summary, and the invoice export.
RPT4-08Known attendance totalCollected driftThe attendance report's totalCollected (raw sum of PAID invoice.amountCents) may diverge from the finance summary's projector-based total for the same event's invoices if any invoice has discounts/partial refunds — a KNOWN gap, not a new bug.
RPT4-09NO_SHOW excluded from stats ratioGET /events/:eventId/attendance/stats checked-in/total counts allowlist only CONFIRMED/PENDING/CHECKED_IN — a NO_SHOW registration does not appear in either numerator or denominator there.
RPT4-10NO_SHOW present in export rowsGET /admin/events/:eventId/attendance (export) includes the same NO_SHOW registration as a row — no status filter applied to the export.
RPT4-11Reconciliation block fieldsdata.report.reconciliation exposes exactly: registered, checkedIn, walkIns, noShow, cancelled, stillPending, capacity, attendanceRate, eventEnded; noShow is 0 pre-event (those registrants count under stillPending instead) and only populates post-event.

➡️ Full click-by-click steps: TC-RPT-004 (Detailed).