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