502 lines
16 KiB
Python
502 lines
16 KiB
Python
"""
|
|
Notification Settings Models - Configurable notification preferences
|
|
|
|
This module provides per-hospital notification configuration allowing
|
|
admins to toggle which notifications are sent via which channels.
|
|
"""
|
|
from django.db import models
|
|
from django.core.cache import cache
|
|
|
|
from apps.core.models import BaseChoices, TimeStampedModel, UUIDModel
|
|
|
|
|
|
class NotificationCategory(BaseChoices):
|
|
"""Categories of notifications"""
|
|
COMPLAINT = 'complaint', 'Complaint Notifications'
|
|
SURVEY = 'survey', 'Survey Notifications'
|
|
ACTION = 'action', 'Action Notifications'
|
|
SYSTEM = 'system', 'System Notifications'
|
|
|
|
|
|
class NotificationEvent(BaseChoices):
|
|
"""
|
|
All notification events that can be configured.
|
|
Each event can have different channels enabled/disabled.
|
|
"""
|
|
# Complaint Events
|
|
COMPLAINT_ACKNOWLEDGMENT = 'complaint_acknowledgment', 'Complaint Acknowledgment'
|
|
COMPLAINT_ASSIGNED = 'complaint_assigned', 'Complaint Assigned'
|
|
COMPLAINT_STATUS_CHANGED = 'complaint_status_changed', 'Complaint Status Changed'
|
|
COMPLAINT_RESOLVED = 'complaint_resolved', 'Complaint Resolved'
|
|
COMPLAINT_CLOSED = 'complaint_closed', 'Complaint Closed'
|
|
|
|
# Explanation Events
|
|
EXPLANATION_REQUESTED = 'explanation_requested', 'Explanation Requested'
|
|
EXPLANATION_REMINDER = 'explanation_reminder', 'Explanation Reminder (24h)'
|
|
EXPLANATION_OVERDUE = 'explanation_overdue', 'Explanation Overdue/Escalation'
|
|
EXPLANATION_RECEIVED = 'explanation_received', 'Explanation Received'
|
|
|
|
# Survey Events
|
|
SURVEY_INVITATION = 'survey_invitation', 'Survey Invitation'
|
|
SURVEY_REMINDER = 'survey_reminder', 'Survey Reminder'
|
|
SURVEY_COMPLETED = 'survey_completed', 'Survey Completed'
|
|
|
|
# Action Events
|
|
ACTION_ASSIGNED = 'action_assigned', 'Action Assigned'
|
|
ACTION_DUE_SOON = 'action_due_soon', 'Action Due Soon'
|
|
ACTION_OVERDUE = 'action_overdue', 'Action Overdue'
|
|
|
|
# SLA Events
|
|
SLA_REMINDER = 'sla_reminder', 'SLA Reminder'
|
|
SLA_BREACH = 'sla_breach', 'SLA Breach Alert'
|
|
|
|
# Onboarding Events
|
|
ONBOARDING_INVITATION = 'onboarding_invitation', 'Onboarding Invitation'
|
|
ONBOARDING_REMINDER = 'onboarding_reminder', 'Onboarding Reminder'
|
|
ONBOARDING_COMPLETION = 'onboarding_completion', 'Onboarding Completion'
|
|
|
|
|
|
class HospitalNotificationSettings(UUIDModel, TimeStampedModel):
|
|
"""
|
|
Per-hospital notification configuration.
|
|
|
|
Allows hospital admins to customize which notifications are sent
|
|
and via which channels (Email, SMS, WhatsApp).
|
|
"""
|
|
|
|
hospital = models.OneToOneField(
|
|
'organizations.Hospital',
|
|
on_delete=models.CASCADE,
|
|
related_name='notification_settings',
|
|
help_text='Hospital these settings apply to'
|
|
)
|
|
|
|
# ==================== COMPLAINT NOTIFICATIONS ====================
|
|
|
|
# Complaint Acknowledgment (to patient)
|
|
complaint_acknowledgment_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when complaint is acknowledged'
|
|
)
|
|
complaint_acknowledgment_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when complaint is acknowledged'
|
|
)
|
|
complaint_acknowledgment_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when complaint is acknowledged'
|
|
)
|
|
|
|
# Complaint Assigned (to assignee)
|
|
complaint_assigned_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when complaint is assigned'
|
|
)
|
|
complaint_assigned_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when complaint is assigned'
|
|
)
|
|
complaint_assigned_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when complaint is assigned'
|
|
)
|
|
|
|
# Complaint Status Changed (to relevant parties)
|
|
complaint_status_changed_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when complaint status changes'
|
|
)
|
|
complaint_status_changed_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when complaint status changes'
|
|
)
|
|
complaint_status_changed_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when complaint status changes'
|
|
)
|
|
|
|
# Complaint Resolved (to patient)
|
|
complaint_resolved_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when complaint is resolved'
|
|
)
|
|
complaint_resolved_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when complaint is resolved'
|
|
)
|
|
complaint_resolved_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when complaint is resolved'
|
|
)
|
|
|
|
# Complaint Closed (to patient)
|
|
complaint_closed_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when complaint is closed'
|
|
)
|
|
complaint_closed_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when complaint is closed'
|
|
)
|
|
complaint_closed_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when complaint is closed'
|
|
)
|
|
|
|
# ==================== EXPLANATION NOTIFICATIONS ====================
|
|
|
|
# Explanation Requested (to staff)
|
|
explanation_requested_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when explanation is requested from staff'
|
|
)
|
|
explanation_requested_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when explanation is requested from staff'
|
|
)
|
|
explanation_requested_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when explanation is requested from staff'
|
|
)
|
|
|
|
# Explanation Reminder at 24h (to staff)
|
|
explanation_reminder_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email reminder at 24h before SLA expires'
|
|
)
|
|
explanation_reminder_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS reminder at 24h before SLA expires'
|
|
)
|
|
explanation_reminder_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp reminder at 24h before SLA expires'
|
|
)
|
|
|
|
# Explanation Overdue/Escalation (to manager)
|
|
explanation_overdue_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when explanation is overdue (escalation)'
|
|
)
|
|
explanation_overdue_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when explanation is overdue (escalation)'
|
|
)
|
|
explanation_overdue_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when explanation is overdue (escalation)'
|
|
)
|
|
|
|
# Explanation Received (to assignee)
|
|
explanation_received_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when staff submits explanation'
|
|
)
|
|
explanation_received_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when staff submits explanation'
|
|
)
|
|
explanation_received_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when staff submits explanation'
|
|
)
|
|
|
|
# Manager CC on Explanation Request
|
|
explanation_manager_cc = models.BooleanField(
|
|
default=False,
|
|
help_text='CC manager when explanation is requested from staff'
|
|
)
|
|
|
|
# ==================== SURVEY NOTIFICATIONS ====================
|
|
|
|
# Survey Invitation (to patient)
|
|
survey_invitation_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email survey invitation to patient'
|
|
)
|
|
survey_invitation_sms = models.BooleanField(
|
|
default=True,
|
|
help_text='Send SMS survey invitation to patient'
|
|
)
|
|
survey_invitation_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp survey invitation to patient'
|
|
)
|
|
|
|
# Survey Reminder (to patient)
|
|
survey_reminder_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email survey reminder'
|
|
)
|
|
survey_reminder_sms = models.BooleanField(
|
|
default=True,
|
|
help_text='Send SMS survey reminder'
|
|
)
|
|
survey_reminder_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp survey reminder'
|
|
)
|
|
|
|
# Survey Completed (to admin)
|
|
survey_completed_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when patient completes survey'
|
|
)
|
|
survey_completed_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when patient completes survey'
|
|
)
|
|
|
|
# ==================== ACTION NOTIFICATIONS ====================
|
|
|
|
# Action Assigned (to assignee)
|
|
action_assigned_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when action is assigned'
|
|
)
|
|
action_assigned_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when action is assigned'
|
|
)
|
|
action_assigned_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when action is assigned'
|
|
)
|
|
|
|
# Action Due Soon (to assignee)
|
|
action_due_soon_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when action is due soon'
|
|
)
|
|
action_due_soon_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when action is due soon'
|
|
)
|
|
|
|
# Action Overdue (to manager)
|
|
action_overdue_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when action is overdue'
|
|
)
|
|
action_overdue_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when action is overdue'
|
|
)
|
|
|
|
# ==================== SLA NOTIFICATIONS ====================
|
|
|
|
# SLA Reminder (to assignee)
|
|
sla_reminder_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email SLA reminder'
|
|
)
|
|
sla_reminder_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS SLA reminder'
|
|
)
|
|
|
|
# SLA Breach (to manager)
|
|
sla_breach_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email when SLA is breached'
|
|
)
|
|
sla_breach_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS when SLA is breached'
|
|
)
|
|
sla_breach_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp when SLA is breached'
|
|
)
|
|
|
|
# ==================== ONBOARDING NOTIFICATIONS ====================
|
|
|
|
# Onboarding Invitation (to new user)
|
|
onboarding_invitation_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email invitation to new provisional users'
|
|
)
|
|
onboarding_invitation_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS invitation to new provisional users'
|
|
)
|
|
onboarding_invitation_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp invitation to new provisional users'
|
|
)
|
|
|
|
# Onboarding Reminder (to pending user)
|
|
onboarding_reminder_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email reminder to complete onboarding'
|
|
)
|
|
onboarding_reminder_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS reminder to complete onboarding'
|
|
)
|
|
onboarding_reminder_whatsapp = models.BooleanField(
|
|
default=False,
|
|
help_text='Send WhatsApp reminder to complete onboarding'
|
|
)
|
|
|
|
# Onboarding Completion (to admins)
|
|
onboarding_completion_email = models.BooleanField(
|
|
default=True,
|
|
help_text='Send email notification to admins when user completes onboarding'
|
|
)
|
|
onboarding_completion_sms = models.BooleanField(
|
|
default=False,
|
|
help_text='Send SMS notification to admins when user completes onboarding'
|
|
)
|
|
|
|
# ==================== GLOBAL SETTINGS ====================
|
|
|
|
# Master switches
|
|
notifications_enabled = models.BooleanField(
|
|
default=True,
|
|
help_text='Master switch to enable/disable all notifications'
|
|
)
|
|
|
|
# Quiet hours
|
|
quiet_hours_enabled = models.BooleanField(
|
|
default=False,
|
|
help_text='Enable quiet hours (no SMS/WhatsApp during these hours)'
|
|
)
|
|
quiet_hours_start = models.TimeField(
|
|
default='22:00',
|
|
help_text='Start of quiet hours (e.g., 22:00 for 10 PM)'
|
|
)
|
|
quiet_hours_end = models.TimeField(
|
|
default='08:00',
|
|
help_text='End of quiet hours (e.g., 08:00 for 8 AM)'
|
|
)
|
|
|
|
# Retry configuration
|
|
retry_failed_notifications = models.BooleanField(
|
|
default=True,
|
|
help_text='Automatically retry failed notifications'
|
|
)
|
|
max_retries = models.IntegerField(
|
|
default=3,
|
|
help_text='Maximum number of retry attempts'
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = 'Hospital Notification Settings'
|
|
verbose_name_plural = 'Hospital Notification Settings'
|
|
|
|
def __str__(self):
|
|
return f"Notification Settings for {self.hospital}"
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""Clear cache when settings are updated"""
|
|
super().save(*args, **kwargs)
|
|
cache.delete(f'notification_settings_{self.hospital_id}')
|
|
|
|
@classmethod
|
|
def get_for_hospital(cls, hospital_id):
|
|
"""
|
|
Get or create settings for a hospital with caching.
|
|
"""
|
|
cache_key = f'notification_settings_{hospital_id}'
|
|
settings = cache.get(cache_key)
|
|
|
|
if settings is None:
|
|
settings, created = cls.objects.get_or_create(
|
|
hospital_id=hospital_id,
|
|
defaults={}
|
|
)
|
|
cache.set(cache_key, settings, 300) # Cache for 5 minutes
|
|
|
|
return settings
|
|
|
|
def is_channel_enabled(self, event, channel):
|
|
"""
|
|
Check if a specific channel is enabled for an event.
|
|
|
|
Args:
|
|
event: NotificationEvent value (e.g., 'explanation_requested')
|
|
channel: 'email', 'sms', or 'whatsapp'
|
|
|
|
Returns:
|
|
bool: True if channel is enabled for this event
|
|
"""
|
|
if not self.notifications_enabled:
|
|
return False
|
|
|
|
field_name = f"{event}_{channel}"
|
|
return getattr(self, field_name, False)
|
|
|
|
def get_enabled_channels(self, event):
|
|
"""
|
|
Get list of enabled channels for a specific event.
|
|
|
|
Args:
|
|
event: NotificationEvent value
|
|
|
|
Returns:
|
|
list: List of enabled channel names ['email', 'sms', ...]
|
|
"""
|
|
if not self.notifications_enabled:
|
|
return []
|
|
|
|
channels = []
|
|
for channel in ['email', 'sms', 'whatsapp']:
|
|
if self.is_channel_enabled(event, channel):
|
|
channels.append(channel)
|
|
|
|
return channels
|
|
|
|
def is_quiet_hours(self, current_time=None):
|
|
"""
|
|
Check if current time is within quiet hours.
|
|
|
|
Returns:
|
|
bool: True if quiet hours (SMS/WhatsApp should not be sent)
|
|
"""
|
|
if not self.quiet_hours_enabled:
|
|
return False
|
|
|
|
from django.utils import timezone
|
|
|
|
if current_time is None:
|
|
current_time = timezone.now().time()
|
|
|
|
start = self.quiet_hours_start
|
|
end = self.quiet_hours_end
|
|
|
|
# Handle overnight quiet hours (e.g., 22:00 - 08:00)
|
|
if start > end:
|
|
return current_time >= start or current_time <= end
|
|
else:
|
|
return start <= current_time <= end
|
|
|
|
|
|
class NotificationSettingsLog(UUIDModel, TimeStampedModel):
|
|
"""
|
|
Audit log for notification settings changes.
|
|
Tracks who changed what and when.
|
|
"""
|
|
|
|
hospital = models.ForeignKey(
|
|
'organizations.Hospital',
|
|
on_delete=models.CASCADE,
|
|
related_name='notification_settings_logs'
|
|
)
|
|
|
|
changed_by = models.ForeignKey(
|
|
'accounts.User',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
|
|
field_name = models.CharField(max_length=100)
|
|
old_value = models.BooleanField(null=True)
|
|
new_value = models.BooleanField(null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
verbose_name = 'Notification Settings Change Log'
|
|
verbose_name_plural = 'Notification Settings Change Logs'
|