HH/apps/notifications/views.py

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
})