Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

References/Invoice

From OHC Network Wiki
referencebillingCARE 3.0+

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

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
title CharField(1024) str
cancelled_reason TextField str
payment_terms TextField str
note TextField str
number CharField(1000) str
issue_date DateTimeField datetime
is_refund BooleanField bool, default False

Charge items

Field Type Spec shape Notes
charge_items ArrayField[int], default [] write: list[UUID4]; storage: list of integer PKs
charge_items_copy JSONField, default [] list[dict] — array of serialized ChargeItemReadSpec

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
total_net DecimalField(20, 6), default 0 Decimal(max_digits=20, decimal_places=6)
total_gross DecimalField(20, 6), default 0 Decimal(max_digits=20, decimal_places=6)

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)
lock_history JSONField, default [] list[dict] — each entry { user, ... }

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; accountAccountMinimalReadSpec; 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; 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.

Pages that link here:

Wiki: ohcnwiki.tellmey.fyi