469 lines
14 KiB
Python
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 |