Files
service_manager/workers/app/tasks/notification_tasks.py
2026-01-12 08:17:17 -07:00

469 lines
14 KiB
Python

"""
Notification Tasks - ServiceManagerWeb Workers
Tareas para notificaciones y comunicaciones
"""
from celery import current_task
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
import structlog
from app.celery import celery_app
from app.core.config import get_settings
from app.core.logging import get_logger
from app.tasks.email_tasks import send_templated_email_task, send_bulk_email_task
settings = get_settings()
logger = get_logger(__name__)
@celery_app.task(
bind=True,
time_limit=900, # 15 minutes
soft_time_limit=780 # 13 minutes
)
def send_daily_digest(self) -> Dict[str, Any]:
"""
Send daily digest emails to agents and managers.
Includes:
- New tickets assigned
- SLA warnings
- Performance summary
- Pending tasks
"""
task_logger = logger.bind(
task_id=self.request.id,
task_name=self.name
)
task_logger.info("Starting daily digest generation")
if not settings.DIGEST_ENABLED:
task_logger.info("Daily digest disabled, skipping")
return {"status": "disabled"}
try:
current_time = datetime.utcnow()
yesterday = current_time - timedelta(days=1)
digest_results = {
"generated_at": current_time.isoformat(),
"period_start": yesterday.isoformat(),
"period_end": current_time.isoformat(),
"digests_sent": 0,
"errors": []
}
# TODO: Get active agents and managers from database
mock_recipients = [
{
"user_id": "user-1",
"email": "agent1@example.com",
"name": "Agent One",
"role": "AGENT",
"tenant_id": "tenant-1"
},
{
"user_id": "user-2",
"email": "manager@example.com",
"name": "Support Manager",
"role": "SUPPORT_MANAGER",
"tenant_id": "tenant-1"
}
]
for recipient in mock_recipients:
try:
task_logger.info(
"Generating digest for user",
user_id=recipient["user_id"],
email=recipient["email"],
role=recipient["role"]
)
# TODO: Generate actual digest data from database
digest_data = generate_digest_data(
recipient["user_id"],
recipient["role"],
recipient["tenant_id"],
yesterday,
current_time
)
# Send digest email
send_templated_email_task.apply_async(kwargs={
"to_email": recipient["email"],
"template_name": "daily_digest",
"context": {
"user_name": recipient["name"],
"role": recipient["role"],
"date": current_time.strftime("%Y-%m-%d"),
**digest_data
},
"tenant_id": recipient["tenant_id"],
"correlation_id": self.request.id
})
digest_results["digests_sent"] += 1
except Exception as e:
error_msg = f"Failed to send digest to {recipient['email']}: {str(e)}"
digest_results["errors"].append(error_msg)
task_logger.error(
"Digest generation failed for user",
user_id=recipient["user_id"],
error=str(e)
)
task_logger.info(
"Daily digest generation completed",
digests_sent=digest_results["digests_sent"],
errors=len(digest_results["errors"])
)
return digest_results
except Exception as exc:
task_logger.error(
"Daily digest generation failed",
error=str(exc),
exc_info=True
)
raise
def generate_digest_data(
user_id: str,
role: str,
tenant_id: str,
period_start: datetime,
period_end: datetime
) -> Dict[str, Any]:
"""
Generate digest data for a specific user.
Args:
user_id: User ID
role: User role
tenant_id: Tenant ID
period_start: Start of digest period
period_end: End of digest period
Returns:
Dict with digest data
"""
# TODO: Implement actual database queries
# For now, return mock data
base_data = {
"summary": {
"new_tickets": 5,
"resolved_tickets": 7,
"pending_tickets": 12,
"overdue_tickets": 2
},
"sla_status": {
"response_sla_met": 8,
"response_sla_missed": 1,
"resolution_sla_met": 6,
"resolution_sla_missed": 2
}
}
if role == "AGENT":
base_data.update({
"assigned_tickets": [
{
"ticket_number": "TKT-2024-000001",
"subject": "Problema de conexión",
"priority": "HIGH",
"created_at": "2024-01-15T10:00:00Z",
"sla_due": "2024-01-15T12:00:00Z"
}
],
"urgent_tickets": 1,
"performance": {
"avg_response_time_hours": 1.5,
"avg_resolution_time_hours": 18.2,
"customer_satisfaction": 4.3
}
})
elif role in ["SUPPORT_MANAGER", "ADMIN"]:
base_data.update({
"team_summary": {
"total_agents": 5,
"active_agents": 4,
"avg_load_per_agent": 6.2
},
"escalations": [
{
"ticket_number": "TKT-2024-000002",
"reason": "SLA violation",
"assigned_to": "agent1@example.com"
}
],
"trends": {
"ticket_volume_change": "+12%",
"resolution_time_change": "-5%"
}
})
return base_data
@celery_app.task(
bind=True,
time_limit=600,
soft_time_limit=540
)
def send_ticket_notifications(
self,
ticket_id: str,
event_type: str,
tenant_id: str,
context: Dict[str, Any],
correlation_id: Optional[str] = None
) -> Dict[str, Any]:
"""
Send ticket-related notifications.
Args:
ticket_id: Ticket ID
event_type: Type of event (created, assigned, updated, resolved, etc.)
tenant_id: Tenant ID
context: Context data for notifications
correlation_id: Correlation ID
Returns:
Dict with notification results
"""
task_logger = logger.bind(
task_id=self.request.id,
task_name=self.name,
ticket_id=ticket_id,
event_type=event_type,
tenant_id=tenant_id,
correlation_id=correlation_id
)
task_logger.info("Starting ticket notifications")
try:
notification_results = {
"ticket_id": ticket_id,
"event_type": event_type,
"notifications_sent": 0,
"notifications": []
}
# Determine who should receive notifications based on event type
recipients = get_notification_recipients(ticket_id, event_type, tenant_id)
for recipient in recipients:
try:
template_name = f"ticket_{event_type}"
# Send notification
result = send_templated_email_task.apply_async(kwargs={
"to_email": recipient["email"],
"template_name": template_name,
"context": {
**context,
"recipient_name": recipient["name"],
"recipient_role": recipient["role"]
},
"tenant_id": tenant_id,
"correlation_id": correlation_id or self.request.id
}).get()
notification_results["notifications"].append({
"recipient": recipient["email"],
"template": template_name,
"success": result.get("success", False),
"error": result.get("error")
})
if result.get("success"):
notification_results["notifications_sent"] += 1
except Exception as e:
task_logger.error(
"Failed to send notification",
recipient_email=recipient["email"],
error=str(e)
)
notification_results["notifications"].append({
"recipient": recipient["email"],
"success": False,
"error": str(e)
})
task_logger.info(
"Ticket notifications completed",
notifications_sent=notification_results["notifications_sent"],
total_recipients=len(recipients)
)
return notification_results
except Exception as exc:
task_logger.error(
"Ticket notifications failed",
error=str(exc),
exc_info=True
)
raise
def get_notification_recipients(
ticket_id: str,
event_type: str,
tenant_id: str
) -> List[Dict[str, Any]]:
"""
Get list of users who should receive notifications for a ticket event.
Args:
ticket_id: Ticket ID
event_type: Event type
tenant_id: Tenant ID
Returns:
List of recipient dicts
"""
# TODO: Implement actual database queries
# For now, return mock recipients based on event type
recipients = []
if event_type == "created":
# Notify assigned agent (if any) and customer
recipients = [
{"email": "customer@example.com", "name": "Customer", "role": "CLIENT_USER"},
{"email": "agent@example.com", "name": "Agent", "role": "AGENT"}
]
elif event_type == "assigned":
# Notify assigned agent and customer
recipients = [
{"email": "agent@example.com", "name": "Assigned Agent", "role": "AGENT"},
{"email": "customer@example.com", "name": "Customer", "role": "CLIENT_USER"}
]
elif event_type == "updated":
# Notify all participants
recipients = [
{"email": "customer@example.com", "name": "Customer", "role": "CLIENT_USER"},
{"email": "agent@example.com", "name": "Agent", "role": "AGENT"}
]
elif event_type == "resolved":
# Notify customer for feedback
recipients = [
{"email": "customer@example.com", "name": "Customer", "role": "CLIENT_USER"}
]
elif event_type == "escalated":
# Notify manager
recipients = [
{"email": "manager@example.com", "name": "Manager", "role": "SUPPORT_MANAGER"}
]
return recipients
@celery_app.task(
bind=True,
time_limit=300,
soft_time_limit=240
)
def send_system_alert(
self,
alert_type: str,
message: str,
severity: str = "INFO",
tenant_id: Optional[str] = None,
context: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Send system alert to administrators.
Args:
alert_type: Type of alert (system_error, sla_violation, etc.)
message: Alert message
severity: Alert severity (INFO, WARNING, ERROR, CRITICAL)
tenant_id: Optional tenant ID
context: Additional context data
Returns:
Dict with alert results
"""
task_logger = logger.bind(
task_id=self.request.id,
task_name=self.name,
alert_type=alert_type,
severity=severity,
tenant_id=tenant_id
)
task_logger.info("Sending system alert", message=message)
try:
# TODO: Get administrators from configuration/database
admin_emails = ["admin@example.com", "alerts@example.com"]
alert_context = {
"alert_type": alert_type,
"message": message,
"severity": severity,
"timestamp": datetime.utcnow().isoformat(),
"environment": settings.ENVIRONMENT,
"tenant_id": tenant_id,
**(context or {})
}
notifications_sent = 0
for admin_email in admin_emails:
try:
send_templated_email_task.apply_async(kwargs={
"to_email": admin_email,
"template_name": "system_alert",
"context": alert_context,
"tenant_id": tenant_id,
"correlation_id": self.request.id
})
notifications_sent += 1
except Exception as e:
task_logger.error(
"Failed to send alert to admin",
admin_email=admin_email,
error=str(e)
)
task_logger.info(
"System alert sent",
notifications_sent=notifications_sent,
total_admins=len(admin_emails)
)
return {
"alert_type": alert_type,
"message": message,
"severity": severity,
"notifications_sent": notifications_sent,
"sent_at": datetime.utcnow().isoformat()
}
except Exception as exc:
task_logger.error(
"System alert failed",
error=str(exc),
exc_info=True
)
raise