350 lines
16 KiB
Python
350 lines
16 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
|
|
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,
|
|
employee_profile__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
|