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

References/Inventory Item

From OHC Network Wiki
Revision as of 04:15, 5 July 2026 by Admin (talk | contribs) (OHC identity seed)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
referencesupplyCARE 3.0+

InventoryItem records how much of one Product is on hand at one facility location. Deliveries move product in, dispenses move it out, and the server recomputes the balance into net_content. You never create these rows — the server does, and the only field a client can write is status, such as marking a line inactive when stock is damaged or pulled from circulation.

Source:

The Django model is storage. The Pydantic resource specs (care/emr/resources/inventory/inventory_item/) define the status enum and the read schema, inlining the product and location objects that the model holds only as foreign keys.

Models

Model Purpose
InventoryItem Stock of one product at one location, with its current net quantity

InventoryItem extends EMRBaseModel, the shared Care EMR base providing external_id, audit fields, and soft-delete semantics. See Base model.

InventoryItem fields

Field Type Required Default Notes
location FK → FacilityLocation yes
product FK → Product yes
status CharField(255) yes
net_content DecimalField no

The (location, product) pair is unique — one inventory item per product per location — enforced in save() (see Methods & save behaviour).

InventoryItemStatusOptions values

Defined in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/spec.py spec.py]. The spec accepts only these three values; the model field itself stays an open CharField:

Value Meaning
active Live and dispensable. Set when the server auto-creates a row.
inactive No longer dispensed — damaged stock, or another hold.
entered_in_error Record created in error.

Two foreign keys anchor the row; two transactional sources set its quantity:

location → FK FacilityLocation (PROTECT)
product  → FK Product (PROTECT)

drives net_content (read in sync_inventory_item):
  SupplyDelivery       (incoming completed, outgoing in-progress/completed)
  MedicationDispense   (dispensed-out, excluding cancelled statuses)

Both foreign keys are on_delete=PROTECT: a location or product can't be hard-deleted while an inventory row references it. The records that move net_content live in References/Supply Delivery and References/Medication Dispense.

Resource specs (API schema)

All specs extend EMRResource (care/emr/resources/base.py), which supplies serialize / de_serialize and the perform_extra_serialization hook that inlines the nested product and location objects.

Spec class Role Fields exposed Notes
BaseInventoryItemSpec shared id, status __model__ = InventoryItem, __exclude__ = []. status typed as InventoryItemStatusOptions.
InventoryItemWriteSpec write id, status Inherits BaseInventoryItemSpec unchanged. Only status is client-writable; product, location, and net_content are server-maintained.
InventoryItemReadSpec read · list id, status, net_content, product, location See serialization below.
InventoryItemRetrieveSpec read · detail same as InventoryItemReadSpec A pass subclass — identical shape to the list spec.

Read serialization (InventoryItemReadSpec.perform_extra_serialization)

Field Serialized shape Source
id UUID4 obj.external_id — the public id, not the internal pk.
net_content Decimal (max_digits=20, decimal_places=0 on the read field) model net_content.
product nested dict ProductReadSpec.serialize(obj.product).to_json() — carries nested product_knowledge and optional charge_item_definition. See References/Product.
location nested dict FacilityLocationListSpec.serialize(obj.location).to_json() — carries parent, mode, has_children, system_availability_status, and optional current_encounter. See References/Location.

status (typed InventoryItemStatusOptions) carries through from BaseInventoryItemSpec. There is no CreateSpec; rows are never created through a client write path.

Methods & save behaviour

save()

On creation only (when self.id is unset), save() enforces the (location, product) uniqueness:

if creating and InventoryItem with same (location, product) exists:
    raise ValueError("Inventory item already exists")
  • The check runs only for new rows; updates to an existing item skip it.
  • A violation raises a plain ValueError, not a database IntegrityError, so callers creating items through the ORM must handle it.

create_inventory_item(product, location)

Helper in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/create_inventory_item.py create_inventory_item.py]. Idempotent get-or-create:

  • Returns the existing (product, location) row if one exists.
  • Otherwise creates one with status=active, net_content=0, and saves it.

sync_inventory_item()

Helper in [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/inventory/inventory_item/sync_inventory_item.py sync_inventory_item.py], the source of truth for net_content. Holding InventoryLock(product, location), it recomputes the balance from the transactional records and writes it back:

net_content =
    Σ incoming completed deliveries  (SupplyDelivery.status == completed,
                                      order.destination == location,
                                      supplied_inventory_item.product == product)
  - Σ outgoing in-progress + completed deliveries
                                     (SupplyDelivery.order.origin is not null,
                                      supplied_inventory_item == this item,
                                      status in {in_progress, completed})
  - Σ dispenses                      (MedicationDispense.item == this item,
                                      excluding cancelled statuses)
  • If no row exists for (product, location), one is auto-created (status=active, net_content=0) before computing.
  • net_content is the aggregate sum of supplied_item_quantity / dispense quantity, and can be negative.
  • Runs whenever a delivery completes or a dispense changes, so the balance stays current.

API integration notes

  • Rows are server-maintained: they come from create_inventory_item / sync_inventory_item, never a direct client create. The only meaningful client write is status (for example, setting inactive).
  • One row exists per (location, product) pair. net_content is adjusted on that row, not by inserting duplicates.
  • Don't patch net_content directly — sync_inventory_item recomputes it under an InventoryLock, and the next sync overwrites your value.
  • Read responses inline the full product (with product_knowledge) and location objects, not just their ids.
  • location and product are PROTECT foreign keys, so referenced products and locations can't be hard-deleted while stock rows exist.

Pages that link here: