Supply Delivery
A supply delivery records product moving into a destination location — either from another location or from an external supplier. Deliveries and dispensing together drive facility stock: supply delivered − supply dispensed = current stock. Each delivery belongs to a DeliveryOrder, the shipment grouping usually created from a supply request.
Source:
- Model:
care/emr/models/supply_delivery.py - Resource specs:
spec.py·delivery_order.py
The Django model is a thin store: status, delivery_type, and supplied_item_condition are plain CharFields, and extensions is an opaque JSONField. Enum values, validation, and the read/write schema split live in the Pydantic resource specs below.
Models
| Model | Purpose |
|---|---|
SupplyDelivery | A single line of delivered product (quantity, condition, source product/inventory) |
DeliveryOrder | Groups deliveries for a shipment between an origin and destination |
Both extend EMRBaseModel, which supplies external_id, the created_by/updated_by and created_date/modified_date audit fields, soft-delete via deleted, and history/meta JSON.
SupplyDelivery fields
Delivered item & quantity
| Field | Type | Notes |
|---|---|---|
supplied_item | FK → Product (CASCADE) | Nullable. The product delivered. Required at the API layer only when the order has no origin (external delivery); scoped to order.destination.facility on write |
supplied_inventory_item | FK → InventoryItem (CASCADE) | Nullable. Specific inventory item (batch/lot) drawn from. Required at the API layer when the order has an origin (intra-facility move); scoped to order.origin.facility |
supplied_item_quantity | DecimalField(max_digits=20, decimal_places=6) | Nullable in DB. On write a Decimal with decimal_places=0 (whole units); on read serialized as int. Auto-derived as pack_quantity × pack_size when both are given |
supplied_item_pack_quantity | IntegerField | Nullable; default None. Number of packs delivered |
supplied_item_pack_size | IntegerField | Nullable; default None. Units per pack |
supplied_item_condition | CharField(255) | Optional. Condition of the delivered item — enum SupplyDeliveryConditionOptions (see below) |
Status & classification
| Field | Type | Notes |
|---|---|---|
status | CharField(255) | Required. Lifecycle state — enum SupplyDeliveryStatusOptions (see below) |
delivery_type | CharField(255) | Enum SupplyDeliveryTypeOptions (product / device). Stored on the model but not exposed on the current Create/Update specs, so it can't be set through the API |
Links & pricing
| Field | Type | Notes |
|---|---|---|
supply_request | FK → SupplyRequest (CASCADE) | Nullable. Request this delivery fulfils. Set by external_id on write |
order | FK → DeliveryOrder (CASCADE) | Nullable in DB, but required on create (referenced by external_id). Optional on update |
total_purchase_price | DecimalField(max_digits=20, decimal_places=6) | Nullable. Total purchase cost. Spec: Decimal max_digits=20, decimal_places=6 |
extensions | JSONField | Default {}. Validated per registered extension handler for ExtensionResource.supply_delivery (see Resource specs) |
SupplyDelivery enum values
These are str enums in spec.py — the stored value equals the member value (snake_case), not a FHIR URI.
SupplyDeliveryStatusOptions (status)
| Value |
|---|
in_progress |
completed |
abandoned |
entered_in_error |
SupplyDeliveryConditionOptions (supplied_item_condition)
| Value |
|---|
normal |
damaged |
SupplyDeliveryTypeOptions (delivery_type)
| Value |
|---|
product |
device |
Related models
DeliveryOrder
A DeliveryOrder is one logical shipment. It groups its SupplyDelivery rows and moves stock between two facility locations, or from an external supplier into a facility.
supplier → FK Organization (CASCADE, nullable) -- external/source supplier
origin → FK FacilityLocation (CASCADE, nullable) -- related_name="origin_delivery_orders"
destination → FK FacilityLocation (CASCADE) -- related_name="destination_delivery_orders"
patient → FK Patient (PROTECT, nullable) -- patient the order is dispensed to
patient_invoice → FK Invoice (PROTECT, nullable) -- linked billing invoice
| Field | Type | Notes |
|---|---|---|
name | CharField(255) | Required. Human-readable order name |
status | CharField(255) | Required. Order lifecycle — enum SupplyDeliveryOrderStatusOptions (see below) |
note | TextField | Nullable. Free-text note |
tags | ArrayField[int] | Default []. Tag IDs; rendered via SingleFacilityTagManager on read |
supplier | FK → Organization (CASCADE) | Nullable. On write must be an Organization with org_type = product_supplier |
origin | FK → FacilityLocation (CASCADE) | Nullable. Source location; absence means stock is entering from an external supplier |
destination | FK → FacilityLocation (CASCADE) | Required. Receiving location |
patient | FK → Patient (PROTECT) | Nullable. Patient the order is dispensed to. Cannot be set together with origin |
patient_invoice | FK → Invoice (PROTECT) | Nullable. Linked billing invoice; serialized as patient_invoice_id on read |
extensions | JSONField | Default {}. Validated for ExtensionResource.supply_delivery_order |
patient and patient_invoice use PROTECT: a referenced patient or invoice can't be deleted while a delivery order points to it.
SupplyDeliveryOrderStatusOptions (status)
| Value | Notes |
|---|---|
draft | Allowed on create |
pending | Allowed on create |
in_progress | |
completed | Terminal (in SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES) |
abandoned | Terminal (in SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES) |
entered_in_error | Terminal (in SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES) |
Resource specs (API schema)
Every spec extends EMRResource (base.py): serialize builds the read payload from the DB object (perform_extra_serialization hook), de_serialize builds the DB object from the request (perform_extra_deserialization hook), and to_json() dumps the model excluding meta.
SupplyDelivery specs
| Spec | Role | Exposes / behaviour |
|---|---|---|
BaseSupplyDeliverySpec | shared | id, status (required), supplied_item_condition?, total_purchase_price?. __exclude__ = ["supplied_item", "supply_request", "supplied_inventory_item"] (these are resolved by hooks, not direct field copy) |
SupplyDeliveryWriteSpec | write · create | Adds supplied_item_pack_quantity?, supplied_item_pack_size?, supplied_item_quantity (Decimal, decimal_places=0), supplied_item?, supplied_inventory_item?, supply_request?, order (required), extensions. Mixes in ExtensionValidator |
SupplyDeliveryUpdateSpec | write · update | order? only (plus shared base fields + extensions). Mixes in ExtensionValidator |
SupplyDeliveryReadSpec | read · list | supplied_item_quantity: int, created_date, modified_date, supplied_item_pack_quantity?, supplied_item_pack_size?, extensions, and nested objects supplied_item, supplied_inventory_item, supply_request, order (serialized as dict). Mixes in ExtensionListRenderer |
SupplyDeliveryRetrieveSpec | read · detail | Extends SupplyDeliveryReadSpec; adds created_by/updated_by (UserSpec). Mixes in ExtensionRetrieveRenderer |
Write validation (SupplyDeliveryWriteSpec):
validate_quantity: when bothsupplied_item_pack_quantityandsupplied_item_pack_sizeare set,supplied_item_quantityis overwritten with their product.validate_supplied_item(resolvesorderfromexternal_id):- if
order.originis set →supplied_inventory_itemis required (intra-facility stock move); - if
order.originis not set →supplied_itemis required (external delivery); supplied_itemandsupplied_inventory_itemcannot both be provided.
- if
perform_extra_deserialization: resolvesorderbyexternal_id; resolvessupplied_itemscoped toorder.destination.facility; iforder.originexists, resolvessupplied_inventory_itemscoped toorder.origin.facility; resolvessupply_requestbyexternal_id.
Update (SupplyDeliveryUpdateSpec): perform_extra_deserialization re-resolves order from external_id only when order is provided.
Read serialization: perform_extra_serialization sets id = external_id and inlines related objects via their read specs — supplied_item → ProductReadSpec, supplied_inventory_item → InventoryItemReadSpec, supply_request → SupplyRequestReadSpec, order → SupplyDeliveryOrderReadSpec.
DeliveryOrder specs
| Spec | Role | Exposes / behaviour |
|---|---|---|
BaseSupplyDeliveryOrderSpec | shared | id, status (required), name (required), note?. Mixes in ExtensionValidator |
SupplyDeliveryOrderWriteSpec | write · create/update | Adds supplier?, origin?, destination (required), patient? (all by external_id) |
SupplyDeliveryOrderReadSpec | read · list | Nested origin?, destination, supplier?, patient? (dict), tags, patient_invoice_id?, created_date, modified_date, created_by?, updated_by?. Mixes in ExtensionListRenderer |
SupplyDeliveryOrderRetrieveSpec | read · detail | Extends SupplyDeliveryOrderReadSpec (no extra fields). Mixes in ExtensionRetrieveRenderer |
Write validation (SupplyDeliveryOrderWriteSpec.perform_extra_deserialization):
- resolves
destination(required),origin?,patient?byexternal_id; - resolves
supplier?constrained toorg_type = product_supplier; patientandorigincannot be provided together;- on create the
statusmust bedraftorpending.
Read serialization: id = external_id; inlines origin/destination via FacilityLocationListSpec, supplier via OrganizationReadSpec, patient via PatientListSpec; renders tags via SingleFacilityTagManager; exposes the linked invoice as patient_invoice_id (string); attaches audit users.
Extensions
extensions is not free-form JSON. On write, ExtensionValidator runs each registered handler for the resource type (supply_delivery / supply_delivery_order): unknown keys are dropped, and known keys are validated and re-serialized. On read, ExtensionListRenderer / ExtensionRetrieveRenderer render every registered extension for the resource (list vs retrieve variants).
API integration notes
- Coded fields carry the snake_case enum values above (e.g.
status = "in_progress"), not FHIR URIs. Validate against the enum, not free text. - A delivery is created against a
DeliveryOrder(orderis required on create). The order'sorigindecides whether you sendsupplied_inventory_item(intra-facility) orsupplied_item(external) — never both. - Send
supplied_item_pack_quantity+supplied_item_pack_sizeto havesupplied_item_quantitycomputed server-side; otherwise sendsupplied_item_quantitydirectly (whole units). - A
DeliveryOrdermust start indraftorpending;completed/abandoned/entered_in_errorare terminal. patientandoriginare mutually exclusive on an order (patient dispense vs location-to-location move).extensionsholds deployment-specific data, but only registered extension keys are persisted.
Related
- Reference: Supply Request
- Reference: Inventory Item
- Reference: Product
- Reference: Location
- Reference: Organization
- Reference: Patient
- Reference: Invoice
- Source: supply_delivery.py (model)
- Source: spec.py (SupplyDelivery specs)
- Source: delivery_order.py (DeliveryOrder specs)
- Source: base.py (EMRResource)