Port two nobat724 Figma screens into the admin SPA for the doctor/clinic
tenant panel:
- node 1 — لیست پرداختها (/admin/my-payments): per-patient payment summary
(invoice count, paid, remaining, derived status paid/unsettled/unpaid),
filters by national code / status / Jalali date range, pagination.
- node 2 — پرداختهای ثبتشده (/admin/my-payments/:patientUuid): a patient's
recorded invoices with patient header, service title, total, status badge,
and an expandable per-invoice item breakdown.
Backend (App\Billing):
- InvoiceRepository::patientPaymentSummary/countPatientPaymentSummary — DQL
aggregation grouped by patient record (arbitrary join Invoice→PatientRecord
→User), draft/void excluded, derived-status HAVING filters.
- InvoiceRepository::invoicesForPatient/count + InvoiceService methods that
shape rows and derive status.
- BillingController: GET /api/v1/my/billing/patient-payments and
GET /api/v1/my/billing/patients/{patientUuid}/invoices (thin, resolveEntity,
tenant-scoped, 403/404). Invoice::getIssuedAt / InvoiceItem::getTitle added.
- docs/api/billing.md documents both endpoints.
Frontend: useMyPayments hooks, MyPaymentsPage, MyPaymentDetailPage, routes in
App.tsx (doctor/secretary/clinic, blockClinicScope) and a sidebar entry.
Persian strings hardcoded per existing admin convention (no i18n infra).
Tests: tests/Billing/PatientPaymentsTest.php (8), useMyPayments + both page
tests (11). Note: pre-existing LoginPage.test failures are unrelated (proven
by stashing this change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
approve/pay accepted any approved_rials/paid_rials with no bounds, so the
claiming tenant could write arbitrary figures into the insurer-debt ledger
(negative, or far above the claimed total). Validate: approved ∈ [0, claimed],
paid ∈ [0, approved] → 422 otherwise. (The "force arbitrary status" half of the
finding was already prevented by Claim::canTransitionTo.)
Regression: tests/Billing/ClaimAmountBoundsTest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /billing/claims loaded every tenant claim with no limit. Add
findByTenant(page, limit) + countByTenant (shared query builder), default
limit 50 / max 100, and expose totals as data.meta — kept inside the existing
{ data: { data: [...] } } envelope so current clients are unaffected.
GET /wallet/transactions was already bounded (findByUser defaulted to limit 50)
but page-less; add page/offset + countByUser + the same additive meta.
Regression: tests/Billing/ClaimsListPaginationTest,
tests/Settlement/WalletTransactionsPaginationTest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>