Skip to main content
Version: 3.0

Charge Item

A ChargeItem is a single billable line for a service or product supplied to a patient — what was charged, the quantity, the unit and total price components, and any discounts or override reasons. You create one against a patient (and optionally an encounter); it always settles onto an Account.

Source:

The Django model is only the storage layer. Several fields are opaque JSONFields whose structure lives in the Pydantic resource specs under care/emr/resources/charge_item/, which also define the enums, validation, read/write API schemas, server-side cost calculation, and status side effects. The field tables below tell you what's stored; the spec and behaviour sections tell you what happens on write.

Models

ModelPurpose
ChargeItemA single billable line item for a service or product rendered to a patient

ChargeItem extends EMRBaseModel (shared Care EMR base with external_id, audit fields — created_by/updated_by/created_date/modified_date — and soft-delete semantics — see Base model).

ChargeItem fields

Context & relationships

FieldTypeNotes
facilityFK → facility.Facility (PROTECT)Set server-side from the URL; a body value is ignored
patientFK → emr.Patient (CASCADE)When you supply an encounter, the patient is taken from it rather than the request
encounterFK → emr.Encounter (CASCADE)Nullable; the encounter the charge relates to
charge_item_definitionFK → emr.ChargeItemDefinition (CASCADE)Nullable; the template the charge was applied from. Not writable via create/update — set only by apply_charge_item_defs
accountFK → emr.Account (CASCADE)The account this charge lands on. Falls back to the patient's default account when omitted

Description & status

FieldTypeNotes
titleCharField(255)Required; display title for the charge
descriptionTextFieldNullable; longer description
statusCharField(255)Required; lifecycle status, constrained to ChargeItemStatusOptions — see Status values
codeJSONFieldNullable; a Coding that identifies the charge (e.g. a billing code), shape Coding { system?, version?, code, display? } — see Coding shape. No bound value set
noteTextFieldNullable; free-text (markdown) comments

Pricing

FieldTypeNotes
quantityDecimalField(20, 6)Required on write; the quantity serviced. Multiplies the base/per-unit components during cost sync
unit_price_componentsJSONFieldRequired on write; list of MonetaryComponent — see MonetaryComponent shape. At most one base component; duplicate component codes are rejected
total_price_componentsJSONFieldNullable; server-computed list of MonetaryComponent (as dicts) — the resolved breakdown after base × quantity, surcharges, discounts, and taxes. Not client-writable
total_priceDecimalField(20, 6)Nullable; server-computed total. Must be ≥ 0 unless the charge is a reversal
override_reasonJSONFieldNullable; ChargeItemOverrideReason { text: str, code?: Coding } explaining a list-price/factor override. No bound value set
discount_configurationJSONFieldNullable (default None); DiscountConfiguration { max_applicable: int (≥0), applicability_order: total_asc | total_desc }. Populated from the definition/facility config when applied from a definition

Service source

These fields trace the charge back to the resource that produced it.

FieldTypeNotes
service_resourceCharField(255)Nullable (default None); resource type, constrained to ChargeItemResourceOptions — see Service resource types
service_resource_idCharField(255)Nullable (default None); external id of the originating resource. Required once service_resource is set
performer_actorFK → users.User (CASCADE)Nullable (default None); the user who performed the service. Must belong to the facility's organization

Invoicing & tags

FieldTypeNotes
paid_invoiceFK → emr.Invoice (CASCADE)Nullable (default None); denormalized link to the settling invoice. Platform-maintained; cleared on cancel
paid_onDateTimeFieldNullable (default None); when the charge was paid. Platform-maintained; cleared on cancel
tagsArrayField[int]Tag IDs, default []. Managed through the tag mixin (SingleFacilityTagManager), serialized as objects on read

Enums

Status values

status is a plain CharField in storage, but every write passes through ChargeItemStatusOptions (care/emr/resources/charge_item/spec.py):

ValueMeaning
billableReady to be invoiced
not_billableWill not be billed
abortedCharge was cancelled before billing
billedIncluded on an invoice
paidSettled (links to paid_invoice / paid_on)
entered_in_errorRecorded in error

(planned exists in the source as a commented-out option and is not currently selectable.)

