HH/apps/complaints/forms.py

981 lines
34 KiB
Python

"""
Complaints forms
"""
import os
from django import forms
from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.utils.translation import gettext_lazy as _
from apps.complaints.models import (
Complaint,
ComplaintCategory,
ComplaintSource,
ComplaintSourceType,
ComplaintStatus,
ComplaintType,
Inquiry,
ComplaintSLAConfig,
EscalationRule,
ComplaintThreshold,
)
from apps.core.models import PriorityChoices, SeverityChoices
from apps.organizations.models import Department, Hospital, Patient, Staff
class MultiFileInput(forms.FileInput):
"""
Custom FileInput widget that supports multiple file uploads.
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'
def value_from_datadict(self, data, files, name):
"""
Get all uploaded files for a given field name.
Returns a list of uploaded files instead of a single file.
"""
if name in files:
return files.getlist(name)
return []
class PublicComplaintForm(forms.ModelForm):
"""
Simplified public complaint submission form.
Key changes for AI-powered classification:
- Fewer required fields (simplified for public users)
- Severity and priority removed (AI will determine these automatically)
- Only essential information collected
- Updated with new fields: relation_to_patient, patient_name, national_id, incident_date, staff_name, expected_result
"""
# Contact Information
complainant_name = forms.CharField(
label=_("Complainant Name"),
max_length=200,
required=True,
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'),
],
required=True,
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')
}
)
)
mobile_number = forms.CharField(
label=_("Mobile Number"),
max_length=20,
required=True,
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': _('Your mobile number')
}
)
)
# Patient Information
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')
}
)
)
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')
}
)
)
incident_date = forms.DateField(
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'),
empty_label=_("Select Hospital"),
required=True,
widget=forms.Select(
attrs={
'class': 'form-control',
'id': 'hospital_select',
'data-action': 'load-departments'
}
)
)
department = forms.ModelChoiceField(
label=_("Department (Optional)"),
queryset=Department.objects.none(),
empty_label=_("Select Department"),
required=False,
widget=forms.Select(
attrs={
'class': 'form-control',
'id': 'department_select'
}
)
)
# Complaint Details - Location Hierarchy
location = forms.ModelChoiceField(
label=_("Location"),
queryset=None,
empty_label=_("Select Location"),
required=True,
widget=forms.Select(
attrs={
'class': 'form-control',
'id': 'location_select',
'data-action': 'load-sections'
}
)
)
main_section = forms.ModelChoiceField(
label=_("Section"),
queryset=None,
empty_label=_("Select Section"),
required=True,
widget=forms.Select(
attrs={
'class': 'form-control',
'id': 'main_section_select',
'data-action': 'load-subsections'
}
)
)
subsection = forms.ModelChoiceField(
label=_("Subsection"),
queryset=None,
empty_label=_("Select Subsection"),
required=True,
widget=forms.Select(
attrs={
'class': 'form-control',
'id': 'subsection_select'
}
)
)
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)')
}
)
)
complaint_details = forms.CharField(
label=_("Complaint Details"),
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.')
}
)
)
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?')
}
)
)
# Hidden fields - these will be set by view or AI
severity = forms.ChoiceField(
label=_("Severity"),
choices=SeverityChoices.choices,
initial=SeverityChoices.MEDIUM,
required=False,
widget=forms.HiddenInput()
)
priority = forms.ChoiceField(
label=_("Priority"),
choices=PriorityChoices.choices,
initial=PriorityChoices.MEDIUM,
required=False,
widget=forms.HiddenInput()
)
# Source type - always external for public complaints
complaint_source_type = forms.ChoiceField(
label=_("Complaint Source Type"),
choices=ComplaintSourceType.choices,
initial=ComplaintSourceType.EXTERNAL,
required=False,
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)')
)
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'
]
# Note: 'attachments' is not in fields because Complaint model doesn't have this field.
# Attachments are handled separately via ComplaintAttachment model in the view.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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()
# Load all locations (no filtering needed)
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_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')
# 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 section_id:
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_id:
# Filter departments
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')
# Remove spaces and dashes
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)'))
return mobile_number
def clean_national_id(self):
"""Validate National ID/Iqama format"""
national_id = self.cleaned_data.get('national_id')
# Remove spaces
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)'))
return national_id
def clean_incident_date(self):
"""Validate incident date is not in the future"""
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'))
return incident_date
def clean_attachments(self):
"""Validate file attachments"""
files = self.files.getlist('attachments')
# Check file count
if len(files) > 5:
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'))
# Check file type
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'))
return files
def clean(self):
"""Custom cross-field validation"""
cleaned_data = super().clean()
# Basic validation - all required fields are already validated by field definitions
# This method is kept for future custom cross-field validation needs
return cleaned_data
class ComplaintForm(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.
"""
# Complaint Type
complaint_type = forms.ChoiceField(
label=_("Feedback Type"),
choices=ComplaintType.choices,
initial=ComplaintType.COMPLAINT,
required=False,
widget=forms.HiddenInput()
)
# Source type
complaint_source_type = forms.ChoiceField(
label=_("Complaint Source Type"),
choices=ComplaintSourceType.choices,
initial=ComplaintSourceType.EXTERNAL,
required=False,
widget=forms.Select(attrs={'class': 'form-select', 'id': 'complaintSourceType'})
)
# Patient Information (text-based fields only)
relation_to_patient = forms.ChoiceField(
label=_("Relation to Patient"),
choices=[
('patient', 'Patient'),
('relative', 'Relative'),
],
required=True,
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')})
)
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')})
)
incident_date = forms.DateField(
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'),
empty_label=_("Select Hospital"),
required=True,
widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'})
)
department = forms.ModelChoiceField(
label=_("Department"),
queryset=Department.objects.none(),
empty_label=_("Select Department"),
required=False,
widget=forms.Select(attrs={'class': 'form-select', 'id': 'departmentSelect'})
)
staff = forms.ModelChoiceField(
label=_("Staff"),
queryset=Staff.objects.none(),
empty_label=_("Select Staff"),
required=False,
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')})
)
# Location Hierarchy Fields
location = forms.ModelChoiceField(
label=_("Location"),
queryset=None,
empty_label=_("Select Location"),
required=True,
widget=forms.Select(attrs={
'class': 'form-select',
'id': 'locationSelect',
'data-action': 'load-sections'
})
)
main_section = forms.ModelChoiceField(
label=_("Section"),
queryset=None,
empty_label=_("Select Section"),
required=True,
widget=forms.Select(attrs={
'class': 'form-select',
'id': 'mainSectionSelect',
'data-action': 'load-subsections'
})
)
subsection = forms.ModelChoiceField(
label=_("Subsection"),
queryset=None,
empty_label=_("Select Subsection"),
required=True,
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)')})
)
description = forms.CharField(
label=_("Description"),
required=True,
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?')})
)
class Meta:
model = Complaint
fields = [
'complaint_type', 'complaint_source_type',
'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):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
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()
# Load all locations (no filtering needed)
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_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')
# 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 section_id:
self.fields['subsection'].queryset = SubSection.objects.filter(
location_id=location_id,
main_section_id=section_id
).order_by('name_en')
# Filter hospitals based on user permissions
if user and not user.is_px_admin() and user.hospital:
self.fields['hospital'].queryset = Hospital.objects.filter(id=user.hospital.id)
self.fields['hospital'].initial = user.hospital
# Check for hospital selection in both initial data and POST data
hospital_id = None
if 'hospital' in self.data:
hospital_id = self.data.get('hospital')
elif 'hospital' in self.initial:
hospital_id = self.initial.get('hospital')
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')
# 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')
class InquiryForm(forms.ModelForm):
"""
Form for creating inquiries by authenticated users.
Similar to ComplaintForm - supports patient search, department filtering,
and proper field validation with AJAX support.
"""
patient = forms.ModelChoiceField(
label=_("Patient (Optional)"),
queryset=Patient.objects.filter(status='active'),
empty_label=_("Select Patient"),
required=False,
widget=forms.Select(attrs={'class': 'form-select', 'id': 'patientSelect'})
)
hospital = forms.ModelChoiceField(
label=_("Hospital"),
queryset=Hospital.objects.filter(status='active'),
empty_label=_("Select Hospital"),
required=True,
widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'})
)
department = forms.ModelChoiceField(
label=_("Department (Optional)"),
queryset=Department.objects.none(),
empty_label=_("Select Department"),
required=False,
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'),
],
required=True,
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')})
)
message = forms.CharField(
label=_("Message"),
required=True,
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'}))
class Meta:
model = Inquiry
fields = ['patient', 'hospital', 'department', 'subject', 'message',
'contact_name', 'contact_phone', 'contact_email']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Filter hospitals based on user role
if user and not user.is_px_admin() and user.hospital:
self.fields['hospital'].queryset = Hospital.objects.filter(id=user.hospital.id)
self.fields['hospital'].initial = user.hospital
self.fields['hospital'].widget.attrs['readonly'] = True
# Check for hospital selection in both initial data and POST data
hospital_id = None
if 'hospital' in self.data:
hospital_id = self.data.get('hospital')
elif 'hospital' in self.initial:
hospital_id = self.initial.get('hospital')
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')
class SLAConfigForm(forms.ModelForm):
"""Form for creating and editing SLA configurations"""
class Meta:
model = ComplaintSLAConfig
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'}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Filter hospitals based on user role
if user and not user.is_px_admin() and user.hospital:
self.fields['hospital'].queryset = Hospital.objects.filter(id=user.hospital.id)
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')
# Validate SLA hours is positive
if sla_hours and sla_hours <= 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'})
# 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
)
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.'
)
return cleaned_data
class EscalationRuleForm(forms.ModelForm):
"""Form for creating and editing escalation rules"""
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'
]
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'}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Filter hospitals based on user role
if user and not user.is_px_admin() and user.hospital:
self.fields['hospital'].queryset = Hospital.objects.filter(id=user.hospital.id)
self.fields['hospital'].initial = user.hospital
self.fields['hospital'].widget.attrs['readonly'] = True
# Filter users for escalate_to_user field
from apps.accounts.models import User
if user and user.is_px_admin():
self.fields['escalate_to_user'].queryset = User.objects.filter(is_active=True)
elif user and user.hospital:
self.fields['escalate_to_user'].queryset = User.objects.filter(
is_active=True,
hospital=user.hospital
)
else:
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')
# 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'})
return cleaned_data
class ComplaintThresholdForm(forms.ModelForm):
"""Form for creating and editing complaint thresholds"""
class Meta:
model = ComplaintThreshold
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'}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Filter hospitals based on user role
if user and not user.is_px_admin() and user.hospital:
self.fields['hospital'].queryset = Hospital.objects.filter(id=user.hospital.id)
self.fields['hospital'].initial = user.hospital
self.fields['hospital'].widget.attrs['readonly'] = True
class PublicInquiryForm(forms.Form):
"""Public inquiry submission form (simpler, for general questions)"""
# Contact Information
name = forms.CharField(
label=_("Name"),
max_length=200,
required=True,
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')
}
)
)
email = forms.EmailField(
label=_("Email Address"),
required=False,
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'),
empty_label=_("Select Hospital"),
required=True,
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'),
],
required=True,
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')
}
)
)
message = forms.CharField(
label=_("Message"),
required=True,
widget=forms.Textarea(
attrs={
'class': 'form-control',
'rows': 5,
'placeholder': _('Describe your inquiry')
}
)
)