feat: Add dedicated dashboard methods to service clients
Created typed, domain-specific methods in service clients instead of using generic .get() calls with paths. This improves type safety, discoverability, and maintainability. Service Client Changes: - ProcurementServiceClient: * get_pending_purchase_orders() - POs awaiting approval * get_critical_alerts() - Critical severity alerts * get_alerts_summary() - Alert counts by severity - ProductionServiceClient: * get_todays_batches() - Today's production timeline * get_production_batches_by_status() - Filter by status - InventoryServiceClient: * get_stock_status() - Dashboard stock metrics * get_sustainability_widget() - Sustainability data Dashboard API Changes: - Updated all endpoints to use new dedicated methods - Cleaner, more maintainable code - Better error handling and logging - Fixed inventory data type handling (list vs dict) Note: Alert endpoints return 404 - alert_processor service needs endpoints: /alerts/summary and /alerts (filtered by severity).
This commit is contained in:
@@ -189,12 +189,9 @@ async def get_bakery_health_status(
|
|||||||
# In a real implementation, these would be fetched from respective services
|
# In a real implementation, these would be fetched from respective services
|
||||||
# For now, we'll make HTTP calls to the services
|
# For now, we'll make HTTP calls to the services
|
||||||
|
|
||||||
# Get alerts - using base client for alert service
|
# Get alerts summary
|
||||||
try:
|
try:
|
||||||
alerts_data = await procurement_client.get(
|
alerts_data = await procurement_client.get_alerts_summary(tenant_id) or {}
|
||||||
"/procurement/alert-processor/alerts/summary",
|
|
||||||
tenant_id=tenant_id
|
|
||||||
) or {}
|
|
||||||
critical_alerts = alerts_data.get("critical_count", 0)
|
critical_alerts = alerts_data.get("critical_count", 0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch alerts: {e}")
|
logger.warning(f"Failed to fetch alerts: {e}")
|
||||||
@@ -202,11 +199,7 @@ async def get_bakery_health_status(
|
|||||||
|
|
||||||
# Get pending PO count
|
# Get pending PO count
|
||||||
try:
|
try:
|
||||||
po_data = await procurement_client.get(
|
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=100) or {}
|
||||||
"/procurement/purchase-orders",
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
params={"status": "pending_approval", "limit": 100}
|
|
||||||
) or {}
|
|
||||||
pending_approvals = len(po_data.get("items", []))
|
pending_approvals = len(po_data.get("items", []))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch POs: {e}")
|
logger.warning(f"Failed to fetch POs: {e}")
|
||||||
@@ -214,10 +207,8 @@ async def get_bakery_health_status(
|
|||||||
|
|
||||||
# Get production delays
|
# Get production delays
|
||||||
try:
|
try:
|
||||||
prod_data = await production_client.get(
|
prod_data = await production_client.get_production_batches_by_status(
|
||||||
"/production/production-batches",
|
tenant_id, status="ON_HOLD", limit=100
|
||||||
tenant_id=tenant_id,
|
|
||||||
params={"status": "ON_HOLD", "limit": 100}
|
|
||||||
) or {}
|
) or {}
|
||||||
production_delays = len(prod_data.get("items", []))
|
production_delays = len(prod_data.get("items", []))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -276,11 +267,7 @@ async def get_orchestration_summary(
|
|||||||
# Enhance with detailed PO and batch summaries
|
# Enhance with detailed PO and batch summaries
|
||||||
if summary["purchaseOrdersCreated"] > 0:
|
if summary["purchaseOrdersCreated"] > 0:
|
||||||
try:
|
try:
|
||||||
po_data = await procurement_client.get(
|
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=10)
|
||||||
"/procurement/purchase-orders",
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
params={"status": "pending_approval", "limit": 10}
|
|
||||||
)
|
|
||||||
if po_data:
|
if po_data:
|
||||||
pos = po_data.get("items", [])
|
pos = po_data.get("items", [])
|
||||||
summary["purchaseOrdersSummary"] = [
|
summary["purchaseOrdersSummary"] = [
|
||||||
@@ -296,10 +283,7 @@ async def get_orchestration_summary(
|
|||||||
|
|
||||||
if summary["productionBatchesCreated"] > 0:
|
if summary["productionBatchesCreated"] > 0:
|
||||||
try:
|
try:
|
||||||
batch_data = await production_client.get(
|
batch_data = await production_client.get_todays_batches(tenant_id)
|
||||||
"/production/production-batches/today",
|
|
||||||
tenant_id=tenant_id
|
|
||||||
)
|
|
||||||
if batch_data:
|
if batch_data:
|
||||||
batches = batch_data.get("batches", [])
|
batches = batch_data.get("batches", [])
|
||||||
summary["productionBatchesSummary"] = [
|
summary["productionBatchesSummary"] = [
|
||||||
@@ -338,11 +322,7 @@ async def get_action_queue(
|
|||||||
# Get pending POs
|
# Get pending POs
|
||||||
pending_pos = []
|
pending_pos = []
|
||||||
try:
|
try:
|
||||||
po_data = await procurement_client.get(
|
po_data = await procurement_client.get_pending_purchase_orders(tenant_id, limit=20)
|
||||||
"/procurement/purchase-orders",
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
params={"status": "pending_approval", "limit": 20}
|
|
||||||
)
|
|
||||||
if po_data:
|
if po_data:
|
||||||
pending_pos = po_data.get("items", [])
|
pending_pos = po_data.get("items", [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -351,11 +331,7 @@ async def get_action_queue(
|
|||||||
# Get critical alerts
|
# Get critical alerts
|
||||||
critical_alerts = []
|
critical_alerts = []
|
||||||
try:
|
try:
|
||||||
alerts_data = await procurement_client.get(
|
alerts_data = await procurement_client.get_critical_alerts(tenant_id, limit=20)
|
||||||
"/procurement/alert-processor/alerts",
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
params={"severity": "critical", "resolved": False, "limit": 20}
|
|
||||||
)
|
|
||||||
if alerts_data:
|
if alerts_data:
|
||||||
critical_alerts = alerts_data.get("alerts", [])
|
critical_alerts = alerts_data.get("alerts", [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -651,6 +651,54 @@ class InventoryServiceClient(BaseServiceClient):
|
|||||||
error=str(e), tenant_id=tenant_id)
|
error=str(e), tenant_id=tenant_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# DASHBOARD METHODS
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
async def get_stock_status(
|
||||||
|
self,
|
||||||
|
tenant_id: str
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get inventory stock status for dashboard insights
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with stock counts and status metrics
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/inventory/dashboard/stock-status",
|
||||||
|
tenant_id=tenant_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching stock status", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_sustainability_widget(
|
||||||
|
self,
|
||||||
|
tenant_id: str
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get sustainability metrics for dashboard
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with sustainability metrics (waste, CO2, etc.)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/inventory/sustainability/widget",
|
||||||
|
tenant_id=tenant_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching sustainability widget", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# UTILITY METHODS
|
# UTILITY METHODS
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|||||||
@@ -573,3 +573,79 @@ class ProcurementServiceClient(BaseServiceClient):
|
|||||||
logger.error("Error triggering price forecasting",
|
logger.error("Error triggering price forecasting",
|
||||||
error=str(e), tenant_id=tenant_id)
|
error=str(e), tenant_id=tenant_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# DASHBOARD METHODS
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
async def get_pending_purchase_orders(
|
||||||
|
self,
|
||||||
|
tenant_id: str,
|
||||||
|
limit: int = 20
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get purchase orders pending approval for dashboard
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
limit: Maximum number of POs to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with {"items": [...], "total": n}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/procurement/purchase-orders",
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
params={"status": "pending_approval", "limit": limit}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching pending purchase orders", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_critical_alerts(
|
||||||
|
self,
|
||||||
|
tenant_id: str,
|
||||||
|
limit: int = 20
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get critical alerts for dashboard
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
limit: Maximum number of alerts to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with {"alerts": [...], "total": n}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/procurement/alert-processor/alerts",
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
params={"severity": "critical", "resolved": False, "limit": limit}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching critical alerts", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_alerts_summary(
|
||||||
|
self,
|
||||||
|
tenant_id: str
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get alerts summary for dashboard health status
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with counts by severity
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/procurement/alert-processor/alerts/summary",
|
||||||
|
tenant_id=tenant_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching alerts summary", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|||||||
@@ -445,6 +445,60 @@ class ProductionServiceClient(BaseServiceClient):
|
|||||||
error=str(e), tenant_id=tenant_id)
|
error=str(e), tenant_id=tenant_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# DASHBOARD METHODS
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
async def get_todays_batches(
|
||||||
|
self,
|
||||||
|
tenant_id: str
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get today's production batches for dashboard timeline
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with {"batches": [...], "summary": {...}}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/production/production-batches/today",
|
||||||
|
tenant_id=tenant_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching today's batches", error=str(e), tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_production_batches_by_status(
|
||||||
|
self,
|
||||||
|
tenant_id: str,
|
||||||
|
status: str,
|
||||||
|
limit: int = 100
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get production batches filtered by status for dashboard
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tenant_id: Tenant ID
|
||||||
|
status: Batch status (e.g., "ON_HOLD", "IN_PROGRESS")
|
||||||
|
limit: Maximum number of batches to return
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with {"items": [...], "total": n}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(
|
||||||
|
"/production/production-batches",
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
params={"status": status, "limit": limit}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error fetching production batches", error=str(e),
|
||||||
|
status=status, tenant_id=tenant_id)
|
||||||
|
return None
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# UTILITY METHODS
|
# UTILITY METHODS
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user