2025-08-12 13:33:25 +03:00

642 lines
25 KiB
Python

"""
Quality app forms with healthcare-focused validation and user experience.
"""
from django import forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import date, timedelta
from accounts.models import User
from .models import (
QualityIndicator, QualityMeasurement, IncidentReport, RiskAssessment,
AuditPlan, AuditFinding, ImprovementProject
)
class QualityIndicatorForm(forms.ModelForm):
"""
Form for creating and updating quality indicators.
"""
class Meta:
model = QualityIndicator
fields = [
'name', 'description', 'category', 'measurement_unit',
'target_value', 'threshold_warning', 'threshold_critical',
'frequency', 'data_source', 'calculation_method',
'is_active',
]
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter indicator name'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Describe what this indicator measures'
}),
'category': forms.Select(attrs={'class': 'form-control'}),
'measurement_unit': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., %, count, ratio'
}),
'target_value': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'threshold_warning': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'threshold_critical': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'frequency': forms.Select(attrs={'class': 'form-control'}),
'data_source': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Source of measurement data'
}),
'calculation_method': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'How is this indicator calculated?'
}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
# 'notes': forms.Textarea(attrs={
# 'class': 'form-control',
# 'rows': 2,
# 'placeholder': 'Additional notes'
# }),
}
help_texts = {
'target_value': 'The desired value for this indicator',
'threshold_warning': 'Value that triggers a warning alert',
'threshold_critical': 'Value that triggers a critical alert',
'measurement_frequency': 'How often should this indicator be measured?',
}
def clean(self):
cleaned_data = super().clean()
target_value = cleaned_data.get('target_value')
threshold_warning = cleaned_data.get('threshold_warning')
threshold_critical = cleaned_data.get('threshold_critical')
# Validate threshold relationships
if all([target_value, threshold_warning, threshold_critical]):
if threshold_critical >= threshold_warning:
raise ValidationError(
'Critical threshold must be lower than warning threshold for most indicators.'
)
return cleaned_data
class QualityMeasurementForm(forms.ModelForm):
"""
Form for creating quality measurements.
"""
class Meta:
model = QualityMeasurement
fields = [
'indicator', 'measurement_date', 'value', 'data_source',
'measurement_method', 'sample_size', 'notes'
]
widgets = {
'indicator': forms.Select(attrs={'class': 'form-control'}),
'measurement_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'value': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01'
}),
'data_source': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Source of measurement data'
}),
'measurement_method': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'How was this measurement obtained?'
}),
'sample_size': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Additional notes about this measurement'
}),
}
help_texts = {
'measurement_date': 'Date when the measurement was taken',
'value': 'The measured value for this indicator',
'sample_size': 'Number of data points used in this measurement',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
# Filter indicators by tenant and active status
self.fields['indicator'].queryset = QualityIndicator.objects.filter(
tenant=user.tenant,
is_active=True
).order_by('category', 'indicator_name')
def clean_measurement_date(self):
measurement_date = self.cleaned_data.get('measurement_date')
if measurement_date and measurement_date > timezone.now().date():
raise ValidationError('Measurement date cannot be in the future.')
return measurement_date
def clean_value(self):
value = self.cleaned_data.get('value')
if value is not None and value < 0:
raise ValidationError('Measurement value cannot be negative.')
return value
class IncidentReportForm(forms.ModelForm):
"""
Form for creating incident reports.
"""
class Meta:
model = IncidentReport
fields = [
'title', 'description', 'incident_type', 'severity',
'incident_date', 'incident_time', 'location',
'corrective_actions', 'contributing_factors',
'patient', 'witness_information'
]
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Brief title describing the incident'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Detailed description of what happened'
}),
'incident_type': forms.Select(attrs={'class': 'form-control'}),
'severity': forms.Select(attrs={'class': 'form-control'}),
'incident_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'incident_time': forms.TimeInput(attrs={
'class': 'form-control',
'type': 'time'
}),
'location': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Where did the incident occur?'
}),
'department': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Department involved'
}),
# 'persons_involved': forms.Textarea(attrs={
# 'class': 'form-control',
# 'rows': 2,
# 'placeholder': 'Names and roles of persons involved'
# }),
'corrective_actions': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What actions were taken immediately?'
}),
'contributing_factors': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What factors contributed to this incident?'
}),
'patient': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'witness_information': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Witness names and contact information'
}),
}
help_texts = {
'severity': 'Select the severity level based on actual or potential impact',
'incident_date': 'Date when the incident occurred',
'incident_time': 'Approximate time when the incident occurred',
'patient_affected': 'Check if a patient was directly affected by this incident',
}
def clean_incident_date(self):
incident_date = self.cleaned_data.get('incident_date')
if incident_date and incident_date > timezone.now().date():
raise ValidationError('Incident date cannot be in the future.')
return incident_date
class RiskAssessmentForm(forms.ModelForm):
"""
Form for creating and updating risk assessments.
"""
class Meta:
model = RiskAssessment
fields = [
'title', 'description', 'risk_category',
'likelihood', 'impact', 'current_controls',
'mitigation_plan', 'target_completion_date', 'responsible_person',
'review_notes'
]
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Brief title describing the risk'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Detailed description of the risk'
}),
# 'department': forms.TextInput(attrs={
# 'class': 'form-control',
# 'placeholder': 'Department or area affected'
# }),
'risk_category': forms.Select(attrs={'class': 'form-control'}),
'likelihood_score': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1',
'max': '5'
}),
'impact_score': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1',
'max': '5'
}),
'current_controls': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What controls are currently in place?'
}),
'mitigation_plan': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What actions will be taken to mitigate this risk?'
}),
'target_completion_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'responsible_person': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Person responsible for mitigation actions'
}),
'review_notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Additional review notes'
}),
}
help_texts = {
'likelihood': 'Rate the likelihood of this risk occurring (1=Very Low, 5=Very High)',
'impact_score': 'Rate the potential impact if this risk occurs (1=Very Low, 5=Very High)',
'target_completion_date': 'When should mitigation actions be completed?',
}
def clean_target_completion_date(self):
target_date = self.cleaned_data.get('target_completion_date')
if target_date and target_date <= timezone.now().date():
raise ValidationError('Target completion date must be in the future.')
return target_date
def clean(self):
cleaned_data = super().clean()
likelihood = cleaned_data.get('likelihood_score')
impact = cleaned_data.get('impact_score')
# Calculate risk level based on scores
if likelihood and impact:
risk_score = likelihood * impact
if risk_score >= 20:
cleaned_data['risk_level'] = 'CRITICAL'
elif risk_score >= 15:
cleaned_data['risk_level'] = 'HIGH'
elif risk_score >= 10:
cleaned_data['risk_level'] = 'MEDIUM'
else:
cleaned_data['risk_level'] = 'LOW'
return cleaned_data
class AuditPlanForm(forms.ModelForm):
"""
Form for creating and updating audit plans.
"""
class Meta:
model = AuditPlan
fields = [
'title', 'description', 'scope', 'audit_type', 'department',
'planned_start_date', 'planned_end_date', 'auditor',
'audit_team', 'criteria', 'objectives', 'notes'
]
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Title of the audit'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Detailed description of the plan'
}),
'scope': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What areas/processes will be audited?'
}),
'audit_type': forms.Select(attrs={'class': 'form-control'}),
'department': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Department or area to be audited'
}),
'planned_start_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'planned_end_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'auditor': forms.Select(attrs={'class': 'form-control'}),
'audit_team': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Names and roles of audit team members'
}),
'criteria': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Standards, policies, or criteria against which the audit will be conducted'
}),
'objectives': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'What are the objectives of this audit?'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Additional notes'
}),
}
help_texts = {
'planned_start_date': 'When will the audit begin?',
'planned_end_date': 'When is the audit expected to be completed?',
'lead_auditor': 'Person responsible for leading this audit',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
# Filter lead auditor by tenant
self.fields['lead_auditor'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True
).order_by('first_name', 'last_name')
def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get('planned_start_date')
end_date = cleaned_data.get('planned_end_date')
if start_date and end_date:
if end_date <= start_date:
raise ValidationError('Planned end date must be after the start date.')
# Check for reasonable audit duration (not more than 6 months)
if (end_date - start_date).days > 180:
raise ValidationError('Audit duration cannot exceed 6 months.')
return cleaned_data
class AuditFindingForm(forms.ModelForm):
"""
Form for creating audit findings.
"""
class Meta:
model = AuditFinding
fields = [
'audit_plan', 'title', 'description', 'severity',
'finding_date', 'evidence', 'root_cause',
'corrective_actions', 'target_completion_date', 'responsible_person'
]
widgets = {
'audit_plan': forms.Select(attrs={'class': 'form-control'}),
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Brief title describing the finding'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Detailed description of the finding'
}),
'severity': forms.Select(attrs={'class': 'form-control'}),
'finding_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
# 'area_affected': forms.TextInput(attrs={
# 'class': 'form-control',
# 'placeholder': 'Area or process affected by this finding'
# }),
'evidence': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Evidence supporting this finding'
}),
'root_cause': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Analysis of the root cause(s)'
}),
'corrective_actions': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Actions required to address this finding'
}),
'target_completion_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'responsible_person': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Person responsible for corrective actions'
}),
}
help_texts = {
'severity': 'Select severity based on the impact of this finding',
'finding_date': 'Date when this finding was identified',
'target_completion_date': 'When should corrective actions be completed?',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
# Filter audit plans by tenant and active status
self.fields['audit_plan'].queryset = AuditPlan.objects.filter(
tenant=user.tenant,
status__in=['PLANNED', 'IN_PROGRESS']
).order_by('-planned_start_date')
def clean_finding_date(self):
finding_date = self.cleaned_data.get('finding_date')
if finding_date and finding_date > timezone.now().date():
raise ValidationError('Finding date cannot be in the future.')
return finding_date
def clean_target_completion_date(self):
target_date = self.cleaned_data.get('target_completion_date')
if target_date and target_date <= timezone.now().date():
raise ValidationError('Target completion date must be in the future.')
return target_date
class ImprovementProjectForm(forms.ModelForm):
"""
Form for creating and updating improvement projects.
"""
class Meta:
model = ImprovementProject
fields = [
'title', 'description', 'methodology', 'project_manager',
'actual_start_date', 'planned_end_date', 'actual_end_date','problem_statement',
'success_metrics', 'estimated_cost', 'actual_cost',
'resources_required', 'sponsor', 'notes'
]
# ['id', 'tenant', 'project_number', 'title', 'description', 'project_type', 'methodology', 'problem_statement',
# 'goal_statement', 'success_metrics', 'baseline_data', 'target_metrics', 'scope', 'project_manager', 'sponsor',
# 'department', 'planned_start_date', 'planned_end_date', 'actual_start_date', 'actual_end_date', 'status',
# 'phase', 'budget', 'actual_cost', 'roi_expected', 'roi_actual', 'lessons_learned', 'sustainability_plan',
# 'created_by', 'created_at', 'updated_at']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Name of the improvement project'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Detailed description of the project'
}),
'methodology': forms.Select(attrs={'class': 'form-control'}),
'project_manager': forms.Select(attrs={'class': 'form-control'}),
'actual_start_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'planned_end_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'actual_end_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'problem_statement': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'What problem is this project trying to solve?'
}),
'success_metrics': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'How will success be measured?'
}),
'estimated_cost': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'actual_cost': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'resources_required': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'What resources are needed for this project?'
}),
'sponsor': forms.Select(attrs={'class': 'form-control'}),
'project_team': forms.Select(attrs={'class': 'form-control'}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Additional notes'
}),
}
help_texts = {
'methodology': 'Quality improvement methodology to be used',
'start_date': 'When will the project begin?',
'target_completion_date': 'When is the project expected to be completed?',
'estimated_cost': 'Estimated cost of implementing this project',
'estimated_savings': 'Expected savings from this project',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
# Filter project manager by tenant
self.fields['project_manager'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True
).order_by('first_name', 'last_name')
def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get('start_date')
target_date = cleaned_data.get('target_completion_date')
if start_date and target_date:
if target_date <= start_date:
raise ValidationError('Target completion date must be after the start date.')
# Check for reasonable project duration (not more than 2 years)
if (target_date - start_date).days > 730:
raise ValidationError('Project duration cannot exceed 2 years.')
# Validate cost and savings
cost = cleaned_data.get('estimated_cost')
savings = cleaned_data.get('estimated_savings')
if cost and cost < 0:
raise ValidationError('Estimated cost cannot be negative.')
if savings and savings < 0:
raise ValidationError('Estimated savings cannot be negative.')
return cleaned_data