""" RCA (Root Cause Analysis) views """ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.db.models import Q, Count from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse_lazy from django.utils import timezone from django.views import View from django.views.generic import ( CreateView, DeleteView, DetailView, ListView, UpdateView, ) from .forms import ( RCAAttachmentForm, RCAClosureForm, RCACorrectiveActionForm, RCANoteForm, RCARootCauseForm, RCAApprovalForm, RCAStatusChangeForm, RootCauseAnalysisForm, ) from .models import ( RCAActionStatus, RCAStatus, RCACorrectiveAction, RCARootCause, RootCauseAnalysis, ) class RCAListView(LoginRequiredMixin, ListView): """List view for Root Cause Analyses""" model = RootCauseAnalysis template_name = 'rca/rca_list.html' context_object_name = 'rcas' paginate_by = 20 def get_queryset(self): queryset = RootCauseAnalysis.objects.filter( is_deleted=False ).select_related( 'hospital', 'department', 'assigned_to', 'created_by' ).prefetch_related( 'root_causes', 'corrective_actions' ) # Get filter parameters status = self.request.GET.get('status') severity = self.request.GET.get('severity') priority = self.request.GET.get('priority') hospital = self.request.GET.get('hospital') search = self.request.GET.get('search') date_from = self.request.GET.get('date_from') date_to = self.request.GET.get('date_to') # Apply filters if status: queryset = queryset.filter(status=status) if severity: queryset = queryset.filter(severity=severity) if priority: queryset = queryset.filter(priority=priority) if hospital: queryset = queryset.filter(hospital_id=hospital) if search: queryset = queryset.filter( Q(title__icontains=search) | Q(description__icontains=search) ) if date_from: queryset = queryset.filter(created_at__gte=date_from) if date_to: queryset = queryset.filter(created_at__lte=date_to) # Filter by user's hospital (if not admin) if not self.request.user.is_superuser: from apps.organizations.models import Hospital user_hospitals = Hospital.objects.filter( staff__user=self.request.user ) queryset = queryset.filter(hospital__in=user_hospitals) return queryset.order_by('-created_at') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Add filter form from .forms import RCAFilterForm context['filter_form'] = RCAFilterForm(self.request.GET) # Add counts context['total_count'] = self.get_queryset().count() context['draft_count'] = self.get_queryset().filter( status=RCAStatus.DRAFT ).count() context['in_progress_count'] = self.get_queryset().filter( status=RCAStatus.IN_PROGRESS ).count() context['review_count'] = self.get_queryset().filter( status=RCAStatus.REVIEW ).count() context['approved_count'] = self.get_queryset().filter( status=RCAStatus.APPROVED ).count() context['closed_count'] = self.get_queryset().filter( status=RCAStatus.CLOSED ).count() return context class RCADetailView(LoginRequiredMixin, DetailView): """Detail view for Root Cause Analysis""" model = RootCauseAnalysis template_name = 'rca/rca_detail.html' context_object_name = 'rca' def get_queryset(self): return RootCauseAnalysis.objects.filter( is_deleted=False ).select_related( 'hospital', 'department', 'assigned_to', 'created_by', 'approved_by', 'closed_by' ).prefetch_related( 'root_causes__verified_by', 'corrective_actions__root_cause', 'attachments', 'notes__created_by', 'status_logs__changed_by' ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Add forms context['root_cause_form'] = RCARootCauseForm() context['corrective_action_form'] = RCACorrectiveActionForm( rca=self.object ) context['attachment_form'] = RCAAttachmentForm() context['note_form'] = RCANoteForm() context['status_change_form'] = RCAStatusChangeForm() context['approval_form'] = RCAApprovalForm() context['closure_form'] = RCAClosureForm() # Calculate progress total_actions = self.object.corrective_actions.count() completed_actions = self.object.corrective_actions.filter( status=RCAActionStatus.COMPLETED ).count() context['progress_percentage'] = ( (completed_actions / total_actions * 100) if total_actions > 0 else 0 ) return context class RCACreateView(LoginRequiredMixin, CreateView): """Create view for Root Cause Analysis""" model = RootCauseAnalysis form_class = RootCauseAnalysisForm template_name = 'rca/rca_form.html' success_url = reverse_lazy('rca:rca_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): rca = form.save(commit=False) rca.created_by = self.request.user # Handle linking to related item (if provided) related_model = self.request.POST.get('related_model') related_id = self.request.POST.get('related_id') if related_model and related_id: try: content_type = ContentType.objects.get(model=related_model) rca.content_type = content_type rca.object_id = related_id except ContentType.DoesNotExist: pass rca.save() # Create status log rca.status_logs.create( old_status='', new_status=rca.status, changed_by=self.request.user, notes='RCA created' ) messages.success( self.request, 'Root Cause Analysis created successfully!' ) return redirect('rca:rca_detail', pk=rca.pk) class RCAUpdateView(LoginRequiredMixin, UpdateView): """Update view for Root Cause Analysis""" model = RootCauseAnalysis form_class = RootCauseAnalysisForm template_name = 'rca/rca_form.html' def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse_lazy('rca:rca_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): rca = form.save() messages.success( self.request, 'Root Cause Analysis updated successfully!' ) return redirect(self.get_success_url()) class RCADeleteView(LoginRequiredMixin, View): """Delete (soft delete) view for Root Cause Analysis""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) rca.soft_delete(user=request.user) messages.success( request, 'Root Cause Analysis deleted successfully!' ) return redirect('rca:rca_list') class RCAStatusChangeView(LoginRequiredMixin, View): """View to change RCA status""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) form = RCAStatusChangeForm(request.POST) if form.is_valid(): old_status = rca.status new_status = form.cleaned_data['new_status'] notes = form.cleaned_data['notes'] rca.status = new_status # Set timestamps based on status if new_status == RCAStatus.IN_PROGRESS: rca.assigned_to = request.user rca.assigned_at = timezone.now() elif new_status == RCAStatus.APPROVED: rca.approved_by = request.user rca.approved_at = timezone.now() rca.save() # Create status log rca.status_logs.create( old_status=old_status, new_status=new_status, changed_by=request.user, notes=notes ) messages.success( request, f'Status changed from {old_status} to {new_status}' ) else: messages.error(request, 'Invalid status change') return redirect('rca:rca_detail', pk=rca.pk) class RCAApprovalView(LoginRequiredMixin, View): """View to approve RCA""" def post(self, request, pk): rca = get_object_or_404( RootCauseAnalysis, pk=pk, is_deleted=False, status=RCAStatus.REVIEW ) form = RCAApprovalForm(request.POST) if form.is_valid(): rca.status = RCAStatus.APPROVED rca.approved_by = request.user rca.approved_at = timezone.now() rca.approval_notes = form.cleaned_data['approval_notes'] rca.save() # Create status log rca.status_logs.create( old_status=RCAStatus.REVIEW, new_status=RCAStatus.APPROVED, changed_by=request.user, notes=rca.approval_notes ) messages.success(request, 'RCA approved successfully!') else: messages.error(request, 'Invalid approval data') return redirect('rca:rca_detail', pk=rca.pk) class RCAClosureView(LoginRequiredMixin, View): """View to close RCA""" def post(self, request, pk): rca = get_object_or_404( RootCauseAnalysis, pk=pk, is_deleted=False, status__in=[RCAStatus.APPROVED, RCAStatus.IN_PROGRESS] ) form = RCAClosureForm(request.POST) if form.is_valid(): rca.status = RCAStatus.CLOSED rca.closed_by = request.user rca.closed_at = timezone.now() rca.closure_notes = form.cleaned_data['closure_notes'] rca.actual_completion_date = form.cleaned_data[ 'actual_completion_date' ] rca.save() # Create status log rca.status_logs.create( old_status=RCAStatus.APPROVED, new_status=RCAStatus.CLOSED, changed_by=request.user, notes=rca.closure_notes ) messages.success(request, 'RCA closed successfully!') else: messages.error(request, 'Invalid closure data') return redirect('rca:rca_detail', pk=rca.pk) class RCARootCauseCreateView(LoginRequiredMixin, View): """View to add a root cause to RCA""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) form = RCARootCauseForm(request.POST) if form.is_valid(): root_cause = form.save(commit=False) root_cause.rca = rca root_cause.save() messages.success( request, 'Root cause added successfully!' ) else: messages.error(request, 'Invalid root cause data') return redirect('rca:rca_detail', pk=rca.pk) class RCARootCauseDeleteView(LoginRequiredMixin, View): """View to delete a root cause""" def post(self, request, pk, root_cause_pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) root_cause = get_object_or_404( RCARootCause, pk=root_cause_pk, rca=rca ) root_cause.delete() messages.success(request, 'Root cause deleted successfully!') return redirect('rca:rca_detail', pk=rca.pk) class RCACorrectiveActionCreateView(LoginRequiredMixin, View): """View to add a corrective action to RCA""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) form = RCACorrectiveActionForm(request.POST, rca=rca) if form.is_valid(): action = form.save(commit=False) action.rca = rca action.save() messages.success( request, 'Corrective action added successfully!' ) else: messages.error(request, 'Invalid corrective action data') return redirect('rca:rca_detail', pk=rca.pk) class RCACorrectiveActionDeleteView(LoginRequiredMixin, View): """View to delete a corrective action""" def post(self, request, pk, action_pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) action = get_object_or_404( RCACorrectiveAction, pk=action_pk, rca=rca ) action.delete() messages.success( request, 'Corrective action deleted successfully!' ) return redirect('rca:rca_detail', pk=rca.pk) class RCAAttachmentCreateView(LoginRequiredMixin, View): """View to add an attachment to RCA""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) form = RCAAttachmentForm(request.POST, request.FILES) if form.is_valid(): attachment = form.save(commit=False) attachment.rca = rca attachment.uploaded_by = request.user attachment.filename = request.FILES['file'].name attachment.file_type = request.FILES['file'].content_type attachment.file_size = request.FILES['file'].size attachment.save() messages.success( request, 'Attachment added successfully!' ) else: messages.error(request, 'Invalid attachment data') return redirect('rca:rca_detail', pk=rca.pk) class RCANoteCreateView(LoginRequiredMixin, View): """View to add a note to RCA""" def post(self, request, pk): rca = get_object_or_404(RootCauseAnalysis, pk=pk, is_deleted=False) form = RCANoteForm(request.POST) if form.is_valid(): note = form.save(commit=False) note.rca = rca note.created_by = request.user note.save() messages.success(request, 'Note added successfully!') else: messages.error(request, 'Invalid note data') return redirect('rca:rca_detail', pk=rca.pk)