964 lines
42 KiB
Python
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
|