""" 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