""" Procurement Service Event Publisher Publishes procurement-related events to RabbitMQ """ import uuid from typing import Optional, Dict, Any from decimal import Decimal import structlog from shared.messaging.rabbitmq import RabbitMQClient from shared.messaging.events import ( PurchaseOrderApprovedEvent, PurchaseOrderRejectedEvent, PurchaseOrderSentToSupplierEvent, DeliveryReceivedEvent ) logger = structlog.get_logger() class ProcurementEventPublisher: """Handles publishing of procurement-related events""" def __init__(self, rabbitmq_client: Optional[RabbitMQClient] = None): self.rabbitmq_client = rabbitmq_client self.service_name = "procurement" async def publish_po_approved_event( self, tenant_id: uuid.UUID, po_id: uuid.UUID, po_number: str, supplier_id: uuid.UUID, supplier_name: str, supplier_email: Optional[str], supplier_phone: Optional[str], total_amount: Decimal, currency: str, required_delivery_date: Optional[str], items: list, approved_by: Optional[uuid.UUID], approved_at: str, correlation_id: Optional[str] = None ) -> bool: """ Publish purchase order approved event This event triggers: - Email/WhatsApp notification to supplier (notification service) - Dashboard refresh (frontend) - Analytics update (reporting service) """ if not self.rabbitmq_client: logger.warning("RabbitMQ client not available, event not published", event="po.approved") return False event_data = { "tenant_id": str(tenant_id), "po_id": str(po_id), "po_number": po_number, "supplier_id": str(supplier_id), "supplier_name": supplier_name, "supplier_email": supplier_email, "supplier_phone": supplier_phone, "total_amount": float(total_amount), "currency": currency, "required_delivery_date": required_delivery_date, "items": [ { "inventory_product_id": str(item.get("inventory_product_id")), "product_name": item.get("product_name"), "ordered_quantity": float(item.get("ordered_quantity")), "unit_of_measure": item.get("unit_of_measure"), "unit_price": float(item.get("unit_price")), "line_total": float(item.get("line_total")) } for item in items ], "approved_by": str(approved_by) if approved_by else None, "approved_at": approved_at, } event = PurchaseOrderApprovedEvent( service_name=self.service_name, data=event_data, correlation_id=correlation_id ) # Publish to procurement.events exchange with routing key po.approved success = await self.rabbitmq_client.publish_event( exchange_name="procurement.events", routing_key="po.approved", event_data=event.to_dict(), persistent=True ) if success: logger.info( "Published PO approved event", tenant_id=str(tenant_id), po_id=str(po_id), po_number=po_number, supplier_name=supplier_name ) return success async def publish_po_rejected_event( self, tenant_id: uuid.UUID, po_id: uuid.UUID, po_number: str, supplier_id: uuid.UUID, supplier_name: str, rejection_reason: str, rejected_by: Optional[uuid.UUID], rejected_at: str, correlation_id: Optional[str] = None ) -> bool: """Publish purchase order rejected event""" if not self.rabbitmq_client: logger.warning("RabbitMQ client not available, event not published", event="po.rejected") return False event_data = { "tenant_id": str(tenant_id), "po_id": str(po_id), "po_number": po_number, "supplier_id": str(supplier_id), "supplier_name": supplier_name, "rejection_reason": rejection_reason, "rejected_by": str(rejected_by) if rejected_by else None, "rejected_at": rejected_at, } event = PurchaseOrderRejectedEvent( service_name=self.service_name, data=event_data, correlation_id=correlation_id ) success = await self.rabbitmq_client.publish_event( exchange_name="procurement.events", routing_key="po.rejected", event_data=event.to_dict(), persistent=True ) if success: logger.info( "Published PO rejected event", tenant_id=str(tenant_id), po_id=str(po_id), po_number=po_number ) return success async def publish_po_sent_to_supplier_event( self, tenant_id: uuid.UUID, po_id: uuid.UUID, po_number: str, supplier_id: uuid.UUID, supplier_name: str, supplier_email: Optional[str], supplier_phone: Optional[str], total_amount: Decimal, currency: str, sent_at: str, correlation_id: Optional[str] = None ) -> bool: """Publish purchase order sent to supplier event""" if not self.rabbitmq_client: logger.warning("RabbitMQ client not available, event not published", event="po.sent_to_supplier") return False event_data = { "tenant_id": str(tenant_id), "po_id": str(po_id), "po_number": po_number, "supplier_id": str(supplier_id), "supplier_name": supplier_name, "supplier_email": supplier_email, "supplier_phone": supplier_phone, "total_amount": float(total_amount), "currency": currency, "sent_at": sent_at, } event = PurchaseOrderSentToSupplierEvent( service_name=self.service_name, data=event_data, correlation_id=correlation_id ) success = await self.rabbitmq_client.publish_event( exchange_name="procurement.events", routing_key="po.sent_to_supplier", event_data=event.to_dict(), persistent=True ) if success: logger.info( "Published PO sent to supplier event", tenant_id=str(tenant_id), po_id=str(po_id), po_number=po_number ) return success async def publish_delivery_received_event( self, tenant_id: uuid.UUID, delivery_id: uuid.UUID, po_id: uuid.UUID, items: list, received_at: str, received_by: Optional[uuid.UUID], correlation_id: Optional[str] = None ) -> bool: """ Publish delivery received event This event triggers: - Automatic stock update (inventory service) - PO status update to 'completed' - Supplier performance metrics update """ if not self.rabbitmq_client: logger.warning("RabbitMQ client not available, event not published", event="delivery.received") return False event_data = { "tenant_id": str(tenant_id), "delivery_id": str(delivery_id), "po_id": str(po_id), "items": [ { "inventory_product_id": str(item.get("inventory_product_id")), "accepted_quantity": float(item.get("accepted_quantity")), "rejected_quantity": float(item.get("rejected_quantity", 0)), "batch_lot_number": item.get("batch_lot_number"), "expiry_date": item.get("expiry_date"), "unit_of_measure": item.get("unit_of_measure") } for item in items ], "received_at": received_at, "received_by": str(received_by) if received_by else None, } event = DeliveryReceivedEvent( service_name=self.service_name, data=event_data, correlation_id=correlation_id ) success = await self.rabbitmq_client.publish_event( exchange_name="procurement.events", routing_key="delivery.received", event_data=event.to_dict(), persistent=True ) if success: logger.info( "Published delivery received event", tenant_id=str(tenant_id), delivery_id=str(delivery_id), po_id=str(po_id) ) return success