refactor(billing): rebuild payments list as flat invoice list (tauri parity)

Align /admin/my-payments with the tauri /payments source (per user): the list
is now a flat, newest-first list of the tenant's recorded invoices — one row
per invoice — instead of the per-patient aggregation built from Figma.

Backend:
- replace InvoiceRepository::patientPaymentSummary aggregation with
  tenantInvoices/countTenantInvoices (flat, joins patient name/national code).
- InvoiceService::patientPaymentList → tenantInvoiceList.
- BillingController: GET /api/v1/my/billing/patient-payments →
  GET /api/v1/my/billing/payments returning
  { invoice_uuid, patient_uuid, patient_name, national_code, issued_at,
    amount_rials, status } rows.
- node-2 patient invoices endpoint unchanged.

Frontend:
- useMyPayments: usePatientPayments → usePayments (flat PaymentRow).
- MyPaymentsPage columns match tauri DetailT: row #, avatar+name, national
  code, date-time, amount paid, مشاهده (no status column); 'اضافه کردن بیمار'
  links to /admin/patients/new. Filters (national code / status / Jalali date
  range) kept.

Tests + docs/api/billing.md updated. Intentionally omitted tauri extras:
mobile Cards view and the advanced ModalFilter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-14 19:00:17 +03:30
co-authored by Claude Opus 4.8
parent 8d6278e125
commit 818506bf36
9 changed files with 166 additions and 192 deletions
+7 -6
View File
@@ -185,15 +185,15 @@
```
`debt = claimed - paid` (حداقل صفر).
## GET /api/v1/my/billing/patient-payments
«لیست پرداخت‌ها» — یک ردیف به‌ازای هر بیمار با جمع صورتحساب‌های همان tenant. صورتحساب‌های `draft`/`void` نادیده گرفته می‌شوند.
## GET /api/v1/my/billing/payments
«لیست پرداخت‌ها» — فهرست مسطح صورتحساب‌های ثبت‌شده‌ی همان tenant (هر ردیف یک صورتحساب)، جدیدترین اول. فقط `finalized`/`paid`؛ `draft`/`void` نادیده گرفته می‌شوند.
**Query params:**
| param | توضیح |
|-------|-------|
| `national_code` | جست‌وجوی جزئی روی کد ملی بیمار (`LIKE`) |
| `status` | `paid` (باقیمانده=۰) · `unpaid` (پرداختی=۰ و باقیمانده>۰) · `unsettled` (هر دو>۰) — روی جمع‌ها اعمال می‌شود |
| `status` | `paid` (پرداخت‌شده) · `unsettled` (تسویه‌نشده = `finalized`) |
| `from` / `to` | بازه‌ی `issued_at` بر حسب ثانیه‌ی Unix |
| `page` / `limit` | صفحه‌بندی (پیش‌فرض ۱ / ۲۰، سقف ۱۰۰) |
@@ -202,13 +202,14 @@
{
"success": true,
"data": [
{ "patient_uuid": "…", "patient_name": "دنیا خلیلی", "national_code": "1744023654",
"invoice_count": 2, "paid_rials": 2350000, "remaining_rials": 500000, "status": "unsettled" }
{ "invoice_uuid": "…", "patient_uuid": "…", "patient_name": "دنیا خلیلی",
"national_code": "1744023654", "issued_at": 1717000000,
"amount_rials": 2350000, "status": "paid" }
],
"meta": { "totalRecords": 12, "totalPages": 1, "currentPage": 1 }
}
```
> `paid_rials` = جمع سهم بیمار روی صورتحساب‌های `paid`؛ `remaining_rials` = جمع سهم بیمار روی صورتحساب‌های `finalized`. `status` سمت سرور از همین دو مشتق می‌شود.
> `amount_rials` = سهم بیمار (`patient_rials`) همان صورتحساب. `status` دو حالته: `paid` یا `unsettled`.
**Errors:** `403` (`ERR_FORBIDDEN_001`) پروفایل tenant یافت نشد.