# Flow: Clinic Billing → Finance Posting

**Actors**: Receptionist/Clinic Admin (invoice, receipt), system (posting)
**Phase**: 11 — the highest-risk phase in this expansion; test end-to-end on a non-production
database before considering it done.

## Step 0 — one-time setup (per environment)

- Super Admin creates Revenue and AR accounts for the clinic in the existing chart-of-accounts
  screen (no seeder exists in this project for any module's accounts — confirmed by search).
- Super Admin sets `clinic_journal_id`, `default_clinic_ar_account_id`,
  `default_clinic_revenue_account_id` via the existing generic Settings UI
  (`SettingService`/`SettingController`) — same mechanism `sales_journal_id` etc. already use.
  `ClinicPostingService` throws if `clinic_journal_id` is unset when a post is attempted, mirroring
  `SalesPostingService::salesJournalId()`'s required-key behavior (not the softer optional
  behavior `purchase_journal_id` uses).

## Customer provisioning (first time only, per patient)

1. On the first `ClinicInvoice` ever drafted for a patient,
   `ClinicPatientCustomerProvisioningService::customerFor($user)` checks for an existing linked
   `Sales\Customer`; if none, creates one (name/contact copied from `User`), following the same
   construction `Sales\CustomerService::create()` uses. The link is stored (a new
   `customer_id`/`user_id` mapping — either a column on `Sales\Customer` or a small pivot,
   decided at build time based on whether `Sales\Customer` can safely gain a nullable `user_id`
   column without affecting non-clinic customers) so subsequent invoices reuse the same customer.

## Drafting the invoice

2. When an encounter with billable content is finalized (consultation fee at minimum; procedure/
   lab/surgery lines added as those modules produce billable events), or when a `SurgeryCase`
   completes, `ClinicInvoiceService::draftFor($encounter | $surgeryCase)` creates a `draft`
   `ClinicInvoice` with one `ClinicInvoiceItem` per billable line (`service_type` set per line —
   `consultation`/`procedure`/`surgery`/`lab_test`).
3. Receptionist/Clinic Admin reviews the draft on the invoice screen, confirms it →
   `status = 'confirmed'`.

## Posting

4. `ClinicPostingService::postInvoice($invoice)` — mirrors `SalesPostingService::postInvoice()`
   step for step:
   - Guard: `$invoice->isConfirmed()`.
   - Resolve `clinic_journal_id` from Settings (throw if unset).
   - Resolve AR account: `$invoice->customer->ar_account_id ?? Setting('default_clinic_ar_account_id') ?? throw`.
   - Resolve revenue account **per line**: `$item->revenue_account_id ?? (per-service_type default, if introduced) ?? Setting('default_clinic_revenue_account_id') ?? throw`.
   - Build balanced lines: Dr AR (total), Cr Revenue per line.
   - `assertBalanced()` → `JournalEntryService::create()` → `JournalPostingService::post()`.
   - On success: `$invoice->journal_entry_id`, `status = 'posted'`, `posted_at` set.

## Collecting payment

5. Receptionist/Clinic Admin records the payment (cash/card, per the confirmed no-insurance
   scope) → `ReceiptVoucherService::create()` with `clinic_invoice_id` set to this invoice (the
   new FK from `07-database-schema-expansion.md` §Altered) instead of the old free-text
   `invoice_reference`.
6. Voucher goes through its existing lifecycle: `approve()` → `ReceiptVoucherPostingService::post()`
   — Dr cash/bank, Cr AR, journal entry created and posted.
7. **New behavior added in this phase**: `ReceiptVoucherPostingService::post()`, after posting,
   checks `clinic_invoice_id` — if set, updates that `ClinicInvoice.paid_amount` (increment by the
   voucher amount) and recomputes `payment_status` (`unpaid`/`partial`/`paid`). This closes the
   gap `06-overview-medical-center-expansion.md` flagged: today even `Sales\Invoice` doesn't get
   this update automatically, because `invoice_reference` is just free text.

## End-to-end check (what "done" looks like)

Booking/encounter → draft invoice → confirmed → posted (balanced journal entry visible on the
trial balance) → receipt voucher created against that invoice → voucher posted → invoice's
`paid_amount`/`payment_status` reflects the payment automatically, with no manual reconciliation
step. Verify this full chain manually (or via a Pest/PHPUnit feature test if the Sales module has
an equivalent precedent to follow) before marking Phase 11 complete.
