Invoice
An Invoice groups charge items raised against a single account into one billable document, with snapshotted line items, totals, and a lifecycle status. You touch it whenever you bill a patient, issue a refund, or reconcile payments.
The data splits across two layers. The Django model is storage: columns plus several opaque JSONFields (charge_items_copy, total_price_components, lock_history) whose real structure lives in the Pydantic resource specs. The specs define the API request/response schemas, the InvoiceStatusOptions enum, the shape of those JSON fields, validation, and the server-side totalling and snapshot behaviour.
Source:
- Model:
care/emr/models/invoice.py - Resource specs:
spec.py·sync_items.py·return_items_invoice.py·default_expression_evaluator.py - Shared types:
base.py·monetary_component.py
Models
| Model | Purpose |
|---|---|
Invoice | A billable grouping of charge items for one account, with snapshotted line items, totals, and status |
Invoice extends EMRBaseModel (shared Care EMR base with external_id, audit fields, and soft-delete semantics).
On issue, the server computes net and gross totals and snapshots each charge item, so the invoice stays stable even when its charge items change later. The retrieve view reports payment reconciliation records — payments and credit notes — against the invoice.
Invoice fields
References
| Field | Type | Notes |
|---|---|---|
facility | FK → facility.Facility (PROTECT) | Facility where the invoice is created |
patient | FK → emr.Patient (PROTECT) | Entity that incurred the charges; set server-side from account.patient on write |
account | FK → emr.Account (PROTECT) | Account being balanced by this invoice |
Status & metadata
| Field | Type | Spec / values | Notes |
|---|---|---|---|
status | CharField(100) | InvoiceStatusOptions enum — required on write | Drives the charge item lifecycle. See values |
title | CharField(1024) | str | None, default None | Optional human-readable label |
cancelled_reason | TextField | str | None | Set when the invoice moves to cancelled |
payment_terms | TextField | str | None | Markdown |
note | TextField | str | None | Free-text comments; markdown |
number | CharField(1000) | str | None, default None | Auto-generated for return invoices via the facility's invoice_number_expression (see Methods) |
issue_date | DateTimeField | datetime | None (tz-aware), default None | Set when the invoice is issued |
is_refund | BooleanField | bool, default False | Flags a refund/return invoice; required for negative totals (see validation) |
Charge items
| Field | Type | Spec shape | Notes |
|---|---|---|---|
charge_items | ArrayField[int], default [] | write: list[UUID4]; storage: list of integer PKs | Live references to the grouped ChargeItem rows. The client sends external UUIDs on write; the server resolves them to PKs |
charge_items_copy | JSONField, default [] | list[dict] — array of serialized ChargeItemReadSpec | Point-in-time snapshot of the line items, written by sync_invoice_items. Served as charge_items on retrieve for any non-draft invoice |
Totals (server-maintained)
sync_invoice_items and calculate_charge_items_summary compute these. Never send them from a client.
| Field | Type | Spec shape | Notes |
|---|---|---|---|
total_price_components | JSONField, default {} | retrieve: list[dict] of MonetaryComponent | Aggregated monetary components across all charge items (one per type+code). See MonetaryComponent shape |
total_net | DecimalField(20, 6), default 0 | Decimal(max_digits=20, decimal_places=6) | Net total = base + surcharge − discount; tax excluded. Rounded with INVOICE_FINAL_AMOUNT_PRECISION / ..._ROUNDING_METHOD |
total_gross | DecimalField(20, 6), default 0 | Decimal(max_digits=20, decimal_places=6) | Gross total = net + tax. Same rounding |
When locked is True, the read specs serialize both total_net and total_gross as 0.
Locking
| Field | Type | Spec shape | Notes |
|---|---|---|---|
locked | BooleanField, default False | bool (read-only on read specs) | Freezes the invoice against further edits; while set, totals serialize as 0 |
lock_history | JSONField, default [] | list[dict] — each entry { user, ... } | Audit trail of lock/unlock events. On retrieve, each entry's user id is hydrated into a UserSpec dict |
Enums
InvoiceStatusOptions values
Defined in care/emr/resources/invoice/spec.py. The API value is entered_in_error (underscore), not the FHIR-style hyphen.
| Value | Meaning |
|---|---|
draft | In progress; retrieve serializes live charge items, not the snapshot |
issued | Issued to the patient/payer |
balanced | Fully settled |
cancelled | Cancelled |
entered_in_error | Recorded in error |
INVOICE_CANCELLED_STATUS = ["cancelled", "entered_in_error"] — the two terminal/void states.
MonetaryComponentType values
Defined in care/emr/resources/common/monetary_component.py. Each type contributes to net and gross differently during totalling.
| Value | Effect on totals |
|---|---|
base | Added to net (one base component per item; must carry an amount) |
surcharge | Added to net |
discount | Subtracted from net |
tax | Added to gross only (excluded from net) |
informational | Carried for display; not summed |
Resource specs (API schema)
All specs subclass EMRResource (serialize / de_serialize, with perform_extra_serialization / perform_extra_deserialization hooks). BaseInvoiceSpec.__model__ = Invoice and __exclude__ = ["account", "charge_items"]; the hooks handle those two fields rather than the generic mapper.
| Spec | Role | Fields beyond base | Server behaviour |
|---|---|---|---|
BaseInvoiceSpec | shared | id, title, status (required), cancelled_reason, payment_terms, note, issue_date, number | Base for all specs |
InvoiceWriteSpec | write · create + update | account: UUID4 (required), charge_items: list[UUID4] (default []) | perform_extra_deserialization: resolves account from external_id, sets patient = account.patient, stages charge_items (rewritten in perform_create) |
InvoiceReadSpec | read · list | total_net, total_gross, locked, created_date, modified_date, account: dict, is_refund | perform_extra_serialization: id = external_id; account → AccountMinimalReadSpec; if locked, zero out total_net/total_gross |
InvoiceRetrieveSpec | read · detail | (all read fields) + charge_items: list[dict], total_price_components: list[dict], created_by, updated_by, payments: list[dict], total_payments, credit_notes: list[dict], total_credit_notes, lock_history: list[dict] | See retrieve serialization below |
InvoiceWriteSpec handles both create and update — there's no separate Update spec. InvoiceRetrieveSpec extends InvoiceReadSpec.
InvoiceRetrieveSpec.perform_extra_serialization
id = external_id;accountserialized viaAccountReadSpec(full account on detail).charge_items: ifstatus == draft, liveChargeItemReadSpec.serialize(...)overChargeItem.objects.filter(id__in=charge_items)(select_relatedpaid_invoice,charge_item_definition); otherwise the storedcharge_items_copysnapshot.created_by/updated_by: hydrated viaserialize_audit_users.- Reconciliations against this invoice with
outcome = completeandstatus = activeare split into:payments(+total_payments) whereis_credit_noteis false,credit_notes(+total_credit_notes) whereis_credit_noteis true,- each serialized via
PaymentReconciliationRetrieveSpec.
lock_history: each entry'suserid replaced with a cachedUserSpec.
MonetaryComponent shape
Stored inside total_price_components and charge_items_copy. Source: care/emr/resources/common/monetary_component.py.
MonetaryComponent {
monetary_component_type: MonetaryComponentType # required (base|surcharge|discount|tax|informational)
code: Coding | None # { system?, version?, code, display? }
factor: Decimal(20,6) | None
amount: Decimal(20,6) | None
tax_included_amount: Decimal(20,6) | None # only valid on base
global_component: bool = False
conditions: list[EvaluatorConditionSpec] = []
}
Per-component validation: base must carry an amount and no conditions; tax_included_amount is allowed only on base; amount and factor are mutually exclusive; at least one of amount/factor is required, unless the component is a global_component with a code. Collection-level rules (MonetaryComponents) forbid duplicate codes, require exactly one base, and reconcile tax_included_amount against the sum of tax components.
Related models
ChargeItem— line items grouped by the invoice; snapshotted intocharge_items_copyviaChargeItemReadSpec.Account— the billing account; resolved on write, rebalanced after return-invoice generation/cancellation.PaymentReconciliation— payments and credit notes reported on the retrieve view (filtered tooutcome=complete,status=active).ChargeItemDefinition— applied when building return-invoice charge items.
Methods & save behaviour
sync_invoice_items(invoice)(sync_items.py) — recomputestotal_net,total_gross,total_price_components, andcharge_items_copyfrom the linked charge items, then rounds totals usingINVOICE_FINAL_AMOUNT_PRECISION/INVOICE_FINAL_AMOUNT_ROUNDING_METHOD. Validation: ifis_refundis false and either total is negative, raisesValidationError("A Refund Ivoice is required for negative values").calculate_charge_items_summary(charge_items)— net = Σ base + Σ surcharge − Σ discount; gross = net + Σ tax; components aggregated pertype + code(key =system + code, elseNo-Code).evaluate_invoice_identifier_default_expression(facility)(default_expression_evaluator.py) — generatesnumberfrom the facility'sinvoice_number_expressionwith context{ invoice_count, current_year_yyyy, current_year_yy }; returns""if unset.evaluate_invoice_dummy_expressionpreviews an expression with sample context.generate_return_invoice(delivery_order)(return_items_invoice.py) — creates adraftrefund invoice (is_refund=True), reverses each completedSupplyDelivery's charge item viaapply_charge_item_definition(reverse=True), marks itemsbilled, runssync_invoice_items, sets statusissued, then triggersrebalance_account_task. Raises if any delivery is stillin_progress.cancel_return_invoice(delivery_order)— sets the invoicecancelled, marks its charge itemsentered_in_error(clearspaid_invoice/paid_on), voids the supply deliveries, rebalances the account, and resyncs inventory.
API integration notes
statusis the control surface for the billing lifecycle. Drive transitions through the API rather than editing rows directly; the values aredraft,issued,balanced,cancelled,entered_in_error.- On write (
InvoiceWriteSpec): sendaccountandcharge_itemsas external UUIDs. The server derivespatientfrom the account, so don't send it.accountandcharge_itemsbypass the generic field mapper and are handled inperform_extra_deserialization. charge_items_copy,total_price_components,total_net, andtotal_grossare platform-maintained snapshots derived from the linked charge items — don't set them directly.- The list view (
InvoiceReadSpec) returns a minimalaccount; the detail view (InvoiceRetrieveSpec) returns the full account plus charge items, aggregated price components, payments, credit notes, and lock history. - When
lockedisTrue, the read specs serializetotal_netandtotal_grossas0.
Related
- Reference: Account
- Reference: Charge Item
- Reference: Charge Item Definition
- Reference: Payment Reconciliation
- Reference: Patient
- Reference: Base Model
- Source: invoice.py on GitHub
- Source: invoice resource specs on GitHub