440 lines
16 KiB
Python
440 lines
16 KiB
Python
"""
|
|
Notification Settings Views
|
|
|
|
Provides admin interface for configuring notification preferences.
|
|
"""
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.http import JsonResponse
|
|
from django.views.decorators.http import require_POST
|
|
|
|
from apps.organizations.models import Hospital
|
|
from .settings_models import HospitalNotificationSettings, NotificationSettingsLog
|
|
|
|
|
|
def can_manage_notifications(user):
|
|
"""Check if user can manage notification settings"""
|
|
if user.is_superuser:
|
|
return True
|
|
if hasattr(user, 'is_px_admin') and user.is_px_admin():
|
|
return True
|
|
if hasattr(user, 'is_hospital_admin') and user.is_hospital_admin():
|
|
return True
|
|
return False
|
|
|
|
|
|
@login_required
|
|
def notification_settings_view(request, hospital_id=None):
|
|
"""
|
|
Notification settings configuration page.
|
|
|
|
Allows hospital admins to toggle notification preferences
|
|
for different events and channels.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
# Get hospital - if superuser, can view any; otherwise only their hospital
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
hospital_id = hospital.id
|
|
|
|
# Get or create settings
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
|
|
|
|
# Group settings by category for display
|
|
notification_categories = [
|
|
{
|
|
'key': 'complaint',
|
|
'name': 'Complaint Notifications',
|
|
'icon': 'bi-exclamation-triangle',
|
|
'description': 'Notifications related to complaint lifecycle',
|
|
'events': [
|
|
{
|
|
'key': 'complaint_acknowledgment',
|
|
'name': 'Complaint Acknowledgment',
|
|
'description': 'Sent to patient when complaint is acknowledged',
|
|
'icon': 'bi-check-circle',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'complaint_assigned',
|
|
'name': 'Complaint Assigned',
|
|
'description': 'Sent to staff when complaint is assigned to them',
|
|
'icon': 'bi-person-check',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'complaint_status_changed',
|
|
'name': 'Complaint Status Changed',
|
|
'description': 'Sent when complaint status is updated',
|
|
'icon': 'bi-arrow-repeat',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'complaint_resolved',
|
|
'name': 'Complaint Resolved',
|
|
'description': 'Sent to patient when complaint is resolved',
|
|
'icon': 'bi-check2-all',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'complaint_closed',
|
|
'name': 'Complaint Closed',
|
|
'description': 'Sent to patient when complaint is closed',
|
|
'icon': 'bi-x-circle',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
]
|
|
},
|
|
{
|
|
'key': 'explanation',
|
|
'name': 'Explanation Notifications',
|
|
'icon': 'bi-chat-left-text',
|
|
'description': 'Notifications for staff explanation workflow',
|
|
'events': [
|
|
{
|
|
'key': 'explanation_requested',
|
|
'name': 'Explanation Requested',
|
|
'description': 'Sent to staff when explanation is requested',
|
|
'icon': 'bi-question-circle',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'explanation_reminder',
|
|
'name': 'Explanation Reminder (24h)',
|
|
'description': 'Reminder sent 24h before SLA deadline',
|
|
'icon': 'bi-clock-history',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'explanation_overdue',
|
|
'name': 'Explanation Overdue/Escalation',
|
|
'description': 'Sent to manager when explanation is overdue',
|
|
'icon': 'bi-exclamation-diamond',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'explanation_received',
|
|
'name': 'Explanation Received',
|
|
'description': 'Sent to assignee when staff submits explanation',
|
|
'icon': 'bi-inbox',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
],
|
|
'extra_settings': [
|
|
{
|
|
'key': 'explanation_manager_cc',
|
|
'name': 'Manager CC on Explanation Request',
|
|
'description': 'CC manager when explanation is requested from staff',
|
|
'icon': 'bi-person-badge'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'key': 'survey',
|
|
'name': 'Survey Notifications',
|
|
'icon': 'bi-clipboard-check',
|
|
'description': 'Notifications for patient surveys',
|
|
'events': [
|
|
{
|
|
'key': 'survey_invitation',
|
|
'name': 'Survey Invitation',
|
|
'description': 'Initial survey invitation sent to patient',
|
|
'icon': 'bi-envelope-open',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'survey_reminder',
|
|
'name': 'Survey Reminder',
|
|
'description': 'Reminder for patients to complete survey',
|
|
'icon': 'bi-bell',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'survey_completed',
|
|
'name': 'Survey Completed',
|
|
'description': 'Sent to admin when survey is completed',
|
|
'icon': 'bi-check-circle-fill',
|
|
'channels': ['email', 'sms']
|
|
},
|
|
]
|
|
},
|
|
{
|
|
'key': 'action',
|
|
'name': 'Action Plan Notifications',
|
|
'icon': 'bi-list-check',
|
|
'description': 'Notifications for action plan assignments',
|
|
'events': [
|
|
{
|
|
'key': 'action_assigned',
|
|
'name': 'Action Assigned',
|
|
'description': 'Sent to staff when action is assigned',
|
|
'icon': 'bi-person-plus',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'action_due_soon',
|
|
'name': 'Action Due Soon',
|
|
'description': 'Reminder when action is approaching deadline',
|
|
'icon': 'bi-calendar-event',
|
|
'channels': ['email', 'sms']
|
|
},
|
|
{
|
|
'key': 'action_overdue',
|
|
'name': 'Action Overdue',
|
|
'description': 'Alert when action is past deadline',
|
|
'icon': 'bi-calendar-x',
|
|
'channels': ['email', 'sms']
|
|
},
|
|
]
|
|
},
|
|
{
|
|
'key': 'sla',
|
|
'name': 'SLA Notifications',
|
|
'icon': 'bi-stopwatch',
|
|
'description': 'Notifications for SLA monitoring',
|
|
'events': [
|
|
{
|
|
'key': 'sla_reminder',
|
|
'name': 'SLA Reminder',
|
|
'description': 'Reminder before SLA breach',
|
|
'icon': 'bi-clock',
|
|
'channels': ['email', 'sms']
|
|
},
|
|
{
|
|
'key': 'sla_breach',
|
|
'name': 'SLA Breach Alert',
|
|
'description': 'Alert when SLA is breached',
|
|
'icon': 'bi-exclamation-octagon',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
]
|
|
},
|
|
{
|
|
'key': 'onboarding',
|
|
'name': 'Onboarding Notifications',
|
|
'icon': 'bi-person-plus',
|
|
'description': 'Notifications for user onboarding and acknowledgements',
|
|
'events': [
|
|
{
|
|
'key': 'onboarding_invitation',
|
|
'name': 'Onboarding Invitation',
|
|
'description': 'Sent to new provisional users to complete registration',
|
|
'icon': 'bi-envelope-plus',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'onboarding_reminder',
|
|
'name': 'Onboarding Reminder',
|
|
'description': 'Reminder to complete onboarding before invitation expires',
|
|
'icon': 'bi-bell',
|
|
'channels': ['email', 'sms', 'whatsapp']
|
|
},
|
|
{
|
|
'key': 'onboarding_completion',
|
|
'name': 'Onboarding Completion',
|
|
'description': 'Sent to admins when user completes onboarding',
|
|
'icon': 'bi-check-circle-fill',
|
|
'channels': ['email', 'sms']
|
|
},
|
|
]
|
|
},
|
|
]
|
|
|
|
# Get recent change logs
|
|
change_logs = NotificationSettingsLog.objects.filter(
|
|
hospital=hospital
|
|
).select_related('changed_by').order_by('-created_at')[:10]
|
|
|
|
context = {
|
|
'hospital': hospital,
|
|
'settings': settings,
|
|
'categories': notification_categories,
|
|
'change_logs': change_logs,
|
|
'is_superuser': request.user.is_superuser,
|
|
}
|
|
|
|
return render(request, 'notifications/settings.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def notification_settings_update(request, hospital_id=None):
|
|
"""
|
|
Update notification settings via AJAX or form POST.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
# Get hospital
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
# Handle master switch
|
|
if 'notifications_enabled' in request.POST:
|
|
old_value = settings.notifications_enabled
|
|
new_value = request.POST.get('notifications_enabled') == 'on'
|
|
settings.notifications_enabled = new_value
|
|
settings.save()
|
|
|
|
NotificationSettingsLog.objects.create(
|
|
hospital=hospital,
|
|
changed_by=request.user,
|
|
field_name='notifications_enabled',
|
|
old_value=old_value,
|
|
new_value=new_value
|
|
)
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': f"Notifications {'enabled' if new_value else 'disabled'}"
|
|
})
|
|
messages.success(request, f"Notifications {'enabled' if new_value else 'disabled'}")
|
|
return redirect('notifications:settings_with_hospital', hospital_id=hospital.id)
|
|
|
|
# Handle individual toggle
|
|
field_name = request.POST.get('field')
|
|
value = request.POST.get('value') == 'true'
|
|
|
|
if field_name and hasattr(settings, field_name):
|
|
old_value = getattr(settings, field_name)
|
|
setattr(settings, field_name, value)
|
|
settings.save()
|
|
|
|
# Log the change
|
|
NotificationSettingsLog.objects.create(
|
|
hospital=hospital,
|
|
changed_by=request.user,
|
|
field_name=field_name,
|
|
old_value=old_value,
|
|
new_value=value
|
|
)
|
|
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
return JsonResponse({
|
|
'success': True,
|
|
'field': field_name,
|
|
'value': value
|
|
})
|
|
|
|
messages.success(request, "Setting updated successfully")
|
|
else:
|
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
|
return JsonResponse({
|
|
'success': False,
|
|
'error': 'Invalid field name'
|
|
}, status=400)
|
|
messages.error(request, "Invalid setting")
|
|
|
|
return redirect('notifications:settings_with_hospital', hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def update_quiet_hours(request, hospital_id=None):
|
|
"""
|
|
Update quiet hours settings.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
settings.quiet_hours_enabled = request.POST.get('quiet_hours_enabled') == 'on'
|
|
settings.quiet_hours_start = request.POST.get('quiet_hours_start', '22:00')
|
|
settings.quiet_hours_end = request.POST.get('quiet_hours_end', '08:00')
|
|
settings.save()
|
|
|
|
messages.success(request, "Quiet hours settings updated")
|
|
return redirect('notifications:settings_with_hospital', hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
def test_notification(request, hospital_id=None):
|
|
"""
|
|
Send a test notification to verify settings.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
channel = request.POST.get('channel', 'email')
|
|
|
|
from .services import NotificationService
|
|
|
|
if channel == 'email' and request.user.email:
|
|
NotificationService.send_email(
|
|
email=request.user.email,
|
|
subject='PX360 Test Notification',
|
|
message='This is a test notification from PX360.\n\nIf you received this, your email notifications are working correctly.',
|
|
metadata={'test': True, 'hospital_id': str(hospital.id)}
|
|
)
|
|
messages.success(request, f"Test email sent to {request.user.email}")
|
|
|
|
elif channel == 'sms':
|
|
phone = request.POST.get('test_phone')
|
|
if phone:
|
|
NotificationService.send_sms(
|
|
phone=phone,
|
|
message='PX360 Test: Your SMS notifications are configured correctly.',
|
|
metadata={'test': True, 'hospital_id': str(hospital.id)}
|
|
)
|
|
messages.success(request, f"Test SMS sent to {phone}")
|
|
else:
|
|
messages.error(request, "Please provide a phone number")
|
|
|
|
else:
|
|
messages.error(request, "Invalid channel selected")
|
|
|
|
return redirect('notifications:settings_with_hospital', hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
def notification_settings_api(request, hospital_id=None):
|
|
"""
|
|
API endpoint to get current notification settings as JSON.
|
|
Useful for AJAX updates and mobile apps.
|
|
"""
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
# Build response with all settings
|
|
settings_dict = {}
|
|
for field in settings._meta.fields:
|
|
if field.name not in ['id', 'uuid', 'created_at', 'updated_at', 'hospital']:
|
|
settings_dict[field.name] = getattr(settings, field.name)
|
|
|
|
return JsonResponse({
|
|
'hospital_id': str(hospital.id),
|
|
'hospital_name': hospital.name,
|
|
'settings': settings_dict
|
|
})
|