Skip to main content
Version: 3.1

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:

Models

ModelPurpose
InvoiceA 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

FieldTypeNotes
facilityFK → facility.Facility (PROTECT)Facility where the invoice is created
patientFK → emr.Patient (PROTECT)Entity that incurred the charges; set server-side from account.patient on write
accountFK → emr.Account (PROTECT)Account being balanced by this invoice

Status & metadata

FieldTypeSpec / valuesNotes
statusCharField(100)InvoiceStatusOptions enum — required on writeDrives the charge item lifecycle. See values
titleCharField(1024)str | None, default NoneOptional human-readable label
cancelled_reasonTextFieldstr | NoneSet when the invoice moves to cancelled
payment_termsTextFieldstr | NoneMarkdown
noteTextFieldstr | NoneFree-text comments; markdown
numberCharField(1000)str | None, default NoneAuto-generated for return invoices via the facility's invoice_number_expression (see Methods)
issue_dateDateTimeFielddatetime | None (tz-aware), default NoneSet when the invoice is issued
is_refundBooleanFieldbool, default FalseFlags a refund/return invoice; required for negative totals (see validation)

Charge items

FieldTypeSpec shapeNotes
charge_itemsArrayField[int], default []write: list[UUID4]; storage: list of integer PKsLive references to the grouped ChargeItem rows. The client sends external UUIDs on write; the server resolves them to PKs
charge_items_copyJSONField, default []list[dict] — array of serialized ChargeItemReadSpecPoint-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.

FieldTypeSpec shapeNotes
total_price_componentsJSONField, default {}retrieve: list[dict] of MonetaryComponentAggregated monetary components across all charge items (one per type+code). See MonetaryComponent shape
total_netDecimalField(20, 6), default 0Decimal(max_digits=20, decimal_places=6)Net total = base + surcharge − discount; tax excluded. Rounded with INVOICE_FINAL_AMOUNT_PRECISION / ..._ROUNDING_METHOD
total_grossDecimalField(20, 6), default 0Decimal(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

FieldTypeSpec shapeNotes
lockedBooleanField, default Falsebool (read-only on read specs)Freezes the invoice against further edits; while set, totals serialize as 0
lock_historyJSONField, 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.

ValueMeaning
draftIn progress; retrieve serializes live charge items, not the snapshot
issuedIssued to the patient/payer
balancedFully settled
cancelledCancelled
entered_in_errorRecorded 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.

ValueEffect on totals
baseAdded to net (one base component per item; must carry an amount)
surchargeAdded to net
discountSubtracted from net
taxAdded to gross only (excluded from net)
informationalCarried 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.

SpecRoleFields beyond baseServer behaviour
BaseInvoiceSpecsharedid, title, status (required), cancelled_reason, payment_terms, note, issue_date, numberBase for all specs
InvoiceWriteSpecwrite · create + updateaccount: 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)
InvoiceReadSpecread · listtotal_net, total_gross, locked, created_date, modified_date, account: dict, is_refundperform_extra_serialization: id = external_id; accountAccountMinimalReadSpec; if locked, zero out total_net/total_gross
InvoiceRetrieveSpecread · 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; account serialized via AccountReadSpec (full account on detail).
  • charge_items: if status == draft, live ChargeItemReadSpec.serialize(...) over ChargeItem.objects.filter(id__in=charge_items) (select_related paid_invoice, charge_item_definition); otherwise the stored charge_items_copy snapshot.
  • created_by / updated_by: hydrated via serialize_audit_users.
  • Reconciliations against this invoice with outcome = complete and status = active are split into:
    • payments (+ total_payments) where is_credit_note is false,
    • credit_notes (+ total_credit_notes) where is_credit_note is true,
    • each serialized via PaymentReconciliationRetrieveSpec.
  • lock_history: each entry's user id replaced with a cached UserSpec.

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.

  • ChargeItem — line items grouped by the invoice; snapshotted into charge_items_copy via ChargeItemReadSpec.
  • Account — the billing account; resolved on write, rebalanced after return-invoice generation/cancellation.
  • PaymentReconciliation — payments and credit notes reported on the retrieve view (filtered to outcome=complete, status=active).
  • ChargeItemDefinition — applied when building return-invoice charge items.

Methods & save behaviour

  • sync_invoice_items(invoice) (sync_items.py) — recomputes total_net, total_gross, total_price_components, and charge_items_copy from the linked charge items, then rounds totals using INVOICE_FINAL_AMOUNT_PRECISION / INVOICE_FINAL_AMOUNT_ROUNDING_METHOD. Validation: if is_refund is false and either total is negative, raises ValidationError("A Refund Ivoice is required for negative values").
  • calculate_charge_items_summary(charge_items) — net = Σ base + Σ surcharge − Σ discount; gross = net + Σ tax; components aggregated per type + code (key = system + code, else No-Code).
  • evaluate_invoice_identifier_default_expression(facility) (default_expression_evaluator.py) — generates number from the facility's invoice_number_expression with context { invoice_count, current_year_yyyy, current_year_yy }; returns "" if unset. evaluate_invoice_dummy_expression previews an expression with sample context.
  • generate_return_invoice(delivery_order) (return_items_invoice.py) — creates a draft refund invoice (is_refund=True), reverses each completed SupplyDelivery's charge item via apply_charge_item_definition(reverse=True), marks items billed, runs sync_invoice_items, sets status issued, then triggers rebalance_account_task. Raises if any delivery is still in_progress.
  • cancel_return_invoice(delivery_order) — sets the invoice cancelled, marks its charge items entered_in_error (clears paid_invoice/paid_on), voids the supply deliveries, rebalances the account, and resyncs inventory.

API integration notes

  • status is the control surface for the billing lifecycle. Drive transitions through the API rather than editing rows directly; the values are draft, issued, balanced, cancelled, entered_in_error.
  • On write (InvoiceWriteSpec): send account and charge_items as external UUIDs. The server derives patient from the account, so don't send it. account and charge_items bypass the generic field mapper and are handled in perform_extra_deserialization.
  • charge_items_copy, total_price_components, total_net, and total_gross are platform-maintained snapshots derived from the linked charge items — don't set them directly.
  • The list view (InvoiceReadSpec) returns a minimal account; the detail view (InvoiceRetrieveSpec) returns the full account plus charge items, aggregated price components, payments, credit notes, and lock history.
  • When locked is True, the read specs serialize total_net and total_gross as 0.