diff --git a/apps/complaints/forms.py b/apps/complaints/forms.py index fd01fa0..a1ece25 100644 --- a/apps/complaints/forms.py +++ b/apps/complaints/forms.py @@ -1,6 +1,7 @@ """ Complaints forms """ + import os from django import forms @@ -35,11 +36,12 @@ class MultiFileInput(forms.FileInput): Unlike standard FileInput which only supports single files, this widget allows users to upload multiple files at once. """ + def __init__(self, attrs=None): # Call parent's __init__ first to avoid Django's 'multiple' check super().__init__(attrs) # Add 'multiple' attribute after initialization - self.attrs['multiple'] = 'multiple' + self.attrs["multiple"] = "multiple" def value_from_datadict(self, data, files, name): """ @@ -68,49 +70,30 @@ class PublicComplaintForm(forms.ModelForm): label=_("Complainant Name"), max_length=200, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Your full name') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your full name")}), ) relation_to_patient = forms.ChoiceField( label=_("Relation to Patient"), choices=[ - ('patient', 'Patient'), - ('relative', 'Relative'), + ("patient", "Patient"), + ("relative", "Relative"), ], required=True, - widget=forms.Select( - attrs={ - 'class': 'form-control' - } - ) + widget=forms.Select(attrs={"class": "form-control"}), ) email = forms.EmailField( label=_("Email Address"), required=False, - widget=forms.EmailInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('your@email.com') - } - ) + widget=forms.EmailInput(attrs={"class": "form-control", "placeholder": _("your@email.com")}), ) mobile_number = forms.CharField( label=_("Mobile Number"), max_length=20, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Your mobile number') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your mobile number")}), ) # Patient Information @@ -118,50 +101,29 @@ class PublicComplaintForm(forms.ModelForm): label=_("Patient Name"), max_length=200, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Name of the patient involved') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Name of the patient involved")}), ) national_id = forms.CharField( label=_("National ID/ Iqama No."), max_length=20, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Saudi National ID or Iqama number') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Saudi National ID or Iqama number")}), ) incident_date = forms.DateField( - label=_("Incident Date"), - required=True, - widget=forms.DateInput( - attrs={ - 'class': 'form-control', - 'type': 'date' - } - ) + label=_("Incident Date"), required=True, widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}) ) # Hospital and Department hospital = forms.ModelChoiceField( label=_("Hospital"), - queryset=Hospital.objects.filter(status='active').order_by('name'), + queryset=Hospital.objects.filter(status="active").order_by("name"), empty_label=_("Select Hospital"), required=True, widget=forms.Select( - attrs={ - 'class': 'form-control', - 'id': 'hospital_select', - 'data-action': 'load-departments' - } - ) + attrs={"class": "form-control", "id": "hospital_select", "data-action": "load-departments"} + ), ) department = forms.ModelChoiceField( @@ -169,12 +131,7 @@ class PublicComplaintForm(forms.ModelForm): queryset=Department.objects.none(), empty_label=_("Select Department"), required=False, - widget=forms.Select( - attrs={ - 'class': 'form-control', - 'id': 'department_select' - } - ) + widget=forms.Select(attrs={"class": "form-control", "id": "department_select"}), ) # Complaint Details - Location Hierarchy @@ -183,13 +140,7 @@ class PublicComplaintForm(forms.ModelForm): queryset=None, empty_label=_("Select Location"), required=True, - widget=forms.Select( - attrs={ - 'class': 'form-control', - 'id': 'location_select', - 'data-action': 'load-sections' - } - ) + widget=forms.Select(attrs={"class": "form-control", "id": "location_select", "data-action": "load-sections"}), ) main_section = forms.ModelChoiceField( @@ -198,12 +149,8 @@ class PublicComplaintForm(forms.ModelForm): empty_label=_("Select Section"), required=True, widget=forms.Select( - attrs={ - 'class': 'form-control', - 'id': 'main_section_select', - 'data-action': 'load-subsections' - } - ) + attrs={"class": "form-control", "id": "main_section_select", "data-action": "load-subsections"} + ), ) subsection = forms.ModelChoiceField( @@ -211,12 +158,7 @@ class PublicComplaintForm(forms.ModelForm): queryset=None, empty_label=_("Select Subsection"), required=True, - widget=forms.Select( - attrs={ - 'class': 'form-control', - 'id': 'subsection_select' - } - ) + widget=forms.Select(attrs={"class": "form-control", "id": "subsection_select"}), ) staff_name = forms.CharField( @@ -224,11 +166,8 @@ class PublicComplaintForm(forms.ModelForm): max_length=200, required=False, widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Name of staff member involved (if known)') - } - ) + attrs={"class": "form-control", "placeholder": _("Name of staff member involved (if known)")} + ), ) complaint_details = forms.CharField( @@ -236,23 +175,21 @@ class PublicComplaintForm(forms.ModelForm): required=True, widget=forms.Textarea( attrs={ - 'class': 'form-control', - 'rows': 6, - 'placeholder': _('Please describe your complaint in detail. Our AI system will analyze and prioritize your complaint accordingly.') + "class": "form-control", + "rows": 6, + "placeholder": _( + "Please describe your complaint in detail. Our AI system will analyze and prioritize your complaint accordingly." + ), } - ) + ), ) expected_result = forms.CharField( label=_("Expected Complaint Result"), required=False, widget=forms.Textarea( - attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': _('What do you expect as a resolution?') - } - ) + attrs={"class": "form-control", "rows": 3, "placeholder": _("What do you expect as a resolution?")} + ), ) # Hidden fields - these will be set by view or AI @@ -261,7 +198,7 @@ class PublicComplaintForm(forms.ModelForm): choices=SeverityChoices.choices, initial=SeverityChoices.MEDIUM, required=False, - widget=forms.HiddenInput() + widget=forms.HiddenInput(), ) priority = forms.ChoiceField( @@ -269,7 +206,7 @@ class PublicComplaintForm(forms.ModelForm): choices=PriorityChoices.choices, initial=PriorityChoices.MEDIUM, required=False, - widget=forms.HiddenInput() + widget=forms.HiddenInput(), ) # Source type - always external for public complaints @@ -278,30 +215,37 @@ class PublicComplaintForm(forms.ModelForm): choices=ComplaintSourceType.choices, initial=ComplaintSourceType.EXTERNAL, required=False, - widget=forms.HiddenInput() + widget=forms.HiddenInput(), ) # File uploads attachments = forms.FileField( label=_("Attach Documents (Optional)"), required=False, - widget=MultiFileInput( - attrs={ - 'class': 'form-control', - 'accept': 'image/*,.pdf,.doc,.docx' - } - ), - help_text=_('You can upload images, PDFs, or Word documents (max 10MB each)') + widget=MultiFileInput(attrs={"class": "form-control", "accept": "image/*,.pdf,.doc,.docx"}), + help_text=_("You can upload images, PDFs, or Word documents (max 10MB each)"), ) class Meta: model = Complaint fields = [ - 'complainant_name', 'email', 'mobile_number', 'hospital', - 'relation_to_patient', 'patient_name', 'national_id', 'incident_date', - 'location', 'main_section', 'subsection', - 'staff_name', 'complaint_details', 'expected_result', 'severity', 'priority', - 'complaint_source_type' + "complainant_name", + "email", + "mobile_number", + "hospital", + "relation_to_patient", + "patient_name", + "national_id", + "incident_date", + "location", + "main_section", + "subsection", + "staff_name", + "complaint_details", + "expected_result", + "severity", + "priority", + "complaint_source_type", ] # Note: 'attachments' is not in fields because Complaint model doesn't have this field. # Attachments are handled separately via ComplaintAttachment model in the view. @@ -311,112 +255,111 @@ class PublicComplaintForm(forms.ModelForm): from apps.organizations.models import Location, MainSection, SubSection # Initialize cascading dropdowns with empty querysets - self.fields['main_section'].queryset = MainSection.objects.none() - self.fields['subsection'].queryset = SubSection.objects.none() + self.fields["main_section"].queryset = MainSection.objects.none() + self.fields["subsection"].queryset = SubSection.objects.none() # Load all locations (no filtering needed) - self.fields['location'].queryset = Location.objects.all().order_by('name_en') + self.fields["location"].queryset = Location.objects.all().order_by("name_en") # Check both initial data and POST data for location to load sections location_id = None - if 'location' in self.initial: - location_id = self.initial['location'] - elif 'location' in self.data: - location_id = self.data['location'] + if "location" in self.initial: + location_id = self.initial["location"] + elif "location" in self.data: + location_id = self.data["location"] if location_id: # Filter sections based on selected location from apps.organizations.models import SubSection - available_sections = SubSection.objects.filter( - location_id=location_id - ).values_list('main_section_id', flat=True).distinct() - self.fields['main_section'].queryset = MainSection.objects.filter( - id__in=available_sections - ).order_by('name_en') + + available_sections = ( + SubSection.objects.filter(location_id=location_id).values_list("main_section_id", flat=True).distinct() + ) + self.fields["main_section"].queryset = MainSection.objects.filter(id__in=available_sections).order_by( + "name_en" + ) # Load subsections if section is selected section_id = None - if 'main_section' in self.initial: - section_id = self.initial['main_section'] - elif 'main_section' in self.data: - section_id = self.data['main_section'] + if "main_section" in self.initial: + section_id = self.initial["main_section"] + elif "main_section" in self.data: + section_id = self.data["main_section"] if section_id: - self.fields['subsection'].queryset = SubSection.objects.filter( - location_id=location_id, - main_section_id=section_id - ).order_by('name_en') + self.fields["subsection"].queryset = SubSection.objects.filter( + location_id=location_id, main_section_id=section_id + ).order_by("name_en") # Also filter departments based on hospital if provided hospital_id = None - if 'hospital' in self.initial: - hospital_id = self.initial['hospital'] - elif 'hospital' in self.data: - hospital_id = self.data['hospital'] + if "hospital" in self.initial: + hospital_id = self.initial["hospital"] + elif "hospital" in self.data: + hospital_id = self.data["hospital"] if hospital_id: # Filter departments - self.fields['department'].queryset = Department.objects.filter( - hospital_id=hospital_id, - status='active' - ).order_by('name') - + self.fields["department"].queryset = Department.objects.filter( + hospital_id=hospital_id, status="active" + ).order_by("name") def clean_mobile_number(self): """Validate mobile number format""" - mobile_number = self.cleaned_data.get('mobile_number') - + mobile_number = self.cleaned_data.get("mobile_number") + # Remove spaces and dashes - mobile_number = mobile_number.replace(' ', '').replace('-', '') - + mobile_number = mobile_number.replace(" ", "").replace("-", "") + # Validate Saudi mobile format (05xxxxxxxx) - if not mobile_number.startswith('05') or len(mobile_number) != 10: - raise ValidationError(_('Please enter a valid Saudi mobile number (10 digits starting with 05)')) - + if not mobile_number.startswith("05") or len(mobile_number) != 10: + raise ValidationError(_("Please enter a valid Saudi mobile number (10 digits starting with 05)")) + return mobile_number def clean_national_id(self): """Validate National ID/Iqama format""" - national_id = self.cleaned_data.get('national_id') - + national_id = self.cleaned_data.get("national_id") + # Remove spaces - national_id = national_id.replace(' ', '') - + national_id = national_id.replace(" ", "") + # Validate it's 10 digits if len(national_id) != 10 or not national_id.isdigit(): - raise ValidationError(_('Please enter a valid National ID or Iqama number (10 digits)')) - + raise ValidationError(_("Please enter a valid National ID or Iqama number (10 digits)")) + return national_id def clean_incident_date(self): """Validate incident date is not in the future""" - incident_date = self.cleaned_data.get('incident_date') - + incident_date = self.cleaned_data.get("incident_date") + from datetime import date + if incident_date and incident_date > date.today(): - raise ValidationError(_('Incident date cannot be in the future')) - + raise ValidationError(_("Incident date cannot be in the future")) + return incident_date def clean_attachments(self): """Validate file attachments""" - files = self.files.getlist('attachments') + files = self.files.getlist("attachments") # Check file count if len(files) > 5: - raise ValidationError(_('Maximum 5 files allowed')) + raise ValidationError(_("Maximum 5 files allowed")) # Check each file for file in files: # Check file size (10MB limit) if file.size > 10 * 1024 * 1024: - raise ValidationError(_('File size must be less than 10MB')) + raise ValidationError(_("File size must be less than 10MB")) # Check file type - allowed_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.doc', '.docx'] + allowed_extensions = [".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx"] ext = os.path.splitext(file.name)[1].lower() if ext not in allowed_extensions: - raise ValidationError(_('Allowed file types: JPG, PNG, GIF, PDF, DOC, DOCX')) + raise ValidationError(_("Allowed file types: JPG, PNG, GIF, PDF, DOC, DOCX")) return files @@ -433,11 +376,11 @@ class PublicComplaintForm(forms.ModelForm): class ComplaintForm(HospitalFieldMixin, forms.ModelForm): """ Form for creating complaints by authenticated users. - + Updated to use location hierarchy (Location, Section, Subsection). Includes new fields for detailed patient information and complaint type. Uses cascading dropdowns for location selection. - + Hospital field visibility: - PX Admins: See dropdown with all hospitals - Others: Hidden field, auto-set to user's hospital @@ -449,7 +392,7 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): choices=ComplaintType.choices, initial=ComplaintType.COMPLAINT, required=False, - widget=forms.HiddenInput() + widget=forms.HiddenInput(), ) # Source type @@ -458,7 +401,7 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): choices=ComplaintSourceType.choices, initial=ComplaintSourceType.EXTERNAL, required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'complaintSourceType'}) + widget=forms.Select(attrs={"class": "form-select", "id": "complaintSourceType"}), ) # PX Source (optional) @@ -467,46 +410,44 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=None, empty_label=_("Select source (optional)"), required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'sourceSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "sourceSelect"}), ) # Patient Information (text-based fields only) relation_to_patient = forms.ChoiceField( label=_("Relation to Patient"), choices=[ - ('patient', 'Patient'), - ('relative', 'Relative'), + ("patient", "Patient"), + ("relative", "Relative"), ], required=True, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'relationToPatient'}) + widget=forms.Select(attrs={"class": "form-select", "id": "relationToPatient"}), ) patient_name = forms.CharField( label=_("Patient Name"), max_length=200, required=True, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Name of the patient involved')}) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Name of the patient involved")}), ) national_id = forms.CharField( label=_("National ID/Iqama No."), max_length=20, required=True, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Saudi National ID or Iqama number')}) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Saudi National ID or Iqama number")}), ) incident_date = forms.DateField( - label=_("Incident Date"), - required=True, - widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}) + label=_("Incident Date"), required=True, widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}) ) hospital = forms.ModelChoiceField( label=_("Hospital"), - queryset=Hospital.objects.filter(status='active'), + queryset=Hospital.objects.filter(status="active"), empty_label=_("Select Hospital"), required=True, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "hospitalSelect"}), ) department = forms.ModelChoiceField( @@ -514,7 +455,7 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=Department.objects.none(), empty_label=_("Select Department"), required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'departmentSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "departmentSelect"}), ) staff = forms.ModelChoiceField( @@ -522,14 +463,13 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=Staff.objects.none(), empty_label=_("Select Staff"), required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'staffSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "staffSelect"}), ) encounter_id = forms.CharField( label=_("Encounter ID"), required=False, - widget=forms.TextInput(attrs={'class': 'form-control', - 'placeholder': _('Optional encounter/visit ID')}) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Optional encounter/visit ID")}), ) # Location Hierarchy Fields @@ -538,11 +478,7 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=None, empty_label=_("Select Location"), required=True, - widget=forms.Select(attrs={ - 'class': 'form-select', - 'id': 'locationSelect', - 'data-action': 'load-sections' - }) + widget=forms.Select(attrs={"class": "form-select", "id": "locationSelect", "data-action": "load-sections"}), ) main_section = forms.ModelChoiceField( @@ -550,11 +486,9 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=None, empty_label=_("Select Section"), required=True, - widget=forms.Select(attrs={ - 'class': 'form-select', - 'id': 'mainSectionSelect', - 'data-action': 'load-subsections' - }) + widget=forms.Select( + attrs={"class": "form-select", "id": "mainSectionSelect", "data-action": "load-subsections"} + ), ) subsection = forms.ModelChoiceField( @@ -562,40 +496,54 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): queryset=None, empty_label=_("Select Subsection"), required=True, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'subsectionSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "subsectionSelect"}), ) staff_name = forms.CharField( label=_("Staff Involved"), max_length=200, required=False, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Name of staff member involved (if known)')}) + widget=forms.TextInput( + attrs={"class": "form-control", "placeholder": _("Name of staff member involved (if known)")} + ), ) description = forms.CharField( label=_("Description"), required=True, - widget=forms.Textarea(attrs={'class': 'form-control', - 'rows': 6, - 'placeholder': _('Detailed description of complaint...')}) + widget=forms.Textarea( + attrs={"class": "form-control", "rows": 6, "placeholder": _("Detailed description of complaint...")} + ), ) expected_result = forms.CharField( label=_("Expected Complaint Result"), required=False, - widget=forms.Textarea(attrs={'class': 'form-control', - 'rows': 3, - 'placeholder': _('What do you expect as a resolution?')}) + widget=forms.Textarea( + attrs={"class": "form-control", "rows": 3, "placeholder": _("What do you expect as a resolution?")} + ), ) class Meta: model = Complaint fields = [ - 'complaint_type', 'complaint_source_type', 'source', - 'relation_to_patient', 'patient_name', - 'national_id', 'incident_date', 'hospital', 'department', - 'location', 'main_section', 'subsection', 'staff', 'staff_name', - 'encounter_id', 'description', 'expected_result' + "complaint_type", + "complaint_source_type", + "source", + "relation_to_patient", + "patient_name", + "national_id", + "incident_date", + "hospital", + "department", + "location", + "main_section", + "subsection", + "staff", + "staff_name", + "encounter_id", + "description", + "expected_result", ] def __init__(self, *args, **kwargs): @@ -605,76 +553,74 @@ class ComplaintForm(HospitalFieldMixin, forms.ModelForm): from apps.px_sources.models import PXSource # Initialize cascading dropdowns with empty querysets - self.fields['main_section'].queryset = MainSection.objects.none() - self.fields['subsection'].queryset = SubSection.objects.none() + self.fields["main_section"].queryset = MainSection.objects.none() + self.fields["subsection"].queryset = SubSection.objects.none() # Load all locations (no filtering needed) - self.fields['location'].queryset = Location.objects.all().order_by('name_en') + self.fields["location"].queryset = Location.objects.all().order_by("name_en") # Load active PX sources for optional selection - self.fields['source'].queryset = PXSource.objects.filter(is_active=True).order_by('name_en') + self.fields["source"].queryset = PXSource.objects.filter(is_active=True).order_by("name_en") # Check both initial data and POST data for location to load sections location_id = None - if 'location' in self.initial: - location_id = self.initial['location'] - elif 'location' in self.data: - location_id = self.data['location'] + if "location" in self.initial: + location_id = self.initial["location"] + elif "location" in self.data: + location_id = self.data["location"] if location_id: # Filter sections based on selected location from apps.organizations.models import SubSection - available_sections = SubSection.objects.filter( - location_id=location_id - ).values_list('main_section_id', flat=True).distinct() - self.fields['main_section'].queryset = MainSection.objects.filter( - id__in=available_sections - ).order_by('name_en') + + available_sections = ( + SubSection.objects.filter(location_id=location_id).values_list("main_section_id", flat=True).distinct() + ) + self.fields["main_section"].queryset = MainSection.objects.filter(id__in=available_sections).order_by( + "name_en" + ) # Load subsections if section is selected section_id = None - if 'main_section' in self.initial: - section_id = self.initial['main_section'] - elif 'main_section' in self.data: - section_id = self.data['main_section'] + if "main_section" in self.initial: + section_id = self.initial["main_section"] + elif "main_section" in self.data: + section_id = self.data["main_section"] if section_id: - self.fields['subsection'].queryset = SubSection.objects.filter( - location_id=location_id, - main_section_id=section_id - ).order_by('name_en') + self.fields["subsection"].queryset = SubSection.objects.filter( + location_id=location_id, main_section_id=section_id + ).order_by("name_en") # Hospital field is configured by HospitalFieldMixin # Now filter departments and staff based on hospital hospital_id = None - if self.data.get('hospital'): - hospital_id = self.data.get('hospital') - elif self.initial.get('hospital'): - hospital_id = self.initial.get('hospital') + if self.data.get("hospital"): + hospital_id = self.data.get("hospital") + elif self.initial.get("hospital"): + hospital_id = self.initial.get("hospital") elif self.user and self.user.hospital: hospital_id = self.user.hospital.id if hospital_id: # Filter departments based on selected hospital - self.fields['department'].queryset = Department.objects.filter( - hospital_id=hospital_id, - status='active' - ).order_by('name') + self.fields["department"].queryset = Department.objects.filter( + hospital_id=hospital_id, status="active" + ).order_by("name") # Filter staff based on selected hospital - self.fields['staff'].queryset = Staff.objects.filter( - hospital_id=hospital_id, - status='active' - ).order_by('first_name', 'last_name') + self.fields["staff"].queryset = Staff.objects.filter(hospital_id=hospital_id, status="active").order_by( + "first_name", "last_name" + ) class InquiryForm(HospitalFieldMixin, forms.ModelForm): """ Form for creating inquiries by authenticated users. - - Similar to ComplaintForm - supports patient search, department filtering, + + Similar to ComplaintForm - supports patient search, department filtering, and proper field validation with AJAX support. - + Hospital field visibility: - PX Admins: See dropdown with all hospitals - Others: Hidden field, auto-set to user's hospital @@ -682,18 +628,18 @@ class InquiryForm(HospitalFieldMixin, forms.ModelForm): patient = forms.ModelChoiceField( label=_("Patient (Optional)"), - queryset=Patient.objects.filter(status='active'), + queryset=Patient.objects.filter(status="active"), empty_label=_("Select Patient"), required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'patientSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "patientSelect"}), ) hospital = forms.ModelChoiceField( label=_("Hospital"), - queryset=Hospital.objects.filter(status='active'), + queryset=Hospital.objects.filter(status="active"), empty_label=_("Select Hospital"), required=True, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "hospitalSelect"}), ) department = forms.ModelChoiceField( @@ -701,47 +647,61 @@ class InquiryForm(HospitalFieldMixin, forms.ModelForm): queryset=Department.objects.none(), empty_label=_("Select Department"), required=False, - widget=forms.Select(attrs={'class': 'form-select', 'id': 'departmentSelect'}) + widget=forms.Select(attrs={"class": "form-select", "id": "departmentSelect"}), ) category = forms.ChoiceField( label=_("Inquiry Type"), choices=[ - ('general', 'General Inquiry'), - ('appointment', 'Appointment Related'), - ('billing', 'Billing & Insurance'), - ('medical_records', 'Medical Records'), - ('pharmacy', 'Pharmacy'), - ('insurance', 'Insurance'), - ('feedback', 'Feedback'), - ('other', 'Other'), + ("general", "General Inquiry"), + ("appointment", "Appointment Related"), + ("billing", "Billing & Insurance"), + ("medical_records", "Medical Records"), + ("pharmacy", "Pharmacy"), + ("insurance", "Insurance"), + ("feedback", "Feedback"), + ("other", "Other"), ], required=True, - widget=forms.Select(attrs={'class': 'form-control'}) + widget=forms.Select(attrs={"class": "form-control"}), ) subject = forms.CharField( label=_("Subject"), max_length=200, required=True, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Brief subject')}) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Brief subject")}), ) message = forms.CharField( label=_("Message"), required=True, - widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': _('Describe your inquiry')}) + widget=forms.Textarea(attrs={"class": "form-control", "rows": 5, "placeholder": _("Describe your inquiry")}), ) # Contact info for inquiries without patient - contact_name = forms.CharField(label=_("Contact Name"), max_length=200, required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) - contact_phone = forms.CharField(label=_("Contact Phone"), max_length=20, required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) - contact_email = forms.EmailField(label=_("Contact Email"), required=False, widget=forms.EmailInput(attrs={'class': 'form-control'})) + contact_name = forms.CharField( + label=_("Contact Name"), max_length=200, required=False, widget=forms.TextInput(attrs={"class": "form-control"}) + ) + contact_phone = forms.CharField( + label=_("Contact Phone"), max_length=20, required=False, widget=forms.TextInput(attrs={"class": "form-control"}) + ) + contact_email = forms.EmailField( + label=_("Contact Email"), required=False, widget=forms.EmailInput(attrs={"class": "form-control"}) + ) class Meta: model = Inquiry - fields = ['patient', 'hospital', 'department', 'subject', 'message', - 'contact_name', 'contact_phone', 'contact_email'] + fields = [ + "patient", + "hospital", + "department", + "subject", + "message", + "contact_name", + "contact_phone", + "contact_email", + ] def __init__(self, *args, **kwargs): # Note: user is handled by HospitalFieldMixin @@ -750,19 +710,18 @@ class InquiryForm(HospitalFieldMixin, forms.ModelForm): # Hospital field is configured by HospitalFieldMixin # Now filter departments based on hospital hospital_id = None - if self.data.get('hospital'): - hospital_id = self.data.get('hospital') - elif self.initial.get('hospital'): - hospital_id = self.initial.get('hospital') + if self.data.get("hospital"): + hospital_id = self.data.get("hospital") + elif self.initial.get("hospital"): + hospital_id = self.initial.get("hospital") elif self.user and self.user.hospital: hospital_id = self.user.hospital.id - + if hospital_id: # Filter departments based on selected hospital - self.fields['department'].queryset = Department.objects.filter( - hospital_id=hospital_id, - status='active' - ).order_by('name') + self.fields["department"].queryset = Department.objects.filter( + hospital_id=hospital_id, status="active" + ).order_by("name") class SLAConfigForm(HospitalFieldMixin, forms.ModelForm): @@ -770,47 +729,39 @@ class SLAConfigForm(HospitalFieldMixin, forms.ModelForm): class Meta: model = ComplaintSLAConfig - fields = ['hospital', 'severity', 'priority', 'sla_hours', 'reminder_hours_before', 'is_active'] + fields = ["hospital", "severity", "priority", "sla_hours", "reminder_hours_before", "is_active"] widgets = { - 'hospital': forms.Select(attrs={'class': 'form-select'}), - 'severity': forms.Select(attrs={'class': 'form-select'}), - 'priority': forms.Select(attrs={'class': 'form-select'}), - 'sla_hours': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), - 'reminder_hours_before': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + "hospital": forms.Select(attrs={"class": "form-select"}), + "severity": forms.Select(attrs={"class": "form-select"}), + "priority": forms.Select(attrs={"class": "form-select"}), + "sla_hours": forms.NumberInput(attrs={"class": "form-control", "min": "1"}), + "reminder_hours_before": forms.NumberInput(attrs={"class": "form-control", "min": "0"}), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } - self.fields['hospital'].initial = user.hospital - self.fields['hospital'].widget.attrs['readonly'] = True def clean(self): cleaned_data = super().clean() - hospital = cleaned_data.get('hospital') - severity = cleaned_data.get('severity') - priority = cleaned_data.get('priority') - sla_hours = cleaned_data.get('sla_hours') - reminder_hours = cleaned_data.get('reminder_hours_before') + hospital = cleaned_data.get("hospital") + severity = cleaned_data.get("severity") + priority = cleaned_data.get("priority") + sla_hours = cleaned_data.get("sla_hours") + reminder_hours = cleaned_data.get("reminder_hours_before") # Validate SLA hours is positive if sla_hours and sla_hours <= 0: - raise ValidationError({'sla_hours': 'SLA hours must be greater than 0'}) + raise ValidationError({"sla_hours": "SLA hours must be greater than 0"}) # Validate reminder hours < SLA hours if sla_hours and reminder_hours and reminder_hours >= sla_hours: - raise ValidationError({'reminder_hours_before': 'Reminder hours must be less than SLA hours'}) + raise ValidationError({"reminder_hours_before": "Reminder hours must be less than SLA hours"}) # Check for unique combination (excluding current instance when editing) if hospital and severity and priority: - queryset = ComplaintSLAConfig.objects.filter( - hospital=hospital, - severity=severity, - priority=priority - ) + queryset = ComplaintSLAConfig.objects.filter(hospital=hospital, severity=severity, priority=priority) if self.instance.pk: queryset = queryset.exclude(pk=self.instance.pk) if queryset.exists(): - raise ValidationError( - 'An SLA configuration for this hospital, severity, and priority already exists.' - ) + raise ValidationError("An SLA configuration for this hospital, severity, and priority already exists.") return cleaned_data @@ -821,27 +772,36 @@ class EscalationRuleForm(HospitalFieldMixin, forms.ModelForm): class Meta: model = EscalationRule fields = [ - 'hospital', 'name', 'description', 'escalation_level', 'max_escalation_level', - 'trigger_on_overdue', 'trigger_hours_overdue', - 'reminder_escalation_enabled', 'reminder_escalation_hours', - 'escalate_to_role', 'escalate_to_user', - 'severity_filter', 'priority_filter', 'is_active' + "hospital", + "name", + "description", + "escalation_level", + "max_escalation_level", + "trigger_on_overdue", + "trigger_hours_overdue", + "reminder_escalation_enabled", + "reminder_escalation_hours", + "escalate_to_role", + "escalate_to_user", + "severity_filter", + "priority_filter", + "is_active", ] widgets = { - 'hospital': forms.Select(attrs={'class': 'form-select'}), - 'name': forms.TextInput(attrs={'class': 'form-control'}), - 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), - 'escalation_level': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), - 'max_escalation_level': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}), - 'trigger_on_overdue': forms.CheckboxInput(attrs={'class': 'form-check-input'}), - 'trigger_hours_overdue': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}), - 'reminder_escalation_enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}), - 'reminder_escalation_hours': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}), - 'escalate_to_role': forms.Select(attrs={'class': 'form-select', 'id': 'escalate_to_role'}), - 'escalate_to_user': forms.Select(attrs={'class': 'form-select'}), - 'severity_filter': forms.Select(attrs={'class': 'form-select'}), - 'priority_filter': forms.Select(attrs={'class': 'form-select'}), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + "hospital": forms.Select(attrs={"class": "form-select"}), + "name": forms.TextInput(attrs={"class": "form-control"}), + "description": forms.Textarea(attrs={"class": "form-control", "rows": 3}), + "escalation_level": forms.NumberInput(attrs={"class": "form-control", "min": "1"}), + "max_escalation_level": forms.NumberInput(attrs={"class": "form-control", "min": 1}), + "trigger_on_overdue": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "trigger_hours_overdue": forms.NumberInput(attrs={"class": "form-control", "min": 0}), + "reminder_escalation_enabled": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "reminder_escalation_hours": forms.NumberInput(attrs={"class": "form-control", "min": 0}), + "escalate_to_role": forms.Select(attrs={"class": "form-select", "id": "escalate_to_role"}), + "escalate_to_user": forms.Select(attrs={"class": "form-select"}), + "severity_filter": forms.Select(attrs={"class": "form-select"}), + "priority_filter": forms.Select(attrs={"class": "form-select"}), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } def __init__(self, *args, **kwargs): @@ -849,24 +809,22 @@ class EscalationRuleForm(HospitalFieldMixin, forms.ModelForm): # Filter users for escalate_to_user field from apps.accounts.models import User + if self.user and self.user.is_px_admin(): - self.fields['escalate_to_user'].queryset = User.objects.filter(is_active=True) + self.fields["escalate_to_user"].queryset = User.objects.filter(is_active=True) elif self.user and self.user.hospital: - self.fields['escalate_to_user'].queryset = User.objects.filter( - is_active=True, - hospital=self.user.hospital - ) + self.fields["escalate_to_user"].queryset = User.objects.filter(is_active=True, hospital=self.user.hospital) else: - self.fields['escalate_to_user'].queryset = User.objects.none() + self.fields["escalate_to_user"].queryset = User.objects.none() def clean(self): cleaned_data = super().clean() - escalate_to_role = cleaned_data.get('escalate_to_role') - escalate_to_user = cleaned_data.get('escalate_to_user') + escalate_to_role = cleaned_data.get("escalate_to_role") + escalate_to_user = cleaned_data.get("escalate_to_user") # If role is 'specific_user', user must be specified - if escalate_to_role == 'specific_user' and not escalate_to_user: - raise ValidationError({'escalate_to_user': 'Please select a user when role is set to Specific User'}) + if escalate_to_role == "specific_user" and not escalate_to_user: + raise ValidationError({"escalate_to_user": "Please select a user when role is set to Specific User"}) return cleaned_data @@ -874,7 +832,7 @@ class EscalationRuleForm(HospitalFieldMixin, forms.ModelForm): class ComplaintThresholdForm(HospitalFieldMixin, forms.ModelForm): """ Form for creating and editing complaint thresholds. - + Hospital field visibility: - PX Admins: See dropdown with all hospitals - Others: Hidden field, auto-set to user's hospital @@ -882,18 +840,17 @@ class ComplaintThresholdForm(HospitalFieldMixin, forms.ModelForm): class Meta: model = ComplaintThreshold - fields = ['hospital', 'threshold_type', 'threshold_value', 'comparison_operator', 'action_type', 'is_active'] + fields = ["hospital", "threshold_type", "threshold_value", "comparison_operator", "action_type", "is_active"] widgets = { - 'hospital': forms.Select(attrs={'class': 'form-select'}), - 'threshold_type': forms.Select(attrs={'class': 'form-select'}), - 'threshold_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}), - 'comparison_operator': forms.Select(attrs={'class': 'form-select'}), - 'action_type': forms.Select(attrs={'class': 'form-select'}), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + "hospital": forms.Select(attrs={"class": "form-select"}), + "threshold_type": forms.Select(attrs={"class": "form-select"}), + "threshold_value": forms.NumberInput(attrs={"class": "form-control", "step": "0.1"}), + "comparison_operator": forms.Select(attrs={"class": "form-select"}), + "action_type": forms.Select(attrs={"class": "form-select"}), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } - class PublicInquiryForm(forms.Form): """Public inquiry submission form (simpler, for general questions)""" @@ -902,144 +859,113 @@ class PublicInquiryForm(forms.Form): label=_("Name"), max_length=200, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Your full name') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your full name")}), ) phone = forms.CharField( label=_("Phone Number"), max_length=20, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Your phone number') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your phone number")}), ) email = forms.EmailField( label=_("Email Address"), required=False, - widget=forms.EmailInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('your@email.com') - } - ) + widget=forms.EmailInput(attrs={"class": "form-control", "placeholder": _("your@email.com")}), ) # Inquiry Details hospital = forms.ModelChoiceField( label=_("Hospital"), - queryset=Hospital.objects.filter(status='active').order_by('name'), + queryset=Hospital.objects.filter(status="active").order_by("name"), empty_label=_("Select Hospital"), required=True, - widget=forms.Select(attrs={'class': 'form-control'}) + widget=forms.Select(attrs={"class": "form-control"}), ) category = forms.ChoiceField( label=_("Inquiry Type"), choices=[ - ('general', 'General Inquiry'), - ('appointment', 'Appointment Related'), - ('billing', 'Billing & Insurance'), - ('medical_records', 'Medical Records'), - ('other', 'Other'), + ("general", "General Inquiry"), + ("appointment", "Appointment Related"), + ("billing", "Billing & Insurance"), + ("medical_records", "Medical Records"), + ("other", "Other"), ], required=True, - widget=forms.Select(attrs={'class': 'form-control'}) + widget=forms.Select(attrs={"class": "form-control"}), ) subject = forms.CharField( label=_("Subject"), max_length=200, required=True, - widget=forms.TextInput( - attrs={ - 'class': 'form-control', - 'placeholder': _('Brief subject') - } - ) + widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Brief subject")}), ) message = forms.CharField( label=_("Message"), required=True, - widget=forms.Textarea( - attrs={ - 'class': 'form-control', - 'rows': 5, - 'placeholder': _('Describe your inquiry') - } - ) + widget=forms.Textarea(attrs={"class": "form-control", "rows": 5, "placeholder": _("Describe your inquiry")}), ) - class ComplaintInvolvedDepartmentForm(forms.ModelForm): """ Form for adding an involved department to a complaint. - + Allows specifying the department, role, and assignment. """ - + class Meta: model = ComplaintInvolvedDepartment - fields = ['department', 'role', 'is_primary', 'notes', 'assigned_to'] + fields = ["department", "role", "is_primary", "notes", "assigned_to"] widgets = { - 'department': forms.Select(attrs={'class': 'form-select'}), - 'role': forms.Select(attrs={'class': 'form-select'}), - 'is_primary': forms.CheckboxInput(attrs={'class': 'form-check-input'}), - 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}), - 'assigned_to': forms.Select(attrs={'class': 'form-select'}), + "department": forms.Select(attrs={"class": "form-select"}), + "role": forms.Select(attrs={"class": "form-select"}), + "is_primary": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "notes": forms.Textarea(attrs={"class": "form-control", "rows": 2}), + "assigned_to": forms.Select(attrs={"class": "form-select"}), } - + def __init__(self, *args, **kwargs): - self.complaint = kwargs.pop('complaint', None) - user = kwargs.pop('user', None) + self.complaint = kwargs.pop("complaint", None) + user = kwargs.pop("user", None) super().__init__(*args, **kwargs) - + # Filter departments based on complaint's hospital if self.complaint and self.complaint.hospital: - self.fields['department'].queryset = Department.objects.filter( - hospital=self.complaint.hospital, - status='active' - ).order_by('name') + self.fields["department"].queryset = Department.objects.filter( + hospital=self.complaint.hospital, status="active" + ).order_by("name") else: - self.fields['department'].queryset = Department.objects.none() - + self.fields["department"].queryset = Department.objects.none() + # Filter assigned_to users based on hospital if self.complaint and self.complaint.hospital: from apps.accounts.models import User - self.fields['assigned_to'].queryset = User.objects.filter( - hospital=self.complaint.hospital, - is_active=True - ).order_by('first_name', 'last_name') + + self.fields["assigned_to"].queryset = User.objects.filter( + hospital=self.complaint.hospital, is_active=True + ).order_by("first_name", "last_name") else: - self.fields['assigned_to'].queryset = User.objects.none() - + self.fields["assigned_to"].queryset = User.objects.none() + # Make assigned_to optional - self.fields['assigned_to'].required = False - + self.fields["assigned_to"].required = False + def clean_department(self): - department = self.cleaned_data.get('department') + department = self.cleaned_data.get("department") if self.complaint and department: # Check if this department is already involved - existing = ComplaintInvolvedDepartment.objects.filter( - complaint=self.complaint, - department=department - ) + existing = ComplaintInvolvedDepartment.objects.filter(complaint=self.complaint, department=department) if self.instance.pk: existing = existing.exclude(pk=self.instance.pk) if existing.exists(): - raise ValidationError(_('This department is already involved in this complaint.')) + raise ValidationError(_("This department is already involved in this complaint.")) return department - + def save(self, commit=True): instance = super().save(commit=False) if self.complaint: @@ -1052,48 +978,45 @@ class ComplaintInvolvedDepartmentForm(forms.ModelForm): class ComplaintInvolvedStaffForm(forms.ModelForm): """ Form for adding an involved staff member to a complaint. - + Allows specifying the staff member and their role in the complaint. """ - + class Meta: model = ComplaintInvolvedStaff - fields = ['staff', 'role', 'notes'] + fields = ["staff", "role", "notes"] widgets = { - 'staff': forms.Select(attrs={'class': 'form-select', 'id': 'involvedStaffSelect'}), - 'role': forms.Select(attrs={'class': 'form-select'}), - 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}), + "staff": forms.Select(attrs={"class": "form-select", "id": "involvedStaffSelect"}), + "role": forms.Select(attrs={"class": "form-select"}), + "notes": forms.Textarea(attrs={"class": "form-control", "rows": 2}), } - + def __init__(self, *args, **kwargs): - self.complaint = kwargs.pop('complaint', None) - user = kwargs.pop('user', None) + self.complaint = kwargs.pop("complaint", None) + user = kwargs.pop("user", None) super().__init__(*args, **kwargs) - + # Filter staff based on complaint's hospital if self.complaint and self.complaint.hospital: from apps.organizations.models import Staff - self.fields['staff'].queryset = Staff.objects.filter( - hospital=self.complaint.hospital, - status='active' - ).order_by('first_name', 'last_name') + + self.fields["staff"].queryset = Staff.objects.filter( + hospital=self.complaint.hospital, status="active" + ).order_by("first_name", "last_name") else: - self.fields['staff'].queryset = Staff.objects.none() - + self.fields["staff"].queryset = Staff.objects.none() + def clean_staff(self): - staff = self.cleaned_data.get('staff') + staff = self.cleaned_data.get("staff") if self.complaint and staff: # Check if this staff is already involved - existing = ComplaintInvolvedStaff.objects.filter( - complaint=self.complaint, - staff=staff - ) + existing = ComplaintInvolvedStaff.objects.filter(complaint=self.complaint, staff=staff) if self.instance.pk: existing = existing.exclude(pk=self.instance.pk) if existing.exists(): - raise ValidationError(_('This staff member is already involved in this complaint.')) + raise ValidationError(_("This staff member is already involved in this complaint.")) return staff - + def save(self, commit=True): instance = super().save(commit=False) if self.complaint: @@ -1107,16 +1030,18 @@ class DepartmentResponseForm(forms.ModelForm): """ Form for an involved department to submit their response. """ - + class Meta: model = ComplaintInvolvedDepartment - fields = ['response_notes'] + fields = ["response_notes"] widgets = { - 'response_notes': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 4, - 'placeholder': _('Enter department response and findings...') - }), + "response_notes": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 4, + "placeholder": _("Enter department response and findings..."), + } + ), } @@ -1124,14 +1049,16 @@ class StaffExplanationForm(forms.ModelForm): """ Form for an involved staff member to submit their explanation. """ - + class Meta: model = ComplaintInvolvedStaff - fields = ['explanation'] + fields = ["explanation"] widgets = { - 'explanation': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 4, - 'placeholder': _('Enter your explanation regarding this complaint...') - }), + "explanation": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 4, + "placeholder": _("Enter your explanation regarding this complaint..."), + } + ), } diff --git a/apps/rca/forms.py b/apps/rca/forms.py index 5c4c0fe..0d760fb 100644 --- a/apps/rca/forms.py +++ b/apps/rca/forms.py @@ -1,8 +1,7 @@ """ RCA (Root Cause Analysis) forms """ -RCA (Root Cause Analysis) forms -""" + from django import forms from django.utils import timezone from apps.core.models import PriorityChoices @@ -26,59 +25,46 @@ class RootCauseAnalysisForm(HospitalFieldMixin, forms.ModelForm): class Meta: model = RootCauseAnalysis fields = [ - 'title', - 'description', - 'background', - 'hospital', - 'department', - 'status', - 'severity', - 'priority', - 'assigned_to', - 'target_completion_date', - 'root_cause_summary', + "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' - }), + "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): super().__init__(*args, **kwargs) # Filter assigned_to to show only active users - if 'assigned_to' in self.fields: + 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') + + self.fields["assigned_to"].queryset = User.objects.filter(is_active=True).order_by("email") class RCARootCauseForm(forms.ModelForm): @@ -87,54 +73,38 @@ class RCARootCauseForm(forms.ModelForm): class Meta: model = RCARootCause fields = [ - 'description', - 'category', - 'contributing_factors', - 'likelihood', - 'impact', - 'evidence', + "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' - }), + "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') + likelihood = self.cleaned_data.get("likelihood") if likelihood and (likelihood < 1 or likelihood > 5): - raise forms.ValidationError('Likelihood must be between 1 and 5') + raise forms.ValidationError("Likelihood must be between 1 and 5") return likelihood def clean_impact(self): - impact = self.cleaned_data.get('impact') + impact = self.cleaned_data.get("impact") if impact and (impact < 1 or impact > 5): - raise forms.ValidationError('Impact must be between 1 and 5') + raise forms.ValidationError("Impact must be between 1 and 5") return impact @@ -144,209 +114,159 @@ class RCACorrectiveActionForm(forms.ModelForm): class Meta: model = RCACorrectiveAction fields = [ - 'description', - 'action_type', - 'root_cause', - 'responsible_person', - 'target_date', - 'completion_date', - 'status', - 'effectiveness_measure', - 'effectiveness_assessment', - 'effectiveness_score', - 'obstacles', + "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' - }), + "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) + 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() + 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') + 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') + 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'}) + 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'}) + 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'}) + 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'}) + 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'}) + 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'}) + 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 + + 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'}) - ) + + 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' - }) + 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' - }) + 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' - }) + 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' - }) + 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() + 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'] + fields = ["file", "description"] widgets = { - 'file': forms.FileInput(attrs={'class': 'form-control'}), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 2, - 'placeholder': 'Describe this attachment' - }), + "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'] + 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' - }), - } \ No newline at end of file + "note": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Add a note"}), + "is_internal": forms.CheckboxInput(attrs={"class": "form-check-input"}), + }