HH/apps/notifications/settings_service.py
ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

964 lines
42 KiB
Python

"""
Notification Settings Service
Integrates notification settings with the notification service.
Checks settings before sending notifications.
"""
import logging
from functools import wraps
from datetime import datetime, timedelta
from celery import shared_task
from django.db import models
from django.utils import timezone
from .settings_models import HospitalNotificationSettings
logger = logging.getLogger(__name__)
def _get_quiet_hours_end_datetime(settings):
now = timezone.now()
end_time = settings.quiet_hours_end
target = now.replace(hour=end_time.hour, minute=end_time.minute, second=0, microsecond=0)
if target <= now:
target += timedelta(days=1)
return target
@shared_task
def _send_deferred_sms(phone, message, related_object_app=None, related_object_model=None, related_object_id=None):
from .services import NotificationService
related_object = None
if related_object_app and related_object_model and related_object_id:
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get(app_label=related_object_app, model=related_object_model)
related_object = ct.get_object_for_this_type(id=related_object_id)
NotificationService.send_sms(phone=phone, message=message, related_object=related_object)
logger.info(f"Deferred SMS sent to {phone}")
@shared_task
def _send_deferred_whatsapp(phone, message, related_object_app=None, related_object_model=None, related_object_id=None):
from .services import NotificationService
related_object = None
if related_object_app and related_object_model and related_object_id:
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get(app_label=related_object_app, model=related_object_model)
related_object = ct.get_object_for_this_type(id=related_object_id)
NotificationService.send_whatsapp(phone=phone, message=message, related_object=related_object)
logger.info(f"Deferred WhatsApp sent to {phone}")
def _serialize_related_object(obj):
if obj is None:
return None, None, None
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get_for_model(obj)
return ct.app_label, ct.model, str(obj.pk)
def check_notification_settings(event, hospital_id=None):
"""
Decorator to check notification settings before sending.
Usage:
@check_notification_settings('explanation_requested', 'hospital_id')
def send_explanation_request(..., hospital_id=None):
...
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Extract hospital_id from kwargs or args
hid = kwargs.get(hospital_id)
if hid is None and len(args) > 0:
# Try to find hospital_id in args based on function signature
import inspect
sig = inspect.signature(func)
params = list(sig.parameters.keys())
if hospital_id in params:
idx = params.index(hospital_id)
if idx < len(args):
hid = args[idx]
if hid is None:
# Try to get from related_object
related_object = kwargs.get("related_object")
if related_object and hasattr(related_object, "hospital_id"):
hid = related_object.hospital_id
if hid:
settings = HospitalNotificationSettings.get_for_hospital(hid)
# Check if notifications are enabled
if not settings.notifications_enabled:
logger.info(f"Notifications disabled for hospital {hid}")
return None
# Check if this event is enabled for the channel
# Channel is determined by the function name or a 'channel' kwarg
channel = kwargs.get("channel", "email")
if "sms" in func.__name__:
channel = "sms"
elif "whatsapp" in func.__name__:
channel = "whatsapp"
elif "email" in func.__name__:
channel = "email"
if not settings.is_channel_enabled(event, channel):
logger.info(f"Notification {event} via {channel} disabled for hospital {hid}")
return None
if channel in ["sms", "whatsapp"] and settings.is_quiet_hours():
eta = _get_quiet_hours_end_datetime(settings)
logger.info(f"Notification {event} deferred to {eta} due to quiet hours for hospital {hid}")
return eta
return func(*args, **kwargs)
return wrapper
return decorator
class NotificationServiceWithSettings:
"""
Wrapper around NotificationService that respects hospital settings.
Usage:
from apps.notifications.settings_service import NotificationServiceWithSettings
NotificationServiceWithSettings.send_explanation_requested(
staff_email, complaint, hospital_id=hospital.id
)
"""
@staticmethod
def _defer_sms_if_quiet_hours(settings, phone, message, related_object):
from .services import NotificationService
if settings.is_quiet_hours():
eta = _get_quiet_hours_end_datetime(settings)
app, model, obj_id = _serialize_related_object(related_object)
_send_deferred_sms.apply_async(
eta=eta,
kwargs={
"phone": phone,
"message": message,
"related_object_app": app,
"related_object_model": model,
"related_object_id": obj_id,
},
)
return ("sms_deferred", eta.isoformat())
result = NotificationService.send_sms(phone=phone, message=message, related_object=related_object)
return ("sms", result)
@staticmethod
def _defer_whatsapp_if_quiet_hours(settings, phone, message, related_object):
from .services import NotificationService
if settings.is_quiet_hours():
eta = _get_quiet_hours_end_datetime(settings)
app, model, obj_id = _serialize_related_object(related_object)
_send_deferred_whatsapp.apply_async(
eta=eta,
kwargs={
"phone": phone,
"message": message,
"related_object_app": app,
"related_object_model": model,
"related_object_id": obj_id,
},
)
return ("whatsapp_deferred", eta.isoformat())
result = NotificationService.send_whatsapp(phone=phone, message=message, related_object=related_object)
return ("whatsapp", result)
@staticmethod
def _get_hospital_id_from_complaint(complaint):
"""Extract hospital_id from complaint object"""
if hasattr(complaint, "hospital_id"):
return complaint.hospital_id
return None
@staticmethod
def send_explanation_requested(staff_email, complaint, custom_message=None):
"""
Send explanation request notification to staff.
Respects hospital notification settings.
"""
from .services import NotificationService
# Build HTML with navy theme
staff_name = getattr(complaint.assigned_to, 'get_full_name', 'Staff Member')() if hasattr(complaint, 'assigned_to') and complaint.assigned_to else 'Staff Member'
html_message = f"""
<!DOCTYPE html>
<html>
<head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: linear-gradient(135deg, #005696 0%, #007bbd 100%); padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
</style></head>
<body>
<div class="container">
<div class="header">
<h1>Department Response Requested</h1>
</div>
<div class="content">
<p>Dear {staff_name},</p>
<p>A department response has been requested for complaint #{complaint.id}.</p>
<p><strong>Title:</strong> {getattr(complaint, 'title', 'N/A')}</p>
<p><strong>Deadline:</strong> Please submit as soon as possible</p>
{f'<p><strong>Note:</strong> {custom_message}</p>' if custom_message else ''}
</div>
</div>
</body>
</html>
"""
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
# Fallback: send without settings check
return NotificationService.send_email(
email=staff_email,
subject=f"Department Response Request - Complaint #{complaint.id}",
message=custom_message or f"Please provide department response for complaint #{complaint.id}",
html_message=html_message,
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
# Check master switch
if not settings.notifications_enabled:
logger.info(f"Notifications disabled for hospital {hospital_id}")
return results
# Email
if settings.explanation_requested_email:
result = NotificationService.send_email(
email=staff_email,
subject=f"Department Response Request - Complaint #{complaint.id}",
message=custom_message or f"Please provide department response for complaint #{complaint.id}",
html_message=html_message,
related_object=complaint,
)
results.append(("email", result))
return results
@staticmethod
def send_inquiry_department_assigned(department, inquiry, context_note_en="", context_note_ar="", recipient_type="staff"):
from apps.accounts.models import User
from .services import NotificationService
context_note_parts = []
if context_note_en:
context_note_parts.append(context_note_en)
if context_note_ar:
context_note_parts.append(context_note_ar)
has_context = bool(context_note_parts)
note_html = ""
if has_context:
note_section = "<br>".join(context_note_parts)
note_html = f"""
<div style="background: #eef6fb; border-left: 4px solid #005696; padding: 15px; margin: 20px 0;">
<strong>Context / Summary:</strong><br>
{note_section}
</div>"""
plain_note = " ".join(context_note_parts)
plain_message = f"A new inquiry has been assigned to {department.get_localized_name()}: {inquiry.subject}"
if plain_note:
plain_message += f"\n\nContext: {plain_note}"
if recipient_type == "department_email":
if department.email:
html_message = f"""
<!DOCTYPE html>
<html><head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: #005696; padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
.info-box {{ background: #fffbeb; border-left: 4px solid #d97706; padding: 15px; margin: 20px 0; }}
</style></head>
<body>
<div class="container">
<div class="header"><h1>New Inquiry Assigned to Your Department</h1></div>
<div class="content">
<p>Dear {department.get_localized_name()} Team,</p>
<p>A new inquiry has been assigned to the <strong>{department.get_localized_name()}</strong> department for response.</p>
<div class="info-box">
<strong>Reference:</strong> {inquiry.reference_number}<br>
<strong>Subject:</strong> {inquiry.subject}<br>
<strong>Category:</strong> {inquiry.get_category_display()}<br>
<strong>Priority:</strong> {inquiry.get_priority_display()}
</div>
{note_html}
<p>Please log in to review and respond to this inquiry.</p>
</div>
</div>
</body></html>
"""
NotificationService.send_email(
email=department.email,
subject=f"New Inquiry for {department.get_localized_name()} - {inquiry.reference_number}",
message=plain_message,
html_message=html_message,
related_object=inquiry,
)
else:
respondents = User.objects.filter(
is_active=True,
department=department,
).filter(
models.Q(groups__name="Champion")
| models.Q(groups__name="Department Manager")
).distinct()
for user in respondents:
html_message = f"""
<!DOCTYPE html>
<html><head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: #005696; padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
.info-box {{ background: #fffbeb; border-left: 4px solid #d97706; padding: 15px; margin: 20px 0; }}
</style></head>
<body>
<div class="container">
<div class="header"><h1>New Inquiry Assigned to Your Department</h1></div>
<div class="content">
<p>Dear {user.get_full_name()},</p>
<p>A new inquiry has been assigned to the <strong>{department.get_localized_name()}</strong> department for response.</p>
<div class="info-box">
<strong>Reference:</strong> {inquiry.reference_number}<br>
<strong>Subject:</strong> {inquiry.subject}<br>
<strong>Category:</strong> {inquiry.get_category_display()}<br>
<strong>Priority:</strong> {inquiry.get_priority_display()}
</div>
{note_html}
<p>Please log in to review and respond to this inquiry.</p>
</div>
</div>
</body></html>
"""
if user.email:
NotificationService.send_email(
email=user.email,
subject=f"New Inquiry for {department.get_localized_name()} - {inquiry.reference_number}",
message=plain_message,
html_message=html_message,
related_object=inquiry,
)
from .services import create_in_app_notification
in_app_msg = f"Inquiry {inquiry.reference_number}: {inquiry.subject[:100]}"
in_app_msg_ar = f"استفسار {inquiry.reference_number}: {inquiry.subject[:100]}"
if context_note_en:
in_app_msg += f"{context_note_en[:80]}"
if context_note_ar:
in_app_msg_ar += f"{context_note_ar[:80]}"
create_in_app_notification(
user=user,
title=f"New inquiry for {department.get_localized_name()}",
title_ar=f"استفسار جديد لقسم {department.name_ar or department.get_localized_name()}",
message=in_app_msg,
message_ar=in_app_msg_ar,
notification_type="inquiry_assigned",
action_url=f"/inquiries/{inquiry.pk}/department-response/",
)
@staticmethod
def send_observation_department_assigned(department, observation, context_note_en="", context_note_ar="", recipient_type="staff"):
from apps.accounts.models import User
from .services import NotificationService
context_note_parts = []
if context_note_en:
context_note_parts.append(context_note_en)
if context_note_ar:
context_note_parts.append(context_note_ar)
has_context = bool(context_note_parts)
note_html = ""
if has_context:
note_section = "<br>".join(context_note_parts)
note_html = f"""
<div style="background: #eef6fb; border-left: 4px solid #005696; padding: 15px; margin: 20px 0;">
<strong>Context / Summary:</strong><br>
{note_section}
</div>"""
plain_note = " ".join(context_note_parts)
tracking = observation.tracking_code or str(observation.pk)[:8]
plain_message = f"A new observation has been assigned to {department.get_localized_name()}: {observation.title or observation.description[:80]}"
if plain_note:
plain_message += f"\n\nContext: {plain_note}"
if recipient_type == "department_email":
if department.email:
html_message = f"""
<!DOCTYPE html>
<html><head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: #6b21a8; padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
.info-box {{ background: #fffbeb; border-left: 4px solid #d97706; padding: 15px; margin: 20px 0; }}
</style></head>
<body>
<div class="container">
<div class="header"><h1>New Observation Assigned to Your Department</h1></div>
<div class="content">
<p>Dear {department.get_localized_name()} Team,</p>
<p>A new observation has been assigned to the <strong>{department.get_localized_name()}</strong> department for response.</p>
<div class="info-box">
<strong>Tracking:</strong> {tracking}<br>
<strong>Title:</strong> {observation.title or 'N/A'}<br>
<strong>Severity:</strong> {observation.get_severity_display()}<br>
<strong>Category:</strong> {observation.category.name_en if observation.category else 'N/A'}
</div>
{note_html}
<p>Please log in to review and respond to this observation.</p>
</div>
</div>
</body></html>
"""
NotificationService.send_email(
email=department.email,
subject=f"New Observation for {department.get_localized_name()} - {tracking}",
message=plain_message,
html_message=html_message,
related_object=observation,
)
else:
respondents = User.objects.filter(
is_active=True,
department=department,
).filter(
models.Q(groups__name="Champion")
| models.Q(groups__name="Department Manager")
).distinct()
for user in respondents:
html_message = f"""
<!DOCTYPE html>
<html><head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: #6b21a8; padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
.info-box {{ background: #fffbeb; border-left: 4px solid #d97706; padding: 15px; margin: 20px 0; }}
</style></head>
<body>
<div class="container">
<div class="header"><h1>New Observation Assigned to Your Department</h1></div>
<div class="content">
<p>Dear {user.get_full_name()},</p>
<p>A new observation has been assigned to the <strong>{department.get_localized_name()}</strong> department for response.</p>
<div class="info-box">
<strong>Tracking:</strong> {tracking}<br>
<strong>Title:</strong> {observation.title or 'N/A'}<br>
<strong>Severity:</strong> {observation.get_severity_display()}<br>
<strong>Category:</strong> {observation.category.name_en if observation.category else 'N/A'}
</div>
{note_html}
<p>Please log in to review and respond to this observation.</p>
</div>
</div>
</body></html>
"""
if user.email:
NotificationService.send_email(
email=user.email,
subject=f"New Observation for {department.get_localized_name()} - {tracking}",
message=plain_message,
html_message=html_message,
related_object=observation,
)
from .services import create_in_app_notification
in_app_msg = f"Observation {tracking}: {(observation.title or observation.description[:80])[:100]}"
in_app_msg_ar = f"ملاحظة {tracking}: {(observation.title or observation.description[:80])[:100]}"
if context_note_en:
in_app_msg += f"{context_note_en[:80]}"
if context_note_ar:
in_app_msg_ar += f"{context_note_ar[:80]}"
create_in_app_notification(
user=user,
title=f"New observation for {department.get_localized_name()}",
title_ar=f"ملاحظة جديدة لقسم {department.name_ar or department.get_localized_name()}",
message=in_app_msg,
message_ar=in_app_msg_ar,
notification_type="observation_assigned",
action_url=f"/observations/{observation.pk}/department-response/",
)
@staticmethod
def send_inquiry_assigned(inquiry):
from .services import NotificationService, create_in_app_notification
if not inquiry.assigned_to:
return
if inquiry.assigned_to.email:
NotificationService.send_email(
email=inquiry.assigned_to.email,
subject=f"Inquiry Assigned to You - {inquiry.reference_number}",
message=f"You have been assigned inquiry {inquiry.reference_number}: {inquiry.subject}",
related_object=inquiry,
)
create_in_app_notification(
user=inquiry.assigned_to,
title=f"Inquiry assigned to you",
title_ar="تم تعيين استفسار لك",
message=f"{inquiry.reference_number}: {inquiry.subject[:100]}",
message_ar=f"{inquiry.reference_number}: {inquiry.subject[:100]}",
notification_type="inquiry_assigned",
action_url=f"/inquiries/{inquiry.pk}/",
)
@staticmethod
def send_inquiry_resolved(inquiry):
from .services import NotificationService, create_in_app_notification
from apps.accounts.models import User
hospital_id = str(inquiry.hospital_id) if inquiry.hospital else None
if hospital_id:
admins = User.objects.filter(
is_active=True, hospital_id=hospital_id
).filter(
models.Q(groups__name="PX Admin") | models.Q(groups__name="Hospital Admin")
).distinct()
else:
admins = User.objects.none()
for admin in admins:
create_in_app_notification(
user=admin,
title=f"Inquiry resolved: {inquiry.reference_number}",
title_ar=f"تم حل الاستفسار: {inquiry.reference_number}",
message=f"{inquiry.subject[:100]}",
message_ar=f"{inquiry.subject[:100]}",
notification_type="inquiry_resolved",
action_url=f"/inquiries/{inquiry.pk}/",
)
@staticmethod
def send_inquiry_reopened(inquiry):
from .services import NotificationService, create_in_app_notification
recipients = []
if inquiry.assigned_to:
recipients.append(inquiry.assigned_to)
from apps.accounts.models import User
if inquiry.hospital:
admins = User.objects.filter(
is_active=True, hospital=inquiry.hospital
).filter(
models.Q(groups__name="PX Admin") | models.Q(groups__name="Hospital Admin")
).distinct()
recipients.extend(admins)
for user in set(recipients):
create_in_app_notification(
user=user,
title=f"Inquiry reopened: {inquiry.reference_number}",
title_ar=f"تم إعادة فتح الاستفسار: {inquiry.reference_number}",
message=f"{inquiry.subject[:100]}",
message_ar=f"{inquiry.subject[:100]}",
notification_type="inquiry_reopened",
action_url=f"/inquiries/{inquiry.pk}/",
)
@staticmethod
def send_explanation_reminder(staff_email, complaint):
"""Send explanation reminder notification"""
from .services import NotificationService
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
return NotificationService.send_email(
email=staff_email,
subject=f"Reminder: Explanation Due - Complaint #{complaint.id}",
message=f"Reminder: Please submit your explanation for complaint #{complaint.id} within 24 hours.",
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.explanation_reminder_email:
result = NotificationService.send_email(
email=staff_email,
subject=f"Reminder: Explanation Due - Complaint #{complaint.id}",
message=f"Reminder: Please submit your explanation for complaint #{complaint.id} within 24 hours.",
related_object=complaint,
)
results.append(("email", result))
if (
settings.explanation_reminder_sms
and hasattr(complaint, "staff")
and complaint.staff
and complaint.staff.phone
):
results.append(
NotificationServiceWithSettings._defer_sms_if_quiet_hours(
settings,
complaint.staff.phone,
f"PX360 Reminder: Explanation due for Complaint #{complaint.id} in 24h.",
complaint,
)
)
return results
@staticmethod
def send_explanation_overdue(manager_email, complaint, staff_name):
"""Send explanation overdue/escalation notification to manager"""
from .services import NotificationService
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
return NotificationService.send_email(
email=manager_email,
subject=f"ESCALATION: Explanation Overdue - Complaint #{complaint.id}",
message=f"Staff member {staff_name} has not submitted explanation for complaint #{complaint.id}.",
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.explanation_overdue_email:
result = NotificationService.send_email(
email=manager_email,
subject=f"ESCALATION: Explanation Overdue - Complaint #{complaint.id}",
message=f"Staff member {staff_name} has not submitted explanation for complaint #{complaint.id}.",
related_object=complaint,
)
results.append(("email", result))
if settings.explanation_overdue_sms:
# Get manager phone if available
result = NotificationService.send_sms(
phone=manager_email, # This would need actual phone lookup
message=f"PX360 ESCALATION: Explanation overdue for Complaint #{complaint.id} by {staff_name}",
related_object=complaint,
)
results.append(("sms", result))
return results
@staticmethod
def send_explanation_received(assignee_email, complaint, explanation):
"""Send notification when explanation is received"""
from .services import NotificationService
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
return NotificationService.send_email(
email=assignee_email,
subject=f"New Explanation Received - Complaint #{complaint.id}",
message=f"A new explanation has been submitted for complaint #{complaint.id}.",
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.explanation_received_email:
result = NotificationService.send_email(
email=assignee_email,
subject=f"New Explanation Received - Complaint #{complaint.id}",
message=f"A new explanation has been submitted for complaint #{complaint.id}.",
related_object=complaint,
)
results.append(("email", result))
return results
@staticmethod
def send_complaint_assigned(assignee_email, complaint):
"""Send notification when complaint is assigned"""
from django.template.loader import render_to_string
from .services import NotificationService
# Build template context
context = {
'assignee_name': getattr(complaint.assigned_to, 'get_full_name', 'Staff Member')() if hasattr(complaint, 'assigned_to') and complaint.assigned_to else 'Staff Member',
'complaint_id': complaint.id,
'complaint_title': getattr(complaint, 'title', 'N/A'),
'department': getattr(complaint.department, 'name_en', 'N/A') if hasattr(complaint, 'department') and complaint.department else 'N/A',
}
# Render HTML template with navy theme
html_message = f"""
<!DOCTYPE html>
<html>
<head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: linear-gradient(135deg, #005696 0%, #007bbd 100%); padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
.info-box {{ background: #eef6fb; border-left: 4px solid #005696; padding: 15px; margin: 20px 0; }}
</style></head>
<body>
<div class="container">
<div class="header">
<h1>Complaint Assigned to You</h1>
</div>
<div class="content">
<p>Dear {context['assignee_name']},</p>
<p>A complaint has been assigned to you for review and response.</p>
<div class="info-box">
<strong>Complaint:</strong> #{context['complaint_id']} - {context['complaint_title']}<br>
<strong>Department:</strong> {context['department']}
</div>
<p>Please review and provide your explanation.</p>
</div>
</div>
</body>
</html>
"""
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
return NotificationService.send_email(
email=assignee_email,
subject=f"Complaint Assigned - #{complaint.id}",
message=f"You have been assigned to complaint #{complaint.id}: {complaint.title}",
html_message=html_message,
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.complaint_assigned_email:
result = NotificationService.send_email(
email=assignee_email,
subject=f"Complaint Assigned - #{complaint.id}",
message=f"You have been assigned to complaint #{complaint.id}: {complaint.title}",
html_message=html_message,
related_object=complaint,
)
results.append(("email", result))
return results
@staticmethod
def send_onboarding_invitation(user_email, provisional_user, custom_message=None):
"""
Send onboarding invitation to new provisional user.
Respects hospital notification settings.
"""
from .services import NotificationService
hospital_id = getattr(provisional_user, "hospital_id", None)
if not hospital_id:
# Fallback: send without settings check
return NotificationService.send_email(
email=user_email,
subject="Welcome to PX360 - Complete Your Registration",
message=custom_message or f"Please complete your registration at PX360.",
related_object=provisional_user,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
# Check master switch
if not settings.notifications_enabled:
logger.info(f"Notifications disabled for hospital {hospital_id}")
return results
# Email invitation
if settings.onboarding_invitation_email:
result = NotificationService.send_email(
email=user_email,
subject="Welcome to PX360 - Complete Your Registration",
message=custom_message or f"Please complete your registration at PX360.",
related_object=provisional_user,
)
results.append(("email", result))
if settings.onboarding_invitation_sms and provisional_user.phone:
results.append(
NotificationServiceWithSettings._defer_sms_if_quiet_hours(
settings,
provisional_user.phone,
"Welcome to PX360! Check your email to complete your registration.",
provisional_user,
)
)
return results
@staticmethod
def send_onboarding_reminder(user_email, provisional_user):
"""Send onboarding reminder notification"""
from .services import NotificationService
hospital_id = getattr(provisional_user, "hospital_id", None)
if not hospital_id:
return NotificationService.send_email(
email=user_email,
subject="Reminder: Complete Your PX360 Registration",
message="Please complete your PX360 registration. Your invitation will expire soon.",
related_object=provisional_user,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.onboarding_reminder_email:
result = NotificationService.send_email(
email=user_email,
subject="Reminder: Complete Your PX360 Registration",
message="Please complete your PX360 registration. Your invitation will expire soon.",
related_object=provisional_user,
)
results.append(("email", result))
if settings.onboarding_reminder_sms and provisional_user.phone:
results.append(
NotificationServiceWithSettings._defer_sms_if_quiet_hours(
settings,
provisional_user.phone,
"Reminder: Complete your PX360 registration. Invitation expires soon.",
provisional_user,
)
)
return results
@staticmethod
def send_onboarding_completion_notification(admin_email, completed_user):
"""Send notification to admin when user completes onboarding"""
from .services import NotificationService
hospital_id = getattr(completed_user, "hospital_id", None)
if not hospital_id:
return NotificationService.send_email(
email=admin_email,
subject=f"Onboarding Complete - {completed_user.get_full_name()}",
message=f"{completed_user.get_full_name()} has completed their onboarding.",
related_object=completed_user,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.onboarding_completion_email:
result = NotificationService.send_email(
email=admin_email,
subject=f"Onboarding Complete - {completed_user.get_full_name()}",
message=f"{completed_user.get_full_name()} has completed their onboarding.",
related_object=completed_user,
)
results.append(("email", result))
return results
@staticmethod
def send_complaint_status_changed(recipient_email, complaint, old_status, new_status):
"""Send notification when complaint status changes"""
from .services import NotificationService
# Build HTML with navy theme
html_message = f"""
<!DOCTYPE html>
<html>
<head><style>
body {{ font-family: 'Segoe UI', Tahoma, sans-serif; background: #f8fafc; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
.header {{ background: linear-gradient(135deg, #005696 0%, #007bbd 100%); padding: 30px; text-align: center; color: white; }}
.content {{ padding: 30px; }}
</style></head>
<body>
<div class="container">
<div class="header">
<h1>Complaint Status Updated</h1>
</div>
<div class="content">
<p>Dear Stakeholder,</p>
<p>The status of complaint #{complaint.id} has been updated.</p>
<p><strong>Previous Status:</strong> {old_status}</p>
<p><strong>New Status:</strong> {new_status}</p>
</div>
</div>
</body>
</html>
"""
hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint)
if not hospital_id:
return NotificationService.send_email(
email=recipient_email,
subject=f"Complaint Status Updated - #{complaint.id}",
message=f"Complaint #{complaint.id} status changed from {old_status} to {new_status}.",
html_message=html_message,
related_object=complaint,
)
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
results = []
if not settings.notifications_enabled:
return results
if settings.complaint_status_changed_email:
result = NotificationService.send_email(
email=recipient_email,
subject=f"Complaint Status Updated - #{complaint.id}",
message=f"Complaint #{complaint.id} status changed from {old_status} to {new_status}.",
html_message=html_message,
related_object=complaint,
)
results.append(("email", result))
return results