Marwan Alwali 4d06ca4b5e update
2025-09-20 14:26:19 +03:00

446 lines
21 KiB
Python

"""
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')
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,
employee_profile__role__in=['PHYSICIAN', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
self.fields['attending_physician'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', 'PHYSICIAN_ASSISTANT']
).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,
employee_profile__role__in=['DOCTOR', 'SPECIALIST', 'PHYSICIAN']
).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,
employee_profile__role__in=['PHYSICIAN', 'SURGEON', 'SPECIALIST']
).order_by('last_name', 'first_name')
self.fields['anesthesiologist'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', '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