← Knowledge library

Inventory

Inventory Item

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…

3.1 · versioned documentation · 5 min read

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 MANSOQ 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 — on_delete=PROTECT. The location holding the stock (central store, ward pharmacy, and so on).
product FK → Product yes — on_delete=PROTECT. The product being tracked.
status CharField(255) yes — The model field is an unconstrained string; the spec restricts it to InventoryItemStatusOptions (see below).
net_content DecimalField no Decimal(0) max_digits=20, decimal_places=6. On-hand quantity, computed by the server — see sync(). Goes negative when dispenses and outgoing deliveries outrun incoming stock.

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

InventoryItemStatusOptions values

Defined in 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.

Related models

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 Supply Delivery and 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 Product.
location nested dict FacilityLocationListSpec.serialize(obj.location).to_json() — carries parent, mode, has_children, system_availability_status, and optional current_encounter. See 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")

create_inventory_item(product, location)

Helper in create_inventory_item.py. Idempotent get-or-create:

sync_inventory_item()

Helper in 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)

API integration notes

Related

Source evidence

References are pinned to the private MANSOQ source repository. Repository access is required to open these links.