365 lines
12 KiB
Python
365 lines
12 KiB
Python
"""
|
|
RCA (Root Cause Analysis) forms
|
|
"""
|
|
from django import forms
|
|
from django.utils import timezone
|
|
|
|
from apps.core.models import PriorityChoices
|
|
|
|
from .models import (
|
|
RCAActionStatus,
|
|
RCAActionType,
|
|
RCACorrectiveAction,
|
|
RCAStatus,
|
|
RCASeverity,
|
|
RCAAttachment,
|
|
RCANote,
|
|
RCARootCause,
|
|
RootCauseAnalysis,
|
|
)
|
|
|
|
|
|
class RootCauseAnalysisForm(forms.ModelForm):
|
|
"""Form for creating and editing RootCauseAnalysis"""
|
|
|
|
class Meta:
|
|
model = RootCauseAnalysis
|
|
fields = [
|
|
'title',
|
|
'description',
|
|
'background',
|
|
'hospital',
|
|
'department',
|
|
'status',
|
|
'severity',
|
|
'priority',
|
|
'assigned_to',
|
|
'target_completion_date',
|
|
'root_cause_summary',
|
|
]
|
|
widgets = {
|
|
'title': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter RCA title'
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Describe the incident or issue'
|
|
}),
|
|
'background': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Provide background information and context'
|
|
}),
|
|
'hospital': forms.Select(attrs={'class': 'form-select'}),
|
|
'department': forms.Select(attrs={'class': 'form-select'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'severity': forms.Select(attrs={'class': 'form-select'}),
|
|
'priority': forms.Select(attrs={'class': 'form-select'}),
|
|
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'target_completion_date': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
'root_cause_summary': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Summary of root cause analysis findings'
|
|
}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Filter assigned_to to show only active users
|
|
if 'assigned_to' in self.fields:
|
|
from apps.accounts.models import User
|
|
self.fields['assigned_to'].queryset = User.objects.filter(
|
|
is_active=True
|
|
).order_by('email')
|
|
|
|
# Set initial hospital if user has one
|
|
if user and not self.instance.pk:
|
|
from apps.organizations.models import Hospital
|
|
try:
|
|
user_hospital = Hospital.objects.filter(
|
|
staff__user=user
|
|
).first()
|
|
if user_hospital:
|
|
self.fields['hospital'].initial = user_hospital
|
|
self.fields['hospital'].widget.attrs['readonly'] = True
|
|
except:
|
|
pass
|
|
|
|
|
|
class RCARootCauseForm(forms.ModelForm):
|
|
"""Form for creating and editing RCARootCause"""
|
|
|
|
class Meta:
|
|
model = RCARootCause
|
|
fields = [
|
|
'description',
|
|
'category',
|
|
'contributing_factors',
|
|
'likelihood',
|
|
'impact',
|
|
'evidence',
|
|
]
|
|
widgets = {
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Describe the root cause'
|
|
}),
|
|
'category': forms.Select(attrs={'class': 'form-select'}),
|
|
'contributing_factors': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'Factors that contributed to this root cause'
|
|
}),
|
|
'likelihood': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'max': 5,
|
|
'placeholder': '1-5'
|
|
}),
|
|
'impact': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'max': 5,
|
|
'placeholder': '1-5'
|
|
}),
|
|
'evidence': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'Evidence supporting this root cause'
|
|
}),
|
|
}
|
|
|
|
def clean_likelihood(self):
|
|
likelihood = self.cleaned_data.get('likelihood')
|
|
if likelihood and (likelihood < 1 or likelihood > 5):
|
|
raise forms.ValidationError('Likelihood must be between 1 and 5')
|
|
return likelihood
|
|
|
|
def clean_impact(self):
|
|
impact = self.cleaned_data.get('impact')
|
|
if impact and (impact < 1 or impact > 5):
|
|
raise forms.ValidationError('Impact must be between 1 and 5')
|
|
return impact
|
|
|
|
|
|
class RCACorrectiveActionForm(forms.ModelForm):
|
|
"""Form for creating and editing RCACorrectiveAction"""
|
|
|
|
class Meta:
|
|
model = RCACorrectiveAction
|
|
fields = [
|
|
'description',
|
|
'action_type',
|
|
'root_cause',
|
|
'responsible_person',
|
|
'target_date',
|
|
'completion_date',
|
|
'status',
|
|
'effectiveness_measure',
|
|
'effectiveness_assessment',
|
|
'effectiveness_score',
|
|
'obstacles',
|
|
]
|
|
widgets = {
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Describe the corrective action'
|
|
}),
|
|
'action_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'root_cause': forms.Select(attrs={'class': 'form-select'}),
|
|
'responsible_person': forms.Select(attrs={'class': 'form-select'}),
|
|
'target_date': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
'completion_date': forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'effectiveness_measure': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'How will effectiveness be measured?'
|
|
}),
|
|
'effectiveness_assessment': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'Assessment of action effectiveness'
|
|
}),
|
|
'effectiveness_score': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'max': 5,
|
|
'placeholder': '1-5'
|
|
}),
|
|
'obstacles': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'Obstacles encountered during implementation'
|
|
}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
rca = kwargs.pop('rca', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Filter root_cause to show only for this RCA
|
|
if 'root_cause' in self.fields and rca:
|
|
self.fields['root_cause'].queryset = rca.root_causes.all()
|
|
|
|
def clean_effectiveness_score(self):
|
|
score = self.cleaned_data.get('effectiveness_score')
|
|
if score and (score < 1 or score > 5):
|
|
raise forms.ValidationError('Effectiveness score must be between 1 and 5')
|
|
return score
|
|
|
|
|
|
class RCAFilterForm(forms.Form):
|
|
"""Form for filtering RCA list"""
|
|
status = forms.ChoiceField(
|
|
required=False,
|
|
choices=[('', 'All Statuses')] + list(RCAStatus.choices),
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
severity = forms.ChoiceField(
|
|
required=False,
|
|
choices=[('', 'All Severities')] + list(RCASeverity.choices),
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
priority = forms.ChoiceField(
|
|
required=False,
|
|
choices=[('', 'All Priorities')] + list(PriorityChoices.choices),
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
hospital = forms.ChoiceField(
|
|
required=False,
|
|
choices=[('', 'All Hospitals')],
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
department = forms.ChoiceField(
|
|
required=False,
|
|
choices=[('', 'All Departments')],
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
search = forms.CharField(
|
|
required=False,
|
|
widget=forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Search RCAs...'
|
|
})
|
|
)
|
|
date_from = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
)
|
|
date_to = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Populate hospital choices
|
|
from apps.organizations.models import Hospital
|
|
hospital_choices = [('', 'All Hospitals')]
|
|
hospital_choices.extend([
|
|
(h.id, h.name)
|
|
for h in Hospital.objects.all()
|
|
])
|
|
self.fields['hospital'].choices = hospital_choices
|
|
|
|
|
|
class RCAStatusChangeForm(forms.Form):
|
|
"""Form for changing RCA status"""
|
|
new_status = forms.ChoiceField(
|
|
choices=RCAStatus.choices,
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
notes = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Add notes about this status change'
|
|
})
|
|
)
|
|
|
|
|
|
class RCAApprovalForm(forms.Form):
|
|
"""Form for approving RCA"""
|
|
approval_notes = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Add approval notes'
|
|
})
|
|
)
|
|
|
|
|
|
class RCAClosureForm(forms.Form):
|
|
"""Form for closing RCA"""
|
|
closure_notes = forms.CharField(
|
|
required=True,
|
|
widget=forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Provide closure notes and summary'
|
|
})
|
|
)
|
|
actual_completion_date = forms.DateField(
|
|
required=True,
|
|
widget=forms.DateInput(attrs={
|
|
'class': 'form-control',
|
|
'type': 'date'
|
|
})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Set default to today
|
|
self.fields['actual_completion_date'].initial = timezone.now().date()
|
|
|
|
|
|
class RCAAttachmentForm(forms.ModelForm):
|
|
"""Form for uploading RCA attachments"""
|
|
class Meta:
|
|
model = RCAAttachment
|
|
fields = ['file', 'description']
|
|
widgets = {
|
|
'file': forms.FileInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 2,
|
|
'placeholder': 'Describe this attachment'
|
|
}),
|
|
}
|
|
|
|
|
|
class RCANoteForm(forms.ModelForm):
|
|
"""Form for adding RCA notes"""
|
|
class Meta:
|
|
model = RCANote
|
|
fields = ['note', 'is_internal']
|
|
widgets = {
|
|
'note': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Add a note'
|
|
}),
|
|
'is_internal': forms.CheckboxInput(attrs={
|
|
'class': 'form-check-input'
|
|
}),
|
|
} |