← Knowledge library

Inventory

Product

A Product is a concrete batch of stock at a facility — a single lot of a medication, nutritional product, or consumable, with its own expiry, lot number, and purchase price. It instantiates a catalogue entry: everything generic about the item…

3.1 · versioned documentation · 6 min read

A Product is a concrete batch of stock at a facility — a single lot of a medication, nutritional product, or consumable, with its own expiry, lot number, and purchase price. It instantiates a catalogue entry: everything generic about the item (name, codes, dosage form, product_type) lives on the linked ProductKnowledge, and a Product adds only what's true of one batch.

The Django model is storage; the shape you send and receive is defined by the Pydantic resource specs, which set the enums, narrow the model's opaque JSONFields, enforce validation, and split read from write. The fields below note where the spec diverges from the column; Resource specs (API schema) covers the full read/write split.

Source:

Models

Model Purpose
Product A concrete, batch-level instantiation of a ProductKnowledge definition at a facility (medication, nutritional product, or consumable)

Product extends EMRBaseModel, which supplies external_id, audit fields, and soft-delete semantics.

Product fields

Relationships

All three foreign keys are PROTECT, so a referenced row can't be deleted while a product points at it.

Field Type Notes
facility FK → facility.Facility (PROTECT) Required at storage, but not a spec field — the viewset sets it from the URL, never the request body.
product_knowledge FK → emr.ProductKnowledge (PROTECT) The catalogue definition this batch instantiates. Write takes a slug string and resolves it; read returns the nested ProductKnowledge object.
charge_item_definition FK → emr.ChargeItemDefinition (PROTECT, nullable) Drives charge-item creation when the product is billed. Write takes a slug string; read returns the nested ChargeItemDefinition object.

Classification & status

Field Type Required Notes
status CharField(255) yes (spec) The spec restricts it to ProductStatusOptions: active / inactive / entered_in_error.
product_type CharField(255) — A storage column the Product spec does not expose. The authoritative product_type lives on the linked ProductKnowledge (ProductTypeOptions: medication / nutritional_product / consumable).

Batch & pricing

Field Type Required Default Notes
batch JSONField (nullable) no dict / None The column holds an opaque dict; the spec narrows it to ProductBatch, a single optional { lot_number: str | None }.
expiration_date DateTimeField (nullable) no None Batch expiry. Spec type datetime | None.
standard_pack_size IntegerField (nullable) no None Units per standard pack. Spec type int | None.
purchase_price DecimalField(max_digits=20, decimal_places=6) (nullable) no None Acquisition cost for this batch. Spec type Decimal | None, held to max_digits=20, decimal_places=6.
extensions JSONField yes (spec) dict Open extension bag. On write it runs through ExtensionValidator against the schemas registered for ExtensionResource.product — unknown keys are dropped, invalid data raises.

Enums

ProductStatusOptions values

Defined in spec.py and bound to the model status field.

Value Meaning
active Product is in use
inactive Product is no longer in use but retained
entered_in_error Record created in error

ProductTypeOptions values

Not a field on the Product spec. It's defined in product_knowledge/spec.py and carried on the linked ProductKnowledge; it appears here only because the model keeps a product_type storage column.

Value
medication
nutritional_product
consumable

Nested JSON shapes

ProductBatch shape

Structure of the batch JSONField (care/emr/resources/inventory/product/spec.py).

Field Type Required Default Notes
lot_number str | None no None The only field in the batch shape; omit it and batch stays None.

Resource specs (API schema)

Every spec derives from EMRResource (care/emr/resources/base.py), which supplies serialize (DB → pydantic, via the perform_extra_serialization hook) and de_serialize (pydantic → DB, via the perform_extra_deserialization hook).

Spec class Role Fields beyond the base Behaviour
BaseProductSpec shared id, status, batch, expiration_date, extensions, standard_pack_size, purchase_price __model__ = Product. __exclude__ = ["product_knowledge", "charge_item_definition"] — these FKs are resolved by the hooks, not by automatic field mapping. ___extension_resource_type__ = ExtensionResource.product.
ProductWriteSpec write · create adds product_knowledge: str (slug), charge_item_definition: str | None (slug) Mixes in ExtensionValidator. perform_extra_deserialization resolves product_knowledge via get_object_or_404(ProductKnowledge, slug=...) and, when present, charge_item_definition via get_object_or_404(ChargeItemDefinition, slug=...).
ProductUpdateSpec write · update adds charge_item_definition: str | None (slug) Mixes in ExtensionValidator. perform_extra_deserialization resolves charge_item_definition via ChargeItemDefinition.objects.get(slug=...) when supplied. product_knowledge is immutable after create, so it is not re-bound.
ProductReadSpec read · list & detail adds product_knowledge: dict, charge_item_definition: dict | None perform_extra_serialization sets id = external_id and inlines product_knowledge (via ProductKnowledgeReadSpec) and, when set, charge_item_definition (via ChargeItemDefinitionReadSpec). One spec serves both list and detail.

Consequences for an integrator:

Methods & save behaviour

Product adds no methods of its own. Persistence, external_id, audit fields, and soft delete all come from EMRBaseModel.

API integration notes

Related

Source evidence

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