294 lines
13 KiB
Python
294 lines
13 KiB
Python
|
|
# shared/clients/production_client.py
|
||
|
|
"""
|
||
|
|
Production Service Client for Inter-Service Communication
|
||
|
|
Provides access to production planning and batch management from other services
|
||
|
|
"""
|
||
|
|
|
||
|
|
import structlog
|
||
|
|
from typing import Dict, Any, Optional, List
|
||
|
|
from uuid import UUID
|
||
|
|
from shared.clients.base_service_client import BaseServiceClient
|
||
|
|
from shared.config.base import BaseServiceSettings
|
||
|
|
|
||
|
|
logger = structlog.get_logger()
|
||
|
|
|
||
|
|
|
||
|
|
class ProductionServiceClient(BaseServiceClient):
|
||
|
|
"""Client for communicating with the Production Service"""
|
||
|
|
|
||
|
|
def __init__(self, config: BaseServiceSettings):
|
||
|
|
super().__init__("production", config)
|
||
|
|
|
||
|
|
def get_service_base_path(self) -> str:
|
||
|
|
return "/api/v1"
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# PRODUCTION PLANNING
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def get_production_requirements(self, tenant_id: str, date: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production requirements for procurement planning"""
|
||
|
|
try:
|
||
|
|
params = {}
|
||
|
|
if date:
|
||
|
|
params["date"] = date
|
||
|
|
|
||
|
|
result = await self.get("requirements", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production requirements from production service",
|
||
|
|
date=date, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production requirements",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_daily_requirements(self, tenant_id: str, date: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get daily production requirements"""
|
||
|
|
try:
|
||
|
|
params = {}
|
||
|
|
if date:
|
||
|
|
params["date"] = date
|
||
|
|
|
||
|
|
result = await self.get("daily-requirements", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved daily production requirements from production service",
|
||
|
|
date=date, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting daily production requirements",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_production_schedule(self, tenant_id: str, start_date: Optional[str] = None, end_date: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production schedule for a date range"""
|
||
|
|
try:
|
||
|
|
params = {}
|
||
|
|
if start_date:
|
||
|
|
params["start_date"] = start_date
|
||
|
|
if end_date:
|
||
|
|
params["end_date"] = end_date
|
||
|
|
|
||
|
|
result = await self.get("schedule", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production schedule from production service",
|
||
|
|
start_date=start_date, end_date=end_date, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production schedule",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# BATCH MANAGEMENT
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def get_active_batches(self, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
|
||
|
|
"""Get currently active production batches"""
|
||
|
|
try:
|
||
|
|
result = await self.get("batches/active", tenant_id=tenant_id)
|
||
|
|
batches = result.get('batches', []) if result else []
|
||
|
|
logger.info("Retrieved active production batches from production service",
|
||
|
|
batches_count=len(batches), tenant_id=tenant_id)
|
||
|
|
return batches
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting active production batches",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return []
|
||
|
|
|
||
|
|
async def create_production_batch(self, tenant_id: str, batch_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Create a new production batch"""
|
||
|
|
try:
|
||
|
|
result = await self.post("batches", data=batch_data, tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Created production batch",
|
||
|
|
batch_id=result.get('id'),
|
||
|
|
product_id=batch_data.get('product_id'),
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error creating production batch",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def update_batch_status(self, tenant_id: str, batch_id: str, status: str, actual_quantity: Optional[float] = None) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Update production batch status"""
|
||
|
|
try:
|
||
|
|
data = {"status": status}
|
||
|
|
if actual_quantity is not None:
|
||
|
|
data["actual_quantity"] = actual_quantity
|
||
|
|
|
||
|
|
result = await self.put(f"batches/{batch_id}/status", data=data, tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Updated production batch status",
|
||
|
|
batch_id=batch_id, status=status, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error updating production batch status",
|
||
|
|
error=str(e), batch_id=batch_id, tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_batch_details(self, tenant_id: str, batch_id: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get detailed information about a production batch"""
|
||
|
|
try:
|
||
|
|
result = await self.get(f"batches/{batch_id}", tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production batch details",
|
||
|
|
batch_id=batch_id, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production batch details",
|
||
|
|
error=str(e), batch_id=batch_id, tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# CAPACITY MANAGEMENT
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def get_capacity_status(self, tenant_id: str, date: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production capacity status for a specific date"""
|
||
|
|
try:
|
||
|
|
params = {}
|
||
|
|
if date:
|
||
|
|
params["date"] = date
|
||
|
|
|
||
|
|
result = await self.get("capacity/status", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production capacity status",
|
||
|
|
date=date, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production capacity status",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def check_capacity_availability(self, tenant_id: str, requirements: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Check if production capacity is available for requirements"""
|
||
|
|
try:
|
||
|
|
result = await self.post("capacity/check-availability",
|
||
|
|
{"requirements": requirements},
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Checked production capacity availability",
|
||
|
|
requirements_count=len(requirements), tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error checking production capacity availability",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# QUALITY CONTROL
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def record_quality_check(self, tenant_id: str, batch_id: str, quality_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Record quality control results for a batch"""
|
||
|
|
try:
|
||
|
|
result = await self.post(f"batches/{batch_id}/quality-check",
|
||
|
|
data=quality_data,
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Recorded quality check for production batch",
|
||
|
|
batch_id=batch_id, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error recording quality check",
|
||
|
|
error=str(e), batch_id=batch_id, tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_yield_metrics(self, tenant_id: str, start_date: str, end_date: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production yield metrics for analysis"""
|
||
|
|
try:
|
||
|
|
params = {
|
||
|
|
"start_date": start_date,
|
||
|
|
"end_date": end_date
|
||
|
|
}
|
||
|
|
result = await self.get("metrics/yield", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production yield metrics",
|
||
|
|
start_date=start_date, end_date=end_date, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production yield metrics",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# DASHBOARD AND ANALYTICS
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def get_dashboard_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production dashboard summary data"""
|
||
|
|
try:
|
||
|
|
result = await self.get("dashboard-summary", tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production dashboard summary",
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production dashboard summary",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_efficiency_metrics(self, tenant_id: str, period: str = "last_30_days") -> Optional[Dict[str, Any]]:
|
||
|
|
"""Get production efficiency metrics"""
|
||
|
|
try:
|
||
|
|
params = {"period": period}
|
||
|
|
result = await self.get("metrics/efficiency", tenant_id=tenant_id, params=params)
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved production efficiency metrics",
|
||
|
|
period=period, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production efficiency metrics",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# ALERTS AND NOTIFICATIONS
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def get_production_alerts(self, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
|
||
|
|
"""Get production-related alerts"""
|
||
|
|
try:
|
||
|
|
result = await self.get("alerts", tenant_id=tenant_id)
|
||
|
|
alerts = result.get('alerts', []) if result else []
|
||
|
|
logger.info("Retrieved production alerts",
|
||
|
|
alerts_count=len(alerts), tenant_id=tenant_id)
|
||
|
|
return alerts
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error getting production alerts",
|
||
|
|
error=str(e), tenant_id=tenant_id)
|
||
|
|
return []
|
||
|
|
|
||
|
|
async def acknowledge_alert(self, tenant_id: str, alert_id: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""Acknowledge a production-related alert"""
|
||
|
|
try:
|
||
|
|
result = await self.post(f"alerts/{alert_id}/acknowledge", data={}, tenant_id=tenant_id)
|
||
|
|
if result:
|
||
|
|
logger.info("Acknowledged production alert",
|
||
|
|
alert_id=alert_id, tenant_id=tenant_id)
|
||
|
|
return result
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error acknowledging production alert",
|
||
|
|
error=str(e), alert_id=alert_id, tenant_id=tenant_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# UTILITY METHODS
|
||
|
|
# ================================================================
|
||
|
|
|
||
|
|
async def health_check(self) -> bool:
|
||
|
|
"""Check if production 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("Production service health check failed", error=str(e))
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
# Factory function for dependency injection
|
||
|
|
def create_production_client(config: BaseServiceSettings) -> ProductionServiceClient:
|
||
|
|
"""Create production service client instance"""
|
||
|
|
return ProductionServiceClient(config)
|