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-12-05 20:07:01 +01: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-12-05 20:07:01 +01:00
|
|
|
logger.info("Searched suppliers from suppliers service",
|
2025-08-21 20:28:14 +02:00
|
|
|
search_term=search, suppliers_count=len(suppliers), tenant_id=tenant_id)
|
|
|
|
|
return suppliers
|
|
|
|
|
except Exception as e:
|
2025-12-05 20:07:01 +01:00
|
|
|
logger.error("Error searching suppliers",
|
2025-08-21 20:28:14 +02:00
|
|
|
error=str(e), tenant_id=tenant_id)
|
|
|
|
|
return []
|
2025-12-05 20:07:01 +01:00
|
|
|
|
|
|
|
|
async def get_suppliers_batch(self, tenant_id: str, supplier_ids: List[str]) -> Optional[List[Dict[str, Any]]]:
|
|
|
|
|
"""
|
|
|
|
|
Get multiple suppliers in a single request for performance optimization.
|
|
|
|
|
|
|
|
|
|
This method eliminates N+1 query patterns when fetching supplier data
|
|
|
|
|
for multiple purchase orders or other entities.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
tenant_id: Tenant ID
|
|
|
|
|
supplier_ids: List of supplier IDs to fetch
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of supplier dictionaries or empty list if error
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
if not supplier_ids:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# Join IDs as comma-separated string
|
|
|
|
|
ids_param = ",".join(supplier_ids)
|
|
|
|
|
params = {"ids": ids_param}
|
|
|
|
|
|
|
|
|
|
result = await self.get("suppliers/batch", tenant_id=tenant_id, params=params)
|
|
|
|
|
suppliers = result if result else []
|
|
|
|
|
|
|
|
|
|
logger.info("Batch retrieved suppliers from suppliers service",
|
|
|
|
|
requested_count=len(supplier_ids),
|
|
|
|
|
found_count=len(suppliers),
|
|
|
|
|
tenant_id=tenant_id)
|
|
|
|
|
return suppliers
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Error batch retrieving suppliers",
|
|
|
|
|
error=str(e),
|
|
|
|
|
requested_count=len(supplier_ids),
|
|
|
|
|
tenant_id=tenant_id)
|
|
|
|
|
return []
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# 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
|
2025-10-21 19:50:07 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# 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-10-23 07:44:54 +02:00
|
|
|
|
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:
|
2025-10-23 07:44:54 +02:00
|
|
|
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:
|
2025-10-23 07:44:54 +02:00
|
|
|
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
|
2025-10-23 07:44:54 +02:00
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# 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-10-23 07:44:54 +02:00
|
|
|
|
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)
|