338 lines
12 KiB
Python
338 lines
12 KiB
Python
"""
|
|
Notification services - Multi-channel notification delivery
|
|
|
|
This module provides a unified interface for sending notifications
|
|
via SMS, WhatsApp, and Email.
|
|
|
|
For now, implements console backends that log to database.
|
|
In production, integrate with actual providers (Twilio, WhatsApp Business API, SMTP).
|
|
"""
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.core.mail import send_mail
|
|
|
|
from .models import NotificationLog
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NotificationService:
|
|
"""
|
|
Unified notification service for all channels.
|
|
|
|
Usage:
|
|
NotificationService.send_sms('+966501234567', 'Your survey is ready')
|
|
NotificationService.send_email('user@email.com', 'Survey', 'Please complete...')
|
|
"""
|
|
|
|
@staticmethod
|
|
def send_sms(phone, message, related_object=None, metadata=None):
|
|
"""
|
|
Send SMS notification.
|
|
|
|
Args:
|
|
phone: Recipient phone number
|
|
message: SMS message text
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
# Create notification log
|
|
log = NotificationLog.objects.create(
|
|
channel='sms',
|
|
recipient=phone,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider='console', # TODO: Replace with actual provider
|
|
metadata=metadata or {}
|
|
)
|
|
|
|
# Check if SMS is enabled
|
|
sms_config = settings.NOTIFICATION_CHANNELS.get('sms', {})
|
|
if not sms_config.get('enabled', False):
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
# TODO: Integrate with actual SMS provider (Twilio, etc.)
|
|
# Example:
|
|
# try:
|
|
# from twilio.rest import Client
|
|
# client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
|
|
# message = client.messages.create(
|
|
# body=message,
|
|
# from_=settings.TWILIO_PHONE_NUMBER,
|
|
# to=phone
|
|
# )
|
|
# log.mark_sent(provider_message_id=message.sid)
|
|
# except Exception as e:
|
|
# log.mark_failed(str(e))
|
|
|
|
# Console backend for now
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
|
|
return log
|
|
|
|
@staticmethod
|
|
def send_whatsapp(phone, message, related_object=None, metadata=None):
|
|
"""
|
|
Send WhatsApp notification.
|
|
|
|
Args:
|
|
phone: Recipient phone number
|
|
message: WhatsApp message text
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
# Create notification log
|
|
log = NotificationLog.objects.create(
|
|
channel='whatsapp',
|
|
recipient=phone,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider='console', # TODO: Replace with actual provider
|
|
metadata=metadata or {}
|
|
)
|
|
|
|
# Check if WhatsApp is enabled
|
|
whatsapp_config = settings.NOTIFICATION_CHANNELS.get('whatsapp', {})
|
|
if not whatsapp_config.get('enabled', False):
|
|
logger.info(f"[WhatsApp Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
# TODO: Integrate with WhatsApp Business API
|
|
# Example:
|
|
# try:
|
|
# response = requests.post(
|
|
# f"{settings.WHATSAPP_API_URL}/messages",
|
|
# headers={'Authorization': f'Bearer {settings.WHATSAPP_API_KEY}'},
|
|
# json={
|
|
# 'to': phone,
|
|
# 'type': 'text',
|
|
# 'text': {'body': message}
|
|
# }
|
|
# )
|
|
# log.mark_sent(provider_message_id=response.json().get('id'))
|
|
# except Exception as e:
|
|
# log.mark_failed(str(e))
|
|
|
|
# Console backend for now
|
|
logger.info(f"[WhatsApp Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
|
|
return log
|
|
|
|
@staticmethod
|
|
def send_email(email, subject, message, html_message=None, related_object=None, metadata=None):
|
|
"""
|
|
Send Email notification.
|
|
|
|
Args:
|
|
email: Recipient email address
|
|
subject: Email subject
|
|
message: Email message (plain text)
|
|
html_message: Email message (HTML) (optional)
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
# Create notification log
|
|
log = NotificationLog.objects.create(
|
|
channel='email',
|
|
recipient=email,
|
|
subject=subject,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider='console', # TODO: Replace with actual provider
|
|
metadata=metadata or {}
|
|
)
|
|
|
|
# Check if Email is enabled
|
|
email_config = settings.NOTIFICATION_CHANNELS.get('email', {})
|
|
if not email_config.get('enabled', True):
|
|
logger.info(f"[Email Console] To: {email} | Subject: {subject} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
# Send email using Django's email backend
|
|
try:
|
|
send_mail(
|
|
subject=subject,
|
|
message=message,
|
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
recipient_list=[email],
|
|
html_message=html_message,
|
|
fail_silently=False
|
|
)
|
|
log.mark_sent()
|
|
logger.info(f"Email sent to {email}: {subject}")
|
|
except Exception as e:
|
|
log.mark_failed(str(e))
|
|
logger.error(f"Failed to send email to {email}: {str(e)}")
|
|
|
|
return log
|
|
|
|
@staticmethod
|
|
def send_notification(recipient, title, message, notification_type='general', related_object=None, metadata=None):
|
|
"""
|
|
Send generic notification to a user.
|
|
|
|
This method determines the best channel to use based on recipient preferences
|
|
or defaults to email.
|
|
|
|
Args:
|
|
recipient: User object
|
|
title: Notification title
|
|
message: Notification message
|
|
notification_type: Type of notification (e.g., 'complaint', 'survey', 'general')
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
# Determine the recipient's contact information
|
|
recipient_email = recipient.email if hasattr(recipient, 'email') else None
|
|
recipient_phone = recipient.phone if hasattr(recipient, 'phone') else None
|
|
|
|
# Try email first (most reliable)
|
|
if recipient_email:
|
|
try:
|
|
return NotificationService.send_email(
|
|
email=recipient_email,
|
|
subject=title,
|
|
message=message,
|
|
related_object=related_object,
|
|
metadata={
|
|
'notification_type': notification_type,
|
|
**(metadata or {})
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to send email notification to {recipient_email}: {str(e)}")
|
|
|
|
# Fallback to SMS if email failed or not available
|
|
if recipient_phone:
|
|
try:
|
|
# Combine title and message for SMS
|
|
sms_message = f"{title}\n\n{message}"
|
|
return NotificationService.send_sms(
|
|
phone=recipient_phone,
|
|
message=sms_message,
|
|
related_object=related_object,
|
|
metadata={
|
|
'notification_type': notification_type,
|
|
**(metadata or {})
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to send SMS notification to {recipient_phone}: {str(e)}")
|
|
|
|
# If all channels failed, log a console notification
|
|
logger.warning(
|
|
f"Could not send notification to {recipient}. "
|
|
f"No valid contact channels available. "
|
|
f"Title: {title}, Message: {message}"
|
|
)
|
|
|
|
# Create a log entry even if we couldn't send
|
|
return NotificationLog.objects.create(
|
|
channel='console',
|
|
recipient=str(recipient),
|
|
subject=title,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider='console',
|
|
metadata={
|
|
'notification_type': notification_type,
|
|
**(metadata or {})
|
|
}
|
|
)
|
|
|
|
@staticmethod
|
|
def send_survey_invitation(survey_instance, language='en'):
|
|
"""
|
|
Send survey invitation to patient.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
language: Language code ('en' or 'ar')
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
patient = survey_instance.patient
|
|
survey_url = survey_instance.get_survey_url()
|
|
|
|
# Determine recipient based on delivery channel
|
|
if survey_instance.delivery_channel == 'sms':
|
|
recipient = survey_instance.recipient_phone or patient.phone
|
|
if language == 'ar':
|
|
message = f"عزيزي {patient.get_full_name()},\n\nنرجو منك إكمال استبيان تجربتك:\n{survey_url}"
|
|
else:
|
|
message = f"Dear {patient.get_full_name()},\n\nPlease complete your experience survey:\n{survey_url}"
|
|
|
|
return NotificationService.send_sms(
|
|
phone=recipient,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata={'survey_id': str(survey_instance.id), 'language': language}
|
|
)
|
|
|
|
elif survey_instance.delivery_channel == 'whatsapp':
|
|
recipient = survey_instance.recipient_phone or patient.phone
|
|
if language == 'ar':
|
|
message = f"عزيزي {patient.get_full_name()},\n\nنرجو منك إكمال استبيان تجربتك:\n{survey_url}"
|
|
else:
|
|
message = f"Dear {patient.get_full_name()},\n\nPlease complete your experience survey:\n{survey_url}"
|
|
|
|
return NotificationService.send_whatsapp(
|
|
phone=recipient,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata={'survey_id': str(survey_instance.id), 'language': language}
|
|
)
|
|
|
|
else: # email
|
|
recipient = survey_instance.recipient_email or patient.email
|
|
if language == 'ar':
|
|
subject = f"استبيان تجربتك - {survey_instance.survey_template.name_ar or survey_instance.survey_template.name}"
|
|
message = f"عزيزي {patient.get_full_name()},\n\nنرجو منك إكمال استبيان تجربتك:\n{survey_url}"
|
|
else:
|
|
subject = f"Your Experience Survey - {survey_instance.survey_template.name}"
|
|
message = f"Dear {patient.get_full_name()},\n\nPlease complete your experience survey:\n{survey_url}"
|
|
|
|
return NotificationService.send_email(
|
|
email=recipient,
|
|
subject=subject,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata={'survey_id': str(survey_instance.id), 'language': language}
|
|
)
|
|
|
|
|
|
# Convenience functions
|
|
def send_sms(phone, message, **kwargs):
|
|
"""Send SMS notification"""
|
|
return NotificationService.send_sms(phone, message, **kwargs)
|
|
|
|
|
|
def send_whatsapp(phone, message, **kwargs):
|
|
"""Send WhatsApp notification"""
|
|
return NotificationService.send_whatsapp(phone, message, **kwargs)
|
|
|
|
|
|
def send_email(email, subject, message, **kwargs):
|
|
"""Send Email notification"""
|
|
return NotificationService.send_email(email, subject, message, **kwargs)
|