""" Complaints forms """ 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, ComplaintStatus, Inquiry ) 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 the 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 """ # Contact Information name = forms.CharField( label=_("Name"), max_length=200, required=True, widget=forms.TextInput( attrs={ 'class': 'form-control', 'placeholder': _('Your full name') } ) ) email = forms.EmailField( label=_("Email Address"), required=True, widget=forms.EmailInput( attrs={ 'class': 'form-control', 'placeholder': _('your@email.com') } ) ) phone = forms.CharField( label=_("Phone Number"), max_length=20, required=True, widget=forms.TextInput( attrs={ 'class': 'form-control', 'placeholder': _('Your phone number') } ) ) # 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 category = forms.ModelChoiceField( label=_("Complaint Category"), queryset=ComplaintCategory.objects.filter(is_active=True).order_by('name_en'), empty_label=_("Select Category"), required=True, widget=forms.Select( attrs={ 'class': 'form-control', 'id': 'category_select' } ) ) title = forms.CharField( label=_("Complaint Title"), max_length=200, required=True, widget=forms.TextInput( attrs={ 'class': 'form-control', 'placeholder': _('Brief title of your complaint') } ) ) description = forms.CharField( label=_("Complaint Description"), 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.') } ) ) # Hidden fields - these will be set by the 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() ) # 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 = [ 'name', 'email', 'phone', 'hospital', 'department', 'category', 'title', 'description', 'severity', 'priority' ] # 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) # Check both initial data and POST data for hospital 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') # Filter categories (show hospital-specific first, then system-wide) self.fields['category'].queryset = ComplaintCategory.objects.filter( models.Q(hospital_id=hospital_id) | models.Q(hospital__isnull=True), is_active=True ).order_by('hospital', 'order', 'name_en') 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'] import os 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. Uses Django form rendering with minimal JavaScript for dependent dropdowns. Category, subcategory, and source are omitted - AI will determine them. """ patient = forms.ModelChoiceField( label=_("Patient"), queryset=Patient.objects.filter(status='active'), empty_label=_("Select Patient"), required=True, 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"), 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')}) ) description = forms.CharField( label=_("Description"), required=True, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 6, 'placeholder': _('Detailed description of complaint...')}) ) class Meta: model = Complaint fields = ['patient', 'hospital', 'department', 'staff', 'encounter_id', 'description'] def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) # Filter hospital and patient by 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['patient'].queryset = Patient.objects.filter( primary_hospital=user.hospital, status='active' ) # Check for hospital selection in both initial data and POST data # This is needed for validation to work correctly 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 hospital by user permissions if user and not user.is_px_admin() and user.hospital: self.fields['hospital'].queryset = Hospital.objects.filter( id=user.hospital.id ) # 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 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') } ) )