From 413f652bbc3abb91664f7f49bd5b75446103d70a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Nov 2025 06:56:30 +0000 Subject: [PATCH] fix: Align dashboard API calls with actual procurement and production service endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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! ✅ --- services/orchestrator/app/api/dashboard.py | 26 +++++++++------------- shared/clients/procurement_client.py | 4 ++-- shared/clients/production_client.py | 15 ++++++++----- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/services/orchestrator/app/api/dashboard.py b/services/orchestrator/app/api/dashboard.py index 1a49d895..a7e6a7c4 100644 --- a/services/orchestrator/app/api/dashboard.py +++ b/services/orchestrator/app/api/dashboard.py @@ -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: diff --git a/shared/clients/procurement_client.py b/shared/clients/procurement_client.py index 40459ef0..f7bc4150 100644 --- a/shared/clients/procurement_client.py +++ b/shared/clients/procurement_client.py @@ -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( diff --git a/shared/clients/production_client.py b/shared/clients/production_client.py index b2bd289b..05748579 100644 --- a/shared/clients/production_client.py +++ b/shared/clients/production_client.py @@ -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),