Procurement - Analytics Service

Location: api/nextango/apps/procurement/analytics_service.py Last Updated: 2026-07-09

Overview

Provides aggregated procurement and supplier metrics for the analytics dashboard. The service reads from the replica database (when USE_REPLICA_DATABASE=True) and caches results with a 5-minute TTL to reduce load on reporting queries. Two of the three methods are called by ViewSet actions bound at /purchase-orders/analytics/summary/ and /suppliers/analytics/performance/.


Class: ProcurementAnalyticsService

Aggregation methods for procurement and supplier analytics. All methods are @staticmethod and accept optional date range parameters.


Method Reference

get_procurement_summary()

Purpose: Returns headline KPIs for all purchase orders in the system, optionally filtered to a date range.

Parameters:

ParamTypeRequiredDescription
start_datedateNoFilter POs created on or after this date
end_datedateNoFilter POs created on or before this date

Returns:

{
    'total_purchase_orders': int,       # Total PO count in period
    'total_po_value': Decimal,          # Sum of total_cost across all POs
    'by_status': [                      # Per-status breakdown
        {
            'status': str,              # e.g. 'Pending', 'Received'
            'count': int,
            'value': Decimal,
        },
        ...
    ],
    'open_po_count': int,               # POs in Pending, Submitted, Partially Received
    'open_po_value': Decimal,           # Sum of total_cost for open POs
}

Example:

from nextango.apps.procurement.analytics_service import ProcurementAnalyticsService
from datetime import date

result = ProcurementAnalyticsService.get_procurement_summary(
    start_date=date(2026, 1, 1),
    end_date=date(2026, 1, 31),
)
# result['total_purchase_orders'] -> 42
# result['by_status'] -> [{'status': 'Received', 'count': 30, 'value': Decimal('15000.00')}, ...]

get_supplier_performance()

Purpose: Returns a ranked list of suppliers by total PO value, with order counts and on-time receipt metrics.

Parameters:

ParamTypeRequiredDescription
start_datedateNoFilter POs created on or after this date
end_datedateNoFilter POs created on or before this date

Returns:

{
    'data': [
        {
            'supplier_name': str,       # Supplier.name
            'supplier_code': str,       # Supplier.supplier_code
            'stated_lead_time': int,    # Supplier.lead_time_days
            'total_orders': int,        # PO count for this supplier
            'total_value': Decimal,     # Sum of PO total_cost
            'received_count': int,      # POs with status='Received'
            'active_pos': int,          # POs in open statuses
        },
        ...
    ]
}

Rows are ordered by total_value descending (highest-spend supplier first).

Example:

from nextango.apps.procurement.analytics_service import ProcurementAnalyticsService

result = ProcurementAnalyticsService.get_supplier_performance()
for supplier in result['data']:
    print(f"{supplier['supplier_name']}: £{supplier['total_value']}")

get_supplier_lead_time_performance()

Purpose: Returns per-supplier on-time receipt rate and lead time variance against the supplier's stated lead_time_days. Not currently bound to a view or URL; call it directly from Python.

Parameters:

ParamTypeRequiredDescription
start_datedateNoFilter POs created on or after this date
end_datedateNoFilter POs created on or before this date

Only purchase orders with status='Received' and a non-null received_date are included.

Returns:

{
    'data': [
        {
            'supplier_name': str,               # Supplier.name
            'supplier_code': str,               # Supplier.supplier_code
            'stated_lead_time': int,            # Supplier.lead_time_days
            'received_count': int,              # Received POs for this supplier in range
            'on_time_count': int,               # Received POs with received_date <= expected_date
            'avg_actual_lead_days': float,      # Average days from created_at to received_date
            'lead_time_variance_days': float,   # avg_actual_lead_days minus stated_lead_time
            'on_time_rate_pct': float,          # on_time_count / received_count * 100
        },
        ...
    ]
}

Rows are ordered by received_count descending.


Caching

All three methods cache their results for 300 seconds (5 minutes), scoped per method and parameter set. Results refresh automatically when the window expires.


  • Views: Analytics endpoints that call this service
  • Models: Supplier, PurchaseOrder, PurchaseOrderLineItem
  • Signals: update_inventory_on_receiving updates inventory when POs are received

Was this page helpful?