421 lines
18 KiB
Python
421 lines
18 KiB
Python
"""
|
|
Survey Delivery Service - Sends surveys to patients via SMS/Email
|
|
|
|
This service handles:
|
|
- Generating secure survey URLs
|
|
- Sending SMS with survey links
|
|
- Sending emails with survey links
|
|
- Tracking delivery status
|
|
|
|
Uses NotificationService for all delivery operations.
|
|
"""
|
|
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SurveyDeliveryService:
|
|
"""Service for delivering surveys to patients"""
|
|
|
|
@staticmethod
|
|
def generate_survey_url(survey_instance) -> str:
|
|
"""
|
|
Generate secure survey URL with access token.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
|
|
Returns:
|
|
Full survey URL
|
|
"""
|
|
base_url = getattr(settings, "SURVEY_BASE_URL", "http://localhost:8000")
|
|
survey_path = survey_instance.get_survey_url()
|
|
full_url = f"{base_url}{survey_path}"
|
|
logger.info(f"Generated survey URL for {survey_instance.id}: {full_url}")
|
|
return full_url
|
|
|
|
@staticmethod
|
|
def generate_sms_message(
|
|
recipient_name: str,
|
|
survey_url: str,
|
|
hospital_name: str = None,
|
|
hospital_name_ar: str = None,
|
|
is_staff: bool = False,
|
|
language: str = "en",
|
|
) -> str:
|
|
"""
|
|
Generate SMS message with survey link.
|
|
|
|
Args:
|
|
recipient_name: Recipient's first name (patient or staff) - not used in new format
|
|
survey_url: Survey link
|
|
hospital_name: Hospital name (English)
|
|
hospital_name_ar: Hospital name (Arabic)
|
|
is_staff: Whether recipient is staff member - not used, same format for all
|
|
language: Language code ('en' or 'ar') - not used, bilingual format for all
|
|
|
|
Returns:
|
|
SMS message text
|
|
"""
|
|
facility = hospital_name or "our facility"
|
|
facility_ar = hospital_name_ar or facility
|
|
|
|
message = f"شكراً على زيارتك {facility_ar} والحمد لله على سلامتك\n\n"
|
|
message += f"نتطلع لسماع رأيك حول تجربتك الأخيرة عبر الرابط التالي لنسهم في تحسين خدماتنا\n"
|
|
message += f"{survey_url}\n\n"
|
|
message += f"دمتم بصحة\n\n"
|
|
message += "---\n\n"
|
|
message += f"Thank you for choosing {facility}\n\n"
|
|
message += f"Please help us improve the patient experience by telling us about your visit via this survey\n"
|
|
message += f"{survey_url}\n\n"
|
|
message += "We value your trust and look forward to receiving your invaluable feedback"
|
|
|
|
return message
|
|
|
|
@staticmethod
|
|
def generate_email_message(
|
|
recipient_name: str, survey_url: str, hospital_name: str = None, is_staff: bool = False
|
|
) -> str:
|
|
"""
|
|
Generate email message with survey link.
|
|
|
|
Args:
|
|
recipient_name: Recipient's first name (patient or staff)
|
|
survey_url: Survey link
|
|
hospital_name: Optional hospital name
|
|
is_staff: Whether recipient is staff member
|
|
|
|
Returns:
|
|
Email message text
|
|
"""
|
|
message = f"Dear {recipient_name},\n\n"
|
|
|
|
if hospital_name:
|
|
message += f"Thank you for being part of {hospital_name}.\n\n"
|
|
else:
|
|
message += "Thank you for being part of our hospital.\n\n"
|
|
|
|
if is_staff:
|
|
message += "We value your feedback and would appreciate it if you could take a moment to complete our staff experience survey.\n\n"
|
|
message += "Your feedback helps us improve our services and create a better work environment.\n\n"
|
|
else:
|
|
message += "We value your feedback and would appreciate it if you could take a moment to complete our patient experience survey.\n\n"
|
|
message += "Your feedback helps us improve our services and provide better care for all our patients.\n\n"
|
|
|
|
message += f"Survey Link: {survey_url}\n\n"
|
|
message += "This survey will take approximately 2-3 minutes to complete.\n\n"
|
|
message += "If you have any questions or concerns, please don't hesitate to contact us.\n\n"
|
|
message += "Thank you for helping us improve our services!\n\n"
|
|
|
|
if is_staff:
|
|
message += "Best regards,\n"
|
|
message += "Hospital Administration Team"
|
|
else:
|
|
message += "Best regards,\n"
|
|
message += "Patient Experience Team"
|
|
|
|
return message
|
|
|
|
@staticmethod
|
|
def send_survey_sms(survey_instance, language: str = "en") -> bool:
|
|
"""
|
|
Send survey via SMS using NotificationService API.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
language: Language code ('en' or 'ar')
|
|
|
|
Returns:
|
|
True if sent successfully, False otherwise
|
|
"""
|
|
logger.info(
|
|
f"Sending SMS for survey {survey_instance.id}, phone={survey_instance.recipient_phone}, language={language}"
|
|
)
|
|
|
|
if not survey_instance.recipient_phone:
|
|
logger.warning(f"No phone number for survey {survey_instance.id}")
|
|
return False
|
|
|
|
try:
|
|
# Generate survey URL and message
|
|
survey_url = SurveyDeliveryService.generate_survey_url(survey_instance)
|
|
full_name = survey_instance.get_recipient_name()
|
|
recipient_name = full_name.split()[0] if full_name and full_name.strip() else "there"
|
|
is_staff = survey_instance.staff is not None
|
|
hospital_name = survey_instance.hospital.get_display_name() if survey_instance.hospital else None
|
|
hospital_name_ar = survey_instance.hospital.get_display_name_ar() if survey_instance.hospital else None
|
|
message = SurveyDeliveryService.generate_sms_message(
|
|
recipient_name, survey_url, hospital_name, hospital_name_ar, is_staff, language
|
|
)
|
|
|
|
# Use NotificationService for delivery (supports API backend)
|
|
from apps.notifications.services import NotificationService
|
|
|
|
# Build metadata
|
|
metadata = {
|
|
"survey_id": str(survey_instance.id),
|
|
"hospital_id": str(survey_instance.hospital.id),
|
|
"is_staff_survey": is_staff,
|
|
}
|
|
|
|
if survey_instance.patient:
|
|
metadata["patient_id"] = str(survey_instance.patient.id)
|
|
if survey_instance.staff:
|
|
metadata["staff_id"] = str(survey_instance.staff.id)
|
|
|
|
# Send SMS via notification service (uses Mshastra if configured)
|
|
notification_log = NotificationService.send_sms(
|
|
phone=survey_instance.recipient_phone,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata=metadata,
|
|
)
|
|
|
|
# Update survey instance based on notification status
|
|
if notification_log and (notification_log.status == "sent" or notification_log.status == "pending"):
|
|
from apps.surveys.models import SurveyStatus
|
|
|
|
survey_instance.status = SurveyStatus.SENT
|
|
survey_instance.sent_at = timezone.now()
|
|
survey_instance.save(update_fields=["status", "sent_at"])
|
|
logger.info(f"Survey SMS sent successfully to {survey_instance.recipient_phone}")
|
|
return True
|
|
else:
|
|
logger.warning(f"Survey SMS delivery failed for {survey_instance.id}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error sending SMS for survey {survey_instance.id}: {str(e)}", exc_info=True)
|
|
return False
|
|
|
|
@staticmethod
|
|
def send_survey_email(survey_instance, language: str = "en") -> bool:
|
|
"""
|
|
Send survey via Email using NotificationService API.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
language: Language code ('en' or 'ar')
|
|
|
|
Returns:
|
|
True if sent successfully, False otherwise
|
|
"""
|
|
logger.info(
|
|
f"Sending Email for survey {survey_instance.id}, email={survey_instance.recipient_email}, language={language}"
|
|
)
|
|
|
|
if not survey_instance.recipient_email:
|
|
logger.warning(f"No email address for survey {survey_instance.id}")
|
|
return False
|
|
|
|
try:
|
|
# Generate survey URL and message
|
|
survey_url = SurveyDeliveryService.generate_survey_url(survey_instance)
|
|
full_name = survey_instance.get_recipient_name()
|
|
recipient_name = full_name.split()[0] if full_name and full_name.strip() else "there" # First name only
|
|
is_staff = survey_instance.staff is not None
|
|
hospital_name = survey_instance.hospital.get_display_name() if survey_instance.hospital else None
|
|
message = SurveyDeliveryService.generate_email_message(recipient_name, survey_url, hospital_name, is_staff)
|
|
|
|
# Use NotificationService for delivery (supports API backend)
|
|
from apps.notifications.services import NotificationService
|
|
|
|
# Build metadata
|
|
metadata = {
|
|
"survey_id": str(survey_instance.id),
|
|
"hospital_id": str(survey_instance.hospital.id),
|
|
"is_staff_survey": is_staff,
|
|
}
|
|
|
|
if survey_instance.patient:
|
|
metadata["patient_id"] = str(survey_instance.patient.id)
|
|
if survey_instance.staff:
|
|
metadata["staff_id"] = str(survey_instance.staff.id)
|
|
|
|
# Set email subject
|
|
hospital_display = survey_instance.hospital.get_display_name() if survey_instance.hospital else ""
|
|
if is_staff:
|
|
subject = f"Staff Experience Survey - {hospital_display}"
|
|
else:
|
|
subject = f"Patient Experience Survey - {hospital_display}"
|
|
|
|
# Try API first, fallback to regular email
|
|
notification_log = NotificationService.send_email_via_api(
|
|
message=message,
|
|
email=survey_instance.recipient_email,
|
|
subject=subject,
|
|
html_message=None, # Plain text for now
|
|
related_object=survey_instance,
|
|
metadata=metadata,
|
|
)
|
|
|
|
# If API is disabled or failed, fallback to regular send_email
|
|
if notification_log is None:
|
|
logger.info("Email API disabled or returned None, falling back to regular email")
|
|
notification_log = NotificationService.send_email(
|
|
email=survey_instance.recipient_email,
|
|
subject=subject,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata=metadata,
|
|
)
|
|
|
|
# Update survey instance based on notification status
|
|
if notification_log and (notification_log.status == "sent" or notification_log.status == "pending"):
|
|
from apps.surveys.models import SurveyStatus
|
|
|
|
survey_instance.status = SurveyStatus.SENT
|
|
survey_instance.sent_at = timezone.now()
|
|
survey_instance.save(update_fields=["status", "sent_at"])
|
|
logger.info(f"Survey email sent successfully to {survey_instance.recipient_email}")
|
|
return True
|
|
else:
|
|
logger.warning(f"Survey email delivery failed for {survey_instance.id}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error sending email for survey {survey_instance.id}: {str(e)}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def deliver_survey(survey_instance, language: str = None) -> bool:
|
|
"""
|
|
Deliver survey based on configured delivery channel.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
language: Language code ('en' or 'ar') - if not provided, auto-detect from patient
|
|
|
|
Returns:
|
|
True if delivered successfully, False otherwise
|
|
"""
|
|
logger.info(
|
|
f"Delivering survey {survey_instance.id}, channel={survey_instance.delivery_channel}, phone={survey_instance.recipient_phone}"
|
|
)
|
|
|
|
# Auto-detect language from patient if not provided
|
|
if not language and survey_instance.patient:
|
|
language = getattr(survey_instance.patient, "preferred_language", "en")
|
|
|
|
# Normalize delivery channel to lowercase for comparison
|
|
delivery_channel = (survey_instance.delivery_channel or "").lower()
|
|
|
|
# Get recipient (patient or staff)
|
|
recipient = survey_instance.get_recipient()
|
|
logger.info(f"Survey {survey_instance.id} recipient: {recipient}")
|
|
|
|
# Ensure contact info is set
|
|
if delivery_channel == "sms":
|
|
if recipient:
|
|
survey_instance.recipient_phone = survey_instance.recipient_phone or recipient.phone
|
|
logger.info(f"Survey {survey_instance.id} SMS - recipient_phone: {survey_instance.recipient_phone}")
|
|
elif delivery_channel == "email":
|
|
if recipient:
|
|
survey_instance.recipient_email = survey_instance.recipient_email or getattr(recipient, "email", None)
|
|
|
|
# Save contact info
|
|
survey_instance.save()
|
|
|
|
# Send based on channel
|
|
if delivery_channel == "sms":
|
|
return SurveyDeliveryService.send_survey_sms(survey_instance, language=language)
|
|
elif delivery_channel == "email":
|
|
return SurveyDeliveryService.send_survey_email(survey_instance, language=language)
|
|
elif delivery_channel == "whatsapp":
|
|
return SurveyDeliveryService.send_survey_whatsapp(survey_instance, language=language)
|
|
else:
|
|
logger.error(f"Unknown delivery channel: {survey_instance.delivery_channel}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def send_survey_whatsapp(survey_instance, language: str = "en") -> bool:
|
|
"""
|
|
Send survey via WhatsApp using NotificationService API.
|
|
|
|
Args:
|
|
survey_instance: SurveyInstance object
|
|
language: Language code ('en' or 'ar')
|
|
|
|
Returns:
|
|
True if sent successfully, False otherwise
|
|
"""
|
|
logger.info(f"Sending WhatsApp for survey {survey_instance.id}, phone={survey_instance.recipient_phone}")
|
|
|
|
if not survey_instance.recipient_phone:
|
|
logger.warning(f"No phone number for WhatsApp survey {survey_instance.id}")
|
|
return False
|
|
|
|
try:
|
|
# Generate survey URL and message
|
|
survey_url = SurveyDeliveryService.generate_survey_url(survey_instance)
|
|
full_name = survey_instance.get_recipient_name()
|
|
recipient_name = full_name.split()[0] if full_name and full_name.strip() else "there"
|
|
is_staff = survey_instance.staff is not None
|
|
hospital_name = survey_instance.hospital.get_display_name() if survey_instance.hospital else None
|
|
hospital_name_ar = survey_instance.hospital.get_display_name_ar() if survey_instance.hospital else None
|
|
|
|
if language == "ar":
|
|
facility = hospital_name_ar or hospital_name or "our facility"
|
|
message = f"مرحباً {recipient_name}،\n\n"
|
|
if facility:
|
|
message += f"شكراً لكونك جزءاً من {facility}. "
|
|
if is_staff:
|
|
message += "نقدر ملاحظاتك! يرجى إكمال استبيان تجربة الموظفين:\n\n"
|
|
else:
|
|
message += "نقدر ملاحظاتك! يرجى إكمال استبيان تجربة المريض:\n\n"
|
|
message += f"{survey_url}\n\n"
|
|
message += "سيستغرق هذا الاستبيان حوالي 2-3 دقائق."
|
|
else:
|
|
facility = hospital_name or "our facility"
|
|
message = f"Hello {recipient_name},\n\n"
|
|
if facility:
|
|
message += f"Thank you for being part of {facility}. "
|
|
if is_staff:
|
|
message += "We value your feedback! Please complete our staff experience survey:\n\n"
|
|
else:
|
|
message += "We value your feedback! Please complete our patient experience survey:\n\n"
|
|
message += f"{survey_url}\n\n"
|
|
message += "This survey will take approximately 2-3 minutes."
|
|
|
|
# Use NotificationService for WhatsApp delivery
|
|
from apps.notifications.services import NotificationService
|
|
|
|
metadata = {
|
|
"survey_id": str(survey_instance.id),
|
|
"hospital_id": str(survey_instance.hospital.id) if survey_instance.hospital else None,
|
|
"is_staff_survey": is_staff,
|
|
"channel": "whatsapp",
|
|
}
|
|
|
|
if survey_instance.patient:
|
|
metadata["patient_id"] = str(survey_instance.patient.id)
|
|
if survey_instance.staff:
|
|
metadata["staff_id"] = str(survey_instance.staff.id)
|
|
|
|
notification_log = NotificationService.send_whatsapp(
|
|
phone=survey_instance.recipient_phone,
|
|
message=message,
|
|
related_object=survey_instance,
|
|
metadata=metadata,
|
|
)
|
|
|
|
# Update survey instance based on notification status
|
|
if notification_log and (notification_log.status == "sent" or notification_log.status == "pending"):
|
|
from apps.surveys.models import SurveyStatus
|
|
|
|
survey_instance.status = SurveyStatus.SENT
|
|
survey_instance.sent_at = timezone.now()
|
|
survey_instance.save(update_fields=["status", "sent_at"])
|
|
logger.info(f"Survey WhatsApp sent successfully to {survey_instance.recipient_phone}")
|
|
return True
|
|
else:
|
|
logger.warning(f"Survey WhatsApp delivery failed for {survey_instance.id}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error sending WhatsApp for survey {survey_instance.id}: {str(e)}", exc_info=True)
|
|
return False
|