Procurement - Supplier & Purchase Order Management

Domain: Procurement Location: api/nextango/apps/procurement/ Last Updated: 2026-07-09

Overview

The Procurement app manages the full purchase order lifecycle for inventory replenishment. It provides a supplier/vendor directory and structured purchase order management with status tracking from draft through to receipt.

Procurement data is bidirectionally synced with Sanity CMS via webhook handlers. The Django models mirror the Sanity supplier.js and purchaseOrder.js schemas, and serializers handle camelCase ↔ snake_case transformation automatically.

Core Documentation

FileDescription
models.mdSupplier, PurchaseOrder, PurchaseOrderLineItem models
serializers.mdProcurement serializers and camelCase transformation
views.mdAll procurement API endpoints
examples.mdRequest/response examples

Key Concepts

Supplier Directory

  • Supplier records hold contact info, payment terms, account numbers, and lead times
  • is_active=False suppliers remain in history but cannot receive new purchase orders
  • supplier_code is a unique internal reference (e.g., SUP-001)

Purchase Order Lifecycle

status is a plain field with six choices; the model does not enforce transition order. The typical flow is:

Pending → Submitted → Partially Received → Awaiting Compliance Verification → Received
                   ↘ Cancelled

Status values match the Sanity schema exactly (capitalized strings).

Dual Storage of Line Items

POs store line items in two ways:

  1. PurchaseOrder.items (JSONField): raw Sanity purchaseOrderItem array for full Sanity structure preservation
  2. PurchaseOrderLineItem model: relational counterpart for Django-side querying and inventory link tracking

Sanity Integration

  • Supplier, PurchaseOrder, and PurchaseOrderLineItem extend SanityIntegratedModel (carries sanity_id field)
  • Serializers resolve Sanity _ref references to Django FK IDs at write time
  • PurchaseOrderSerializer.to_internal_value() resolves supplier._refSupplier.pk

API Endpoints

Suppliers

EndpointMethodDescription
/suppliers/GETList all suppliers
/suppliers/POSTCreate a supplier
/suppliers/{id}/GETRetrieve supplier
/suppliers/{id}/PUT / PATCHUpdate supplier
/suppliers/{id}/DELETEDelete supplier
/suppliers/analytics/performance/GETSupplier performance analytics

Purchase Orders

EndpointMethodDescription
/purchase-orders/GETList purchase orders
/purchase-orders/POSTCreate a purchase order
/purchase-orders/{id}/GETRetrieve PO with line items
/purchase-orders/{id}/PUT / PATCHUpdate PO
/purchase-orders/{id}/DELETEDelete PO
/purchase-orders/analytics/summary/GETProcurement summary analytics

Full endpoint documentation: views.md

Automated Reorder Monitoring

The check_reorder_levels Celery task runs daily at 6 AM (configured in Celery beat schedule) and scans all active InventoryLevel records where reorder_point > 0, stock_on_hand is at or below that reorder point, and the linked product variant has custom_inventory_management enabled. For each matching SKU, it emits a WARNING log containing SKU, store name, current quantity, reorder point, and suggested order quantity.

Deployment requirement: Celery beat must be running for the task to fire automatically. Manual execution is also possible via check_reorder_levels.delay().

Note: The task surfaces alerts for operations staff to act on: it does not automatically create purchase orders. PO creation remains a manual step after reviewing the reorder alerts.

See tasks.md for full task reference.

Architecture Highlights

SanityIntegratedModel Base

Supplier, PurchaseOrder, and PurchaseOrderLineItem all inherit SanityIntegratedModel, which provides:

  • sanity_id CharField: links to the Sanity document _id
  • created_at / updated_at timestamps

Analytics Service

ProcurementAnalyticsService (analytics_service.py) provides cached aggregation methods:

  • get_procurement_summary(): PO counts, total value, status breakdown, open PO totals. Exposed at /purchase-orders/analytics/summary/
  • get_supplier_performance(): per-supplier order counts, total spend, received/active counts. Exposed at /suppliers/analytics/performance/
  • get_supplier_lead_time_performance(): per-supplier on-time receipt rate and lead time variance against Supplier.lead_time_days. Not currently bound to a view or URL

All three methods support optional start_date / end_date filtering and use the replica database when USE_REPLICA_DATABASE=True.

  • Products: InventoryLevel FK on PurchaseOrderLineItem, updated when a PO is received
  • Integrations: Sanity webhook adapter syncs supplier/PO records
  • Transactions: Transaction payments may drive procurement replenishment decisions

Was this page helpful?