New alert service

This commit is contained in:
Urtzi Alfaro
2025-12-05 20:07:01 +01:00
parent 1fe3a73549
commit 667e6e0404
393 changed files with 26002 additions and 61033 deletions

View File

@@ -1,33 +1,25 @@
# services/orders/app/services/procurement_notification_service.py
"""
Procurement Notification Service - Send alerts and notifications for procurement events
Procurement Notification Service - Send alerts and notifications for procurement events using EventPublisher
Handles PO approval notifications, reminders, escalations, and summaries
"""
from typing import Dict, List, Any, Optional
from uuid import UUID
from datetime import datetime, timedelta, timezone
from datetime import datetime, timezone
import structlog
from shared.config.base import BaseServiceSettings
from shared.alerts.base_service import BaseAlertService
from shared.messaging import UnifiedEventPublisher
logger = structlog.get_logger()
class ProcurementNotificationService(BaseAlertService):
"""Service for sending procurement-related notifications and alerts"""
class ProcurementNotificationService:
"""Service for sending procurement-related notifications and alerts using EventPublisher"""
def __init__(self, config: BaseServiceSettings):
super().__init__(config)
def setup_scheduled_checks(self):
"""Procurement service doesn't use scheduled checks - alerts are event-driven"""
pass
async def register_db_listeners(self, conn):
"""Procurement service doesn't use database triggers - alerts are event-driven"""
pass
def __init__(self, event_publisher: UnifiedEventPublisher):
self.publisher = event_publisher
async def send_pos_pending_approval_alert(
self,
@@ -51,34 +43,33 @@ class ProcurementNotificationService(BaseAlertService):
if critical_count > 0 or total_amount > 5000:
severity = "high"
elif total_amount > 10000:
severity = "critical"
severity = "urgent"
alert_data = {
"type": "procurement_pos_pending_approval",
"severity": severity,
"title": f"{len(pos_data)} Pedidos Pendientes de Aprobación",
"message": f"Se han creado {len(pos_data)} pedidos de compra que requieren tu aprobación. Total: €{total_amount:.2f}",
"metadata": {
"tenant_id": str(tenant_id),
"pos_count": len(pos_data),
"total_amount": total_amount,
"critical_count": critical_count,
"pos": [
{
"po_id": po.get("po_id"),
"po_number": po.get("po_number"),
"supplier_id": po.get("supplier_id"),
"total_amount": po.get("total_amount"),
"auto_approved": po.get("auto_approved", False)
}
for po in pos_data
],
"action_required": True,
"action_url": "/app/comprar"
}
metadata = {
"tenant_id": str(tenant_id),
"pos_count": len(pos_data),
"total_amount": total_amount,
"critical_count": critical_count,
"pos": [
{
"po_id": po.get("po_id"),
"po_number": po.get("po_number"),
"supplier_id": po.get("supplier_id"),
"total_amount": po.get("total_amount"),
"auto_approved": po.get("auto_approved", False)
}
for po in pos_data
],
"action_required": True,
"action_url": "/app/comprar"
}
await self.publish_item(tenant_id, alert_data, item_type='alert')
await self.publisher.publish_alert(
event_type="procurement.pos_pending_approval",
tenant_id=tenant_id,
severity=severity,
data=metadata
)
logger.info("POs pending approval alert sent",
tenant_id=str(tenant_id),
@@ -100,25 +91,27 @@ class ProcurementNotificationService(BaseAlertService):
Send reminder for POs that haven't been approved within threshold
"""
try:
alert_data = {
"type": "procurement_approval_reminder",
"severity": "medium" if hours_pending < 36 else "high",
"title": f"Recordatorio: Pedido {po_data.get('po_number')} Pendiente",
"message": f"El pedido {po_data.get('po_number')} lleva {hours_pending} horas sin aprobarse. Total: €{po_data.get('total_amount', 0):.2f}",
"metadata": {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"hours_pending": hours_pending,
"created_at": po_data.get("created_at"),
"action_required": True,
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
# Determine severity based on pending hours
severity = "medium" if hours_pending < 36 else "high"
metadata = {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"hours_pending": hours_pending,
"created_at": po_data.get("created_at"),
"action_required": True,
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
await self.publish_item(tenant_id, alert_data, item_type='alert')
await self.publisher.publish_alert(
event_type="procurement.approval_reminder",
tenant_id=tenant_id,
severity=severity,
data=metadata
)
logger.info("Approval reminder sent",
tenant_id=str(tenant_id),
@@ -141,27 +134,26 @@ class ProcurementNotificationService(BaseAlertService):
Send escalation alert for critical/urgent POs not approved in time
"""
try:
alert_data = {
"type": "procurement_critical_po",
"severity": "critical",
"title": f"🚨 URGENTE: Pedido Crítico {po_data.get('po_number')}",
"message": f"El pedido crítico {po_data.get('po_number')} lleva {hours_pending} horas sin aprobar. Se requiere acción inmediata.",
"metadata": {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"priority": po_data.get("priority"),
"required_delivery_date": po_data.get("required_delivery_date"),
"hours_pending": hours_pending,
"escalated": True,
"action_required": True,
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
metadata = {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"priority": po_data.get("priority"),
"required_delivery_date": po_data.get("required_delivery_date"),
"hours_pending": hours_pending,
"escalated": True,
"action_required": True,
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
await self.publish_item(tenant_id, alert_data, item_type='alert')
await self.publisher.publish_alert(
event_type="procurement.critical_po_escalation",
tenant_id=tenant_id,
severity="urgent",
data=metadata
)
logger.warning("Critical PO escalation sent",
tenant_id=str(tenant_id),
@@ -191,24 +183,22 @@ class ProcurementNotificationService(BaseAlertService):
# No activity, skip notification
return
alert_data = {
"type": "procurement_auto_approval_summary",
"severity": "low",
"title": "Resumen Diario de Pedidos",
"message": f"Hoy se aprobaron automáticamente {auto_approved_count} pedidos (€{total_amount:.2f}). {manual_approval_count} requieren aprobación manual.",
"metadata": {
"tenant_id": str(tenant_id),
"auto_approved_count": auto_approved_count,
"total_auto_approved_amount": total_amount,
"manual_approval_count": manual_approval_count,
"summary_date": summary_data.get("date"),
"auto_approved_pos": summary_data.get("auto_approved_pos", []),
"pending_approval_pos": summary_data.get("pending_approval_pos", []),
"action_url": "/app/comprar"
}
metadata = {
"tenant_id": str(tenant_id),
"auto_approved_count": auto_approved_count,
"total_auto_approved_amount": total_amount,
"manual_approval_count": manual_approval_count,
"summary_date": summary_data.get("date"),
"auto_approved_pos": summary_data.get("auto_approved_pos", []),
"pending_approval_pos": summary_data.get("pending_approval_pos", []),
"action_url": "/app/comprar"
}
await self.publish_item(tenant_id, alert_data, item_type='notification')
await self.publisher.publish_notification(
event_type="procurement.auto_approval_summary",
tenant_id=tenant_id,
data=metadata
)
logger.info("Auto-approval summary sent",
tenant_id=str(tenant_id),
@@ -231,27 +221,23 @@ class ProcurementNotificationService(BaseAlertService):
Send confirmation when a PO is approved
"""
try:
approval_type = "automáticamente" if auto_approved else f"por {approved_by}"
alert_data = {
"type": "procurement_po_approved",
"severity": "low",
"title": f"Pedido {po_data.get('po_number')} Aprobado",
"message": f"El pedido {po_data.get('po_number')} ha sido aprobado {approval_type}. Total: €{po_data.get('total_amount', 0):.2f}",
"metadata": {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"approved_by": approved_by,
"auto_approved": auto_approved,
"approved_at": datetime.now(timezone.utc).isoformat(),
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
metadata = {
"tenant_id": str(tenant_id),
"po_id": po_data.get("po_id"),
"po_number": po_data.get("po_number"),
"supplier_name": po_data.get("supplier_name"),
"total_amount": po_data.get("total_amount"),
"approved_by": approved_by,
"auto_approved": auto_approved,
"approved_at": datetime.now(timezone.utc).isoformat(),
"action_url": f"/app/comprar?po={po_data.get('po_id')}"
}
await self.publish_item(tenant_id, alert_data, item_type='notification')
await self.publisher.publish_notification(
event_type="procurement.po_approved_confirmation",
tenant_id=tenant_id,
data=metadata
)
logger.info("PO approved confirmation sent",
tenant_id=str(tenant_id),
@@ -262,4 +248,4 @@ class ProcurementNotificationService(BaseAlertService):
logger.error("Error sending PO approved confirmation",
tenant_id=str(tenant_id),
po_id=po_data.get("po_id"),
error=str(e))
error=str(e))