Skip to main content
Version: 3.1

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:

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

ModelPurpose
SupplyDeliveryA single line of delivered product (quantity, condition, source product/inventory)
DeliveryOrderGroups 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

FieldTypeNotes
supplied_itemFK → 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_itemFK → 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_quantityDecimalField(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_quantityIntegerFieldNullable; default None. Number of packs delivered
supplied_item_pack_sizeIntegerFieldNullable; default None. Units per pack
supplied_item_conditionCharField(255)Optional. Condition of the delivered item — enum SupplyDeliveryConditionOptions (see below)

Status & classification

FieldTypeNotes
statusCharField(255)Required. Lifecycle state — enum SupplyDeliveryStatusOptions (see below)
delivery_typeCharField(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
FieldTypeNotes
supply_requestFK → SupplyRequest (CASCADE)Nullable. Request this delivery fulfils. Set by external_id on write
orderFK → DeliveryOrder (CASCADE)Nullable in DB, but required on create (referenced by external_id). Optional on update
total_purchase_priceDecimalField(max_digits=20, decimal_places=6)Nullable. Total purchase cost. Spec: Decimal max_digits=20, decimal_places=6
extensionsJSONFieldDefault {}. 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

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
FieldTypeNotes
nameCharField(255)Required. Human-readable order name
statusCharField(255)Required. Order lifecycle — enum SupplyDeliveryOrderStatusOptions (see below)
noteTextFieldNullable. Free-text note
tagsArrayField[int]Default []. Tag IDs; rendered via SingleFacilityTagManager on read
supplierFK → Organization (CASCADE)Nullable. On write must be an Organization with org_type = product_supplier
originFK → FacilityLocation (CASCADE)Nullable. Source location; absence means stock is entering from an external supplier
destinationFK → FacilityLocation (CASCADE)Required. Receiving location
patientFK → Patient (PROTECT)Nullable. Patient the order is dispensed to. Cannot be set together with origin
patient_invoiceFK → Invoice (PROTECT)Nullable. Linked billing invoice; serialized as patient_invoice_id on read
extensionsJSONFieldDefault {}. 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)

ValueNotes
draftAllowed on create
pendingAllowed on create
in_progress
completedTerminal (in SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES)
abandonedTerminal (in SUPPLY_DELIVERY_ORDER_COMPLETED_STATUSES)
entered_in_errorTerminal (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

SpecRoleExposes / behaviour
BaseSupplyDeliverySpecsharedid, 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)
SupplyDeliveryWriteSpecwrite · createAdds 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
SupplyDeliveryUpdateSpecwrite · updateorder? only (plus shared base fields + extensions). Mixes in ExtensionValidator
SupplyDeliveryReadSpecread · listsupplied_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
SupplyDeliveryRetrieveSpecread · detailExtends SupplyDeliveryReadSpec; adds created_by/updated_by (UserSpec). Mixes in ExtensionRetrieveRenderer

Write validation (SupplyDeliveryWriteSpec):

  • validate_quantity: when both supplied_item_pack_quantity and supplied_item_pack_size are set, supplied_item_quantity is overwritten with their product.
  • validate_supplied_item (resolves order from external_id):
    • if order.origin is set → supplied_inventory_item is required (intra-facility stock move);
    • if order.origin is not set → supplied_item is required (external delivery);
    • supplied_item and supplied_inventory_item cannot both be provided.
  • perform_extra_deserialization: resolves order by external_id; resolves supplied_item scoped to order.destination.facility; if order.origin exists, resolves supplied_inventory_item scoped to order.origin.facility; resolves supply_request by external_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_itemProductReadSpec, supplied_inventory_itemInventoryItemReadSpec, supply_requestSupplyRequestReadSpec, orderSupplyDeliveryOrderReadSpec.

DeliveryOrder specs

SpecRoleExposes / behaviour
BaseSupplyDeliveryOrderSpecsharedid, status (required), name (required), note?. Mixes in ExtensionValidator
SupplyDeliveryOrderWriteSpecwrite · create/updateAdds supplier?, origin?, destination (required), patient? (all by external_id)
SupplyDeliveryOrderReadSpecread · listNested origin?, destination, supplier?, patient? (dict), tags, patient_invoice_id?, created_date, modified_date, created_by?, updated_by?. Mixes in ExtensionListRenderer
SupplyDeliveryOrderRetrieveSpecread · detailExtends SupplyDeliveryOrderReadSpec (no extra fields). Mixes in ExtensionRetrieveRenderer

Write validation (SupplyDeliveryOrderWriteSpec.perform_extra_deserialization):

  • resolves destination (required), origin?, patient? by external_id;
  • resolves supplier? constrained to org_type = product_supplier;
  • patient and origin cannot be provided together;
  • on create the status must be draft or pending.

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 (order is required on create). The order's origin decides whether you send supplied_inventory_item (intra-facility) or supplied_item (external) — never both.
  • Send supplied_item_pack_quantity + supplied_item_pack_size to have supplied_item_quantity computed server-side; otherwise send supplied_item_quantity directly (whole units).
  • A DeliveryOrder must start in draft or pending; completed/abandoned/entered_in_error are terminal.
  • patient and origin are mutually exclusive on an order (patient dispense vs location-to-location move).
  • extensions holds deployment-specific data, but only registered extension keys are persisted.