Fix new services implementation 1
This commit is contained in:
347
shared/clients/inventory_client.py
Normal file
347
shared/clients/inventory_client.py
Normal file
@@ -0,0 +1,347 @@
|
||||
# shared/clients/inventory_client.py
|
||||
"""
|
||||
Inventory Service Client - Inter-service communication
|
||||
Handles communication with the inventory service for all other services
|
||||
"""
|
||||
|
||||
import structlog
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from shared.clients.base_service_client import BaseServiceClient
|
||||
from shared.config.base import BaseServiceSettings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class InventoryServiceClient(BaseServiceClient):
|
||||
"""Client for communicating with the inventory service via gateway"""
|
||||
|
||||
def __init__(self, config: BaseServiceSettings):
|
||||
super().__init__("inventory", config)
|
||||
|
||||
def get_service_base_path(self) -> str:
|
||||
"""Return the base path for inventory service APIs"""
|
||||
return "/api/v1"
|
||||
|
||||
# ================================================================
|
||||
# INGREDIENT MANAGEMENT
|
||||
# ================================================================
|
||||
|
||||
async def get_ingredient_by_id(self, ingredient_id: UUID, tenant_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get ingredient details by ID"""
|
||||
try:
|
||||
result = await self.get(f"ingredients/{ingredient_id}", tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Retrieved ingredient from inventory service",
|
||||
ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error fetching ingredient by ID",
|
||||
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def search_ingredients(
|
||||
self,
|
||||
tenant_id: str,
|
||||
search: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Search ingredients with filters"""
|
||||
try:
|
||||
params = {
|
||||
"skip": skip,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
if search:
|
||||
params["search"] = search
|
||||
if category:
|
||||
params["category"] = category
|
||||
if is_active is not None:
|
||||
params["is_active"] = is_active
|
||||
|
||||
result = await self.get("ingredients", tenant_id=tenant_id, params=params)
|
||||
ingredients = result if isinstance(result, list) else []
|
||||
|
||||
logger.info("Searched ingredients in inventory service",
|
||||
search_term=search, count=len(ingredients), tenant_id=tenant_id)
|
||||
return ingredients
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error searching ingredients",
|
||||
error=str(e), search=search, tenant_id=tenant_id)
|
||||
return []
|
||||
|
||||
async def get_all_ingredients(self, tenant_id: str, is_active: Optional[bool] = True) -> List[Dict[str, Any]]:
|
||||
"""Get all ingredients for a tenant (paginated)"""
|
||||
try:
|
||||
params = {}
|
||||
if is_active is not None:
|
||||
params["is_active"] = is_active
|
||||
|
||||
ingredients = await self.get_paginated("ingredients", tenant_id=tenant_id, params=params)
|
||||
|
||||
logger.info("Retrieved all ingredients from inventory service",
|
||||
count=len(ingredients), tenant_id=tenant_id)
|
||||
return ingredients
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error fetching all ingredients",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return []
|
||||
|
||||
async def create_ingredient(self, ingredient_data: Dict[str, Any], tenant_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Create a new ingredient"""
|
||||
try:
|
||||
result = await self.post("ingredients", data=ingredient_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Created ingredient in inventory service",
|
||||
ingredient_name=ingredient_data.get('name'), tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error creating ingredient",
|
||||
error=str(e), ingredient_data=ingredient_data, tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def update_ingredient(
|
||||
self,
|
||||
ingredient_id: UUID,
|
||||
ingredient_data: Dict[str, Any],
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Update an existing ingredient"""
|
||||
try:
|
||||
result = await self.put(f"ingredients/{ingredient_id}", data=ingredient_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Updated ingredient in inventory service",
|
||||
ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error updating ingredient",
|
||||
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def delete_ingredient(self, ingredient_id: UUID, tenant_id: str) -> bool:
|
||||
"""Delete (deactivate) an ingredient"""
|
||||
try:
|
||||
result = await self.delete(f"ingredients/{ingredient_id}", tenant_id=tenant_id)
|
||||
success = result is not None
|
||||
if success:
|
||||
logger.info("Deleted ingredient in inventory service",
|
||||
ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.error("Error deleting ingredient",
|
||||
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return False
|
||||
|
||||
async def get_ingredient_stock(
|
||||
self,
|
||||
ingredient_id: UUID,
|
||||
tenant_id: str,
|
||||
include_unavailable: bool = False
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Get stock entries for an ingredient"""
|
||||
try:
|
||||
params = {}
|
||||
if include_unavailable:
|
||||
params["include_unavailable"] = include_unavailable
|
||||
|
||||
result = await self.get(f"ingredients/{ingredient_id}/stock", tenant_id=tenant_id, params=params)
|
||||
stock_entries = result if isinstance(result, list) else []
|
||||
|
||||
logger.info("Retrieved ingredient stock from inventory service",
|
||||
ingredient_id=ingredient_id, stock_count=len(stock_entries), tenant_id=tenant_id)
|
||||
return stock_entries
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error fetching ingredient stock",
|
||||
error=str(e), ingredient_id=ingredient_id, tenant_id=tenant_id)
|
||||
return []
|
||||
|
||||
# ================================================================
|
||||
# STOCK MANAGEMENT
|
||||
# ================================================================
|
||||
|
||||
async def get_stock_levels(self, tenant_id: str, ingredient_ids: Optional[List[UUID]] = None) -> List[Dict[str, Any]]:
|
||||
"""Get current stock levels"""
|
||||
try:
|
||||
params = {}
|
||||
if ingredient_ids:
|
||||
params["ingredient_ids"] = [str(id) for id in ingredient_ids]
|
||||
|
||||
result = await self.get("stock", tenant_id=tenant_id, params=params)
|
||||
stock_levels = result if isinstance(result, list) else []
|
||||
|
||||
logger.info("Retrieved stock levels from inventory service",
|
||||
count=len(stock_levels), tenant_id=tenant_id)
|
||||
return stock_levels
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error fetching stock levels",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return []
|
||||
|
||||
async def get_low_stock_alerts(self, tenant_id: str) -> List[Dict[str, Any]]:
|
||||
"""Get low stock alerts"""
|
||||
try:
|
||||
result = await self.get("alerts", tenant_id=tenant_id, params={"type": "low_stock"})
|
||||
alerts = result if isinstance(result, list) else []
|
||||
|
||||
logger.info("Retrieved low stock alerts from inventory service",
|
||||
count=len(alerts), tenant_id=tenant_id)
|
||||
return alerts
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error fetching low stock alerts",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return []
|
||||
|
||||
async def consume_stock(
|
||||
self,
|
||||
consumption_data: Dict[str, Any],
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Record stock consumption"""
|
||||
try:
|
||||
result = await self.post("stock/consume", data=consumption_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Recorded stock consumption",
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error recording stock consumption",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def receive_stock(
|
||||
self,
|
||||
receipt_data: Dict[str, Any],
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Record stock receipt"""
|
||||
try:
|
||||
result = await self.post("stock/receive", data=receipt_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Recorded stock receipt",
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error recording stock receipt",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
# ================================================================
|
||||
# PRODUCT CLASSIFICATION (for onboarding)
|
||||
# ================================================================
|
||||
|
||||
async def classify_product(
|
||||
self,
|
||||
product_name: str,
|
||||
sales_volume: Optional[float],
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Classify a single product for inventory creation"""
|
||||
try:
|
||||
classification_data = {
|
||||
"product_name": product_name,
|
||||
"sales_volume": sales_volume
|
||||
}
|
||||
|
||||
result = await self.post("inventory/classify-product", data=classification_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Classified product",
|
||||
product=product_name,
|
||||
classification=result.get('product_type'),
|
||||
confidence=result.get('confidence_score'),
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error classifying product",
|
||||
error=str(e), product=product_name, tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def classify_products_batch(
|
||||
self,
|
||||
products: List[Dict[str, Any]],
|
||||
tenant_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Classify multiple products for onboarding automation"""
|
||||
try:
|
||||
classification_data = {
|
||||
"products": products
|
||||
}
|
||||
|
||||
result = await self.post("inventory/classify-products-batch", data=classification_data, tenant_id=tenant_id)
|
||||
if result:
|
||||
suggestions = result.get('suggestions', [])
|
||||
business_model = result.get('business_model_analysis', {}).get('model', 'unknown')
|
||||
|
||||
logger.info("Batch classification complete",
|
||||
total_products=len(suggestions),
|
||||
business_model=business_model,
|
||||
tenant_id=tenant_id)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error in batch classification",
|
||||
error=str(e), products_count=len(products), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
# ================================================================
|
||||
# DASHBOARD AND ANALYTICS
|
||||
# ================================================================
|
||||
|
||||
async def get_inventory_dashboard(self, tenant_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get inventory dashboard data"""
|
||||
try:
|
||||
result = await self.get("dashboard", tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Retrieved inventory dashboard data", tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error fetching inventory dashboard",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def get_inventory_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get inventory summary statistics"""
|
||||
try:
|
||||
result = await self.get("dashboard/summary", tenant_id=tenant_id)
|
||||
if result:
|
||||
logger.info("Retrieved inventory summary", tenant_id=tenant_id)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error("Error fetching inventory summary",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
# ================================================================
|
||||
# UTILITY METHODS
|
||||
# ================================================================
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Check if inventory 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("Inventory service health check failed", error=str(e))
|
||||
return False
|
||||
|
||||
|
||||
# Factory function for dependency injection
|
||||
def create_inventory_client(config: BaseServiceSettings) -> InventoryServiceClient:
|
||||
"""Create inventory service client instance"""
|
||||
return InventoryServiceClient(config)
|
||||
|
||||
|
||||
# Convenience function for quick access (requires config to be passed)
|
||||
async def get_inventory_client(config: BaseServiceSettings) -> InventoryServiceClient:
|
||||
"""Get inventory service client instance"""
|
||||
return create_inventory_client(config)
|
||||
Reference in New Issue
Block a user