# Feature: Payment Reporting ## Overview A monthly view of paid payments with **HST aggregation** and a downloadable spreadsheet (CSV) export. The studio admin sees every instructor and can filter; each instructor sees only their own payments. The report is built entirely from `us_payments` — no additional table. ## Data Source Reads `{prefix}us_payments` (see `payments.md`), filtered to `paid` rows whose `paid_at` falls within the selected calendar month, optionally narrowed to one instructor. Each report row resolves the student and instructor for display: | Report Column | Source | |---------------|---------------------------------------------------| | Date | `us_payments.paid_at` (date portion) | | Student | `us_payments.student_id` → display name | | Instructor | `us_payments.instructor_id` → display name | | Method | `us_payments.method` | | Status | `us_payments.status` | | Subtotal | `us_payments.amount` / `currency` | | HST | `us_payments.tax_amount` (with `tax_rate` %) | | Total | `amount + tax_amount` (`Payment::total()`) | Three totals are summed over the selected rows: **subtotal**, **HST collected** (the figure the studio remits), and **grand total**. ## Access Rules - Studio admin (`view_all_payments`): all instructors; may filter by `instructor_id`. - Instructor (`view_own_payments`): always scoped to their own user ID; the instructor filter is hidden and any requested `instructor_id` is overridden. - Export requires `export_payments` and is scoped to the same rows the caller may view. ## Admin Interface **Payment Report** in wp-admin (top-level menu, gated on `export_payments` so instructors — who lack `manage_billing` — can reach it): - Month picker (defaults to the current month) and, for studio admin, an instructor filter - A summary line of HST collected / subtotal / total collected - A table of paid payments with a totals footer - An **Export CSV** button that downloads the filtered rows (plus a totals row) ## Export CSV export is served by an `admin-post.php` handler (`admin_post_usc_export_payments`) rather than a REST route, so the browser downloads it directly with a nonce-protected link. It honours the same `month` and `instructor_id` query params as the page and returns `text/csv` with a `Content-Disposition: attachment` header. Instructor requests are scoped to their own rows regardless of `instructor_id`. Fields that a spreadsheet would interpret as a formula (leading `=`, `+`, `-`, `@`, tab, or CR — e.g. a hostile student display name) are prefixed with an apostrophe so the export can never carry CSV formula injection into Excel or Google Sheets. ## Implementation - Report aggregator (pure totals + CSV): `Unsupervised\Schedular\Payment\PaymentReport` - Report controller (page + export): `Unsupervised\Schedular\Payment\PaymentReportController` - Report query: `Unsupervised\Schedular\Payment\PaymentRepository::findPaidBetween()` - Template: `templates/admin/payment-report.php` - Menu + `admin_post` wiring: `Unsupervised\Schedular\AdminMenu` ## Tests - `tests/Unit/Payment/PaymentReportTest.php` — totals and CSV rendering - `tests/Unit/Payment/PaymentRepositoryTest.php` — `findPaidBetween` query