784 lines
28 KiB
Python
784 lines
28 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.
|
|
|
|
Routing order:
|
|
1. External SMS API (if SMS_API_ENABLED)
|
|
2. Mshastra (if SMS_PROVIDER=mshastra and credentials configured)
|
|
3. Twilio (if SMS_PROVIDER=twilio and credentials configured)
|
|
4. Console logger (fallback)
|
|
|
|
Args:
|
|
phone: Recipient phone number
|
|
message: SMS message text
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
sms_api_config = settings.EXTERNAL_NOTIFICATION_API.get("sms", {})
|
|
if sms_api_config.get("enabled", False):
|
|
return NotificationService.send_sms_via_api(
|
|
message=message, phone=phone, related_object=related_object, metadata=metadata
|
|
)
|
|
|
|
sms_config = settings.NOTIFICATION_CHANNELS.get("sms", {})
|
|
provider = sms_config.get("provider", "console")
|
|
|
|
log = NotificationLog.objects.create(
|
|
channel="sms",
|
|
recipient=phone,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider=provider,
|
|
metadata=metadata or {},
|
|
)
|
|
|
|
if provider == "mshastra":
|
|
return NotificationService._send_sms_mshastra(log, phone, message)
|
|
|
|
if provider == "twilio":
|
|
return NotificationService._send_sms_twilio(log, phone, message)
|
|
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
@staticmethod
|
|
def _send_sms_twilio(log, phone, message):
|
|
"""
|
|
Send SMS via Twilio SDK.
|
|
|
|
Requires: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and
|
|
either TWILIO_PHONE_NUMBER or TWILIO_MESSAGING_SERVICE_SID.
|
|
"""
|
|
from twilio.base.exceptions import TwilioRestException
|
|
from twilio.rest import Client
|
|
|
|
account_sid = settings.TWILIO_ACCOUNT_SID
|
|
auth_token = settings.TWILIO_AUTH_TOKEN
|
|
|
|
if not account_sid or not auth_token:
|
|
logger.warning("Twilio credentials not configured, falling back to console")
|
|
log.provider = "console"
|
|
log.save(update_fields=["provider"])
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
try:
|
|
client = Client(account_sid, auth_token)
|
|
|
|
twilio_kwargs = {
|
|
"body": message,
|
|
"to": phone,
|
|
}
|
|
|
|
if settings.TWILIO_MESSAGING_SERVICE_SID:
|
|
twilio_kwargs["messaging_service_sid"] = settings.TWILIO_MESSAGING_SERVICE_SID
|
|
elif settings.TWILIO_PHONE_NUMBER:
|
|
twilio_kwargs["from_"] = settings.TWILIO_PHONE_NUMBER
|
|
else:
|
|
logger.warning("Twilio phone number or messaging service SID not configured, falling back to console")
|
|
log.provider = "console"
|
|
log.save(update_fields=["provider"])
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
twilio_message = client.messages.create(**twilio_kwargs)
|
|
|
|
log.mark_sent(provider_message_id=twilio_message.sid)
|
|
log.provider_response = {
|
|
"sid": twilio_message.sid,
|
|
"status": twilio_message.status,
|
|
"direction": twilio_message.direction,
|
|
"to": twilio_message.to,
|
|
}
|
|
log.save(update_fields=["provider_response"])
|
|
|
|
logger.info(f"SMS sent via Twilio to {phone}: sid={twilio_message.sid}")
|
|
return log
|
|
|
|
except TwilioRestException as e:
|
|
logger.error(f"Twilio error sending SMS to {phone}: {e}")
|
|
log.mark_failed(str(e))
|
|
return log
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error sending SMS via Twilio to {phone}: {e}", exc_info=True)
|
|
log.mark_failed(str(e))
|
|
return log
|
|
|
|
@staticmethod
|
|
def _send_sms_mshastra(log, phone, message):
|
|
"""
|
|
Send SMS via Mshastra API.
|
|
|
|
Requires: MSHASTRA_USERNAME, MSHASTRA_PASSWORD, and MSHASTRA_SENDER_ID.
|
|
API: https://mshastra.com/sendurl.aspx
|
|
"""
|
|
import requests
|
|
|
|
username = settings.MSHASTRA_USERNAME
|
|
password = settings.MSHASTRA_PASSWORD
|
|
sender_id = settings.MSHASTRA_SENDER_ID
|
|
|
|
if not username or not password:
|
|
logger.warning("Mshastra credentials not configured, falling back to console")
|
|
log.provider = "console"
|
|
log.save(update_fields=["provider"])
|
|
logger.info(f"[SMS Console] To: {phone} | Message: {message}")
|
|
log.mark_sent()
|
|
return log
|
|
|
|
try:
|
|
url = "https://mshastra.com/sendurl.aspx"
|
|
params = {
|
|
"user": username,
|
|
"pwd": password,
|
|
"senderid": sender_id,
|
|
"mobileno": phone,
|
|
"msgtext": message,
|
|
"priority": "High",
|
|
"CountryCode": "ALL",
|
|
}
|
|
|
|
response = requests.get(url, params=params, timeout=30)
|
|
response_text = response.text.strip()
|
|
|
|
log.provider_response = {"status_code": response.status_code, "response": response_text}
|
|
log.save(update_fields=["provider_response"])
|
|
|
|
if "Send Successful" in response_text:
|
|
log.mark_sent()
|
|
logger.info(f"SMS sent via Mshastra to {phone}: {response_text}")
|
|
else:
|
|
log.mark_failed(response_text)
|
|
logger.warning(f"Mshastra SMS failed for {phone}: {response_text}")
|
|
|
|
return log
|
|
|
|
except requests.exceptions.Timeout:
|
|
logger.error(f"Mshastra API timeout for {phone}")
|
|
log.mark_failed("Request timeout")
|
|
return log
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
logger.error(f"Mshastra API connection error for {phone}")
|
|
log.mark_failed("Connection error")
|
|
return log
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error sending SMS via Mshastra to {phone}: {e}", exc_info=True)
|
|
log.mark_failed(str(e))
|
|
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,
|
|
notification_type="system",
|
|
user=None,
|
|
):
|
|
"""
|
|
Send Email notification and create corresponding in-app 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)
|
|
notification_type: Type of notification (default: 'system')
|
|
user: User instance to create in-app notification for (optional, will try to lookup by email)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
from apps.accounts.models import User
|
|
|
|
# Check if Email API is enabled and use it (simulator or external API)
|
|
email_api_config = settings.EXTERNAL_NOTIFICATION_API.get("email", {})
|
|
if email_api_config.get("enabled", False):
|
|
log = NotificationService.send_email_via_api(
|
|
message=message,
|
|
email=email,
|
|
subject=subject,
|
|
html_message=html_message,
|
|
related_object=related_object,
|
|
metadata=metadata,
|
|
)
|
|
# Create in-app notification after sending via API
|
|
NotificationService._create_in_app_notification_from_email(
|
|
log, email, subject, message, notification_type, related_object, user
|
|
)
|
|
return log
|
|
|
|
# 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 {},
|
|
)
|
|
|
|
# Create in-app notification
|
|
NotificationService._create_in_app_notification_from_email(
|
|
log, email, subject, message, notification_type, related_object, user
|
|
)
|
|
|
|
# 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 _create_in_app_notification_from_email(
|
|
log, email, subject, message, notification_type, related_object=None, user=None
|
|
):
|
|
"""
|
|
Create in-app notification from email data.
|
|
Private helper method.
|
|
"""
|
|
from apps.accounts.models import User
|
|
from .models import UserNotification
|
|
|
|
# Try to find user if not provided
|
|
if not user:
|
|
try:
|
|
user = User.objects.get(email=email)
|
|
except User.DoesNotExist:
|
|
# User not found, skip in-app notification
|
|
return None
|
|
|
|
# Create in-app notification
|
|
notification = UserNotification.objects.create(
|
|
user=user,
|
|
title=subject,
|
|
message=message[:500], # Truncate for preview
|
|
notification_type=notification_type,
|
|
email_log=log,
|
|
content_object=related_object,
|
|
)
|
|
|
|
logger.info(f"In-app notification created for {user.email}: {subject}")
|
|
return notification
|
|
|
|
@staticmethod
|
|
def send_email_via_api(message, email, subject, html_message=None, related_object=None, metadata=None):
|
|
"""
|
|
Send email via external API endpoint with retry logic.
|
|
|
|
Args:
|
|
message: Email message (plain text)
|
|
email: Recipient email address
|
|
subject: Email subject
|
|
html_message: Email message (HTML) (optional)
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
import requests
|
|
import time
|
|
|
|
# Check if enabled
|
|
email_config = settings.EXTERNAL_NOTIFICATION_API.get("email", {})
|
|
if not email_config.get("enabled", False):
|
|
logger.warning("Email API is disabled. Skipping send_email_via_api")
|
|
return None
|
|
|
|
# Create notification log
|
|
log = NotificationLog.objects.create(
|
|
channel="email",
|
|
recipient=email,
|
|
subject=subject,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider="api",
|
|
metadata={
|
|
"api_url": email_config.get("url"),
|
|
"auth_method": email_config.get("auth_method"),
|
|
**(metadata or {}),
|
|
},
|
|
)
|
|
|
|
# Prepare request payload
|
|
payload = {
|
|
"to": email,
|
|
"subject": subject,
|
|
"message": message,
|
|
}
|
|
if html_message:
|
|
payload["html_message"] = html_message
|
|
|
|
# Prepare headers
|
|
headers = {"Content-Type": "application/json"}
|
|
api_key = email_config.get("api_key", "")
|
|
auth_method = email_config.get("auth_method", "bearer")
|
|
|
|
if auth_method == "bearer":
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
elif auth_method == "api_key":
|
|
headers["X-API-KEY"] = api_key
|
|
|
|
# Retry logic
|
|
max_retries = email_config.get("max_retries", 3)
|
|
retry_delay = email_config.get("retry_delay", 2)
|
|
timeout = email_config.get("timeout", 10)
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
logger.info(f"Sending email via API (attempt {attempt + 1}/{max_retries}) to {email}")
|
|
|
|
response = requests.post(email_config.get("url"), json=payload, headers=headers, timeout=timeout)
|
|
|
|
# API runs in background, accept any 2xx response
|
|
if 200 <= response.status_code < 300:
|
|
log.mark_sent()
|
|
logger.info(f"Email sent via API to {email}: {subject}")
|
|
return log
|
|
else:
|
|
logger.warning(f"API returned status {response.status_code}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed(f"API returned status {response.status_code}")
|
|
continue
|
|
|
|
except requests.exceptions.Timeout:
|
|
logger.warning(f"Timeout on attempt {attempt + 1}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed("Request timeout")
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
logger.warning(f"Connection error on attempt {attempt + 1}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed("Connection error")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error: {str(e)}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed(str(e))
|
|
|
|
# Wait before retry (exponential backoff)
|
|
if attempt < max_retries - 1:
|
|
time.sleep(retry_delay * (2**attempt))
|
|
|
|
return log
|
|
|
|
@staticmethod
|
|
def send_sms_via_api(message, phone, related_object=None, metadata=None):
|
|
"""
|
|
Send SMS via external API endpoint with retry logic.
|
|
|
|
Args:
|
|
message: SMS message text
|
|
phone: Recipient phone number
|
|
related_object: Related model instance (optional)
|
|
metadata: Additional metadata dict (optional)
|
|
|
|
Returns:
|
|
NotificationLog instance
|
|
"""
|
|
import requests
|
|
import time
|
|
|
|
# Check if enabled
|
|
sms_config = settings.EXTERNAL_NOTIFICATION_API.get("sms", {})
|
|
if not sms_config.get("enabled", False):
|
|
logger.warning("SMS API is disabled. Skipping send_sms_via_api")
|
|
return None
|
|
|
|
# Create notification log
|
|
log = NotificationLog.objects.create(
|
|
channel="sms",
|
|
recipient=phone,
|
|
message=message,
|
|
content_object=related_object,
|
|
provider="api",
|
|
metadata={
|
|
"api_url": sms_config.get("url"),
|
|
"auth_method": sms_config.get("auth_method"),
|
|
**(metadata or {}),
|
|
},
|
|
)
|
|
|
|
# Prepare request payload
|
|
payload = {
|
|
"to": phone,
|
|
"message": message,
|
|
}
|
|
|
|
# Prepare headers
|
|
headers = {"Content-Type": "application/json"}
|
|
api_key = sms_config.get("api_key", "")
|
|
auth_method = sms_config.get("auth_method", "bearer")
|
|
|
|
if auth_method == "bearer":
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
elif auth_method == "api_key":
|
|
headers["X-API-KEY"] = api_key
|
|
|
|
# Retry logic
|
|
max_retries = sms_config.get("max_retries", 3)
|
|
retry_delay = sms_config.get("retry_delay", 2)
|
|
timeout = sms_config.get("timeout", 10)
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
logger.info(f"Sending SMS via API (attempt {attempt + 1}/{max_retries}) to {phone}")
|
|
|
|
response = requests.post(sms_config.get("url"), json=payload, headers=headers, timeout=timeout)
|
|
|
|
# API runs in background, accept any 2xx response
|
|
if 200 <= response.status_code < 300:
|
|
log.mark_sent()
|
|
logger.info(f"SMS sent via API to {phone}")
|
|
return log
|
|
else:
|
|
logger.warning(f"API returned status {response.status_code}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed(f"API returned status {response.status_code}")
|
|
continue
|
|
|
|
except requests.exceptions.Timeout:
|
|
logger.warning(f"Timeout on attempt {attempt + 1}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed("Request timeout")
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
logger.warning(f"Connection error on attempt {attempt + 1}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed("Connection error")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error: {str(e)}")
|
|
if attempt == max_retries - 1:
|
|
log.mark_failed(str(e))
|
|
|
|
# Wait before retry (exponential backoff)
|
|
if attempt < max_retries - 1:
|
|
time.sleep(retry_delay * (2**attempt))
|
|
|
|
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
|
|
survey_label = (
|
|
survey_instance.survey_template.name_ar or survey_instance.survey_template.name
|
|
if survey_instance.survey_template
|
|
else survey_instance.metadata.get("patient_type", "Patient Experience Survey")
|
|
)
|
|
if language == "ar":
|
|
subject = f"استبيان تجربتك - {survey_label}"
|
|
message = f"عزيزي {patient.get_full_name()},\n\nنرجو منك إكمال استبيان تجربتك:\n{survey_url}"
|
|
else:
|
|
subject = f"Your Experience Survey - {survey_label}"
|
|
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)
|
|
|
|
|
|
def send_notification(recipient, title, message, **kwargs):
|
|
"""Send generic notification to a user"""
|
|
return NotificationService.send_notification(recipient, title, message, **kwargs)
|
|
|
|
|
|
def create_in_app_notification(
|
|
user,
|
|
title,
|
|
message,
|
|
notification_type="system",
|
|
title_ar="",
|
|
message_ar="",
|
|
related_object=None,
|
|
action_url="",
|
|
email_log=None,
|
|
):
|
|
"""
|
|
Create an in-app notification for a user.
|
|
|
|
This is automatically called when sending emails to create
|
|
corresponding in-app notifications.
|
|
|
|
Args:
|
|
user: User instance to notify
|
|
title: Notification title (EN)
|
|
message: Notification message (EN)
|
|
notification_type: Type of notification (e.g., 'complaint_assigned')
|
|
title_ar: Arabic title (optional)
|
|
message_ar: Arabic message (optional)
|
|
related_object: Related model instance (optional)
|
|
action_url: URL to navigate when clicked (optional)
|
|
email_log: Associated NotificationLog instance (optional)
|
|
|
|
Returns:
|
|
UserNotification instance
|
|
"""
|
|
from .models import UserNotification
|
|
|
|
notification = UserNotification.objects.create(
|
|
user=user,
|
|
title=title,
|
|
title_ar=title_ar or title,
|
|
message=message,
|
|
message_ar=message_ar or message,
|
|
notification_type=notification_type,
|
|
action_url=action_url,
|
|
email_log=email_log,
|
|
)
|
|
|
|
# Set related object if provided
|
|
if related_object:
|
|
notification.content_object = related_object
|
|
notification.save(update_fields=["content_type", "object_id"])
|
|
|
|
return notification
|