HH/apps/surveys/services.py
2026-04-19 10:53:12 +03:00

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