Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

2772 lines
102 KiB
Python

"""
Quality app views with healthcare-focused CRUD operations.
Implements appropriate access patterns for quality management and compliance workflows.
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
)
from django.http import JsonResponse, HttpResponse
from django.db.models import Q, Count, Avg, Sum, F, Case, When, IntegerField
from django.utils import timezone
from django.contrib import messages
from django.urls import reverse_lazy, reverse
from django.core.paginator import Paginator
from django.template.loader import render_to_string
from datetime import datetime, timedelta, date
import json
from core.utils import AuditLogger
from .models import (
QualityIndicator, QualityMeasurement, IncidentReport, RiskAssessment,
AuditPlan, AuditFinding, ImprovementProject
)
from .forms import (
QualityIndicatorForm, QualityMeasurementForm, IncidentReportForm,
RiskAssessmentForm, AuditPlanForm, AuditFindingForm, ImprovementProjectForm
)
# ============================================================================
# DASHBOARD AND OVERVIEW VIEWS
# ============================================================================
class QualityDashboardView(LoginRequiredMixin, TemplateView):
"""
Main quality dashboard with key metrics and recent activity.
"""
template_name = 'quality/dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
today = timezone.now().date()
this_month = timezone.now().replace(day=1).date()
# Dashboard statistics
context.update({
'total_indicators': QualityIndicator.objects.filter(
tenant=tenant,
is_active=True
).count(),
'indicators_meeting_target': QualityIndicator.objects.filter(
tenant=tenant,
is_active=True,
current_value__gte=F('target_value')
).count(),
'indicators_below_target': QualityIndicator.objects.filter(
tenant=tenant,
is_active=True,
current_value__lt=F('target_value')
).count(),
'measurements_this_month': QualityMeasurement.objects.filter(
tenant=tenant,
measurement_date__gte=this_month
).count(),
'incidents_this_month': IncidentReport.objects.filter(
tenant=tenant,
incident_date__gte=this_month
).count(),
'incidents_open': IncidentReport.objects.filter(
tenant=tenant,
status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
).count(),
'high_risk_incidents': IncidentReport.objects.filter(
tenant=tenant,
severity__in=['HIGH', 'CRITICAL'],
status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
).count(),
'risk_assessments_pending': RiskAssessment.objects.filter(
tenant=tenant,
status='PENDING'
).count(),
'audits_active': AuditPlan.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).count(),
'audit_findings_open': AuditFinding.objects.filter(
tenant=tenant,
status__in=['OPEN', 'IN_PROGRESS']
).count(),
'improvement_projects_active': ImprovementProject.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).count(),
})
# Recent incidents
context['recent_incidents'] = IncidentReport.objects.filter(
tenant=tenant
).select_related('reported_by').order_by('-incident_date')[:10]
# Recent measurements
context['recent_measurements'] = QualityMeasurement.objects.filter(
tenant=tenant
).select_related('indicator', 'created_by').order_by('-measurement_date')[:10]
# Active improvement projects
context['active_projects'] = ImprovementProject.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).select_related('project_manager').order_by('-actual_start_date')[:5]
# Quality indicators performance
context['indicator_performance'] = QualityIndicator.objects.filter(
tenant=tenant,
is_active=True
).annotate(
performance_percentage=Case(
When(target_value__gt=0, then=F('current_value') * 100 / F('target_value')),
default=0,
output_field=IntegerField()
)
).order_by('-performance_percentage')[:10]
return context
# ============================================================================
# QUALITY INDICATOR VIEWS (FULL CRUD - Master Data)
# ============================================================================
class QualityIndicatorListView(LoginRequiredMixin, ListView):
"""
List all quality indicators with filtering and search.
"""
model = QualityIndicator
template_name = 'quality/indicators/quality_indicator_list.html'
context_object_name = 'quality_indicators'
paginate_by = 25
def get_queryset(self):
queryset = QualityIndicator.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(indicator_name__icontains=search) |
Q(description__icontains=search) |
Q(category__icontains=search)
)
# Filter by category
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(category=category)
# Filter by frequency
frequency = self.request.GET.get('frequency')
if frequency:
queryset = queryset.filter(measurement_frequency=frequency)
# Filter by active status
active_only = self.request.GET.get('active_only')
if active_only:
queryset = queryset.filter(is_active=True)
# Filter by performance
performance = self.request.GET.get('performance')
if performance == 'meeting_target':
queryset = queryset.filter(current_value__gte=F('target_value'))
elif performance == 'below_target':
queryset = queryset.filter(current_value__lt=F('target_value'))
return queryset.order_by('category', 'name')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'categories': QualityIndicator._meta.get_field('category').choices,
'frequencies': QualityIndicator._meta.get_field('frequency').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class QualityIndicatorDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a quality indicator.
"""
model = QualityIndicator
template_name = 'quality/indicators/quality_indicator_detail.html'
context_object_name = 'quality_indicator'
def get_queryset(self):
return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
indicator = self.object
# Get recent measurements for this indicator
context['recent_measurements'] = indicator.measurements.all().select_related(
'measured_by'
).order_by('-measurement_date')[:20]
# Calculate performance statistics
measurements = indicator.measurements.all()
if measurements.exists():
context['measurement_stats'] = {
'total_measurements': measurements.count(),
'average_value': measurements.aggregate(avg=Avg('value'))['avg'],
'latest_value': measurements.first().value if measurements.first() else None,
'trend_direction': self._calculate_trend(measurements[:5]) if measurements.count() >= 5 else 'stable',
}
# Performance percentage
if indicator.target_value and indicator.target_value > 0:
context['performance_percentage'] = (
indicator.current_value / indicator.target_value * 100
)
return context
def _calculate_trend(self, recent_measurements):
"""Calculate trend direction based on recent measurements."""
if len(recent_measurements) < 2:
return 'stable'
values = [m.value for m in recent_measurements if m.value is not None]
if len(values) < 2:
return 'stable'
# Simple trend calculation
first_half_avg = sum(values[:len(values)//2]) / (len(values)//2)
second_half_avg = sum(values[len(values)//2:]) / (len(values) - len(values)//2)
if second_half_avg > first_half_avg * 1.05:
return 'improving'
elif second_half_avg < first_half_avg * 0.95:
return 'declining'
else:
return 'stable'
class QualityIndicatorCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new quality indicator.
"""
model = QualityIndicator
form_class = QualityIndicatorForm
template_name = 'quality/indicators/quality_indicator_form.html'
permission_required = 'quality.add_qualityindicator'
success_url = reverse_lazy('quality:quality_indicator_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.created_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='QUALITY_INDICATOR_CREATED',
model='QualityIndicator',
object_id=str(self.object.id),
details={
'indicator_name': self.object.indicator_name,
'category': self.object.category,
'target_value': str(self.object.target_value)
}
)
messages.success(self.request, f'Quality indicator "{self.object.indicator_name}" created successfully.')
return response
class QualityIndicatorUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update an existing quality indicator.
"""
model = QualityIndicator
form_class = QualityIndicatorForm
template_name = 'quality/indicators/quality_indicator_form.html'
permission_required = 'quality.change_qualityindicator'
def get_queryset(self):
return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('quality:quality_indicator_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='QUALITY_INDICATOR_UPDATED',
model='QualityIndicator',
object_id=str(self.object.id),
details={
'indicator_name': self.object.indicator_name,
'changes': form.changed_data
}
)
messages.success(self.request, f'Quality indicator "{self.object.indicator_name}" updated successfully.')
return response
class QualityIndicatorDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""
Delete a quality indicator (soft delete by deactivating).
"""
model = QualityIndicator
template_name = 'quality/indicators/quality_indicator_confirm_delete.html'
permission_required = 'quality.delete_qualityindicator'
success_url = reverse_lazy('quality:quality_indicator_list')
def get_queryset(self):
return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
# Check if indicator has measurements
measurements_count = self.object.measurements.count()
if measurements_count > 0:
messages.warning(
request,
f'Quality indicator "{self.object.indicator_name}" has {measurements_count} measurements. '
f'It will be deactivated instead of deleted.'
)
# Soft delete by deactivating
self.object.is_active = False
self.object.save()
# Log the action
AuditLogger.log_event(
user=request.user,
action='QUALITY_INDICATOR_DEACTIVATED',
model='QualityIndicator',
object_id=str(self.object.id),
details={'indicator_name': self.object.indicator_name}
)
messages.success(request, f'Quality indicator "{self.object.indicator_name}" deactivated successfully.')
return redirect(self.success_url)
# ============================================================================
# IMPROVEMENT PROJECT VIEWS (FULL CRUD - Operational Data)
# ============================================================================
class ImprovementProjectListView(LoginRequiredMixin, ListView):
"""
List all improvement projects with filtering and search.
"""
model = ImprovementProject
template_name = 'quality/projects/project_list.html'
context_object_name = 'improvement_projects'
paginate_by = 25
def get_queryset(self):
queryset = ImprovementProject.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(project_name__icontains=search) |
Q(description__icontains=search) |
Q(methodology__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by methodology
methodology = self.request.GET.get('methodology')
if methodology:
queryset = queryset.filter(methodology=methodology)
# Filter by project manager
manager_id = self.request.GET.get('manager')
if manager_id:
queryset = queryset.filter(project_manager_id=manager_id)
return queryset.select_related('project_manager').order_by('-actual_start_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': ImprovementProject._meta.get_field('status').choices,
'methodologies': ImprovementProject._meta.get_field('methodology').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class ImprovementProjectDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about an improvement project.
"""
model = ImprovementProject
template_name = 'quality/projects/project_detail.html'
context_object_name = 'improvement_project'
def get_queryset(self):
return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
project = self.object
# Calculate project progress
if project.start_date and project.target_completion_date:
total_days = (project.target_completion_date - project.start_date).days
if project.status == 'COMPLETED' and project.actual_completion_date:
elapsed_days = (project.actual_completion_date - project.start_date).days
else:
elapsed_days = (timezone.now().date() - project.start_date).days
context['progress_percentage'] = min(100, max(0, (elapsed_days / total_days * 100))) if total_days > 0 else 0
# Calculate ROI if applicable
if project.estimated_cost and project.estimated_savings:
context['estimated_roi'] = (
(project.estimated_savings - project.estimated_cost) / project.estimated_cost * 100
)
return context
class ImprovementProjectCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new improvement project.
"""
model = ImprovementProject
form_class = ImprovementProjectForm
template_name = 'quality/projects/project_form.html'
permission_required = 'quality.add_improvementproject'
success_url = reverse_lazy('quality:improvement_project_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='IMPROVEMENT_PROJECT_CREATED',
model='ImprovementProject',
object_id=str(self.object.id),
details={
'project_name': self.object.project_name,
'methodology': self.object.methodology,
'project_manager': f"{self.object.project_manager.first_name} {self.object.project_manager.last_name}" if self.object.project_manager else None
}
)
messages.success(self.request, f'Improvement project "{self.object.project_name}" created successfully.')
return response
class ImprovementProjectUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update an existing improvement project.
"""
model = ImprovementProject
form_class = ImprovementProjectForm
template_name = 'quality/projects/project_form.html'
permission_required = 'quality.change_improvementproject'
def get_queryset(self):
return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('quality:improvement_project_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='IMPROVEMENT_PROJECT_UPDATED',
model='ImprovementProject',
object_id=str(self.object.id),
details={
'project_name': self.object.project_name,
'changes': form.changed_data
}
)
messages.success(self.request, f'Improvement project "{self.object.project_name}" updated successfully.')
return response
class ImprovementProjectDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""
Delete an improvement project.
"""
model = ImprovementProject
template_name = 'quality/projects/project_confirm_delete.html'
permission_required = 'quality.delete_improvementproject'
success_url = reverse_lazy('quality:improvement_project_list')
def get_queryset(self):
return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
project_name = self.object.project_name
# Log the action before deletion
AuditLogger.log_event(
user=request.user,
action='IMPROVEMENT_PROJECT_DELETED',
model='ImprovementProject',
object_id=str(self.object.id),
details={'project_name': project_name}
)
response = super().delete(request, *args, **kwargs)
messages.success(request, f'Improvement project "{project_name}" deleted successfully.')
return response
# ============================================================================
# AUDIT PLAN VIEWS (LIMITED CRUD - Operational Data)
# ============================================================================
class AuditPlanListView(LoginRequiredMixin, ListView):
"""
List all audit plans with filtering and search.
"""
model = AuditPlan
template_name = 'quality/audits/audit_list.html'
context_object_name = 'audit_plans'
paginate_by = 25
def get_queryset(self):
queryset = AuditPlan.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(audit_name__icontains=search) |
Q(audit_scope__icontains=search) |
Q(department__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by audit type
audit_type = self.request.GET.get('audit_type')
if audit_type:
queryset = queryset.filter(audit_type=audit_type)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(planned_start_date__gte=date_from)
if date_to:
queryset = queryset.filter(planned_end_date__lte=date_to)
return queryset.select_related('lead_auditor').order_by('-planned_start_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': AuditPlan._meta.get_field('status').choices,
'audit_types': AuditPlan._meta.get_field('audit_type').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class AuditPlanDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about an audit plan.
"""
model = AuditPlan
template_name = 'quality/audits/audit_detail.html'
context_object_name = 'audit_plan'
def get_queryset(self):
return AuditPlan.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
audit_plan = self.object
# Get audit findings for this plan
context['audit_findings'] = audit_plan.audit_findings.all().order_by('-finding_date')
# Calculate audit progress
if audit_plan.planned_start_date and audit_plan.planned_end_date:
total_days = (audit_plan.planned_end_date - audit_plan.planned_start_date).days
if audit_plan.status == 'COMPLETED' and audit_plan.actual_end_date:
elapsed_days = (audit_plan.actual_end_date - audit_plan.planned_start_date).days
elif audit_plan.status == 'IN_PROGRESS':
elapsed_days = (timezone.now().date() - audit_plan.planned_start_date).days
else:
elapsed_days = 0
context['progress_percentage'] = min(100, max(0, (elapsed_days / total_days * 100))) if total_days > 0 else 0
return context
class AuditPlanCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new audit plan.
"""
model = AuditPlan
form_class = AuditPlanForm
template_name = 'quality/audits/audit_form.html'
permission_required = 'quality.add_auditplan'
success_url = reverse_lazy('quality:audit_plan_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='AUDIT_PLAN_CREATED',
model='AuditPlan',
object_id=str(self.object.id),
details={
'audit_name': self.object.audit_name,
'audit_type': self.object.audit_type,
'department': self.object.department
}
)
messages.success(self.request, f'Audit plan "{self.object.audit_name}" created successfully.')
return response
class AuditPlanUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update an audit plan (limited to status and notes after start).
"""
model = AuditPlan
template_name = 'quality/audits/audit_form.html'
permission_required = 'quality.change_auditplan'
def get_queryset(self):
return AuditPlan.objects.filter(tenant=self.request.user.tenant)
def get_form_class(self):
# Limit fields based on audit status
if self.object.status in ['IN_PROGRESS', 'COMPLETED']:
# Limited fields for audits that have started
class RestrictedAuditPlanForm(AuditPlanForm):
class Meta(AuditPlanForm.Meta):
fields = ['status', 'actual_start_date', 'actual_end_date', 'notes']
return RestrictedAuditPlanForm
else:
return AuditPlanForm
def get_success_url(self):
return reverse('quality:audit_plan_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='AUDIT_PLAN_UPDATED',
model='AuditPlan',
object_id=str(self.object.id),
details={
'audit_name': self.object.audit_name,
'changes': form.changed_data
}
)
messages.success(self.request, f'Audit plan "{self.object.audit_name}" updated successfully.')
return response
# ============================================================================
# QUALITY MEASUREMENT VIEWS (LIMITED CRUD - Operational Data)
# ============================================================================
class QualityMeasurementListView(LoginRequiredMixin, ListView):
"""
List all quality measurements with filtering and search.
"""
model = QualityMeasurement
template_name = 'quality/measurements/measurement_list.html'
context_object_name = 'quality_measurements'
paginate_by = 25
def get_queryset(self):
queryset = QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
# Filter by indicator
indicator_id = self.request.GET.get('indicator')
if indicator_id:
queryset = queryset.filter(indicator_id=indicator_id)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(measurement_date__gte=date_from)
if date_to:
queryset = queryset.filter(measurement_date__lte=date_to)
return queryset.select_related(
'indicator', 'measured_by', 'verified_by'
).order_by('-measurement_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'indicators': QualityIndicator.objects.filter(
tenant=self.request.user.tenant,
is_active=True
).order_by('name'),
'statuses': QualityMeasurement._meta.get_field('status').choices,
})
return context
class QualityMeasurementDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a quality measurement.
"""
model = QualityMeasurement
template_name = 'quality/measurements/measurement_detail.html'
context_object_name = 'quality_measurement'
def get_queryset(self):
return QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
class QualityMeasurementCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new quality measurement.
"""
model = QualityMeasurement
form_class = QualityMeasurementForm
template_name = 'quality/measurements/measurement_form.html'
permission_required = 'quality.add_qualitymeasurement'
success_url = reverse_lazy('quality:quality_measurement_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.measured_by = self.request.user
response = super().form_valid(form)
# Update indicator current value
indicator = self.object.indicator
indicator.current_value = self.object.value
indicator.last_measured_date = self.object.measurement_date
indicator.save()
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='QUALITY_MEASUREMENT_CREATED',
model='QualityMeasurement',
object_id=str(self.object.id),
details={
'indicator_name': self.object.indicator.indicator_name,
'value': str(self.object.value),
'measurement_date': str(self.object.measurement_date)
}
)
messages.success(self.request, 'Quality measurement recorded successfully.')
return response
class QualityMeasurementUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update quality measurement (limited to notes and verification).
"""
model = QualityMeasurement
fields = ['notes', 'status'] # Restricted fields
template_name = 'quality/measurements/measurement_form.html'
permission_required = 'quality.change_qualitymeasurement'
def get_queryset(self):
return QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('quality:quality_measurement_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
# Set verification details if status is being changed to verified
if form.cleaned_data.get('status') == 'VERIFIED' and self.object.status != 'VERIFIED':
form.instance.verified_by = self.request.user
form.instance.verification_date = timezone.now().date()
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='QUALITY_MEASUREMENT_UPDATED',
model='QualityMeasurement',
object_id=str(self.object.id),
details={
'indicator_name': self.object.indicator.indicator_name,
'changes': form.changed_data
}
)
messages.success(self.request, 'Quality measurement updated successfully.')
return response
# ============================================================================
# RISK ASSESSMENT VIEWS (RESTRICTED CRUD - Clinical/Operational Data)
# ============================================================================
class RiskAssessmentListView(LoginRequiredMixin, ListView):
"""
List all risk assessments with filtering and search.
"""
model = RiskAssessment
template_name = 'quality/risk_assessments/risk_assessment_list.html'
context_object_name = 'risk_assessments'
paginate_by = 25
def get_queryset(self):
queryset = RiskAssessment.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(risk_title__icontains=search) |
Q(risk_description__icontains=search) |
Q(department__icontains=search)
)
# Filter by risk level
risk_level = self.request.GET.get('risk_level')
if risk_level:
queryset = queryset.filter(risk_level=risk_level)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by department
department = self.request.GET.get('department')
if department:
queryset = queryset.filter(department=department)
return queryset.select_related('assessed_by', 'approved_by').order_by('-assessment_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'risk_levels': RiskAssessment._meta.get_field('risk_level').choices,
'statuses': RiskAssessment._meta.get_field('status').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class RiskAssessmentDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a risk assessment.
"""
model = RiskAssessment
template_name = 'quality/risk_assessments/risk_assessment_detail.html'
context_object_name = 'risk_assessment'
def get_queryset(self):
return RiskAssessment.objects.filter(tenant=self.request.user.tenant)
class RiskAssessmentCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new risk assessment.
"""
model = RiskAssessment
form_class = RiskAssessmentForm
template_name = 'quality/risk_assessments/risk_assessment_form.html'
permission_required = 'quality.add_riskassessment'
success_url = reverse_lazy('quality:risk_assessment_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.assessed_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='RISK_ASSESSMENT_CREATED',
model='RiskAssessment',
object_id=str(self.object.id),
details={
'risk_title': self.object.risk_title,
'risk_level': self.object.risk_level,
'department': self.object.department
}
)
messages.success(self.request, f'Risk assessment "{self.object.risk_title}" created successfully.')
return response
class RiskAssessmentUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update risk assessment (limited after approval).
"""
model = RiskAssessment
template_name = 'quality/risk_assessments/risk_assessment_form.html'
permission_required = 'quality.change_riskassessment'
def get_queryset(self):
return RiskAssessment.objects.filter(tenant=self.request.user.tenant)
def get_form_class(self):
# Limit fields based on approval status
if self.object.status == 'APPROVED':
# Very limited fields for approved assessments
class RestrictedRiskAssessmentForm(RiskAssessmentForm):
class Meta(RiskAssessmentForm.Meta):
fields = ['mitigation_actions', 'review_notes']
return RestrictedRiskAssessmentForm
else:
return RiskAssessmentForm
def get_success_url(self):
return reverse('quality:risk_assessment_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='RISK_ASSESSMENT_UPDATED',
model='RiskAssessment',
object_id=str(self.object.id),
details={
'risk_title': self.object.risk_title,
'changes': form.changed_data
}
)
messages.success(self.request, f'Risk assessment "{self.object.risk_title}" updated successfully.')
return response
# ============================================================================
# INCIDENT REPORT VIEWS (APPEND-ONLY - Clinical Records)
# ============================================================================
class IncidentReportListView(LoginRequiredMixin, ListView):
"""
List all incident reports with filtering and search.
"""
model = IncidentReport
template_name = 'quality/incident_reports/incident_report_list.html'
context_object_name = 'incident_reports'
paginate_by = 25
def get_queryset(self):
queryset = IncidentReport.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(incident_title__icontains=search) |
Q(incident_description__icontains=search) |
Q(location__icontains=search)
)
# Filter by severity
severity = self.request.GET.get('severity')
if severity:
queryset = queryset.filter(severity=severity)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by incident type
incident_type = self.request.GET.get('incident_type')
if incident_type:
queryset = queryset.filter(incident_type=incident_type)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(incident_date__gte=date_from)
if date_to:
queryset = queryset.filter(incident_date__lte=date_to)
return queryset.select_related('reported_by').order_by('-incident_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'severities': IncidentReport._meta.get_field('severity').choices,
'statuses': IncidentReport._meta.get_field('status').choices,
'incident_types': IncidentReport._meta.get_field('incident_type').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class IncidentReportDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about an incident report.
"""
model = IncidentReport
template_name = 'quality/incident_reports/incident_report_detail.html'
context_object_name = 'incident_report'
def get_queryset(self):
return IncidentReport.objects.filter(tenant=self.request.user.tenant)
class IncidentReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new incident report.
"""
model = IncidentReport
form_class = IncidentReportForm
template_name = 'quality/incident_reports/incident_report_form.html'
permission_required = 'quality.add_incidentreport'
success_url = reverse_lazy('quality:incident_report_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.reported_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='INCIDENT_REPORT_CREATED',
model='IncidentReport',
object_id=str(self.object.id),
details={
'incident_title': self.object.incident_title,
'severity': self.object.severity,
'incident_type': self.object.incident_type
}
)
messages.success(self.request, f'Incident report "{self.object.incident_title}" created successfully.')
return response
# Note: No UpdateView or DeleteView for IncidentReport - Append-only for patient safety
# ============================================================================
# AUDIT FINDING VIEWS (APPEND-ONLY - Audit Records)
# ============================================================================
class AuditFindingListView(LoginRequiredMixin, ListView):
"""
List all audit findings with filtering and search.
"""
model = AuditFinding
template_name = 'quality/findings/finding_list.html'
context_object_name = 'audit_findings'
paginate_by = 25
def get_queryset(self):
queryset = AuditFinding.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(finding_title__icontains=search) |
Q(finding_description__icontains=search) |
Q(audit_plan__audit_name__icontains=search)
)
# Filter by severity
severity = self.request.GET.get('severity')
if severity:
queryset = queryset.filter(severity=severity)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by audit plan
audit_plan_id = self.request.GET.get('audit_plan')
if audit_plan_id:
queryset = queryset.filter(audit_plan_id=audit_plan_id)
return queryset.select_related('audit_plan', 'identified_by').order_by('-finding_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'severities': AuditFinding._meta.get_field('severity').choices,
'statuses': AuditFinding._meta.get_field('status').choices,
'audit_plans': AuditPlan.objects.filter(
tenant=self.request.user.tenant
).order_by('-planned_start_date'),
'search_query': self.request.GET.get('search', ''),
})
return context
class AuditFindingDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about an audit finding.
"""
model = AuditFinding
template_name = 'quality/findings/finding_detail.html'
context_object_name = 'audit_finding'
def get_queryset(self):
return AuditFinding.objects.filter(tenant=self.request.user.tenant)
class AuditFindingCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new audit finding.
"""
model = AuditFinding
form_class = AuditFindingForm
template_name = 'quality/findings/finding_form.html'
permission_required = 'quality.add_auditfinding'
success_url = reverse_lazy('quality:audit_finding_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.identified_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='AUDIT_FINDING_CREATED',
model='AuditFinding',
object_id=str(self.object.id),
details={
'finding_title': self.object.finding_title,
'severity': self.object.severity,
'audit_plan': self.object.audit_plan.audit_name
}
)
messages.success(self.request, f'Audit finding "{self.object.finding_title}" created successfully.')
return response
# Note: No UpdateView or DeleteView for AuditFinding - Append-only for compliance
# ============================================================================
# HTMX VIEWS FOR REAL-TIME UPDATES
# ============================================================================
@login_required
def quality_stats(request):
"""
HTMX endpoint for quality dashboard statistics.
"""
tenant = request.user.tenant
today = timezone.now().date()
this_month = timezone.now().replace(day=1).date()
stats = {
'indicators_meeting_target': QualityIndicator.objects.filter(
tenant=tenant,
is_active=True,
current_value__gte=F('target_value')
).count(),
'incidents_open': IncidentReport.objects.filter(
tenant=tenant,
status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
).count(),
'high_risk_incidents': IncidentReport.objects.filter(
tenant=tenant,
severity__in=['HIGH', 'CRITICAL'],
status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
).count(),
'improvement_projects_active': ImprovementProject.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).count(),
}
return render(request, 'quality/partials/quality_metrics.html', {'stats': stats})
@login_required
def incident_search(request):
"""
HTMX endpoint for incident report search.
"""
search = request.GET.get('search', '')
severity = request.GET.get('severity', '')
status = request.GET.get('status', '')
queryset = IncidentReport.objects.filter(tenant=request.user.tenant)
if search:
queryset = queryset.filter(
Q(incident_title__icontains=search) |
Q(incident_description__icontains=search) |
Q(location__icontains=search)
)
if severity:
queryset = queryset.filter(severity=severity)
if status:
queryset = queryset.filter(status=status)
incidents = queryset.select_related('reported_by').order_by('-incident_date')[:20]
return render(request, 'quality/partials/incident_list.html', {'incidents': incidents})
# ============================================================================
# ACTION VIEWS
# ============================================================================
@login_required
def approve_risk_assessment(request, assessment_id):
"""
Approve a risk assessment.
"""
if request.method == 'POST':
assessment = get_object_or_404(
RiskAssessment,
id=assessment_id,
tenant=request.user.tenant
)
# Only allow approval if assessment is pending
if assessment.status != 'PENDING':
messages.error(request, 'Only pending risk assessments can be approved.')
return redirect('quality:risk_assessment_detail', pk=assessment.pk)
assessment.status = 'APPROVED'
assessment.approved_by = request.user
assessment.approval_date = timezone.now().date()
assessment.save()
# Log the action
AuditLogger.log_event(
user=request.user,
action='RISK_ASSESSMENT_APPROVED',
model='RiskAssessment',
object_id=str(assessment.id),
details={'risk_title': assessment.risk_title}
)
messages.success(request, f'Risk assessment "{assessment.risk_title}" approved successfully.')
if request.headers.get('HX-Request'):
return render(request, 'quality/partials/risk_assessment_status.html', {'assessment': assessment})
return redirect('quality:risk_assessment_detail', pk=assessment.pk)
return JsonResponse({'success': False})
@login_required
def verify_measurement(request, measurement_id):
"""
Verify a quality measurement.
"""
if request.method == 'POST':
measurement = get_object_or_404(
QualityMeasurement,
id=measurement_id,
tenant=request.user.tenant
)
# Only allow verification if measurement is pending
if measurement.status != 'PENDING':
messages.error(request, 'Only pending measurements can be verified.')
return redirect('quality:quality_measurement_detail', pk=measurement.pk)
measurement.status = 'VERIFIED'
measurement.verified_by = request.user
measurement.verification_date = timezone.now().date()
measurement.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='QUALITY_MEASUREMENT_VERIFIED',
model='QualityMeasurement',
object_id=str(measurement.id),
details={
'indicator_name': measurement.indicator.indicator_name,
'value': str(measurement.value)
}
)
messages.success(request, 'Quality measurement verified successfully.')
if request.headers.get('HX-Request'):
return render(request, 'quality/partials/measurement_status.html', {'measurement': measurement})
return redirect('quality:quality_measurement_detail', pk=measurement.pk)
return JsonResponse({'success': False})
@login_required
def close_incident(request, incident_id):
"""
Close an incident report.
"""
if request.method == 'POST':
incident = get_object_or_404(
IncidentReport,
id=incident_id,
tenant=request.user.tenant
)
# Only allow closing if incident is not already closed
if incident.status == 'CLOSED':
messages.error(request, 'Incident is already closed.')
return redirect('quality:incident_report_detail', pk=incident.pk)
incident.status = 'CLOSED'
incident.resolution_date = timezone.now().date()
incident.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='INCIDENT_REPORT_CLOSED',
model='IncidentReport',
object_id=str(incident.id),
details={'incident_title': incident.incident_title}
)
messages.success(request, f'Incident report "{incident.incident_title}" closed successfully.')
if request.headers.get('HX-Request'):
return render(request, 'quality/partials/incident_status.html', {'incident': incident})
return redirect('quality:incident_report_detail', pk=incident.pk)
return JsonResponse({'success': False})
#
# """
# Quality app views with healthcare-focused CRUD operations.
# Implements appropriate access patterns for quality management and compliance workflows.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
# )
# from django.http import JsonResponse, HttpResponse
# from django.db.models import Q, Count, Avg, Sum, F, Case, When, IntegerField
# from django.utils import timezone
# from django.contrib import messages
# from django.urls import reverse_lazy, reverse
# from django.core.paginator import Paginator
# from django.template.loader import render_to_string
# from datetime import datetime, timedelta, date
# import json
#
# from core.utils import AuditLogger
# from .models import (
# QualityIndicator, QualityMeasurement, IncidentReport, RiskAssessment,
# AuditPlan, AuditFinding, ImprovementProject
# )
# from .forms import (
# QualityIndicatorForm, QualityMeasurementForm, IncidentReportForm,
# RiskAssessmentForm, AuditPlanForm, AuditFindingForm, ImprovementProjectForm
# )
#
#
# # ============================================================================
# # DASHBOARD AND OVERVIEW VIEWS
# # ============================================================================
#
# class QualityDashboardView(LoginRequiredMixin, TemplateView):
# """
# Main quality dashboard with key metrics and recent activity.
# """
# template_name = 'quality/dashboard.html'
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
# today = timezone.now().date()
# this_month = timezone.now().replace(day=1).date()
#
# # Dashboard statistics
# context.update({
# 'total_indicators': QualityIndicator.objects.filter(
# tenant=tenant,
# is_active=True
# ).count(),
# 'indicators_meeting_target': QualityIndicator.objects.filter(
# tenant=tenant,
# is_active=True,
# current_value__gte=F('target_value')
# ).count(),
# 'indicators_below_target': QualityIndicator.objects.filter(
# tenant=tenant,
# is_active=True,
# current_value__lt=F('target_value')
# ).count(),
# 'measurements_this_month': QualityMeasurement.objects.filter(
# tenant=tenant,
# measurement_date__gte=this_month
# ).count(),
# 'incidents_this_month': IncidentReport.objects.filter(
# tenant=tenant,
# incident_date__gte=this_month
# ).count(),
# 'incidents_open': IncidentReport.objects.filter(
# tenant=tenant,
# status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
# ).count(),
# 'high_risk_incidents': IncidentReport.objects.filter(
# tenant=tenant,
# severity__in=['HIGH', 'CRITICAL'],
# status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
# ).count(),
# 'risk_assessments_pending': RiskAssessment.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'audits_active': AuditPlan.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'audit_findings_open': AuditFinding.objects.filter(
# tenant=tenant,
# status__in=['OPEN', 'IN_PROGRESS']
# ).count(),
# 'improvement_projects_active': ImprovementProject.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# })
#
# # Recent incidents
# context['recent_incidents'] = IncidentReport.objects.filter(
# tenant=tenant
# ).select_related('reported_by').order_by('-incident_date')[:10]
#
# # Recent measurements
# context['recent_measurements'] = QualityMeasurement.objects.filter(
# tenant=tenant
# ).select_related('indicator', 'measured_by').order_by('-measurement_date')[:10]
#
# # Active improvement projects
# context['active_projects'] = ImprovementProject.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).select_related('project_manager').order_by('-start_date')[:5]
#
# # Quality indicators performance
# context['indicator_performance'] = QualityIndicator.objects.filter(
# tenant=tenant,
# is_active=True
# ).annotate(
# performance_percentage=Case(
# When(target_value__gt=0, then=F('current_value') * 100 / F('target_value')),
# default=0,
# output_field=IntegerField()
# )
# ).order_by('-performance_percentage')[:10]
#
# return context
#
#
# # ============================================================================
# # QUALITY INDICATOR VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class QualityIndicatorListView(LoginRequiredMixin, ListView):
# """
# List all quality indicators with filtering and search.
# """
# model = QualityIndicator
# template_name = 'quality/quality_indicator_list.html'
# context_object_name = 'quality_indicators'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = QualityIndicator.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(indicator_name__icontains=search) |
# Q(description__icontains=search) |
# Q(category__icontains=search)
# )
#
# # Filter by category
# category = self.request.GET.get('category')
# if category:
# queryset = queryset.filter(category=category)
#
# # Filter by frequency
# frequency = self.request.GET.get('frequency')
# if frequency:
# queryset = queryset.filter(measurement_frequency=frequency)
#
# # Filter by active status
# active_only = self.request.GET.get('active_only')
# if active_only:
# queryset = queryset.filter(is_active=True)
#
# # Filter by performance
# performance = self.request.GET.get('performance')
# if performance == 'meeting_target':
# queryset = queryset.filter(current_value__gte=F('target_value'))
# elif performance == 'below_target':
# queryset = queryset.filter(current_value__lt=F('target_value'))
#
# return queryset.order_by('category', 'indicator_name')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'categories': QualityIndicator._meta.get_field('category').choices,
# 'frequencies': QualityIndicator._meta.get_field('measurement_frequency').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class QualityIndicatorDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a quality indicator.
# """
# model = QualityIndicator
# template_name = 'quality/quality_indicator_detail.html'
# context_object_name = 'quality_indicator'
#
# def get_queryset(self):
# return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# indicator = self.object
#
# # Get recent measurements for this indicator
# context['recent_measurements'] = indicator.measurements.all().select_related(
# 'measured_by'
# ).order_by('-measurement_date')[:20]
#
# # Calculate performance statistics
# measurements = indicator.measurements.all()
# if measurements.exists():
# context['measurement_stats'] = {
# 'total_measurements': measurements.count(),
# 'average_value': measurements.aggregate(avg=Avg('value'))['avg'],
# 'latest_value': measurements.first().value if measurements.first() else None,
# 'trend_direction': self._calculate_trend(measurements[:5]) if measurements.count() >= 5 else 'stable',
# }
#
# # Performance percentage
# if indicator.target_value and indicator.target_value > 0:
# context['performance_percentage'] = (
# indicator.current_value / indicator.target_value * 100
# )
#
# return context
#
# def _calculate_trend(self, recent_measurements):
# """Calculate trend direction based on recent measurements."""
# if len(recent_measurements) < 2:
# return 'stable'
#
# values = [m.value for m in recent_measurements if m.value is not None]
# if len(values) < 2:
# return 'stable'
#
# # Simple trend calculation
# first_half_avg = sum(values[:len(values) // 2]) / (len(values) // 2)
# second_half_avg = sum(values[len(values) // 2:]) / (len(values) - len(values) // 2)
#
# if second_half_avg > first_half_avg * 1.05:
# return 'improving'
# elif second_half_avg < first_half_avg * 0.95:
# return 'declining'
# else:
# return 'stable'
#
#
# class QualityIndicatorCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new quality indicator.
# """
# model = QualityIndicator
# form_class = QualityIndicatorForm
# template_name = 'quality/quality_indicator_form.html'
# permission_required = 'quality.add_qualityindicator'
# success_url = reverse_lazy('quality:quality_indicator_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.created_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QUALITY_INDICATOR_CREATED',
# model='QualityIndicator',
# object_id=str(self.object.id),
# details={
# 'indicator_name': self.object.indicator_name,
# 'category': self.object.category,
# 'target_value': str(self.object.target_value)
# }
# )
#
# messages.success(self.request, f'Quality indicator "{self.object.indicator_name}" created successfully.')
# return response
#
#
# class QualityIndicatorUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing quality indicator.
# """
# model = QualityIndicator
# form_class = QualityIndicatorForm
# template_name = 'quality/quality_indicator_form.html'
# permission_required = 'quality.change_qualityindicator'
#
# def get_queryset(self):
# return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('quality:quality_indicator_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QUALITY_INDICATOR_UPDATED',
# model='QualityIndicator',
# object_id=str(self.object.id),
# details={
# 'indicator_name': self.object.indicator_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Quality indicator "{self.object.indicator_name}" updated successfully.')
# return response
#
#
# class QualityIndicatorDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete a quality indicator (soft delete by deactivating).
# """
# model = QualityIndicator
# template_name = 'quality/quality_indicator_confirm_delete.html'
# permission_required = 'quality.delete_qualityindicator'
# success_url = reverse_lazy('quality:quality_indicator_list')
#
# def get_queryset(self):
# return QualityIndicator.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Check if indicator has measurements
# measurements_count = self.object.measurements.count()
# if measurements_count > 0:
# messages.warning(
# request,
# f'Quality indicator "{self.object.indicator_name}" has {measurements_count} measurements. '
# f'It will be deactivated instead of deleted.'
# )
#
# # Soft delete by deactivating
# self.object.is_active = False
# self.object.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='QUALITY_INDICATOR_DEACTIVATED',
# model='QualityIndicator',
# object_id=str(self.object.id),
# details={'indicator_name': self.object.indicator_name}
# )
#
# messages.success(request, f'Quality indicator "{self.object.indicator_name}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # IMPROVEMENT PROJECT VIEWS (FULL CRUD - Operational Data)
# # ============================================================================
#
# class ImprovementProjectListView(LoginRequiredMixin, ListView):
# """
# List all improvement projects with filtering and search.
# """
# model = ImprovementProject
# template_name = 'quality/improvement_project_list.html'
# context_object_name = 'improvement_projects'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ImprovementProject.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(project_name__icontains=search) |
# Q(description__icontains=search) |
# Q(methodology__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by methodology
# methodology = self.request.GET.get('methodology')
# if methodology:
# queryset = queryset.filter(methodology=methodology)
#
# # Filter by project manager
# manager_id = self.request.GET.get('manager')
# if manager_id:
# queryset = queryset.filter(project_manager_id=manager_id)
#
# return queryset.select_related('project_manager').order_by('-start_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': ImprovementProject._meta.get_field('status').choices,
# 'methodologies': ImprovementProject._meta.get_field('methodology').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class ImprovementProjectDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an improvement project.
# """
# model = ImprovementProject
# template_name = 'quality/improvement_project_detail.html'
# context_object_name = 'improvement_project'
#
# def get_queryset(self):
# return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# project = self.object
#
# # Calculate project progress
# if project.start_date and project.target_completion_date:
# total_days = (project.target_completion_date - project.start_date).days
# if project.status == 'COMPLETED' and project.actual_completion_date:
# elapsed_days = (project.actual_completion_date - project.start_date).days
# else:
# elapsed_days = (timezone.now().date() - project.start_date).days
#
# context['progress_percentage'] = min(100,
# max(0, (elapsed_days / total_days * 100))) if total_days > 0 else 0
#
# # Calculate ROI if applicable
# if project.estimated_cost and project.estimated_savings:
# context['estimated_roi'] = (
# (project.estimated_savings - project.estimated_cost) / project.estimated_cost * 100
# )
#
# return context
#
#
# class ImprovementProjectCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new improvement project.
# """
# model = ImprovementProject
# form_class = ImprovementProjectForm
# template_name = 'quality/improvement_project_form.html'
# permission_required = 'quality.add_improvementproject'
# success_url = reverse_lazy('quality:improvement_project_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMPROVEMENT_PROJECT_CREATED',
# model='ImprovementProject',
# object_id=str(self.object.id),
# details={
# 'project_name': self.object.project_name,
# 'methodology': self.object.methodology,
# 'project_manager': f"{self.object.project_manager.first_name} {self.object.project_manager.last_name}" if self.object.project_manager else None
# }
# )
#
# messages.success(self.request, f'Improvement project "{self.object.project_name}" created successfully.')
# return response
#
#
# class ImprovementProjectUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing improvement project.
# """
# model = ImprovementProject
# form_class = ImprovementProjectForm
# template_name = 'quality/improvement_project_form.html'
# permission_required = 'quality.change_improvementproject'
#
# def get_queryset(self):
# return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('quality:improvement_project_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='IMPROVEMENT_PROJECT_UPDATED',
# model='ImprovementProject',
# object_id=str(self.object.id),
# details={
# 'project_name': self.object.project_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Improvement project "{self.object.project_name}" updated successfully.')
# return response
#
#
# class ImprovementProjectDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete an improvement project.
# """
# model = ImprovementProject
# template_name = 'quality/improvement_project_confirm_delete.html'
# permission_required = 'quality.delete_improvementproject'
# success_url = reverse_lazy('quality:improvement_project_list')
#
# def get_queryset(self):
# return ImprovementProject.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
# project_name = self.object.project_name
#
# # Log the action before deletion
# AuditLogger.log_action(
# user=request.user,
# action='IMPROVEMENT_PROJECT_DELETED',
# model='ImprovementProject',
# object_id=str(self.object.id),
# details={'project_name': project_name}
# )
#
# response = super().delete(request, *args, **kwargs)
# messages.success(request, f'Improvement project "{project_name}" deleted successfully.')
# return response
#
#
# # ============================================================================
# # AUDIT PLAN VIEWS (LIMITED CRUD - Operational Data)
# # ============================================================================
#
# class AuditPlanListView(LoginRequiredMixin, ListView):
# """
# List all audit plans with filtering and search.
# """
# model = AuditPlan
# template_name = 'quality/audit_plan_list.html'
# context_object_name = 'audit_plans'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = AuditPlan.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(audit_name__icontains=search) |
# Q(audit_scope__icontains=search) |
# Q(department__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by audit type
# audit_type = self.request.GET.get('audit_type')
# if audit_type:
# queryset = queryset.filter(audit_type=audit_type)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(planned_start_date__gte=date_from)
# if date_to:
# queryset = queryset.filter(planned_end_date__lte=date_to)
#
# return queryset.select_related('lead_auditor').order_by('-planned_start_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': AuditPlan._meta.get_field('status').choices,
# 'audit_types': AuditPlan._meta.get_field('audit_type').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class AuditPlanDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an audit plan.
# """
# model = AuditPlan
# template_name = 'quality/audit_plan_detail.html'
# context_object_name = 'audit_plan'
#
# def get_queryset(self):
# return AuditPlan.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# audit_plan = self.object
#
# # Get audit findings for this plan
# context['audit_findings'] = audit_plan.audit_findings.all().order_by('-finding_date')
#
# # Calculate audit progress
# if audit_plan.planned_start_date and audit_plan.planned_end_date:
# total_days = (audit_plan.planned_end_date - audit_plan.planned_start_date).days
# if audit_plan.status == 'COMPLETED' and audit_plan.actual_end_date:
# elapsed_days = (audit_plan.actual_end_date - audit_plan.planned_start_date).days
# elif audit_plan.status == 'IN_PROGRESS':
# elapsed_days = (timezone.now().date() - audit_plan.planned_start_date).days
# else:
# elapsed_days = 0
#
# context['progress_percentage'] = min(100,
# max(0, (elapsed_days / total_days * 100))) if total_days > 0 else 0
#
# return context
#
#
# class AuditPlanCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new audit plan.
# """
# model = AuditPlan
# form_class = AuditPlanForm
# template_name = 'quality/audit_plan_form.html'
# permission_required = 'quality.add_auditplan'
# success_url = reverse_lazy('quality:audit_plan_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='AUDIT_PLAN_CREATED',
# model='AuditPlan',
# object_id=str(self.object.id),
# details={
# 'audit_name': self.object.audit_name,
# 'audit_type': self.object.audit_type,
# 'department': self.object.department
# }
# )
#
# messages.success(self.request, f'Audit plan "{self.object.audit_name}" created successfully.')
# return response
#
#
# class AuditPlanUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an audit plan (limited to status and notes after start).
# """
# model = AuditPlan
# template_name = 'quality/audit_plan_update_form.html'
# permission_required = 'quality.change_auditplan'
#
# def get_queryset(self):
# return AuditPlan.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_class(self):
# # Limit fields based on audit status
# if self.object.status in ['IN_PROGRESS', 'COMPLETED']:
# # Limited fields for audits that have started
# class RestrictedAuditPlanForm(AuditPlanForm):
# class Meta(AuditPlanForm.Meta):
# fields = ['status', 'actual_start_date', 'actual_end_date', 'notes']
#
# return RestrictedAuditPlanForm
# else:
# return AuditPlanForm
#
# def get_success_url(self):
# return reverse('quality:audit_plan_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='AUDIT_PLAN_UPDATED',
# model='AuditPlan',
# object_id=str(self.object.id),
# details={
# 'audit_name': self.object.audit_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Audit plan "{self.object.audit_name}" updated successfully.')
# return response
#
#
# # ============================================================================
# # QUALITY MEASUREMENT VIEWS (LIMITED CRUD - Operational Data)
# # ============================================================================
#
# class QualityMeasurementListView(LoginRequiredMixin, ListView):
# """
# List all quality measurements with filtering and search.
# """
# model = QualityMeasurement
# template_name = 'quality/quality_measurement_list.html'
# context_object_name = 'quality_measurements'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by indicator
# indicator_id = self.request.GET.get('indicator')
# if indicator_id:
# queryset = queryset.filter(indicator_id=indicator_id)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(measurement_date__gte=date_from)
# if date_to:
# queryset = queryset.filter(measurement_date__lte=date_to)
#
# return queryset.select_related(
# 'indicator', 'measured_by', 'verified_by'
# ).order_by('-measurement_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'indicators': QualityIndicator.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('indicator_name'),
# 'statuses': QualityMeasurement._meta.get_field('status').choices,
# })
# return context
#
#
# class QualityMeasurementDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a quality measurement.
# """
# model = QualityMeasurement
# template_name = 'quality/quality_measurement_detail.html'
# context_object_name = 'quality_measurement'
#
# def get_queryset(self):
# return QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
#
#
# class QualityMeasurementCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new quality measurement.
# """
# model = QualityMeasurement
# form_class = QualityMeasurementForm
# template_name = 'quality/quality_measurement_form.html'
# permission_required = 'quality.add_qualitymeasurement'
# success_url = reverse_lazy('quality:quality_measurement_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.measured_by = self.request.user
# response = super().form_valid(form)
#
# # Update indicator current value
# indicator = self.object.indicator
# indicator.current_value = self.object.value
# indicator.last_measured_date = self.object.measurement_date
# indicator.save()
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QUALITY_MEASUREMENT_CREATED',
# model='QualityMeasurement',
# object_id=str(self.object.id),
# details={
# 'indicator_name': self.object.indicator.indicator_name,
# 'value': str(self.object.value),
# 'measurement_date': str(self.object.measurement_date)
# }
# )
#
# messages.success(self.request, 'Quality measurement recorded successfully.')
# return response
#
#
# class QualityMeasurementUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update quality measurement (limited to notes and verification).
# """
# model = QualityMeasurement
# fields = ['notes', 'status'] # Restricted fields
# template_name = 'quality/quality_measurement_update_form.html'
# permission_required = 'quality.change_qualitymeasurement'
#
# def get_queryset(self):
# return QualityMeasurement.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('quality:quality_measurement_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# # Set verification details if status is being changed to verified
# if form.cleaned_data.get('status') == 'VERIFIED' and self.object.status != 'VERIFIED':
# form.instance.verified_by = self.request.user
# form.instance.verification_date = timezone.now().date()
#
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QUALITY_MEASUREMENT_UPDATED',
# model='QualityMeasurement',
# object_id=str(self.object.id),
# details={
# 'indicator_name': self.object.indicator.indicator_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Quality measurement updated successfully.')
# return response
#
#
# # ============================================================================
# # RISK ASSESSMENT VIEWS (RESTRICTED CRUD - Clinical/Operational Data)
# # ============================================================================
#
# class RiskAssessmentListView(LoginRequiredMixin, ListView):
# """
# List all risk assessments with filtering and search.
# """
# model = RiskAssessment
# template_name = 'quality/risk_assessment_list.html'
# context_object_name = 'risk_assessments'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = RiskAssessment.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(risk_title__icontains=search) |
# Q(risk_description__icontains=search) |
# Q(department__icontains=search)
# )
#
# # Filter by risk level
# risk_level = self.request.GET.get('risk_level')
# if risk_level:
# queryset = queryset.filter(risk_level=risk_level)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by department
# department = self.request.GET.get('department')
# if department:
# queryset = queryset.filter(department=department)
#
# return queryset.select_related('assessed_by', 'approved_by').order_by('-assessment_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'risk_levels': RiskAssessment._meta.get_field('risk_level').choices,
# 'statuses': RiskAssessment._meta.get_field('status').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class RiskAssessmentDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a risk assessment.
# """
# model = RiskAssessment
# template_name = 'quality/risk_assessment_detail.html'
# context_object_name = 'risk_assessment'
#
# def get_queryset(self):
# return RiskAssessment.objects.filter(tenant=self.request.user.tenant)
#
#
# class RiskAssessmentCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new risk assessment.
# """
# model = RiskAssessment
# form_class = RiskAssessmentForm
# template_name = 'quality/risk_assessment_form.html'
# permission_required = 'quality.add_riskassessment'
# success_url = reverse_lazy('quality:risk_assessment_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.assessed_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='RISK_ASSESSMENT_CREATED',
# model='RiskAssessment',
# object_id=str(self.object.id),
# details={
# 'risk_title': self.object.risk_title,
# 'risk_level': self.object.risk_level,
# 'department': self.object.department
# }
# )
#
# messages.success(self.request, f'Risk assessment "{self.object.risk_title}" created successfully.')
# return response
#
#
# class RiskAssessmentUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update risk assessment (limited after approval).
# """
# model = RiskAssessment
# template_name = 'quality/risk_assessment_update_form.html'
# permission_required = 'quality.change_riskassessment'
#
# def get_queryset(self):
# return RiskAssessment.objects.filter(tenant=self.request.user.tenant)
#
# def get_form_class(self):
# # Limit fields based on approval status
# if self.object.status == 'APPROVED':
# # Very limited fields for approved assessments
# class RestrictedRiskAssessmentForm(RiskAssessmentForm):
# class Meta(RiskAssessmentForm.Meta):
# fields = ['mitigation_actions', 'review_notes']
#
# return RestrictedRiskAssessmentForm
# else:
# return RiskAssessmentForm
#
# def get_success_url(self):
# return reverse('quality:risk_assessment_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='RISK_ASSESSMENT_UPDATED',
# model='RiskAssessment',
# object_id=str(self.object.id),
# details={
# 'risk_title': self.object.risk_title,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Risk assessment "{self.object.risk_title}" updated successfully.')
# return response
#
#
# # ============================================================================
# # INCIDENT REPORT VIEWS (APPEND-ONLY - Clinical Records)
# # ============================================================================
#
# class IncidentReportListView(LoginRequiredMixin, ListView):
# """
# List all incident reports with filtering and search.
# """
# model = IncidentReport
# template_name = 'quality/incident_report_list.html'
# context_object_name = 'incident_reports'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = IncidentReport.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(incident_title__icontains=search) |
# Q(incident_description__icontains=search) |
# Q(location__icontains=search)
# )
#
# # Filter by severity
# severity = self.request.GET.get('severity')
# if severity:
# queryset = queryset.filter(severity=severity)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by incident type
# incident_type = self.request.GET.get('incident_type')
# if incident_type:
# queryset = queryset.filter(incident_type=incident_type)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(incident_date__gte=date_from)
# if date_to:
# queryset = queryset.filter(incident_date__lte=date_to)
#
# return queryset.select_related('reported_by').order_by('-incident_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'severities': IncidentReport._meta.get_field('severity').choices,
# 'statuses': IncidentReport._meta.get_field('status').choices,
# 'incident_types': IncidentReport._meta.get_field('incident_type').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class IncidentReportDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an incident report.
# """
# model = IncidentReport
# template_name = 'quality/incident_report_detail.html'
# context_object_name = 'incident_report'
#
# def get_queryset(self):
# return IncidentReport.objects.filter(tenant=self.request.user.tenant)
#
#
# class IncidentReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new incident report.
# """
# model = IncidentReport
# form_class = IncidentReportForm
# template_name = 'quality/incident_report_form.html'
# permission_required = 'quality.add_incidentreport'
# success_url = reverse_lazy('quality:incident_report_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.reported_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='INCIDENT_REPORT_CREATED',
# model='IncidentReport',
# object_id=str(self.object.id),
# details={
# 'incident_title': self.object.incident_title,
# 'severity': self.object.severity,
# 'incident_type': self.object.incident_type
# }
# )
#
# messages.success(self.request, f'Incident report "{self.object.incident_title}" created successfully.')
# return response
#
#
# # Note: No UpdateView or DeleteView for IncidentReport - Append-only for patient safety
#
#
# # ============================================================================
# # AUDIT FINDING VIEWS (APPEND-ONLY - Audit Records)
# # ============================================================================
#
# class AuditFindingListView(LoginRequiredMixin, ListView):
# """
# List all audit findings with filtering and search.
# """
# model = AuditFinding
# template_name = 'quality/audit_finding_list.html'
# context_object_name = 'audit_findings'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = AuditFinding.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(finding_title__icontains=search) |
# Q(finding_description__icontains=search) |
# Q(audit_plan__audit_name__icontains=search)
# )
#
# # Filter by severity
# severity = self.request.GET.get('severity')
# if severity:
# queryset = queryset.filter(severity=severity)
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by audit plan
# audit_plan_id = self.request.GET.get('audit_plan')
# if audit_plan_id:
# queryset = queryset.filter(audit_plan_id=audit_plan_id)
#
# return queryset.select_related('audit_plan', 'identified_by').order_by('-finding_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'severities': AuditFinding._meta.get_field('severity').choices,
# 'statuses': AuditFinding._meta.get_field('status').choices,
# 'audit_plans': AuditPlan.objects.filter(
# tenant=self.request.user.tenant
# ).order_by('-planned_start_date'),
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class AuditFindingDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about an audit finding.
# """
# model = AuditFinding
# template_name = 'quality/audit_finding_detail.html'
# context_object_name = 'audit_finding'
#
# def get_queryset(self):
# return AuditFinding.objects.filter(tenant=self.request.user.tenant)
#
#
# class AuditFindingCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new audit finding.
# """
# model = AuditFinding
# form_class = AuditFindingForm
# template_name = 'quality/audit_finding_form.html'
# permission_required = 'quality.add_auditfinding'
# success_url = reverse_lazy('quality:audit_finding_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.identified_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='AUDIT_FINDING_CREATED',
# model='AuditFinding',
# object_id=str(self.object.id),
# details={
# 'finding_title': self.object.finding_title,
# 'severity': self.object.severity,
# 'audit_plan': self.object.audit_plan.audit_name
# }
# )
#
# messages.success(self.request, f'Audit finding "{self.object.finding_title}" created successfully.')
# return response
#
#
# # Note: No UpdateView or DeleteView for AuditFinding - Append-only for compliance
#
#
# # ============================================================================
# # HTMX VIEWS FOR REAL-TIME UPDATES
# # ============================================================================
#
# @login_required
# def quality_stats(request):
# """
# HTMX endpoint for quality dashboard statistics.
# """
# tenant = request.user.tenant
# today = timezone.now().date()
# this_month = timezone.now().replace(day=1).date()
#
# stats = {
# 'indicators_meeting_target': QualityIndicator.objects.filter(
# tenant=tenant,
# is_active=True,
# current_value__gte=F('target_value')
# ).count(),
# 'incidents_open': IncidentReport.objects.filter(
# tenant=tenant,
# status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
# ).count(),
# 'high_risk_incidents': IncidentReport.objects.filter(
# tenant=tenant,
# severity__in=['HIGH', 'CRITICAL'],
# status__in=['REPORTED', 'INVESTIGATING', 'PENDING_REVIEW']
# ).count(),
# 'improvement_projects_active': ImprovementProject.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# }
#
# return render(request, 'quality/partials/quality_metrics.html', {'stats': stats})
#
#
# @login_required
# def incident_search(request):
# """
# HTMX endpoint for incident report search.
# """
# search = request.GET.get('search', '')
# severity = request.GET.get('severity', '')
# status = request.GET.get('status', '')
#
# queryset = IncidentReport.objects.filter(tenant=request.user.tenant)
#
# if search:
# queryset = queryset.filter(
# Q(incident_title__icontains=search) |
# Q(incident_description__icontains=search) |
# Q(location__icontains=search)
# )
#
# if severity:
# queryset = queryset.filter(severity=severity)
#
# if status:
# queryset = queryset.filter(status=status)
#
# incidents = queryset.select_related('reported_by').order_by('-incident_date')[:20]
#
# return render(request, 'quality/partials/incident_list.html', {'incidents': incidents})
#
#
# # ============================================================================
# # ACTION VIEWS
# # ============================================================================
#
# @login_required
# def approve_risk_assessment(request, assessment_id):
# """
# Approve a risk assessment.
# """
# if request.method == 'POST':
# assessment = get_object_or_404(
# RiskAssessment,
# id=assessment_id,
# tenant=request.user.tenant
# )
#
# # Only allow approval if assessment is pending
# if assessment.status != 'PENDING':
# messages.error(request, 'Only pending risk assessments can be approved.')
# return redirect('quality:risk_assessment_detail', pk=assessment.pk)
#
# assessment.status = 'APPROVED'
# assessment.approved_by = request.user
# assessment.approval_date = timezone.now().date()
# assessment.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='RISK_ASSESSMENT_APPROVED',
# model='RiskAssessment',
# object_id=str(assessment.id),
# details={'risk_title': assessment.risk_title}
# )
#
# messages.success(request, f'Risk assessment "{assessment.risk_title}" approved successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'quality/partials/risk_assessment_status.html', {'assessment': assessment})
#
# return redirect('quality:risk_assessment_detail', pk=assessment.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def verify_measurement(request, measurement_id):
# """
# Verify a quality measurement.
# """
# if request.method == 'POST':
# measurement = get_object_or_404(
# QualityMeasurement,
# id=measurement_id,
# tenant=request.user.tenant
# )
#
# # Only allow verification if measurement is pending
# if measurement.status != 'PENDING':
# messages.error(request, 'Only pending measurements can be verified.')
# return redirect('quality:quality_measurement_detail', pk=measurement.pk)
#
# measurement.status = 'VERIFIED'
# measurement.verified_by = request.user
# measurement.verification_date = timezone.now().date()
# measurement.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='QUALITY_MEASUREMENT_VERIFIED',
# model='QualityMeasurement',
# object_id=str(measurement.id),
# details={
# 'indicator_name': measurement.indicator.indicator_name,
# 'value': str(measurement.value)
# }
# )
#
# messages.success(request, 'Quality measurement verified successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'quality/partials/measurement_status.html', {'measurement': measurement})
#
# return redirect('quality:quality_measurement_detail', pk=measurement.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def close_incident(request, incident_id):
# """
# Close an incident report.
# """
# if request.method == 'POST':
# incident = get_object_or_404(
# IncidentReport,
# id=incident_id,
# tenant=request.user.tenant
# )
#
# # Only allow closing if incident is not already closed
# if incident.status == 'CLOSED':
# messages.error(request, 'Incident is already closed.')
# return redirect('quality:incident_report_detail', pk=incident.pk)
#
# incident.status = 'CLOSED'
# incident.resolution_date = timezone.now().date()
# incident.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='INCIDENT_REPORT_CLOSED',
# model='IncidentReport',
# object_id=str(incident.id),
# details={'incident_title': incident.incident_title}
# )
#
# messages.success(request, f'Incident report "{incident.incident_title}" closed successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'quality/partials/incident_status.html', {'incident': incident})
#
# return redirect('quality:incident_report_detail', pk=incident.pk)
#
# return JsonResponse({'success': False})
#
#
#