The admin SPA never called POST /billing/invoices, so every session had
invoice_uuid = null and the view-invoice buttons on the patient services
tab (and MyPatients visit modal) were permanently disabled. Add a shared
useIssueInvoice hook (idempotent create + finalize when draft) and wire
it into:
- DetailsStep: the wizard's final step now really issues the invoice
- SessionServiceCard / PatientDetailPage: clicking view-invoice on a
session without an invoice issues it first, then opens the summary
- MyPatientsPage visit modal: same, replacing the dead disabled button
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend:
- Add session_at and inventory_package_id to patient_sessions, new
session_consumables table (migration Version20260716102537)
- New SessionConsumable entity/repository mirroring SessionService;
price snapshot, quantity >= 1, tenant-scoped silent skip
- PatientService::createSession accepts session_at, consumables[] and
inventory_package_uuid; consumables are fully patient-paid (no
insurance coverage) and added to final_price_rials
- Functional tests: success, foreign-tenant/unknown skip, empty and
zero-quantity edges (tests/Patient/SessionConsumableTest.php)
- docs/api/patient.md updated for the new Create Session fields
Frontend (admin):
- NewSessionPage rewritten as the tauri /files/create-service 3-step
wizard (ایجاد سرویس ← پرداخت ← جزییات) using SessionStepper
- New CreateStep: acceptance date/time (Jalali), section/service/staff,
consumables with counters, package select, conditional insurance
block (insured service or insured patient profile), price summary
- PaymentStep/DetailsStep extracted from SessionPaymentPage and shared
between both pages (behavior unchanged, tests still green)
- UserTick and FilesServiceAddCard icons ported verbatim from tauri
- Vitest coverage for the wizard incl. empty-data states
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the tauri /files/create-service page (payment mode) to the admin SPA
and back it with real multi-part session settlement:
Backend:
- New SessionPayment entity (session_payments table): partial payments
per session with method (wallet/pos/cash/card), amount, paid_at, actor
- PatientSession: settlement discount (percent/fixed), discount_rials,
paid_at, payments relation; remaining debt derived from
final - discount - paid total
- POST /api/v1/session/{uuid}/payments: register a partial payment;
wallet method debits the patient wallet; zero remaining marks paid
- PATCH /api/v1/session/{uuid}: accepts discount_type/discount_value
(null removes) and paid_at, backward compatible
- New error codes: ERR_SESSION_PAYMENT_INVALID/_EXCEEDS,
ERR_SESSION_DISCOUNT_INVALID
- Migration + 14 functional tests (partial/full/wallet/exceed/discount)
Frontend (admin):
- SessionPaymentPage: two-step stepper (پرداخت ← جزییات) ported from
tauri AddService payment mode — service cost, settlement discount
input, Jalali payment date, wallet balance, 4-method payment accordion,
paid-list box, details summary
- SessionStepper + stepper/payment icons ported verbatim from tauri SVGs
- «تکمیل پرداخت» on SessionServiceCard now navigates to the payment page
(replaces the small settle modal on PatientDetailPage)
- Routes for patients/ and my-patients/ variants; vitest coverage
- docs/api/patient.md updated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>