not_billable, aborted, and entered_in_error form the cancelled set (CHARGE_ITEM_CANCELLED_STATUS). Moving into this set on update triggers the cancel side effect (see Methods & save behaviour). billed and paid cannot be set by hand — the viewset rejects manual transitions into them.

Service resource types

service_resource is constrained by ChargeItemResourceOptions:

Value
service_request
medication_dispense
appointment
bed_association

On create, the viewset validates the referenced resource: service_request must exist (and not be completed) for the patient/encounter, and bed_association must reference a FacilityLocationEncounter on a non-completed encounter in the facility.

MonetaryComponentType values

Used by every entry in unit_price_components / total_price_components (care/emr/resources/common/monetary_component.py):

ValueRole in cost sync
basePer-unit base price. Exactly one allowed; must carry amount (not factor); no conditions. base.amount × quantity seeds the running total
surchargeAdded on top of base; amount or factor (% of base)
discountSubtracted from the net price; amount or factor (% of net). Filtered/limited by discount_configuration
taxAdded on the post-discount taxable price; amount or factor (% of taxable)
informationalCarried through without affecting the computed total

DiscountApplicability values

discount_configuration.applicability_order (care/emr/resources/common/monetary_component.py):

ValueEffect
total_ascSort candidate discounts by amount ascending before applying max_applicable
total_descSort by amount descending before applying max_applicable

max_applicable = 0 drops all discounts.

JSON field shapes

Coding shape

code, plus the code field inside components and override_reason (care/emr/resources/common/coding.py, extra="forbid"):

Coding {
system?: str
version?: str
code: str # required
display?: str
}

MonetaryComponent shape

Each entry of unit_price_components and total_price_components (care/emr/resources/common/monetary_component.py):

MonetaryComponent {
monetary_component_type: base | surcharge | discount | tax | informational # required
code?: Coding
factor?: Decimal(20,6) # percentage; mutually exclusive with amount
amount?: Decimal(20,6) # absolute; mutually exclusive with factor
tax_included_amount?: Decimal(20,6) # base component only
global_component: bool = False # resolved against facility monetary config
conditions: [EvaluatorConditionSpec] = [] # not allowed on base
}

Validation: exactly one of amount/factor (unless global_component with a code); base must have amount, no factor, no conditions; tax_included_amount only on base; duplicate component codes are rejected.

EvaluatorConditionSpec { metric: str, operation: str, value: dict | str } — conditions are evaluated against patient/facility/encounter context when a definition is applied; a component whose conditions fail is dropped.

ChargeItemOverrideReason shape

override_reason (care/emr/resources/charge_item/spec.py):

ChargeItemOverrideReason {
text: str # required
code?: Coding # no bound value set
}

DiscountConfiguration shape

discount_configuration (care/emr/resources/common/monetary_component.py):

DiscountConfiguration {
max_applicable: int (>= 0) # 0 drops all discounts
applicability_order: total_asc | total_desc
}

Resource specs (API schema)

Charge items are exposed through ChargeItemViewSet (create / retrieve / update / upsert / list / tag actions, plus the apply_charge_item_defs and change_account custom actions). The Pydantic specs in care/emr/resources/charge_item/spec.py build on EMRResource (serialize / de_serialize, with perform_extra_serialization / perform_extra_deserialization hooks).

SpecRoleNotes
ChargeItemSpecshared base__model__ = ChargeItem, __exclude__ = ["encounter", "account"]. Fields: id, title, description, status, code, quantity, unit_price_components, note, override_reason. Validators: no duplicate component codes, ≤1 base component
ChargeItemWriteSpecwrite · createExtends base with encounter, patient, account, service_resource, service_resource_id, performer_actor (all UUID4/enum, optional). Requires encounter or patient; requires service_resource_id when service_resource set
ChargeItemUpdateSpecwrite · updateBase fields + performer_actor. Does not re-bind patient/encounter/account
ChargeItemReadSpecread · list & detailBase fields + server-side: total_price_components, total_price, charge_item_definition (nested), paid_invoice (nested), tags (objects), service_resource(_id), created_date, modified_date, paid_on, performer_actor, created_by, updated_by, discount_configuration

