Procurement Domain - Views Documentation
Source: api/nextango/apps/procurement/views.py
Routes registered in: api/nextango/urls.py
Last Updated: 2026-07-09
Table of Contents
Overview
The Procurement domain exposes two ModelViewSets. Routes are registered on the main application router (no app-level urls.py).
| Base URL | ViewSet | Description |
|---|---|---|
/suppliers/ | SupplierViewSet | Supplier CRUD + analytics |
/purchase-orders/ | PurchaseOrderViewSet | Purchase order CRUD + analytics |
Authentication inherits DEFAULT_PERMISSION_CLASSES (IsActiveUser in production, AllowAny in DEBUG mode).
SupplierViewSet
Source: views.py:17-59
Configuration
queryset = Supplier.objects.all()
serializer_class = SupplierSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['is_active', 'payment_terms']
search_fields = ['name', 'supplier_code']
ordering_fields = ['name', 'created_at']
ordering = ['name']
Endpoints
List Suppliers
GET /suppliers/
Returns all suppliers, alphabetical by name.
Query Parameters:
is_active=true/false: Active or inactive suppliers onlypayment_terms=<value>: Exact match (e.g.,net_30,net_60,prepaid)search=<term>: Search supplier name and supplier_codeordering=<field>:name,created_at(prefix-for descending)
Response (200 OK):
[
{
"id": "uuid",
"sanity_id": "supplier-sanity-id",
"name": "Acme Wholesale",
"supplier_code": "SUP-001",
"is_active": true,
"payment_terms": "net_30",
"lead_time_days": 7,
...
}
]
Create Supplier
POST /suppliers/
Request Body:
{
"name": "Acme Wholesale",
"supplier_code": "SUP-001",
"payment_terms": "net_30",
"lead_time_days": 7,
"minimum_order_amount": "500.00"
}
Retrieve / Update / Delete Supplier
- GET
/suppliers/{id}/ - PUT
/suppliers/{id}/ - PATCH
/suppliers/{id}/ - DELETE
/suppliers/{id}/
PurchaseOrderViewSet
Source: views.py:62-107
Configuration
queryset = PurchaseOrder.objects.select_related('supplier').all()
serializer_class = PurchaseOrderSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['status', 'supplier']
search_fields = ['po_number', 'supplier__name']
ordering_fields = ['expected_date', 'created_at', 'total_cost']
ordering = ['-created_at']
Note: select_related('supplier') avoids N+1 queries when serializing supplier_name / supplier_code.
Endpoints
List Purchase Orders
GET /purchase-orders/
Returns all purchase orders, newest first.
Query Parameters:
status=<value>: exact match againstPending,Submitted,Partially Received,Awaiting Compliance Verification,Received,Cancelledsupplier=<uuid>: Filter by Supplier UUIDsearch=<term>: Searchpo_numberand supplier nameordering=<field>:expected_date,created_at,total_cost
Response (200 OK):
[
{
"id": "uuid",
"sanity_id": "po-sanity-id",
"po_number": "PO-2026-001",
"supplier": "supplier-uuid",
"supplier_name": "Acme Wholesale",
"supplier_code": "SUP-001",
"expected_date": "2026-03-15",
"status": "Submitted",
"total_cost": "1250.00",
"line_items": [...],
...
}
]
Create Purchase Order
POST /purchase-orders/
Request Body:
{
"po_number": "PO-2026-001",
"supplier": "supplier-uuid",
"expected_date": "2026-03-15",
"status": "Pending",
"total_cost": "1250.00",
"notes": "Rush order"
}
Retrieve / Update / Delete PO
- GET
/purchase-orders/{id}/: includes nestedline_items - PUT
/purchase-orders/{id}/ - PATCH
/purchase-orders/{id}/ - DELETE
/purchase-orders/{id}/
Analytics Endpoints
Both analytics endpoints are implemented as @action methods on their ViewSets, but api/nextango/urls.py also binds each one to an explicit path() pattern placed before the router include, so the explicit binding resolves first.
Supplier Performance
GET /suppliers/analytics/performance/
Returns per-supplier order counts, total spend, received/active PO counts.
Query Parameters:
start_date=YYYY-MM-DD: Start of date rangeend_date=YYYY-MM-DD: End of date range
Response (200 OK):
{
"data": [
{
"supplier_name": "Acme Wholesale",
"supplier_code": "SUP-001",
"stated_lead_time": 7,
"total_orders": 12,
"total_value": "15000.00",
"received_count": 10,
"active_pos": 2
}
]
}
Results are cached for 300 seconds (5 minutes). Uses replica database when USE_REPLICA_DATABASE=True.
Procurement Summary
GET /purchase-orders/analytics/summary/
Returns aggregate PO counts, total value, status breakdown, and open PO totals.
Query Parameters:
start_date=YYYY-MM-DD: Start of date rangeend_date=YYYY-MM-DD: End of date range
Response (200 OK):
{
"total_purchase_orders": 42,
"total_po_value": "52500.00",
"by_status": [
{"status": "Pending", "count": 5, "value": "6250.00"},
{"status": "Received", "count": 30, "value": "37500.00"}
],
"open_po_count": 8,
"open_po_value": "10000.00"
}
Open statuses: Pending, Submitted, Partially Received.
Results are cached for 300 seconds (5 minutes). Uses replica database when USE_REPLICA_DATABASE=True.