""" Notifications models - Multi-channel notification delivery This module implements the notification system that: - Sends SMS, WhatsApp, and Email notifications - Logs all notification attempts - Tracks delivery status - Supports retry logic """ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from apps.core.models import BaseChoices, TimeStampedModel, UUIDModel class NotificationChannel(BaseChoices): """Notification channel choices""" SMS = 'sms', 'SMS' WHATSAPP = 'whatsapp', 'WhatsApp' EMAIL = 'email', 'Email' PUSH = 'push', 'Push Notification' class NotificationStatus(BaseChoices): """Notification delivery status""" PENDING = 'pending', 'Pending' SENDING = 'sending', 'Sending' SENT = 'sent', 'Sent' DELIVERED = 'delivered', 'Delivered' FAILED = 'failed', 'Failed' BOUNCED = 'bounced', 'Bounced' class NotificationLog(UUIDModel, TimeStampedModel): """ Notification log - tracks all notification attempts. Logs every SMS, WhatsApp, and Email sent by the system. Used for: - Delivery tracking - Debugging - Compliance - Analytics """ # Channel and recipient channel = models.CharField( max_length=20, choices=NotificationChannel.choices, db_index=True ) recipient = models.CharField( max_length=200, help_text="Phone number or email address" ) # Message content subject = models.CharField(max_length=500, blank=True) message = models.TextField() # Related object (generic foreign key) content_type = models.ForeignKey( ContentType, on_delete=models.SET_NULL, null=True, blank=True ) object_id = models.UUIDField(null=True, blank=True) content_object = GenericForeignKey('content_type', 'object_id') # Delivery status status = models.CharField( max_length=20, choices=NotificationStatus.choices, default=NotificationStatus.PENDING, db_index=True ) # Timestamps sent_at = models.DateTimeField(null=True, blank=True) delivered_at = models.DateTimeField(null=True, blank=True) # Provider response provider = models.CharField( max_length=50, blank=True, help_text="SMS/Email provider used" ) provider_message_id = models.CharField( max_length=200, blank=True, help_text="Message ID from provider" ) provider_response = models.JSONField( default=dict, blank=True, help_text="Full response from provider" ) # Error tracking error = models.TextField(blank=True) retry_count = models.IntegerField(default=0) # Metadata metadata = models.JSONField( default=dict, blank=True, help_text="Additional metadata (campaign, template, etc.)" ) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['channel', 'status', '-created_at']), models.Index(fields=['recipient', '-created_at']), models.Index(fields=['content_type', 'object_id']), ] def __str__(self): return f"{self.channel} to {self.recipient} ({self.status})" def mark_sent(self, provider_message_id=None): """Mark notification as sent""" from django.utils import timezone self.status = NotificationStatus.SENT self.sent_at = timezone.now() if provider_message_id: self.provider_message_id = provider_message_id self.save(update_fields=['status', 'sent_at', 'provider_message_id']) def mark_delivered(self): """Mark notification as delivered""" from django.utils import timezone self.status = NotificationStatus.DELIVERED self.delivered_at = timezone.now() self.save(update_fields=['status', 'delivered_at']) def mark_failed(self, error_message): """Mark notification as failed""" self.status = NotificationStatus.FAILED self.error = error_message self.retry_count += 1 self.save(update_fields=['status', 'error', 'retry_count']) class NotificationTemplate(UUIDModel, TimeStampedModel): """ Notification template for consistent messaging. Supports: - Bilingual templates (AR/EN) - Variable substitution - Multiple channels """ name = models.CharField(max_length=200, unique=True) description = models.TextField(blank=True) # Template type template_type = models.CharField( max_length=50, choices=[ ('survey_invitation', 'Survey Invitation'), ('survey_reminder', 'Survey Reminder'), ('complaint_acknowledgment', 'Complaint Acknowledgment'), ('complaint_update', 'Complaint Update'), ('action_assignment', 'Action Assignment'), ('sla_reminder', 'SLA Reminder'), ('sla_breach', 'SLA Breach'), ], db_index=True ) # Channel-specific templates sms_template = models.TextField( blank=True, help_text="SMS template with {{variables}}" ) sms_template_ar = models.TextField(blank=True) whatsapp_template = models.TextField( blank=True, help_text="WhatsApp template with {{variables}}" ) whatsapp_template_ar = models.TextField(blank=True) email_subject = models.CharField(max_length=500, blank=True) email_subject_ar = models.CharField(max_length=500, blank=True) email_template = models.TextField( blank=True, help_text="Email HTML template with {{variables}}" ) email_template_ar = models.TextField(blank=True) # Configuration is_active = models.BooleanField(default=True) class Meta: ordering = ['name'] def __str__(self): return self.name