fix: Align dashboard API calls with actual procurement and production service endpoints
CRITICAL FIX: Dashboard was calling non-existent API endpoints
The Problem:
------------
The orchestrator dashboard service was calling API endpoints that don't exist:
1. Procurement: Expected dict {items: [...]} but API returns array [...]
2. Production: Called /production/production-batches/today - doesn't exist
3. Production: Called /production/production-batches - doesn't exist
Root Cause:
-----------
Created client methods without verifying actual backend API structure.
Made assumptions about response formats that didn't match reality.
The Fix:
--------
**1. Procurement Client (shared/clients/procurement_client.py)**
- Fixed get_pending_purchase_orders return type: Dict → List
- Procurement API returns: List[PurchaseOrderResponse] directly
- Changed: "Dict with {items: [...], total: n}" → "List of purchase order dicts"
**2. Production Client (shared/clients/production_client.py)**
- Fixed get_todays_batches endpoint:
OLD: "/production/production-batches/today" (doesn't exist)
NEW: "/production/batches?start_date=today&end_date=today"
- Fixed get_production_batches_by_status endpoint:
OLD: "/production/production-batches?status=X"
NEW: "/production/batches?status=X"
- Updated return type docs: {"items": [...]} → {"batches": [...], "total_count": n}
- Response structure: ProductionBatchListResponse (batches, total_count, page, page_size)
**3. Orchestrator Dashboard API (services/orchestrator/app/api/dashboard.py)**
- Fixed all po_data access patterns:
OLD: po_data.get("items", [])
NEW: direct list access or po_data if isinstance(po_data, list)
- Fixed production batch access:
OLD: prod_data.get("items", [])
NEW: prod_data.get("batches", [])
- Updated 6 locations:
* Line 206: health-status pending POs count
* Line 216: health-status production delays count
* Line 274-281: orchestration-summary PO summaries
* Line 328-329: action-queue pending POs
* Line 472-487: insights deliveries calculation
* Line 499-519: insights savings calculation
Verified Against:
-----------------
Frontend successfully calls these exact APIs:
- /tenants/{id}/procurement/purchase-orders (ProcurementPage.tsx)
- /tenants/{id}/production/batches (production hooks)
Both return arrays/objects as documented in their respective API files:
- services/procurement/app/api/purchase_orders.py: returns List[PurchaseOrderResponse]
- services/production/app/api/production_batches.py: returns ProductionBatchListResponse
Now dashboard calls match actual backend APIs! ✅
This commit is contained in:
@@ -202,8 +202,8 @@ async def get_bakery_health_status(
|
||||
|
||||
# Get pending PO count
|
||||
try:
|
||||
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=100) or {}
|
||||
pending_approvals = len(po_data.get("items", []))
|
||||
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=100) or []
|
||||
pending_approvals = len(po_data) if isinstance(po_data, list) else 0
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch POs: {e}")
|
||||
pending_approvals = 0
|
||||
@@ -213,7 +213,7 @@ async def get_bakery_health_status(
|
||||
prod_data = await production_client.get_production_batches_by_status(
|
||||
tenant_id, status="ON_HOLD", limit=100
|
||||
) or {}
|
||||
production_delays = len(prod_data.get("items", []))
|
||||
production_delays = len(prod_data.get("batches", []))
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch production batches: {e}")
|
||||
production_delays = 0
|
||||
@@ -271,15 +271,14 @@ async def get_orchestration_summary(
|
||||
if summary["purchaseOrdersCreated"] > 0:
|
||||
try:
|
||||
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=10)
|
||||
if po_data:
|
||||
pos = po_data.get("items", [])
|
||||
if po_data and isinstance(po_data, list):
|
||||
summary["purchaseOrdersSummary"] = [
|
||||
PurchaseOrderSummary(
|
||||
supplierName=po.get("supplier_name", "Unknown"),
|
||||
itemCategories=[item.get("ingredient_name", "Item") for item in po.get("items", [])[:3]],
|
||||
totalAmount=float(po.get("total_amount", 0))
|
||||
)
|
||||
for po in pos[:5] # Show top 5
|
||||
for po in po_data[:5] # Show top 5
|
||||
]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch PO details: {e}")
|
||||
@@ -326,8 +325,8 @@ async def get_action_queue(
|
||||
pending_pos = []
|
||||
try:
|
||||
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=20)
|
||||
if po_data:
|
||||
pending_pos = po_data.get("items", [])
|
||||
if po_data and isinstance(po_data, list):
|
||||
pending_pos = po_data
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch pending POs: {e}")
|
||||
|
||||
@@ -470,15 +469,14 @@ async def get_insights(
|
||||
try:
|
||||
# Get recent POs with pending deliveries
|
||||
pos_result = await procurement_client.get_pending_purchase_orders(tenant_id, limit=100)
|
||||
if pos_result and isinstance(pos_result, dict):
|
||||
pos = pos_result.get("items", [])
|
||||
if pos_result and isinstance(pos_result, list):
|
||||
# Count deliveries expected today
|
||||
from datetime import datetime, timezone
|
||||
today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
today_end = today_start.replace(hour=23, minute=59, second=59)
|
||||
|
||||
deliveries_today = 0
|
||||
for po in pos:
|
||||
for po in pos_result:
|
||||
expected_date = po.get("expected_delivery_date")
|
||||
if expected_date:
|
||||
if isinstance(expected_date, str):
|
||||
@@ -498,12 +496,10 @@ async def get_insights(
|
||||
seven_days_ago = datetime.now(timezone.utc) - timedelta(days=7)
|
||||
|
||||
pos_result = await procurement_client.get_pending_purchase_orders(tenant_id, limit=200)
|
||||
if pos_result and isinstance(pos_result, dict):
|
||||
pos = pos_result.get("items", [])
|
||||
|
||||
if pos_result and isinstance(pos_result, list):
|
||||
weekly_savings = 0
|
||||
# Calculate savings from price optimization
|
||||
for po in pos:
|
||||
for po in pos_result:
|
||||
# Check if PO was created in last 7 days
|
||||
created_at = po.get("created_at")
|
||||
if created_at:
|
||||
|
||||
@@ -582,7 +582,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
self,
|
||||
tenant_id: str,
|
||||
limit: int = 20
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
) -> Optional[List[Dict[str, Any]]]:
|
||||
"""
|
||||
Get purchase orders pending approval for dashboard
|
||||
|
||||
@@ -591,7 +591,7 @@ class ProcurementServiceClient(BaseServiceClient):
|
||||
limit: Maximum number of POs to return
|
||||
|
||||
Returns:
|
||||
Dict with {"items": [...], "total": n}
|
||||
List of purchase order dicts (API returns array directly)
|
||||
"""
|
||||
try:
|
||||
return await self.get(
|
||||
|
||||
@@ -460,12 +460,15 @@ class ProductionServiceClient(BaseServiceClient):
|
||||
tenant_id: Tenant ID
|
||||
|
||||
Returns:
|
||||
Dict with {"batches": [...], "summary": {...}}
|
||||
Dict with ProductionBatchListResponse: {"batches": [...], "total_count": n, "page": 1, "page_size": n}
|
||||
"""
|
||||
try:
|
||||
from datetime import date
|
||||
today = date.today()
|
||||
return await self.get(
|
||||
"/production/production-batches/today",
|
||||
tenant_id=tenant_id
|
||||
"/production/batches",
|
||||
tenant_id=tenant_id,
|
||||
params={"start_date": today.isoformat(), "end_date": today.isoformat(), "page_size": 100}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error fetching today's batches", error=str(e), tenant_id=tenant_id)
|
||||
@@ -486,13 +489,13 @@ class ProductionServiceClient(BaseServiceClient):
|
||||
limit: Maximum number of batches to return
|
||||
|
||||
Returns:
|
||||
Dict with {"items": [...], "total": n}
|
||||
Dict with ProductionBatchListResponse: {"batches": [...], "total_count": n, "page": 1, "page_size": n}
|
||||
"""
|
||||
try:
|
||||
return await self.get(
|
||||
"/production/production-batches",
|
||||
"/production/batches",
|
||||
tenant_id=tenant_id,
|
||||
params={"status": status, "limit": limit}
|
||||
params={"status": status, "page_size": limit}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error fetching production batches", error=str(e),
|
||||
|
||||
Reference in New Issue
Block a user