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 URLViewSetDescription
/suppliers/SupplierViewSetSupplier CRUD + analytics
/purchase-orders/PurchaseOrderViewSetPurchase 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 only
  • payment_terms=<value>: Exact match (e.g., net_30, net_60, prepaid)
  • search=<term>: Search supplier name and supplier_code
  • ordering=<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 against Pending, Submitted, Partially Received, Awaiting Compliance Verification, Received, Cancelled
  • supplier=<uuid>: Filter by Supplier UUID
  • search=<term>: Search po_number and supplier name
  • ordering=<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 nested line_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 range
  • end_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 range
  • end_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.

Was this page helpful?