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