""" Forms for Inpatients app CRUD operations. """ from django import forms from django.core.exceptions import ValidationError from django.utils import timezone from datetime import datetime, time, timedelta from .models import Ward, Bed, Admission, Transfer, DischargeSummary, SurgerySchedule from patients.models import PatientProfile from accounts.models import User class WardForm(forms.ModelForm): """ Form for ward management. """ class Meta: model = Ward fields = [ 'name', 'description', 'ward_type', 'specialty', 'building', 'floor', 'total_beds', 'nurse_manager', 'is_active', 'is_accepting_admissions' ] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'ward_type': forms.Select(attrs={'class': 'form-select'}), 'specialty': forms.Select(attrs={'class': 'form-select'}), 'building': forms.TextInput(attrs={'class': 'form-control'}), 'floor': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), 'total_beds': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), 'nurse_manager': forms.Select(attrs={'class': 'form-select'}), 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'is_accepting_admissions': forms.CheckboxInput(attrs={'class': 'form-check-input'}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['nurse_manager'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['NURSE', 'NURSE_MANAGER'] ).order_by('last_name', 'first_name') def clean_total_beds(self): total_beds = self.cleaned_data.get('total_beds') if total_beds and total_beds < 1: raise ValidationError('Ward must have at least 1 bed.') if total_beds and total_beds > 200: raise ValidationError('Ward capacity cannot exceed 200 beds.') return total_beds class BedForm(forms.ModelForm): """ Form for bed management. """ class Meta: model = Bed fields = [ 'ward', 'bed_number', 'room_number', 'bed_type', 'room_type', 'status', 'equipment', 'features', 'maintenance_notes' ] widgets = { 'ward': forms.Select(attrs={'class': 'form-select'}), 'bed_number': forms.TextInput(attrs={'class': 'form-control'}), 'room_number': forms.TextInput(attrs={'class': 'form-control'}), 'bed_type': forms.Select(attrs={'class': 'form-select'}), 'room_type': forms.Select(attrs={'class': 'form-select'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'equipment': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'features': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'maintenance_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['ward'].queryset = Ward.objects.filter( tenant=user.tenant, is_active=True ).order_by('name') def clean(self): cleaned_data = super().clean() ward = cleaned_data.get('ward') bed_number = cleaned_data.get('bed_number') room_number = cleaned_data.get('room_number') if ward and bed_number: # Check for duplicate bed numbers within the same ward and room existing_bed = Bed.objects.filter( ward=ward, room_number=room_number, bed_number=bed_number ).exclude(pk=self.instance.pk if self.instance else None) if existing_bed.exists(): raise ValidationError(f'Bed number {bed_number} already exists in room {room_number} of {ward.name}.') return cleaned_data class AdmissionForm(forms.ModelForm): """ Form for admission management. """ class Meta: model = Admission fields = [ 'patient', 'current_ward', 'current_bed', 'admission_datetime', 'admission_type', 'admission_source', 'priority', 'admitting_physician', 'attending_physician', 'chief_complaint', 'admitting_diagnosis', 'estimated_length_of_stay', 'insurance_verified', 'authorization_number', 'status', 'admission_notes' ] widgets = { 'patient': forms.Select(attrs={'class': 'form-select'}), 'current_ward': forms.Select(attrs={'class': 'form-select'}), 'current_bed': forms.Select(attrs={'class': 'form-select'}), 'admission_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}), 'admission_type': forms.Select(attrs={'class': 'form-select'}), 'admission_source': forms.Select(attrs={'class': 'form-select'}), 'priority': forms.Select(attrs={'class': 'form-select'}), 'admitting_physician': forms.Select(attrs={'class': 'form-select'}), 'attending_physician': forms.Select(attrs={'class': 'form-select'}), 'chief_complaint': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'admitting_diagnosis': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'estimated_length_of_stay': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), 'insurance_verified': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'authorization_number': forms.TextInput(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'admission_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['patient'].queryset = PatientProfile.objects.filter( tenant=user.tenant, is_active=True ).order_by('last_name', 'first_name') self.fields['current_ward'].queryset = Ward.objects.filter( tenant=user.tenant, is_active=True, is_accepting_admissions=True ).order_by('name') # Initialize empty bed queryset - will be populated via AJAX self.fields['current_bed'].queryset = Bed.objects.filter( ward__tenant=user.tenant, status='AVAILABLE' ).select_related('ward').order_by('ward__name', 'room_number', 'bed_number') self.fields['admitting_physician'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'SPECIALIST'] ).order_by('last_name', 'first_name') self.fields['attending_physician'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'SPECIALIST'] ).order_by('last_name', 'first_name') def clean_admission_datetime(self): admission_datetime = self.cleaned_data.get('admission_datetime') if admission_datetime and admission_datetime > timezone.now() + timedelta(days=30): raise ValidationError('Admission cannot be scheduled more than 30 days in advance.') return admission_datetime def clean_estimated_length_of_stay(self): length_of_stay = self.cleaned_data.get('estimated_length_of_stay') if length_of_stay and length_of_stay < 1: raise ValidationError('Estimated length of stay must be at least 1 day.') if length_of_stay and length_of_stay > 365: raise ValidationError('Estimated length of stay cannot exceed 365 days.') return length_of_stay def clean(self): cleaned_data = super().clean() current_bed = cleaned_data.get('current_bed') status = cleaned_data.get('status') if status == 'ADMITTED' and not current_bed: raise ValidationError('A bed must be assigned for admitted patients.') if current_bed and current_bed.status != 'AVAILABLE' and not self.instance.pk: raise ValidationError(f'Selected bed is not available. Current status: {current_bed.get_status_display()}') return cleaned_data class TransferForm(forms.ModelForm): """ Form for transfer management. """ class Meta: model = Transfer fields = [ 'admission', 'patient', 'transfer_type', 'from_ward', 'from_bed', 'to_ward', 'to_bed', 'scheduled_datetime', 'reason', 'priority', 'status', 'requested_by', 'approved_by', 'transport_method', 'patient_condition', 'notes' ] widgets = { 'admission': forms.Select(attrs={'class': 'form-select'}), 'patient': forms.Select(attrs={'class': 'form-select', 'disabled': 'disabled'}), 'transfer_type': forms.Select(attrs={'class': 'form-select'}), 'from_ward': forms.Select(attrs={'class': 'form-select', 'disabled': 'disabled'}), 'from_bed': forms.Select(attrs={'class': 'form-select', 'disabled': 'disabled'}), 'to_ward': forms.Select(attrs={'class': 'form-select'}), 'to_bed': forms.Select(attrs={'class': 'form-select'}), 'requested_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}), 'scheduled_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}), 'reason': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'priority': forms.Select(attrs={'class': 'form-select'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'requested_by': forms.Select(attrs={'class': 'form-select'}), 'approved_by': forms.Select(attrs={'class': 'form-select'}), 'transport_method': forms.Select(attrs={'class': 'form-select'}), 'patient_condition': forms.Select(attrs={'class': 'form-select'}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['admission'].queryset = Admission.objects.filter( tenant=user.tenant, status='ADMITTED' ).select_related('patient', 'current_ward', 'current_bed').order_by('patient__last_name', 'patient__first_name') self.fields['patient'].queryset = PatientProfile.objects.filter( tenant=user.tenant, is_active=True ).order_by('last_name', 'first_name') self.fields['to_ward'].queryset = Ward.objects.filter( tenant=user.tenant, is_active=True, is_accepting_admissions=True ).order_by('name') self.fields['to_bed'].queryset = Bed.objects.filter( ward__tenant=user.tenant, status='AVAILABLE' ).select_related('ward').order_by('ward__name', 'room_number', 'bed_number') self.fields['requested_by'].queryset = User.objects.filter( tenant=user.tenant, is_active=True ).order_by('last_name', 'first_name') self.fields['approved_by'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'NURSE_MANAGER', 'ADMIN'] ).order_by('last_name', 'first_name') def clean(self): cleaned_data = super().clean() from_bed = cleaned_data.get('from_bed') to_bed = cleaned_data.get('to_bed') scheduled_datetime = cleaned_data.get('scheduled_datetime') if from_bed and to_bed and from_bed == to_bed: raise ValidationError('From bed and to bed cannot be the same.') if scheduled_datetime and scheduled_datetime < timezone.now(): raise ValidationError('Scheduled transfer time cannot be in the past.') return cleaned_data class DischargeSummaryForm(forms.ModelForm): """ Form for discharge summary management. """ class Meta: model = DischargeSummary fields = [ 'admission', 'discharge_date', 'discharge_time', 'length_of_stay', 'admission_diagnosis', 'final_diagnosis', 'secondary_diagnoses', 'procedures_performed', 'hospital_course', 'complications', 'discharge_medications', 'medication_changes', 'follow_up_instructions', 'discharge_disposition', 'discharge_location', 'discharging_physician', 'summary_completed', 'summary_signed', 'patient_copy_provided' ] widgets = { 'admission': forms.Select(attrs={'class': 'form-select'}), 'discharge_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'discharge_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}), 'length_of_stay': forms.NumberInput(attrs={'class': 'form-control', 'readonly': 'readonly'}), 'admission_diagnosis': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'final_diagnosis': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'secondary_diagnoses': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'procedures_performed': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), 'hospital_course': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}), 'complications': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'discharge_medications': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), 'medication_changes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'follow_up_instructions': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), 'discharge_disposition': forms.Select(attrs={'class': 'form-select'}), 'discharge_location': forms.TextInput(attrs={'class': 'form-control'}), 'discharging_physician': forms.Select(attrs={'class': 'form-select'}), 'summary_completed': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'summary_signed': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'patient_copy_provided': forms.CheckboxInput(attrs={'class': 'form-check-input'}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['admission'].queryset = Admission.objects.filter( tenant=user.tenant, status='ADMITTED' ).select_related('patient', 'current_ward', 'current_bed').order_by('patient__last_name', 'patient__first_name') self.fields['discharging_physician'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'SPECIALIST'] ).order_by('last_name', 'first_name') def clean(self): cleaned_data = super().clean() discharge_date = cleaned_data.get('discharge_date') summary_signed = cleaned_data.get('summary_signed') discharging_physician = cleaned_data.get('discharging_physician') if discharge_date and discharge_date > timezone.now().date() + timedelta(days=1): raise ValidationError('Discharge date cannot be more than 1 day in the future.') if summary_signed and not discharging_physician: raise ValidationError('A discharging physician must be specified to sign the summary.') return cleaned_data class SurgeryScheduleForm(forms.ModelForm): """ Form for surgery schedule management. """ class Meta: model = SurgerySchedule fields = [ 'admission', 'patient', 'procedure_name', 'procedure_code', 'surgery_type', 'scheduled_date', 'scheduled_start_time', 'estimated_duration_minutes', 'operating_room', 'primary_surgeon', 'anesthesiologist', 'anesthesia_type', 'preop_diagnosis', 'consent_obtained', 'special_equipment', 'priority', 'status', 'surgery_notes' ] widgets = { 'admission': forms.Select(attrs={'class': 'form-select'}), 'patient': forms.Select(attrs={'class': 'form-select', 'disabled': 'disabled'}), 'procedure_name': forms.TextInput(attrs={'class': 'form-control'}), 'procedure_code': forms.TextInput(attrs={'class': 'form-control'}), 'surgery_type': forms.Select(attrs={'class': 'form-select'}), 'scheduled_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'scheduled_start_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}), 'estimated_duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}), 'operating_room': forms.TextInput(attrs={'class': 'form-control'}), 'primary_surgeon': forms.Select(attrs={'class': 'form-select'}), 'anesthesiologist': forms.Select(attrs={'class': 'form-select'}), 'anesthesia_type': forms.Select(attrs={'class': 'form-select'}), 'preop_diagnosis': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'consent_obtained': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'special_equipment': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'priority': forms.Select(attrs={'class': 'form-select'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'surgery_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['admission'].queryset = Admission.objects.filter( tenant=user.tenant, status='ADMITTED' ).select_related('patient').order_by('patient__last_name', 'patient__first_name') self.fields['patient'].queryset = PatientProfile.objects.filter( tenant=user.tenant, is_active=True ).order_by('last_name', 'first_name') self.fields['primary_surgeon'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'SURGEON', 'SPECIALIST'] ).order_by('last_name', 'first_name') self.fields['anesthesiologist'].queryset = User.objects.filter( tenant=user.tenant, is_active=True, role__in=['DOCTOR', 'ANESTHESIOLOGIST'] ).order_by('last_name', 'first_name') def clean_scheduled_date(self): scheduled_date = self.cleaned_data.get('scheduled_date') if scheduled_date and scheduled_date < timezone.now().date(): raise ValidationError('Surgery date cannot be in the past.') if scheduled_date and scheduled_date > timezone.now().date() + timedelta(days=90): raise ValidationError('Surgery cannot be scheduled more than 90 days in advance.') return scheduled_date def clean_estimated_duration_minutes(self): duration = self.cleaned_data.get('estimated_duration_minutes') if duration and duration < 15: raise ValidationError('Surgery duration must be at least 15 minutes.') if duration and duration > 1440: # 24 hours raise ValidationError('Surgery duration cannot exceed 24 hours (1440 minutes).') return duration def clean(self): cleaned_data = super().clean() scheduled_date = cleaned_data.get('scheduled_date') scheduled_start_time = cleaned_data.get('scheduled_start_time') consent_obtained = cleaned_data.get('consent_obtained') status = cleaned_data.get('status') if scheduled_date and scheduled_start_time: scheduled_datetime = datetime.combine(scheduled_date, scheduled_start_time) if scheduled_datetime < timezone.now(): raise ValidationError('Surgery date and time cannot be in the past.') if status in ['CONFIRMED', 'PREP', 'IN_PROGRESS'] and not consent_obtained: raise ValidationError('Patient consent must be obtained before surgery can be confirmed or started.') return cleaned_data