Ledger to Invoice Bridge (createFromLedger)
Closes the automated-charge-capture to claim seam from the append-only ledger.
Closes the automated-charge-capture → claim seam. The append-only
billable_ledger(menu-price-ledger) accumulates charges as care happens, but nothing yet materializes those lines into aSalesOrder/ClaimInvoice. This is the missing path the billing-rcm master doc names as buildable but absent.Status: design + skeleton. The NestJS endpoint is specified here and the bridging edge function ships as a documented skeleton (
close-encounter-billing). The endpoint implementation inservices/financialis the remaining work.
The two pieces
1. POST /v2/financial/salesOrders/createFromLedger (NestJS — to build)
Location: services/financial/src/api/financial/modules/salesOrder/ (new action).
Request: { encounterId, closeReason?, idempotencyKey }
Behaviour:
1. Read billable_ledger lines for encounterId (canonical pricing already stamped
by resolve_menu_price() — contract → scheme → payor → standard).
2. Group into SalesOrder + OrderRequestItem[] (one SO per encounter per close —
INVARIANT, enforced via idempotencyKey = `${encounterId}:${closeHash}`).
3. Split patient-responsible vs insurer-claimable from the coverage context
(insurance_context / china_insurer_gop_policies copay_pct).
4. Emit manifest.financial.salesOrderCreated → orchestrator → read models.
Response: { salesOrderId, total, patientResponsible, insurerClaimable }
Invariants (from billing-rcm-unified-master): one SO per encounter per close; closed bill is frozen; idempotent (re-run returns the same SO); no silent zero.
2. close-encounter-billing (Deno edge fn — skeleton shipped)
Location: infrastructure/medbase/functions/close-encounter-billing/index.ts.
Bridges the read-model trigger (encounter close / cashier action) to the NestJS
endpoint above. On success, stamps encounter.billClosed/billClosedAt/billClosedBy
(per delivery-room-remaining-backend-work.md §2) and the journey-cache projection.
Flow
encounter close (cashier action)
│
▼
close-encounter-billing (edge fn)
│ reads billable_ledger lines for the encounter
▼
POST /v2/financial/salesOrders/createFromLedger (NestJS gateway)
│ builds SalesOrder + items, splits copay/claim
▼
manifest.financial.salesOrderCreated → encounter-orchestrator
│
├─▶ encounter_journey_cache.financial_summary (+ billClosed flag)
└─▶ department_queues (billing-pending → settled) via trg_sync_billing_queue
China specifics
- The copay/claim split reads
china_insurer_gop_policies.copay_pct(medos-china pack). - The resulting SO feeds the
cn-commercial-direct/cn-shebao-basicconnector pack for claim export, and the Fapiao template (fapiao-template.json) for tax invoicing.
Open work
- Implement the NestJS
createFromLedgeraction (read-model → SO mapper + idempotency). - Wire
close-encounter-billingto the real gateway URL + service auth (TODO in skeleton). - Confirm
billable_ledgercolumn contract before building the mapper. - Add
manifest.financial.salesOrderCreatedto the orchestrator if not already handled.