170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
"""
|
|
Inventory Notification Service - Simplified
|
|
|
|
Emits minimal events using EventPublisher.
|
|
All enrichment handled by alert_processor.
|
|
|
|
These are NOTIFICATIONS (not alerts) - informational state changes that don't require user action.
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Optional, Dict, Any
|
|
from uuid import UUID
|
|
import structlog
|
|
|
|
from shared.messaging import UnifiedEventPublisher
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
class InventoryNotificationService:
|
|
"""
|
|
Service for emitting inventory notifications using EventPublisher.
|
|
"""
|
|
|
|
def __init__(self, event_publisher: UnifiedEventPublisher):
|
|
self.publisher = event_publisher
|
|
|
|
async def emit_stock_received_notification(
|
|
self,
|
|
tenant_id: UUID,
|
|
stock_receipt_id: str,
|
|
ingredient_id: str,
|
|
ingredient_name: str,
|
|
quantity_received: float,
|
|
unit: str,
|
|
supplier_name: Optional[str] = None,
|
|
delivery_id: Optional[str] = None,
|
|
) -> None:
|
|
"""
|
|
Emit notification when stock is received.
|
|
"""
|
|
message = f"Received {quantity_received} {unit} of {ingredient_name}"
|
|
if supplier_name:
|
|
message += f" from {supplier_name}"
|
|
|
|
metadata = {
|
|
"stock_receipt_id": stock_receipt_id,
|
|
"ingredient_id": ingredient_id,
|
|
"ingredient_name": ingredient_name,
|
|
"quantity_received": float(quantity_received),
|
|
"unit": unit,
|
|
"supplier_name": supplier_name,
|
|
"delivery_id": delivery_id,
|
|
"received_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
await self.publisher.publish_notification(
|
|
event_type="inventory.stock_received",
|
|
tenant_id=tenant_id,
|
|
data=metadata
|
|
)
|
|
|
|
logger.info(
|
|
"stock_received_notification_emitted",
|
|
tenant_id=str(tenant_id),
|
|
ingredient_name=ingredient_name,
|
|
quantity_received=quantity_received
|
|
)
|
|
|
|
async def emit_stock_movement_notification(
|
|
self,
|
|
tenant_id: UUID,
|
|
movement_id: str,
|
|
ingredient_id: str,
|
|
ingredient_name: str,
|
|
quantity: float,
|
|
unit: str,
|
|
movement_type: str, # 'transfer', 'adjustment', 'waste', 'return'
|
|
from_location: Optional[str] = None,
|
|
to_location: Optional[str] = None,
|
|
reason: Optional[str] = None,
|
|
) -> None:
|
|
"""
|
|
Emit notification for stock movements (transfers, adjustments, waste).
|
|
"""
|
|
# Build message based on movement type
|
|
if movement_type == "transfer":
|
|
message = f"Transferred {quantity} {unit} of {ingredient_name}"
|
|
if from_location and to_location:
|
|
message += f" from {from_location} to {to_location}"
|
|
elif movement_type == "adjustment":
|
|
message = f"Adjusted {ingredient_name} by {quantity} {unit}"
|
|
if reason:
|
|
message += f" - {reason}"
|
|
elif movement_type == "waste":
|
|
message = f"Waste recorded: {quantity} {unit} of {ingredient_name}"
|
|
if reason:
|
|
message += f" - {reason}"
|
|
elif movement_type == "return":
|
|
message = f"Returned {quantity} {unit} of {ingredient_name}"
|
|
else:
|
|
message = f"Stock movement: {quantity} {unit} of {ingredient_name}"
|
|
|
|
metadata = {
|
|
"movement_id": movement_id,
|
|
"ingredient_id": ingredient_id,
|
|
"ingredient_name": ingredient_name,
|
|
"quantity": float(quantity),
|
|
"unit": unit,
|
|
"movement_type": movement_type,
|
|
"from_location": from_location,
|
|
"to_location": to_location,
|
|
"reason": reason,
|
|
"moved_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
await self.publisher.publish_notification(
|
|
event_type="inventory.stock_movement",
|
|
tenant_id=tenant_id,
|
|
data=metadata
|
|
)
|
|
|
|
logger.info(
|
|
"stock_movement_notification_emitted",
|
|
tenant_id=str(tenant_id),
|
|
movement_type=movement_type,
|
|
ingredient_name=ingredient_name
|
|
)
|
|
|
|
async def emit_stock_updated_notification(
|
|
self,
|
|
tenant_id: UUID,
|
|
ingredient_id: str,
|
|
ingredient_name: str,
|
|
old_quantity: float,
|
|
new_quantity: float,
|
|
unit: str,
|
|
update_reason: str,
|
|
) -> None:
|
|
"""
|
|
Emit notification when stock is updated.
|
|
"""
|
|
quantity_change = new_quantity - old_quantity
|
|
change_direction = "increased" if quantity_change > 0 else "decreased"
|
|
|
|
message = f"Stock {change_direction} by {abs(quantity_change)} {unit} - {update_reason}"
|
|
|
|
metadata = {
|
|
"ingredient_id": ingredient_id,
|
|
"ingredient_name": ingredient_name,
|
|
"old_quantity": float(old_quantity),
|
|
"new_quantity": float(new_quantity),
|
|
"quantity_change": float(quantity_change),
|
|
"unit": unit,
|
|
"update_reason": update_reason,
|
|
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
await self.publisher.publish_notification(
|
|
event_type="inventory.stock_updated",
|
|
tenant_id=tenant_id,
|
|
data=metadata
|
|
)
|
|
|
|
logger.info(
|
|
"stock_updated_notification_emitted",
|
|
tenant_id=str(tenant_id),
|
|
ingredient_name=ingredient_name,
|
|
quantity_change=quantity_change
|
|
) |