Marwan Alwali 263292f6be update
2025-11-04 00:50:06 +03:00

1076 lines
40 KiB
Python

"""
Forms for Appointments app CRUD operations.
"""
from django import forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import datetime, time, timedelta, date
from .models import *
from patients.models import PatientProfile
from accounts.models import User
from hr.models import Employee, Department
class AppointmentRequestForm(forms.ModelForm):
"""
Form for appointment request management.
Patient is set automatically in the view; it is NOT exposed here.
"""
class Meta:
model = AppointmentRequest
# do not include 'patient' so it cannot be tampered with
exclude = ['patient','status']
fields = [
'provider', 'appointment_type', 'specialty', 'preferred_date',
'preferred_time', 'duration_minutes', 'priority', 'chief_complaint',
'clinical_notes', 'is_telemedicine', 'location', 'room_number'
]
widgets = {
'provider': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'appointment_type': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'specialty': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'preferred_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'preferred_time': forms.TimeInput(attrs={
'class': 'form-control',
'type': 'time',
'required': True
}),
'duration_minutes': forms.NumberInput(attrs={
'class': 'form-control',
'min': '15',
'step': '15',
'value': '30'
}),
'priority': forms.Select(attrs={'class': 'form-select'}),
'chief_complaint': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Describe the reason for this appointment...',
'required': True
}),
'clinical_notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Additional clinical information (optional)'
}),
'is_telemedicine': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'location': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Building, Floor, Room (if in-person)'
}),
'room_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Room number (if applicable)'
}),
}
help_texts = {
'provider': 'Select the healthcare provider for this appointment',
'appointment_type': 'Type of appointment (consultation, follow-up, etc.)',
'specialty': 'Medical specialty for this appointment',
'preferred_date': 'Preferred date for the appointment',
'preferred_time': 'Preferred time for the appointment',
'duration_minutes': 'Expected duration in minutes (default: 30 minutes)',
'priority': 'Urgency level of this appointment',
'chief_complaint': 'Main reason for the appointment',
'is_telemedicine': 'Check if this is a virtual/telemedicine appointment',
'location': 'Physical location (required for in-person appointments)',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# Scope provider list to tenant
if user and hasattr(user, 'tenant'):
self.fields['provider'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
# Set minimum date to today
self.fields['preferred_date'].widget.attrs['min'] = timezone.now().date().isoformat()
def clean_preferred_date(self):
"""Validate preferred date is not in the past."""
preferred_date = self.cleaned_data.get('preferred_date')
if preferred_date and preferred_date < timezone.now().date():
raise ValidationError(
'Appointment cannot be scheduled in the past. '
'Please select today or a future date.'
)
return preferred_date
def clean_duration_minutes(self):
"""Validate appointment duration is within acceptable range."""
duration = self.cleaned_data.get('duration_minutes')
if duration and duration < 15:
raise ValidationError(
'Appointment duration must be at least 15 minutes. '
'Please increase the duration.'
)
if duration and duration > 480: # 8 hours
raise ValidationError(
'Appointment duration cannot exceed 8 hours (480 minutes). '
'Please reduce the duration or split into multiple appointments.'
)
return duration
def clean(self):
"""Cross-field validation for appointment request."""
cleaned_data = super().clean()
is_telemedicine = cleaned_data.get('is_telemedicine')
location = cleaned_data.get('location')
preferred_date = cleaned_data.get('preferred_date')
preferred_time = cleaned_data.get('preferred_time')
# Validate location for in-person appointments
if not is_telemedicine and not location:
raise ValidationError({
'location': 'Location is required for in-person appointments. '
'Please specify the building, floor, or room.'
})
# Validate appointment is not too far in the future (e.g., 1 year)
if preferred_date:
max_future_date = timezone.now().date() + timedelta(days=365)
if preferred_date > max_future_date:
raise ValidationError({
'preferred_date': 'Appointments cannot be scheduled more than 1 year in advance. '
'Please select an earlier date.'
})
# Validate business hours (8 AM to 6 PM)
if preferred_time:
if preferred_time < time(8, 0) or preferred_time >= time(18, 0):
self.add_error('preferred_time',
'Appointments should be scheduled during business hours (8:00 AM - 6:00 PM). '
'For after-hours appointments, please contact the scheduling office.'
)
return cleaned_data
class SlotAvailabilityForm(forms.ModelForm):
"""
Form for slot availability management.
"""
class Meta:
model = SlotAvailability
fields = [
'provider', 'date', 'start_time', 'end_time', 'duration_minutes',
'max_appointments', 'location', 'room_number', 'specialty',
'availability_type', 'is_active', 'supports_telemedicine'
]
widgets = {
'provider': forms.Select(attrs={'class': 'form-select'}),
'date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'start_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
'end_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
'duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}),
'max_appointments': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'location': forms.TextInput(attrs={'class': 'form-control'}),
'room_number': forms.TextInput(attrs={'class': 'form-control'}),
'specialty': forms.TextInput(attrs={'class': 'form-control'}),
'availability_type': forms.Select(attrs={'class': 'form-select'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'supports_telemedicine': 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['provider'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
def clean(self):
cleaned_data = super().clean()
date = cleaned_data.get('date')
start_time = cleaned_data.get('start_time')
end_time = cleaned_data.get('end_time')
if date and date < timezone.now().date():
raise ValidationError('Availability date cannot be in the past.')
if start_time and end_time and start_time >= end_time:
raise ValidationError('Start time must be before end time.')
return cleaned_data
class WaitingQueueForm(forms.ModelForm):
"""
Form for waiting queue management.
"""
# Additional fields for providers (many-to-many)
providers = forms.ModelMultipleChoiceField(
queryset=User.objects.none(),
required=False,
widget=forms.SelectMultiple(attrs={
'class': 'form-select',
'multiple': 'multiple',
'data-placeholder': 'Select providers...'
}),
help_text='Select providers assigned to this queue'
)
# Additional field for accepting patients status
is_accepting_patients = forms.BooleanField(
required=False,
initial=True,
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
help_text='Queue is accepting new patients'
)
class Meta:
model = WaitingQueue
fields = [
'name', 'description', 'queue_type', 'location', 'specialty',
'max_queue_size', 'average_service_time_minutes', 'is_active',
'is_accepting_patients'
]
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter queue name',
'required': True
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Describe the purpose and scope of this queue'
}),
'queue_type': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'location': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., Building A, Floor 2, Room 201'
}),
'specialty': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., Cardiology, Pediatrics'
}),
'max_queue_size': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1',
'max': '200',
'placeholder': '50',
'required': True
}),
'average_service_time_minutes': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1',
'max': '480',
'placeholder': '30',
'required': True
}),
'is_active': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
}
help_texts = {
'name': 'Unique name for this queue',
'description': 'Detailed description of queue purpose and scope',
'queue_type': 'Type of queue (provider, specialty, location, etc.)',
'location': 'Physical location of the queue',
'specialty': 'Medical specialty served by this queue',
'max_queue_size': 'Maximum number of patients allowed in queue (1-200)',
'average_service_time_minutes': 'Average time per patient in minutes (1-480)',
'is_active': 'Queue is currently active and accepting patients',
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Set up providers queryset
if user and hasattr(user, 'tenant'):
tenant = user.tenant
if tenant:
self.fields['providers'].queryset = User.objects.filter(
tenant=tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'SURGEON', 'PHARMACIST', 'LAB_TECH', 'RADIOLOGIST', 'THERAPIST']
).order_by('last_name', 'first_name')
# If editing existing queue, set initial providers
if self.instance and self.instance.pk:
self.fields['providers'].initial = self.instance.providers.all()
# Load existing operating hours and priority weights
if self.instance.operating_hours:
self.initial_operating_hours = self.instance.operating_hours
if self.instance.priority_weights:
self.initial_priority_weights = self.instance.priority_weights
def clean(self):
cleaned_data = super().clean()
# Validate max queue size
max_queue_size = cleaned_data.get('max_queue_size')
if max_queue_size and max_queue_size < 1:
raise ValidationError('Maximum queue size must be at least 1.')
# Validate average service time
avg_service_time = cleaned_data.get('average_service_time_minutes')
if avg_service_time and avg_service_time < 1:
raise ValidationError('Average service time must be at least 1 minute.')
return cleaned_data
def save(self, commit=True):
instance = super().save(commit=False)
# Handle operating hours from POST data
if hasattr(self, 'operating_hours_data'):
instance.operating_hours = self.operating_hours_data
# Handle priority weights from POST data
if hasattr(self, 'priority_weights_data'):
instance.priority_weights = self.priority_weights_data
if commit:
instance.save()
# Save many-to-many relationships
if 'providers' in self.cleaned_data:
instance.providers.set(self.cleaned_data['providers'])
self.save_m2m()
return instance
class QueueEntryForm(forms.ModelForm):
"""
Form for queue entry management.
"""
class Meta:
model = QueueEntry
fields = [
'queue', 'patient', 'appointment', 'priority_score', 'assigned_provider',
'status', 'notes'
]
widgets = {
'queue': forms.Select(attrs={'class': 'form-select'}),
'patient': forms.Select(attrs={'class': 'form-select'}),
'appointment': forms.Select(attrs={'class': 'form-select'}),
'priority_score': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}),
'assigned_provider': forms.Select(attrs={'class': 'form-select'}),
'status': 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['queue'].queryset = WaitingQueue.objects.filter(
tenant=user.tenant,
is_active=True
).order_by('name')
self.fields['patient'].queryset = PatientProfile.objects.filter(
tenant=user.tenant,
is_active=True
).order_by('last_name', 'first_name')
self.fields['appointment'].queryset = AppointmentRequest.objects.filter(
tenant=user.tenant,
status__in=['SCHEDULED', 'CONFIRMED']
).order_by('-preferred_date')
class TelemedicineSessionForm(forms.ModelForm):
"""
Form for telemedicine session management.
"""
class Meta:
model = TelemedicineSession
fields = [
'appointment', 'platform', 'meeting_url', 'meeting_id', 'meeting_password',
'scheduled_start', 'scheduled_end', 'status', 'waiting_room_enabled',
'recording_enabled', 'recording_consent'
]
widgets = {
'appointment': forms.Select(attrs={'class': 'form-select'}),
'platform': forms.Select(attrs={'class': 'form-select'}),
'meeting_url': forms.URLInput(attrs={'class': 'form-control'}),
'meeting_id': forms.TextInput(attrs={'class': 'form-control'}),
'meeting_password': forms.PasswordInput(attrs={'class': 'form-control'}),
'scheduled_start': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'scheduled_end': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'waiting_room_enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'recording_enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'recording_consent': 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['appointment'].queryset = AppointmentRequest.objects.filter(
tenant=user.tenant,
is_telemedicine=True,
status__in=['SCHEDULED', 'CONFIRMED', 'IN_PROGRESS']
).order_by('-preferred_date')
def clean(self):
cleaned_data = super().clean()
start_time = cleaned_data.get('session_start_time')
end_time = cleaned_data.get('session_end_time')
if start_time and end_time and start_time >= end_time:
raise ValidationError('Start time must be before end time.')
return cleaned_data
class AppointmentTemplateForm(forms.ModelForm):
"""
Form for appointment template management.
"""
class Meta:
model = AppointmentTemplate
fields = [
'name', 'description', 'appointment_type', 'duration_minutes',
'specialty', 'advance_booking_days', 'minimum_notice_hours',
'pre_appointment_instructions', 'post_appointment_instructions',
'is_active'
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'appointment_type': forms.TextInput(attrs={'class': 'form-control'}),
'duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}),
'specialty': forms.TextInput(attrs={'class': 'form-control'}),
'advance_booking_days': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'minimum_notice_hours': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'pre_appointment_instructions': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'post_appointment_instructions': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'is_active': 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['provider'].queryset = User.objects.filter(
# tenant=user.tenant,
# is_active=True,
# employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
# ).order_by('last_name', 'first_name')
class AppointmentSearchForm(forms.Form):
"""
Form for searching appointment data.
"""
search = forms.CharField(
max_length=255,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search appointments, patients, providers...'
})
)
appointment_type = forms.ChoiceField(
choices=[('', 'All Types')] + list(AppointmentRequest._meta.get_field('appointment_type').choices),
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
status = forms.ChoiceField(
choices=[('', 'All Status')] + list(AppointmentRequest._meta.get_field('status').choices),
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
priority = forms.ChoiceField(
choices=[('', 'All Priorities')] + list(AppointmentRequest._meta.get_field('priority').choices),
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
provider = forms.ModelChoiceField(
queryset=User.objects.none(),
required=False,
empty_label='All Providers',
widget=forms.Select(attrs={'class': 'form-select'})
)
specialty = forms.CharField(
max_length=100,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Specialty'
})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
is_telemedicine = forms.ChoiceField(
choices=[
('', 'All Appointments'),
('true', 'Telemedicine Only'),
('false', 'In-Person Only')
],
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
self.fields['provider'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
class QueueSearchForm(forms.Form):
"""
Form for searching queues.
"""
search = forms.CharField(
max_length=200,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search queues...'
})
)
queue_type = forms.ChoiceField(
choices=[('', 'All Types')] + [
('PROVIDER', 'Provider Queue'),
('SPECIALTY', 'Specialty Queue'),
('LOCATION', 'Location Queue'),
('PROCEDURE', 'Procedure Queue'),
('EMERGENCY', 'Emergency Queue'),
],
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
is_active = forms.ChoiceField(
choices=[('', 'All'), ('true', 'Active'), ('false', 'Inactive')],
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
class SlotSearchForm(forms.Form):
"""
Form for searching appointment slots.
"""
search = forms.CharField(
max_length=200,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search slots...'
})
)
provider = forms.ModelChoiceField(
queryset=User.objects.none(),
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
is_available = forms.ChoiceField(
choices=[('', 'All'), ('true', 'Available'), ('false', 'Booked')],
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user:
self.fields['provider'].queryset = User.objects.filter(
tenant=user.tenant,
employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
class WaitingListForm(forms.ModelForm):
"""
Form for creating and updating waiting list entries.
"""
class Meta:
model = WaitingList
fields = [
'patient', 'department', 'provider', 'appointment_type', 'specialty',
'priority', 'urgency_score', 'clinical_indication', 'diagnosis_codes',
'preferred_date', 'preferred_time', 'flexible_scheduling',
'earliest_acceptable_date', 'latest_acceptable_date',
'acceptable_days', 'acceptable_times',
'contact_method', 'contact_phone', 'contact_email',
'requires_interpreter', 'interpreter_language',
'accessibility_requirements', 'transportation_needed',
'insurance_verified', 'authorization_required',
'referring_provider', 'referral_date', 'referral_urgency',
'notes'
]
widgets = {
'patient': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'department': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'provider': forms.Select(attrs={
'class': 'form-select'
}),
'appointment_type': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'specialty': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'priority': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'urgency_score': forms.NumberInput(attrs={
'class': 'form-control',
'min': 1,
'max': 10,
'required': True
}),
'clinical_indication': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'required': True,
'placeholder': 'Describe the clinical reason for this appointment request...'
}),
'diagnosis_codes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Enter ICD-10 codes separated by commas'
}),
'preferred_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'min': date.today().isoformat()
}),
'preferred_time': forms.TimeInput(attrs={
'class': 'form-control',
'type': 'time'
}),
'flexible_scheduling': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'earliest_acceptable_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'min': date.today().isoformat()
}),
'latest_acceptable_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'acceptable_days': forms.CheckboxSelectMultiple(attrs={
'class': 'form-check-input'
}),
'contact_method': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'contact_phone': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '(555) 123-4567'
}),
'contact_email': forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'patient@example.com'
}),
'requires_interpreter': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'interpreter_language': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'e.g., Spanish, Mandarin, ASL'
}),
'accessibility_requirements': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Describe any accessibility needs...'
}),
'transportation_needed': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'insurance_verified': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'authorization_required': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'referring_provider': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Dr. Smith, Internal Medicine'
}),
'referral_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'referral_urgency': forms.Select(attrs={
'class': 'form-select'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Additional notes and comments...'
}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter choices based on tenant
if self.tenant:
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(
tenant=self.tenant
)
self.fields['department'].queryset = self.fields['department'].queryset.filter(
tenant=self.tenant
)
if 'provider' in self.fields:
self.fields['provider'].queryset = self.fields['provider'].queryset.filter(
tenant=self.tenant
)
def clean(self):
cleaned_data = super().clean()
# Validate date ranges
preferred_date = cleaned_data.get('preferred_date')
earliest_date = cleaned_data.get('earliest_acceptable_date')
latest_date = cleaned_data.get('latest_acceptable_date')
if preferred_date and preferred_date < date.today():
raise ValidationError("Preferred date cannot be in the past.")
if earliest_date and latest_date and earliest_date > latest_date:
raise ValidationError("Earliest acceptable date must be before latest acceptable date.")
if preferred_date and earliest_date and preferred_date < earliest_date:
raise ValidationError("Preferred date must be within acceptable date range.")
if preferred_date and latest_date and preferred_date > latest_date:
raise ValidationError("Preferred date must be within acceptable date range.")
# Validate contact information
contact_method = cleaned_data.get('contact_method')
contact_phone = cleaned_data.get('contact_phone')
contact_email = cleaned_data.get('contact_email')
if contact_method == 'PHONE' and not contact_phone:
raise ValidationError("Phone number is required when phone is selected as contact method.")
if contact_method == 'EMAIL' and not contact_email:
raise ValidationError("Email address is required when email is selected as contact method.")
# Validate interpreter requirements
requires_interpreter = cleaned_data.get('requires_interpreter')
interpreter_language = cleaned_data.get('interpreter_language')
if requires_interpreter and not interpreter_language:
raise ValidationError("Interpreter language is required when interpreter services are needed.")
return cleaned_data
class WaitingListContactLogForm(forms.ModelForm):
"""
Form for logging contact attempts with waiting list patients.
"""
class Meta:
model = WaitingListContactLog
fields = [
'contact_method', 'contact_outcome', 'appointment_offered',
'offered_date', 'offered_time', 'patient_response',
'notes', 'next_contact_date'
]
widgets = {
'contact_method': forms.Select(attrs={
'class': 'form-select form-select-sm',
'required': True
}),
'contact_outcome': forms.Select(attrs={
'class': 'form-select form-select-sm',
'required': True
}),
'appointment_offered': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'offered_date': forms.DateInput(attrs={
'class': 'form-control form-control-sm',
'type': 'date'
}),
'offered_time': forms.TimeInput(attrs={
'class': 'form-control form-control-sm',
'type': 'time'
}),
'patient_response': forms.Select(attrs={
'class': 'form-select form-select-sm'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control form-control-sm',
'rows': 4,
'placeholder': 'Notes from contact attempt...'
}),
'next_contact_date': forms.DateInput(attrs={
'class': 'form-control form-control-sm',
'type': 'date',
'min': date.today().isoformat()
}),
}
def clean(self):
cleaned_data = super().clean()
appointment_offered = cleaned_data.get('appointment_offered')
offered_date = cleaned_data.get('offered_date')
offered_time = cleaned_data.get('offered_time')
patient_response = cleaned_data.get('patient_response')
if appointment_offered:
if not offered_date:
raise ValidationError("Offered date is required when appointment is offered.")
if not offered_time:
raise ValidationError("Offered time is required when appointment is offered.")
if not patient_response:
raise ValidationError("Patient response is required when appointment is offered.")
next_contact_date = cleaned_data.get('next_contact_date')
if next_contact_date and next_contact_date < date.today():
raise ValidationError("Next contact date cannot be in the past.")
return cleaned_data
class WaitingListFilterForm(forms.Form):
"""
Form for filtering waiting list entries.
"""
PRIORITY_CHOICES = [
('', 'All Priorities'),
('EMERGENCY', 'Emergency'),
('STAT', 'STAT'),
('URGENT', 'Urgent'),
('ROUTINE', 'Routine'),
]
STATUS_CHOICES = [
('', 'All Status'),
('ACTIVE', 'Active'),
('CONTACTED', 'Contacted'),
('OFFERED', 'Appointment Offered'),
('SCHEDULED', 'Scheduled'),
('CANCELLED', 'Cancelled'),
('EXPIRED', 'Expired'),
]
department = forms.ModelChoiceField(
queryset=None,
required=False,
empty_label="All Departments",
widget=forms.Select(attrs={'class': 'form-select'})
)
specialty = forms.ChoiceField(
choices=[('', 'All Specialties')] + WaitingList._meta.get_field('specialty').choices,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
priority = forms.ChoiceField(
choices=PRIORITY_CHOICES,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
status = forms.ChoiceField(
choices=STATUS_CHOICES,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
provider = forms.ModelChoiceField(
queryset=None,
required=False,
empty_label="All Providers",
widget=forms.Select(attrs={'class': 'form-select'})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
urgency_min = forms.IntegerField(
required=False,
min_value=1,
max_value=10,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Min urgency (1-10)'
})
)
urgency_max = forms.IntegerField(
required=False,
min_value=1,
max_value=10,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Max urgency (1-10)'
})
)
def __init__(self, *args, **kwargs):
tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
if tenant:
self.fields['department'].queryset = Department.objects.filter(tenant=tenant)
self.fields['provider'].queryset = User.objects.filter(
tenant=tenant
)
class WaitingListBulkActionForm(forms.Form):
"""
Form for bulk actions on waiting list entries.
"""
ACTION_CHOICES = [
('', 'Select Action'),
('contact', 'Mark as Contacted'),
('cancel', 'Cancel Entries'),
('update_priority', 'Update Priority'),
('transfer_provider', 'Transfer to Provider'),
('export', 'Export Selected'),
]
action = forms.ChoiceField(
choices=ACTION_CHOICES,
required=True,
widget=forms.Select(attrs={'class': 'form-select'})
)
# Fields for specific actions
new_priority = forms.ChoiceField(
choices=WaitingList._meta.get_field('priority').choices,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
transfer_provider = forms.ModelChoiceField(
queryset=None,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
contact_notes = forms.CharField(
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Notes for contact action...'
})
)
cancellation_reason = forms.CharField(
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Reason for cancellation...'
})
)
def __init__(self, *args, **kwargs):
tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
if tenant:
from django.contrib.auth import get_user_model
User = get_user_model()
self.fields['transfer_provider'].queryset = User.objects.filter(
tenant=tenant
)