Files
bakery-ia/shared/clients/suppliers_client.py

439 lines
19 KiB
Python
Raw Normal View History

2025-08-21 20:28:14 +02:00
# shared/clients/suppliers_client.py
"""
Suppliers Service Client for Inter-Service Communication
Provides access to supplier data and performance metrics from other services
"""
import structlog
from typing import Dict, Any, Optional, List
from shared.clients.base_service_client import BaseServiceClient
from shared.config.base import BaseServiceSettings
logger = structlog.get_logger()
class SuppliersServiceClient(BaseServiceClient):
"""Client for communicating with the Suppliers Service"""
2025-11-05 13:34:56 +01:00
def __init__(self, config: BaseServiceSettings, calling_service_name: str = "unknown"):
super().__init__(calling_service_name, config)
2025-08-21 20:28:14 +02:00
def get_service_base_path(self) -> str:
return "/api/v1"
# ================================================================
# SUPPLIER MANAGEMENT
# ================================================================
async def get_supplier_by_id(self, tenant_id: str, supplier_id: str) -> Optional[Dict[str, Any]]:
"""Get supplier details by ID"""
try:
2025-10-30 21:08:07 +01:00
result = await self.get(f"suppliers/{supplier_id}", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier details from suppliers service",
supplier_id=supplier_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier details",
error=str(e), supplier_id=supplier_id, tenant_id=tenant_id)
return None
async def get_all_suppliers(self, tenant_id: str, is_active: Optional[bool] = True) -> Optional[List[Dict[str, Any]]]:
"""Get all suppliers for a tenant"""
try:
params = {}
if is_active is not None:
params["is_active"] = is_active
2025-11-05 13:34:56 +01:00
result = await self.get_paginated("suppliers", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
logger.info("Retrieved all suppliers from suppliers service",
2025-11-05 13:34:56 +01:00
suppliers_count=len(result) if result else 0, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
return result
except Exception as e:
logger.error("Error getting all suppliers",
error=str(e), tenant_id=tenant_id)
return []
async def search_suppliers(self, tenant_id: str, search: Optional[str] = None, category: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
"""Search suppliers with filters"""
try:
params = {}
if search:
2025-11-05 13:34:56 +01:00
params["search_term"] = search
2025-08-21 20:28:14 +02:00
if category:
2025-11-05 13:34:56 +01:00
params["supplier_type"] = category
2025-08-21 20:28:14 +02:00
2025-11-05 13:34:56 +01:00
result = await self.get("suppliers", tenant_id=tenant_id, params=params)
suppliers = result if result else []
2025-08-21 20:28:14 +02:00
logger.info("Searched suppliers from suppliers service",
search_term=search, suppliers_count=len(suppliers), tenant_id=tenant_id)
return suppliers
except Exception as e:
logger.error("Error searching suppliers",
error=str(e), tenant_id=tenant_id)
return []
# ================================================================
# SUPPLIER RECOMMENDATIONS
# ================================================================
async def get_supplier_recommendations(self, tenant_id: str, ingredient_id: str) -> Optional[Dict[str, Any]]:
"""Get supplier recommendations for procurement"""
try:
params = {"ingredient_id": ingredient_id}
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/recommendations", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier recommendations from suppliers service",
ingredient_id=ingredient_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier recommendations",
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
return None
async def get_best_supplier_for_ingredient(self, tenant_id: str, ingredient_id: str, criteria: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
"""Get best supplier for a specific ingredient based on criteria"""
try:
data = {
"ingredient_id": ingredient_id,
"criteria": criteria or {}
}
2025-10-06 15:27:01 +02:00
result = await self.post("suppliers/operations/find-best-supplier", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved best supplier from suppliers service",
ingredient_id=ingredient_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting best supplier for ingredient",
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
return None
# ================================================================
# PURCHASE ORDER MANAGEMENT
# ================================================================
async def create_purchase_order(self, tenant_id: str, order_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new purchase order"""
try:
2025-10-06 15:27:01 +02:00
result = await self.post("suppliers/purchase-orders", data=order_data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Created purchase order",
order_id=result.get('id'),
supplier_id=order_data.get('supplier_id'),
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error creating purchase order",
error=str(e), tenant_id=tenant_id)
return None
async def get_purchase_orders(self, tenant_id: str, status: Optional[str] = None, supplier_id: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
"""Get purchase orders with optional filtering"""
try:
params = {}
if status:
params["status"] = status
if supplier_id:
params["supplier_id"] = supplier_id
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/purchase-orders", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
orders = result.get('orders', []) if result else []
logger.info("Retrieved purchase orders from suppliers service",
orders_count=len(orders), tenant_id=tenant_id)
return orders
except Exception as e:
logger.error("Error getting purchase orders",
error=str(e), tenant_id=tenant_id)
return []
async def update_purchase_order_status(self, tenant_id: str, order_id: str, status: str) -> Optional[Dict[str, Any]]:
"""Update purchase order status"""
try:
data = {"status": status}
2025-10-06 15:27:01 +02:00
result = await self.put(f"suppliers/purchase-orders/{order_id}/status", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
2025-10-21 19:50:07 +02:00
logger.info("Updated purchase order status",
2025-08-21 20:28:14 +02:00
order_id=order_id, status=status, tenant_id=tenant_id)
return result
except Exception as e:
2025-10-21 19:50:07 +02:00
logger.error("Error updating purchase order status",
2025-08-21 20:28:14 +02:00
error=str(e), order_id=order_id, tenant_id=tenant_id)
return None
2025-10-21 19:50:07 +02:00
async def approve_purchase_order(
self,
tenant_id: str,
po_id: str,
approval_data: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
"""
Auto-approve a purchase order
Args:
tenant_id: Tenant ID
po_id: Purchase Order ID
approval_data: Approval data including:
- approved_by: User ID or "system" for auto-approval
- approval_notes: Notes about the approval
- auto_approved: Boolean flag indicating auto-approval
- approval_reasons: List of reasons for auto-approval
Returns:
Updated purchase order data or None
"""
try:
# Format the approval request payload
payload = {
"action": "approve",
"notes": approval_data.get("approval_notes", "Auto-approved by system")
}
result = await self.post(
f"suppliers/purchase-orders/{po_id}/approve",
data=payload,
tenant_id=tenant_id
)
if result:
logger.info("Auto-approved purchase order",
po_id=po_id,
tenant_id=tenant_id,
auto_approved=approval_data.get("auto_approved", True))
return result
except Exception as e:
logger.error("Error auto-approving purchase order",
error=str(e),
po_id=po_id,
tenant_id=tenant_id)
return None
async def get_supplier(self, tenant_id: str, supplier_id: str) -> Optional[Dict[str, Any]]:
"""
Get supplier details with performance metrics
Args:
tenant_id: Tenant ID
supplier_id: Supplier ID
Returns:
Supplier data including performance metrics or None
"""
try:
# Use the existing get_supplier_by_id method which returns full supplier data
result = await self.get_supplier_by_id(tenant_id, supplier_id)
if result:
logger.info("Retrieved supplier data for auto-approval",
supplier_id=supplier_id,
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier data",
error=str(e),
supplier_id=supplier_id,
tenant_id=tenant_id)
return None
2025-08-21 20:28:14 +02:00
# ================================================================
# DELIVERY MANAGEMENT
# ================================================================
async def get_deliveries(self, tenant_id: str, status: Optional[str] = None, date: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
"""Get deliveries with optional filtering"""
try:
params = {}
if status:
params["status"] = status
if date:
params["date"] = date
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/deliveries", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
deliveries = result.get('deliveries', []) if result else []
logger.info("Retrieved deliveries from suppliers service",
deliveries_count=len(deliveries), tenant_id=tenant_id)
return deliveries
except Exception as e:
logger.error("Error getting deliveries",
error=str(e), tenant_id=tenant_id)
return []
async def update_delivery_status(self, tenant_id: str, delivery_id: str, status: str, notes: Optional[str] = None) -> Optional[Dict[str, Any]]:
"""Update delivery status"""
try:
data = {"status": status}
if notes:
data["notes"] = notes
2025-10-06 15:27:01 +02:00
result = await self.put(f"suppliers/deliveries/{delivery_id}/status", data=data, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Updated delivery status",
delivery_id=delivery_id, status=status, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error updating delivery status",
error=str(e), delivery_id=delivery_id, tenant_id=tenant_id)
return None
async def get_supplier_order_summaries(self, tenant_id: str) -> Optional[Dict[str, Any]]:
"""Get supplier order summaries for central bakery dashboard"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/dashboard/order-summaries", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier order summaries from suppliers service",
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier order summaries",
error=str(e), tenant_id=tenant_id)
return None
# ================================================================
# PERFORMANCE TRACKING
# ================================================================
async def get_supplier_performance(self, tenant_id: str, supplier_id: str, period: str = "last_30_days") -> Optional[Dict[str, Any]]:
"""Get supplier performance metrics"""
try:
params = {"period": period}
2025-10-06 15:27:01 +02:00
result = await self.get(f"suppliers/analytics/performance/{supplier_id}", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier performance from suppliers service",
supplier_id=supplier_id, period=period, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier performance",
error=str(e), supplier_id=supplier_id, tenant_id=tenant_id)
return None
async def get_performance_alerts(self, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
"""Get supplier performance alerts"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/alerts/performance", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
alerts = result.get('alerts', []) if result else []
logger.info("Retrieved supplier performance alerts",
alerts_count=len(alerts), tenant_id=tenant_id)
return alerts
except Exception as e:
logger.error("Error getting supplier performance alerts",
error=str(e), tenant_id=tenant_id)
return []
async def record_supplier_rating(self, tenant_id: str, supplier_id: str, rating_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Record a rating/review for a supplier"""
try:
result = await self.post(f"suppliers/{supplier_id}/rating", data=rating_data, tenant_id=tenant_id)
if result:
logger.info("Recorded supplier rating",
supplier_id=supplier_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error recording supplier rating",
error=str(e), supplier_id=supplier_id, tenant_id=tenant_id)
return None
# ================================================================
# DASHBOARD AND ANALYTICS
# ================================================================
async def get_dashboard_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
"""Get suppliers dashboard summary data"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/dashboard/summary", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved suppliers dashboard summary",
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting suppliers dashboard summary",
error=str(e), tenant_id=tenant_id)
return None
async def get_cost_analysis(self, tenant_id: str, start_date: str, end_date: str) -> Optional[Dict[str, Any]]:
"""Get cost analysis across suppliers"""
try:
params = {
"start_date": start_date,
"end_date": end_date
}
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/analytics/cost-analysis", tenant_id=tenant_id, params=params)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier cost analysis",
start_date=start_date, end_date=end_date, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier cost analysis",
error=str(e), tenant_id=tenant_id)
return None
async def get_supplier_reliability_metrics(self, tenant_id: str) -> Optional[Dict[str, Any]]:
"""Get supplier reliability and quality metrics"""
try:
2025-10-06 15:27:01 +02:00
result = await self.get("suppliers/analytics/reliability-metrics", tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Retrieved supplier reliability metrics",
tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error getting supplier reliability metrics",
error=str(e), tenant_id=tenant_id)
return None
# ================================================================
# ALERTS AND NOTIFICATIONS
# ================================================================
2025-08-21 20:28:14 +02:00
async def acknowledge_alert(self, tenant_id: str, alert_id: str) -> Optional[Dict[str, Any]]:
"""Acknowledge a supplier-related alert"""
try:
2025-10-06 15:27:01 +02:00
result = await self.post(f"suppliers/alerts/{alert_id}/acknowledge", data={}, tenant_id=tenant_id)
2025-08-21 20:28:14 +02:00
if result:
logger.info("Acknowledged supplier alert",
2025-08-21 20:28:14 +02:00
alert_id=alert_id, tenant_id=tenant_id)
return result
except Exception as e:
logger.error("Error acknowledging supplier alert",
2025-08-21 20:28:14 +02:00
error=str(e), alert_id=alert_id, tenant_id=tenant_id)
return None
# ================================================================
# COUNT AND STATISTICS
# ================================================================
async def count_suppliers(self, tenant_id: str) -> int:
"""
Get the count of suppliers for a tenant
Used for subscription limit tracking
Returns:
int: Number of suppliers for the tenant
"""
try:
result = await self.get("suppliers/count", tenant_id=tenant_id)
count = result.get('count', 0) if result else 0
logger.info("Retrieved supplier count from suppliers service",
count=count, tenant_id=tenant_id)
return count
except Exception as e:
logger.error("Error getting supplier count",
error=str(e), tenant_id=tenant_id)
return 0
2025-08-21 20:28:14 +02:00
# ================================================================
# UTILITY METHODS
# ================================================================
2025-08-21 20:28:14 +02:00
async def health_check(self) -> bool:
"""Check if suppliers service is healthy"""
try:
result = await self.get("../health") # Health endpoint is not tenant-scoped
return result is not None
except Exception as e:
logger.error("Suppliers service health check failed", error=str(e))
return False
# Factory function for dependency injection
def create_suppliers_client(config: BaseServiceSettings) -> SuppliersServiceClient:
"""Create suppliers service client instance"""
2025-10-30 21:08:07 +01:00
return SuppliersServiceClient(config)