Procurement Domain - Models Documentation
Source: api/nextango/apps/procurement/models.py
Last Updated: 2026-07-09
Table of Contents
Overview
The Procurement models define the supplier and purchase order management layer. All three models use SanityIntegratedModel as their base, which provides a sanity_id field for Sanity CMS synchronization.
| Model | DB Table | Purpose |
|---|---|---|
Supplier | supplier | Vendor/supplier directory |
PurchaseOrder | purchase_order | Purchase order header |
PurchaseOrderLineItem | purchase_order_line_item | Relational PO line items |
Supplier
File: models.py:16-89
DB table: supplier
Inherits: SanityIntegratedModel
Mirrors: studio/schemaTypes/documents/supplier.js
Fields
| Field | Type | Constraints | Description |
|---|---|---|---|
name | CharField(255) | required | Supplier display name |
supplier_code | CharField(20) | unique, indexed | Short internal reference code (e.g., SUP-001) |
tax_identification | CharField(50) | optional | Business tax ID number |
is_active | BooleanField | default=True, indexed | Inactive suppliers cannot receive new POs |
notes | TextField | optional | Internal notes |
address | JSONField | default={} | Address object (mirrors Sanity address object) |
contacts | JSONField | default=[] | Array of contact objects (mirrors supplierContact array) |
payment_terms | CharField(30) | choices, optional | See payment terms choices below |
account_number | CharField(100) | optional | Your account number with this supplier |
minimum_order_amount | DecimalField(10,2) | null/blank | Minimum PO value this supplier accepts |
lead_time_days | PositiveIntegerField | null/blank | Average days from order to delivery |
return_policy | TextField | optional | Supplier return policy |
supplied_product_types | JSONField | default=[] | Array of Sanity product type references |
supplied_products | JSONField | default=[] | Array of Sanity product references |
documents | JSONField | default=[] | Array of supplier document objects |
Payment Terms Choices
| Value | Display |
|---|---|
due_on_receipt | Due on Receipt |
net_15 | Net 15 |
net_30 | Net 30 |
net_60 | Net 60 |
net_90 | Net 90 |
prepaid | Prepaid |
Ordering
Default ordering: ['name'] (alphabetical)
__str__
Returns "<name> (<supplier_code>)" if supplier_code is set, otherwise just "<name>".
PurchaseOrder
File: models.py:92-154
DB table: purchase_order
Inherits: SanityIntegratedModel
Mirrors: studio/schemaTypes/documents/purchaseOrder.js
Fields
| Field | Type | Constraints | Description |
|---|---|---|---|
po_number | CharField(50) | unique, indexed | Purchase order number |
supplier | ForeignKey(Supplier) | PROTECT, related_name='purchase_orders' | Linked supplier |
expected_date | DateField | null/blank | Estimated delivery date |
status | CharField(35) | choices, default=Pending, indexed | PO lifecycle status |
items | JSONField | default=[] | Raw Sanity purchaseOrderItem array |
total_cost | DecimalField(12,2) | default=0.00 | Total PO cost. Kept in sync with the sum of line item costs automatically on every line-item save or delete (see signals.md) |
received_date | DateField | null/blank | Actual delivery date. Set automatically when the PO status transitions to Received; a manually-set value is overwritten when that transition fires. See signals.md |
compliance_verification | JSONField | default={} | Compliance verification data for this order (verifiedAt, verifiedBy, packageIds). A swappable slot populated by a compliance integration or a compliance manager |
notes | TextField | optional | Internal notes |
Status Choices
Status values are capitalized strings matching the Sanity schema exactly. The field does not enforce transition order.
| Value | Display |
|---|---|
Pending | Pending |
Submitted | Submitted |
Partially Received | Partially Received |
Awaiting Compliance Verification | Awaiting Compliance Verification |
Received | Received |
Cancelled | Cancelled |
Open statuses (for analytics): Pending, Submitted, Partially Received
Indexes
po_status_expected_idxon(status, expected_date): supports filtering open POs by expected delivery
Ordering
Default ordering: ['-created_at'] (newest first)
__str__
Returns "PO <po_number>".
PurchaseOrderLineItem
File: models.py:157-205
DB table: purchase_order_line_item
Inherits: SanityIntegratedModel
Relational line item providing Django-side queryability of PO items. Counterpart to the purchaseOrderItem Sanity object type stored in PurchaseOrder.items.
Fields
| Field | Type | Constraints | Description |
|---|---|---|---|
purchase_order | ForeignKey(PurchaseOrder) | CASCADE, related_name='line_items' | Parent PO |
sanity_key | CharField(255) | indexed, optional | Sanity _key within the PO items array |
inventory_level | ForeignKey(products.InventoryLevel) | PROTECT, null/blank, related_name='purchase_order_lines' | The inventory item being ordered |
inventory_level_sanity_ref | CharField(255) | optional | Raw Sanity _ref for unresolved inventory references |
quantity | PositiveIntegerField | default=1 | Units ordered |
unit_cost | DecimalField(10,2) | required | Cost per unit |
quantity_received | PositiveIntegerField | null, blank | Actual units received against this line. null indicates the line has not yet been received. Enables partial-receipt workflows and reconciliation against quantity |
Constraints
unique_together = [('purchase_order', 'sanity_key')]: prevents duplicate Sanity items per PO
__str__
Returns "<quantity>x <variant_name_or_sku> @ <unit_cost>". Falls back to inventory_level_sanity_ref or 'Unknown' if inventory is unresolved.
Model Relationships
Supplier
└─ purchase_orders (reverse FK) → PurchaseOrder (many)
└─ line_items (reverse FK) → PurchaseOrderLineItem (many)
└─ inventory_level → products.InventoryLevel
Cascade rules:
- Deleting a
Supplieris blocked (PROTECT) if it has purchase orders - Deleting a
PurchaseOrdercascades to itsPurchaseOrderLineItemrecords - Deleting an
InventoryLevelis blocked (PROTECT) if referenced by line items