References/Account
More languages
More actions
An Account is the financial bucket that aggregates every transaction made against a patient within a facility. You touch it indirectly: charge items land on a patient's default account, and invoices and reconciliations settle against it.
Several model fields are opaque JSONFields whose real structure lives in the Pydantic resource specs under care/emr/resources/account/. Each field below is paired with the spec-defined enums, JSON shapes, validation, and read/write schemas that govern it.
Source:
- Model: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/account.py
care/emr/models/account.py] - Resource spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py
care/emr/resources/account/spec.py] - Default account helper: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py
account/default_account.py] - Balance sync: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py
account/sync_items.py] - Base resource: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py
care/emr/resources/base.py]
Models
| Model | Purpose |
|---|---|
Account
|
Aggregates charges and balances for one patient in one facility across encounters |
Account extends EMRBaseModel, which contributes external_id, the audit fields created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON.
Accounts are perpetual. One active, open account exists per patient per facility (see get_default_account), and charge items land on it. After discharge, the account can be balanced and closed. Invoices are raised against an account or against the items within it.
Account fields
Identity & scope
| Field | Type | Required | Notes |
|---|---|---|---|
facility |
FK → facility.Facility (PROTECT) |
yes | |
patient |
FK → emr.Patient (PROTECT) |
yes | |
name |
CharField(255) |
yes | |
description |
TextField |
no | |
primary_encounter |
FK → emr.Encounter (SET_NULL, nullable) |
no |
Status
| Field | Type | Required | Notes |
|---|---|---|---|
status |
CharField(255) |
yes | |
billing_status |
CharField(255) |
yes | |
service_period |
JSONField (default {}) |
yes (on spec) |
Balances & totals
These decimal totals (max_digits=20, decimal_places=6, default 0) are platform-maintained aggregates that sync_account_items recomputes. None are client-writable, and none appear on a write spec.
| Field | Type | Notes |
|---|---|---|
total_net |
DecimalField |
Net total before adjustments. Not populated — the sync_account_items logic is commented out, so it holds the default 0
|
total_gross |
DecimalField |
Sum of total_price over charge items in paid or billed status
|
total_paid |
DecimalField |
Sum of active+complete payment reconciliations, minus credit-note reconciliations
|
total_balance |
DecimalField |
total_gross − total_paid
|
total_billable_charge_items |
DecimalField |
Sum of total_price over charge items in billable status
{{Field|model=Account|section=Balances & totals|name=total_price_components|type=JSONField (default {})|notes=Intended price-component breakdown (taxes, discounts, etc.) as a list of |
cached_items |
JSONField (default {}) |
Denormalized snapshot of the account's charge items, serialized as a list on retrieve. Not populated — the sync logic is commented out
|
calculated_at |
DateTimeField (nullable) |
When the balances were last computed; set to care_now() on each sync
|
Tags & extensions
| Field | Type | Notes |
|---|---|---|
tags |
ArrayField[int] (default []) |
Tag IDs applied to the account; rendered through SingleFacilityTagManager on read
|
extensions |
JSONField (default {}) |
Open bag for deployment-specific metadata. ExtensionValidator checks it against ExtensionResource.account; surfaced only on the retrieve spec
|
Enum values
AccountStatusOptions values
Stored as the raw enum value string in status.
| Value | Meaning |
|---|---|
active
|
Open and in use (the default-account state) |
inactive
|
No longer actively used |
entered_in_error
|
Created in error |
on_hold
|
Temporarily paused |
Values use underscores (entered_in_error, on_hold), not the FHIR-style hyphenated forms.
AccountBillingStatusOptions values
Stored as the raw enum value string in billing_status.
| Value | Meaning |
|---|---|
open
|
Billing open and accruing (the default-account state) |
carecomplete_notbilled
|
Care complete, not yet billed |
billing
|
Billing in progress |
closed_baddebt
|
Closed as bad debt |
closed_voided
|
Closed and voided |
closed_completed
|
Closed, billing completed |
closed_combined
|
Closed, combined into another account |
JSON field shapes
service_period shape
service_period stores a PeriodSpec (from [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py resources/base.py]):
PeriodSpec {
start: datetime | null # ISO 8601, timezone-aware
end: datetime | null # ISO 8601, timezone-aware
}
PeriodSpec.validate_period enforces three rules:
start, if present, must be timezone-aware — a naive datetime raises"Start Date must be timezone aware".end, if present, must be timezone-aware —"End Date must be timezone aware".- With both present,
start <= end, or"Start Date cannot be greater than End Date".
The default account sets only start, formatted as "%Y-%m-%dT%H:%M:%S.%fZ" from care_now().
The FHIR-style [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/period.py Period] common type ({ id?, start?, end? }, extra="forbid") is a related but distinct shape — Account binds service_period to PeriodSpec, not Period.
MonetaryComponent shape
total_price_components is meant to hold a list of [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/common/monetary_component.py MonetaryComponent] entries, the same shape used across billing:
MonetaryComponent {
monetary_component_type: base | surcharge | discount | tax | informational
code: Coding | null
factor: Decimal(max_digits=20, decimal_places=6) | null
amount: Decimal(max_digits=20, decimal_places=6) | null
tax_included_amount: Decimal(max_digits=20, decimal_places=6) | null
global_component: bool = false
conditions: EvaluatorConditionSpec[] = []
}
MonetaryComponent validation: tax_included_amount is allowed only on a base component; a base component must carry an amount, no factor, and no conditions; amount and factor are mutually exclusive; exactly one of amount/factor is required unless the entry is a global_component with a code.
Resource specs (API schema)
The API layer lives in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/spec.py resources/account/spec.py]. Every spec builds on EMRResource (serialize / de_serialize, with the perform_extra_serialization / perform_extra_deserialization hooks). AccountSpec sets __exclude__ = ["patient"] and binds ___extension_resource_type__ = ExtensionResource.account.
| Spec class | Role | Fields exposed (beyond base) |
|---|---|---|
AccountSpec
|
shared base | id, status, billing_status, name, service_period (PeriodSpec), description
|
AccountCreateSpec
|
write · create | base + patient (UUID, required)
|
AccountUpdateSpec
|
write · update | base + primary_encounter (UUID, optional)
|
AccountMinimalReadSpec
|
read · minimal | base + total_gross, total_paid, total_balance, total_billable_charge_items (all Decimal 20/6), calculated_at, created_date, modified_date
|
AccountReadSpec
|
read · list | minimal + patient (serialized PatientListSpec), tags (rendered tag dicts)
|
AccountRetrieveSpec
|
read · detail | read + patient (serialized PatientRetrieveSpec), primary_encounter (serialized EncounterRetrieveSpec when set), cached_items (list), total_price_components (dict), extensions (dict)
|
Validation & server-side behaviour
- Create (
AccountCreateSpec): mixes inExtensionValidatorto validateextensions, and requirespatientas a UUID.perform_extra_deserializationresolves it viaget_object_or_404(Patient, external_id=...)and assignsobj.patient;patientis otherwise excluded from the base serialize loop. - Update (
AccountUpdateSpec): mixes inExtensionValidator;primary_encounteris optional. When supplied,perform_extra_deserializationresolves it viaget_object_or_404(Encounter, external_id=...)and assignsobj.primary_encounter. - Status / billing_status: the Pydantic enums
AccountStatusOptions/AccountBillingStatusOptionsreject any value outside their members. - Read serialization:
perform_extra_serializationsetsmapping["id"] = obj.external_id.AccountReadSpecserializes the patient (PatientListSpec) and renders tags viaSingleFacilityTagManager;AccountRetrieveSpecserializes the full patient (PatientRetrieveSpec, scoped toobj.facility) and, when present, the primary encounter (EncounterRetrieveSpec). - Totals are read-only: balance and aggregate fields appear only on read specs.
sync_account_itemsrecomputes them; client payloads never set them.
<a id="default-account"></a>
Default account
get_default_account(patient, facility) ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/default_account.py default_account.py]) returns the first account for that patient + facility with status = active and billing_status = open. If none exists, it creates one with:
status = active,billing_status = openservice_period = { "start": care_now() }(start only)name = "{patient.name} {YYYY-MM-DD}"
This runs when the first charge item is added, so the default account materializes on demand. Clients normally never create accounts directly.
<a id="balance-sync"></a>
Balance sync
sync_account_items(account) ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/account/sync_items.py sync_items.py]) recomputes the totals under an AccountLock:
total_billable_charge_items=Σ total_priceof charge items with statusbillabletotal_gross=Σ total_priceof charge items with statuspaidorbilledtotal_paid=Σ amountofactive+completenon-credit-note payment reconciliations − the same for credit notestotal_balance=total_gross − total_paidcalculated_at=care_now()
Every sum runs through care_round. total_net, cached_items, and total_price_components are not updated — their logic is commented out — so they hold their defaults. rebalance_account_task(account_id) is the Celery wrapper that runs the sync and saves.
Related models
Account is the only class in account.py. Its relationships:
facility → FK facility.Facility (PROTECT)
patient → FK emr.Patient (PROTECT)
primary_encounter → FK emr.Encounter (SET_NULL, nullable)
Charge items reference the account they belong to and drive its totals; invoices and payment reconciliations settle against the account or its items.
API integration notes
- The REST API aligns to the FHIR
Accountresource, but spec field names and enum values differ (enums use underscores, not hyphens). get_default_accountcreates the default account when the first charge item is added for a patient in a facility; clients normally do not create accounts directly.- On create, supply
patientas the patient'sexternal_id(UUID);facilityis server-scoped. - On update,
primary_encounteraccepts an encounterexternal_id(UUID); only the base fields plusprimary_encounterare mutable. - Balance and aggregate fields (
total_net,total_gross,total_paid,total_balance,total_billable_charge_items,total_price_components,cached_items,calculated_at) are platform-maintained and appear only on read specs — never set them from clients. service_periodrequires timezone-aware datetimes withstart <= end.extensionsis the supported place for custom key-value data without schema migrations;ExtensionValidatorchecks it, and it surfaces only on the retrieve spec.- Gate account data behind permissions that restrict access to users with rights to financial information.
Related
Pages that link here:
- Concept: Concepts/Patient
- Reference: References/Base models & conventions