HH/apps/complaints/ui_views_explanation.py
2026-02-22 08:35:53 +03:00

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}