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
- Confirm
?format=xlsxon 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). - 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. - Confirm invoice-export dates render in the tenant's local timezone, while the attendance export's dates are raw UTC.
- Confirm
projectInvoiceTotalsparity across invoice detail / listing / finance summary / invoice export for the SAME invoice — and confirm the known attendancetotalCollecteddivergence. - Confirm NO_SHOW registrations are excluded from the attendance stats checked-in/total ratio but present in the attendance export rows.
Scenario summary
| ID | Scenario | Key data assertion |
|---|---|---|
| RPT4-01 | Real xlsx on invoice/payments export | GET /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-02 | No xlsx on attendance/members | GET /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-03 | Formula-injection neutralized | A 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-04 | Mid-string dash/plus untouched | A 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-05 | Tenant-local invoice-export dates | Invoice export's issuedDate/dueDate/paidDate match the org's configured timezone (default Asia/Manila), not raw UTC. |
| RPT4-06 | UTC attendance-export dates | Attendance export date/timestamp columns are raw UTC (.toISOString()) — do NOT expect tenant-local conversion here. |
| RPT4-07 | projectInvoiceTotals parity | The same invoice's amountCents/balanceCents match exactly across detail, listing, finance summary, and the invoice export. |
| RPT4-08 | Known attendance totalCollected drift | The 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-09 | NO_SHOW excluded from stats ratio | GET /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-10 | NO_SHOW present in export rows | GET /admin/events/:eventId/attendance (export) includes the same NO_SHOW registration as a row — no status filter applied to the export. |
| RPT4-11 | Reconciliation block fields | data.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).