# services/inventory/app/services/messaging.py """ Messaging service for inventory events """ from typing import Dict, Any, Optional from uuid import UUID import structlog from shared.messaging.rabbitmq import MessagePublisher from shared.messaging.events import ( EVENT_TYPES, InventoryEvent, StockAlertEvent, StockMovementEvent ) logger = structlog.get_logger() class InventoryMessagingService: """Service for publishing inventory-related events""" def __init__(self): self.publisher = MessagePublisher() async def publish_ingredient_created( self, tenant_id: UUID, ingredient_id: UUID, ingredient_data: Dict[str, Any] ): """Publish ingredient creation event""" try: event = InventoryEvent( event_type=EVENT_TYPES.INVENTORY.INGREDIENT_CREATED, tenant_id=str(tenant_id), ingredient_id=str(ingredient_id), data=ingredient_data ) await self.publisher.publish_event( routing_key="inventory.ingredient.created", event=event ) logger.info( "Published ingredient created event", tenant_id=tenant_id, ingredient_id=ingredient_id ) except Exception as e: logger.error( "Failed to publish ingredient created event", error=str(e), tenant_id=tenant_id, ingredient_id=ingredient_id ) async def publish_stock_added( self, tenant_id: UUID, ingredient_id: UUID, stock_id: UUID, quantity: float, batch_number: Optional[str] = None ): """Publish stock addition event""" try: movement_event = StockMovementEvent( event_type=EVENT_TYPES.INVENTORY.STOCK_ADDED, tenant_id=str(tenant_id), ingredient_id=str(ingredient_id), stock_id=str(stock_id), quantity=quantity, movement_type="purchase", data={ "batch_number": batch_number, "movement_type": "purchase" } ) await self.publisher.publish_event( routing_key="inventory.stock.added", event=movement_event ) logger.info( "Published stock added event", tenant_id=tenant_id, ingredient_id=ingredient_id, quantity=quantity ) except Exception as e: logger.error( "Failed to publish stock added event", error=str(e), tenant_id=tenant_id, ingredient_id=ingredient_id ) async def publish_stock_consumed( self, tenant_id: UUID, ingredient_id: UUID, consumed_items: list, total_quantity: float, reference_number: Optional[str] = None ): """Publish stock consumption event""" try: for item in consumed_items: movement_event = StockMovementEvent( event_type=EVENT_TYPES.INVENTORY.STOCK_CONSUMED, tenant_id=str(tenant_id), ingredient_id=str(ingredient_id), stock_id=item['stock_id'], quantity=item['quantity_consumed'], movement_type="production_use", data={ "batch_number": item.get('batch_number'), "reference_number": reference_number, "movement_type": "production_use" } ) await self.publisher.publish_event( routing_key="inventory.stock.consumed", event=movement_event ) logger.info( "Published stock consumed events", tenant_id=tenant_id, ingredient_id=ingredient_id, total_quantity=total_quantity, items_count=len(consumed_items) ) except Exception as e: logger.error( "Failed to publish stock consumed event", error=str(e), tenant_id=tenant_id, ingredient_id=ingredient_id ) async def publish_low_stock_alert( self, tenant_id: UUID, ingredient_id: UUID, ingredient_name: str, current_stock: float, threshold: float, needs_reorder: bool = False ): """Publish low stock alert event""" try: alert_event = StockAlertEvent( event_type=EVENT_TYPES.INVENTORY.LOW_STOCK_ALERT, tenant_id=str(tenant_id), ingredient_id=str(ingredient_id), alert_type="low_stock" if not needs_reorder else "reorder_needed", severity="medium" if not needs_reorder else "high", data={ "ingredient_name": ingredient_name, "current_stock": current_stock, "threshold": threshold, "needs_reorder": needs_reorder } ) await self.publisher.publish_event( routing_key="inventory.alerts.low_stock", event=alert_event ) logger.info( "Published low stock alert", tenant_id=tenant_id, ingredient_id=ingredient_id, current_stock=current_stock ) except Exception as e: logger.error( "Failed to publish low stock alert", error=str(e), tenant_id=tenant_id, ingredient_id=ingredient_id ) async def publish_expiration_alert( self, tenant_id: UUID, ingredient_id: UUID, stock_id: UUID, ingredient_name: str, batch_number: Optional[str], expiration_date: str, days_to_expiry: int, quantity: float ): """Publish expiration alert event""" try: severity = "critical" if days_to_expiry <= 1 else "high" alert_event = StockAlertEvent( event_type=EVENT_TYPES.INVENTORY.EXPIRATION_ALERT, tenant_id=str(tenant_id), ingredient_id=str(ingredient_id), alert_type="expiring_soon", severity=severity, data={ "stock_id": str(stock_id), "ingredient_name": ingredient_name, "batch_number": batch_number, "expiration_date": expiration_date, "days_to_expiry": days_to_expiry, "quantity": quantity } ) await self.publisher.publish_event( routing_key="inventory.alerts.expiration", event=alert_event ) logger.info( "Published expiration alert", tenant_id=tenant_id, ingredient_id=ingredient_id, days_to_expiry=days_to_expiry ) except Exception as e: logger.error( "Failed to publish expiration alert", error=str(e), tenant_id=tenant_id, ingredient_id=ingredient_id )