Notable validation and binding:

  • code, override_reason.code, and component codes are FHIR-aligned Codings with no bound value set.
  • ChargeItemWriteSpec.perform_extra_deserialization resolves patient/encounter/account/performer_actor from external ids; supplying encounter overrides patient with the encounter's patient, and account is looked up scoped to that patient.
  • ChargeItemReadSpec.perform_extra_serialization nests the full ChargeItemDefinitionReadSpec and InvoiceReadSpec, renders tags, and resolves performer_actor / created_by / updated_by from the user cache.
SpecUsed byShape
Codingcode, component & override codessee Coding shape
MonetaryComponentunit_price_components, total_price_componentssee MonetaryComponent shape
ChargeItemOverrideReasonoverride_reason{ text, code? }
DiscountConfigurationdiscount_configuration{ max_applicable, applicability_order }
ChargeItemDefinitionReadSpeccharge_item_definition (read)full nested definition — see Charge Item Definition
InvoiceReadSpecpaid_invoice (read)full nested invoice — see Invoice
UserSpecperformer_actor, audit users (read)see User

ChargeItem is a leaf model — it holds foreign keys rather than owning child rows:

facility → FK facility.Facility (PROTECT)
patient → FK emr.Patient (CASCADE)
encounter → FK emr.Encounter (CASCADE, nullable)
charge_item_definition → FK emr.ChargeItemDefinition (CASCADE, nullable)
account → FK emr.Account (CASCADE)
paid_invoice → FK emr.Invoice (CASCADE, nullable)
performer_actor → FK users.User (CASCADE, nullable)

Methods & save behaviour

Cost resolution and status side effects live in the spec helpers and the viewset, not in the model's save().

  • Cost sync (sync_charge_item_costs) — runs on every create and on update (unless suppressed). It walks unit_price_components: multiplies the base amount by quantity to seed total_price/base, adds surcharges, applies discounts (filtered by discount_configuration via apply_discount_configuration), then adds taxes on the taxable price. It writes the resolved breakdown to total_price_components and the sum to total_price, and raises ValidationError if total_price < 0 on a non-reversal charge.
  • Apply from definition (apply_charge_item_definition / POST apply_charge_item_defs) — builds a ChargeItem from a ChargeItemDefinition: copies title/description, merges category + global components, evaluates each component's conditions against patient/facility/encounter context (dropping unmet ones), sets status = billable, resolves discount_configuration, defaults the account to the patient's default account, and runs cost sync. reverse negates component amounts to produce a credit.
  • Cancel (handle_charge_item_cancel) — fires on update when status moves into CHARGE_ITEM_CANCELLED_STATUS (not_billable / aborted / entered_in_error). If the charge sits on a draft invoice, it is removed from the invoice, the invoice is rebalanced and saved, and paid_invoice / paid_on are cleared. Cancelling a charge on a non-draft invoice raises ValidationError. Cost sync is skipped for cancellations.
  • Update guards (validate_data / perform_update) — a charge in a cancelled status can't be updated; updates are blocked when the linked invoice is balanced/issued; status can't be moved by hand into billed/paid; and if the source definition has can_edit_charge_item = False, the pricing fields (unit_price_components, total_price_components, total_price, quantity) are reset from the previous object and cost sync is skipped. After update, a draft paid_invoice is re-synced.
  • Cancel authorization (authorize_cancel) — cancellation is free within CHARGE_ITEM_FREE_CANCEL_PERIOD_MINUTES of creation; after that it needs the can_cancel_charge_item_in_facility permission.
  • Change account (POST change_account) — bulk-moves up to 100 billable charge items to a target account, then queues rebalance_account_task for every affected account.

API integration notes

  • The viewset sets facility from the URL and ignores any body value; patient is derived from encounter when one is supplied.
  • A charge item must resolve to an account — with none supplied, the patient's default account is used. encounter and charge_item_definition are optional.
  • total_price, total_price_components, paid_invoice, and paid_on are server-maintained; don't set them from clients.
  • Create and update enforce that performer_actor (and, when applying definitions, the actor) belongs to the facility's organization.
  • discount_configuration and tags are structured but deployment-tunable: discount_configuration follows DiscountConfiguration, and tags resolve through the single-facility tag manager.