317 lines
11 KiB
Python
317 lines
11 KiB
Python
"""
|
|
Explanation request UI views for complaints.
|
|
"""
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponseForbidden
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext as _
|
|
from django.views.decorators.http import require_http_methods
|
|
|
|
from apps.core.services import AuditService
|
|
from apps.notifications.services import NotificationService
|
|
from apps.organizations.models import Staff
|
|
|
|
from .models import Complaint, ComplaintExplanation
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET", "POST"])
|
|
def request_explanation_form(request, pk):
|
|
"""
|
|
Form to request explanations from involved staff members.
|
|
|
|
Shows all involved staff with their managers and departments.
|
|
All staff and managers are selected by default.
|
|
"""
|
|
complaint = get_object_or_404(
|
|
Complaint.objects.prefetch_related(
|
|
'involved_staff__staff__department',
|
|
'involved_staff__staff__report_to',
|
|
),
|
|
pk=pk
|
|
)
|
|
|
|
# Check permissions
|
|
user = request.user
|
|
can_request = (
|
|
user.is_px_admin() or
|
|
user.is_hospital_admin() or
|
|
(user.is_department_manager() and complaint.department == user.department) or
|
|
(complaint.hospital == user.hospital)
|
|
)
|
|
|
|
if not can_request:
|
|
return HttpResponseForbidden(_("You don't have permission to request explanations."))
|
|
|
|
# Check complaint is in active status
|
|
if not complaint.is_active_status:
|
|
messages.error(
|
|
request,
|
|
_("Cannot request explanation for complaint with status '{}'. Complaint must be Open, In Progress, or Partially Resolved.").format(
|
|
complaint.get_status_display()
|
|
)
|
|
)
|
|
return redirect('complaints:complaint_detail', pk=complaint.pk)
|
|
|
|
# Get all involved staff with their managers
|
|
involved_staff = complaint.involved_staff.select_related(
|
|
'staff', 'staff__department', 'staff__report_to'
|
|
).all()
|
|
|
|
if not involved_staff.exists():
|
|
messages.error(request, _("No staff members are involved in this complaint."))
|
|
return redirect('complaints:complaint_detail', pk=complaint.pk)
|
|
|
|
# Build list of recipients (staff + managers)
|
|
recipients = []
|
|
manager_ids = set()
|
|
|
|
for staff_inv in involved_staff:
|
|
staff = staff_inv.staff
|
|
manager = staff.report_to
|
|
|
|
recipient = {
|
|
'staff_inv': staff_inv,
|
|
'staff': staff,
|
|
'staff_id': str(staff.id),
|
|
'staff_name': staff.get_full_name(),
|
|
'staff_email': staff.email or (staff.user.email if staff.user else None),
|
|
'department': staff.department.name if staff.department else '-',
|
|
'role': staff_inv.get_role_display(),
|
|
'manager': manager,
|
|
'manager_id': str(manager.id) if manager else None,
|
|
'manager_name': manager.get_full_name() if manager else None,
|
|
'manager_email': manager.email if manager else None,
|
|
}
|
|
recipients.append(recipient)
|
|
|
|
# Track unique managers
|
|
if manager and manager.id not in manager_ids:
|
|
manager_ids.add(manager.id)
|
|
|
|
if request.method == 'POST':
|
|
# Get selected staff and managers
|
|
selected_staff_ids = request.POST.getlist('selected_staff')
|
|
selected_manager_ids = request.POST.getlist('selected_managers')
|
|
request_message = request.POST.get('request_message', '').strip()
|
|
|
|
if not selected_staff_ids:
|
|
messages.error(request, _("Please select at least one staff member."))
|
|
return render(request, 'complaints/request_explanation_form.html', {
|
|
'complaint': complaint,
|
|
'recipients': recipients,
|
|
'manager_ids': manager_ids,
|
|
})
|
|
|
|
# Send explanation requests
|
|
results = _send_explanation_requests(
|
|
request, complaint, recipients, selected_staff_ids,
|
|
selected_manager_ids, request_message
|
|
)
|
|
|
|
messages.success(
|
|
request,
|
|
_("Explanation requests sent successfully! Staff: {}, Managers notified: {}.").format(
|
|
results['staff_count'], results['manager_count']
|
|
)
|
|
)
|
|
return redirect('complaints:complaint_detail', pk=complaint.pk)
|
|
|
|
return render(request, 'complaints/request_explanation_form.html', {
|
|
'complaint': complaint,
|
|
'recipients': recipients,
|
|
'manager_ids': manager_ids,
|
|
})
|
|
|
|
|
|
def _send_explanation_requests(request, complaint, recipients, selected_staff_ids,
|
|
selected_manager_ids, request_message):
|
|
"""
|
|
Send explanation request emails to selected staff and managers.
|
|
|
|
Staff receive a link to submit their explanation.
|
|
Managers receive a notification email only.
|
|
"""
|
|
from django.contrib.sites.shortcuts import get_current_site
|
|
import secrets
|
|
|
|
site = get_current_site(request)
|
|
user = request.user
|
|
|
|
staff_count = 0
|
|
manager_count = 0
|
|
|
|
# Track which managers we've already notified
|
|
notified_managers = set()
|
|
|
|
for recipient in recipients:
|
|
staff = recipient['staff']
|
|
staff_id = recipient['staff_id']
|
|
|
|
# Skip if staff not selected
|
|
if staff_id not in selected_staff_ids:
|
|
continue
|
|
|
|
# Check if staff has email
|
|
staff_email = recipient['staff_email']
|
|
if not staff_email:
|
|
continue
|
|
|
|
# Generate unique token
|
|
staff_token = secrets.token_urlsafe(32)
|
|
|
|
# Create or update explanation record
|
|
explanation, created = ComplaintExplanation.objects.update_or_create(
|
|
complaint=complaint,
|
|
staff=staff,
|
|
defaults={
|
|
'token': staff_token,
|
|
'is_used': False,
|
|
'requested_by': user,
|
|
'request_message': request_message,
|
|
'email_sent_at': timezone.now(),
|
|
'submitted_via': 'email_link',
|
|
}
|
|
)
|
|
|
|
# Build staff email with link
|
|
staff_link = f"https://{site.domain}/complaints/{complaint.id}/explain/{staff_token}/"
|
|
staff_subject = f"Explanation Request - Complaint #{complaint.reference_number}"
|
|
|
|
staff_email_body = f"""Dear {recipient['staff_name']},
|
|
|
|
We have received a complaint that requires your explanation.
|
|
|
|
COMPLAINT DETAILS:
|
|
----------------
|
|
Reference: {complaint.reference_number}
|
|
Title: {complaint.title}
|
|
Severity: {complaint.get_severity_display()}
|
|
Priority: {complaint.get_priority_display()}
|
|
|
|
{complaint.description or 'No description provided.'}
|
|
|
|
"""
|
|
|
|
# Add patient info if available
|
|
if complaint.patient:
|
|
staff_email_body += f"""
|
|
PATIENT INFORMATION:
|
|
------------------
|
|
Name: {complaint.patient.get_full_name()}
|
|
MRN: {complaint.patient.mrn or 'N/A'}
|
|
"""
|
|
|
|
# Add request message if provided
|
|
if request_message:
|
|
staff_email_body += f"""
|
|
|
|
ADDITIONAL MESSAGE:
|
|
------------------
|
|
{request_message}
|
|
"""
|
|
|
|
staff_email_body += f"""
|
|
|
|
SUBMIT YOUR EXPLANATION:
|
|
------------------------
|
|
Please submit your explanation about this complaint:
|
|
{staff_link}
|
|
|
|
Note: This link can only be used once. After submission, it will expire.
|
|
|
|
If you have any questions, please contact the PX team.
|
|
|
|
---
|
|
This is an automated message from PX360 Complaint Management System.
|
|
"""
|
|
|
|
# Send email to staff
|
|
try:
|
|
NotificationService.send_email(
|
|
email=staff_email,
|
|
subject=staff_subject,
|
|
message=staff_email_body,
|
|
related_object=complaint,
|
|
metadata={
|
|
'notification_type': 'explanation_request',
|
|
'staff_id': str(staff.id),
|
|
'complaint_id': str(complaint.id),
|
|
}
|
|
)
|
|
staff_count += 1
|
|
except Exception as e:
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(f"Failed to send explanation request to staff {staff.id}: {e}")
|
|
|
|
# Send notification to manager if selected and not already notified
|
|
manager = recipient['manager']
|
|
if manager and recipient['manager_id'] in selected_manager_ids:
|
|
if manager.id not in notified_managers:
|
|
manager_email = recipient['manager_email']
|
|
if manager_email:
|
|
manager_subject = f"Staff Explanation Requested - Complaint #{complaint.reference_number}"
|
|
|
|
manager_email_body = f"""Dear {recipient['manager_name']},
|
|
|
|
This is an informational notification that an explanation has been requested from a staff member who reports to you.
|
|
|
|
STAFF MEMBER:
|
|
------------
|
|
Name: {recipient['staff_name']}
|
|
Department: {recipient['department']}
|
|
Role in Complaint: {recipient['role']}
|
|
|
|
COMPLAINT DETAILS:
|
|
----------------
|
|
Reference: {complaint.reference_number}
|
|
Title: {complaint.title}
|
|
Severity: {complaint.get_severity_display()}
|
|
|
|
The staff member has been sent a link to submit their explanation. You will be notified when they respond.
|
|
|
|
If you have any questions, please contact the PX team.
|
|
|
|
---
|
|
This is an automated message from PX360 Complaint Management System.
|
|
"""
|
|
|
|
try:
|
|
NotificationService.send_email(
|
|
email=manager_email,
|
|
subject=manager_subject,
|
|
message=manager_email_body,
|
|
related_object=complaint,
|
|
metadata={
|
|
'notification_type': 'explanation_request_manager_notification',
|
|
'manager_id': str(manager.id),
|
|
'staff_id': str(staff.id),
|
|
'complaint_id': str(complaint.id),
|
|
}
|
|
)
|
|
manager_count += 1
|
|
notified_managers.add(manager.id)
|
|
except Exception as e:
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(f"Failed to send manager notification to {manager.id}: {e}")
|
|
|
|
# Log audit event
|
|
AuditService.log_event(
|
|
event_type='explanation_request',
|
|
description=f'Explanation requests sent to {staff_count} staff and {manager_count} managers',
|
|
user=user,
|
|
content_object=complaint,
|
|
metadata={
|
|
'staff_count': staff_count,
|
|
'manager_count': manager_count,
|
|
'selected_staff_ids': selected_staff_ids,
|
|
'selected_manager_ids': selected_manager_ids,
|
|
}
|
|
)
|
|
|
|
return {'staff_count': staff_count, 'manager_count': manager_count}
|