442 lines
18 KiB
Python
442 lines
18 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
|
|
from .models import (
|
|
AppointmentRequest, SlotAvailability, WaitingQueue, QueueEntry,
|
|
TelemedicineSession, AppointmentTemplate
|
|
)
|
|
from patients.models import PatientProfile
|
|
from accounts.models import User
|
|
|
|
|
|
class AppointmentRequestForm(forms.ModelForm):
|
|
"""
|
|
Form for appointment request management.
|
|
"""
|
|
class Meta:
|
|
model = AppointmentRequest
|
|
fields = [
|
|
'patient', 'provider', 'appointment_type', 'specialty', 'preferred_date',
|
|
'preferred_time', 'duration_minutes', 'priority', 'chief_complaint',
|
|
'clinical_notes', 'status', 'is_telemedicine', 'location', 'room_number'
|
|
]
|
|
widgets = {
|
|
'patient': forms.Select(attrs={'class': 'form-select'}),
|
|
'provider': forms.Select(attrs={'class': 'form-select'}),
|
|
'appointment_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'specialty': forms.Select(attrs={'class': 'form-select'}),
|
|
'preferred_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'preferred_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
|
'duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}),
|
|
'priority': forms.Select(attrs={'class': 'form-select'}),
|
|
'chief_complaint': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'clinical_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'is_telemedicine': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'location': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'room_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
}
|
|
|
|
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['provider'].queryset = User.objects.filter(
|
|
tenant=user.tenant,
|
|
is_active=True,
|
|
role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
|
|
).order_by('last_name', 'first_name')
|
|
|
|
def clean_preferred_date(self):
|
|
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.')
|
|
return preferred_date
|
|
|
|
def clean_duration_minutes(self):
|
|
duration = self.cleaned_data.get('duration_minutes')
|
|
if duration and duration < 15:
|
|
raise ValidationError('Appointment duration must be at least 15 minutes.')
|
|
if duration and duration > 480: # 8 hours
|
|
raise ValidationError('Appointment duration cannot exceed 8 hours.')
|
|
return duration
|
|
|
|
|
|
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,
|
|
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.
|
|
"""
|
|
class Meta:
|
|
model = WaitingQueue
|
|
fields = [
|
|
'name', 'description', 'queue_type', 'location', 'specialty',
|
|
'max_queue_size', 'average_service_time_minutes', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'queue_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'location': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'specialty': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'max_queue_size': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
'average_service_time_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
|
|
'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,
|
|
role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
|
|
).order_by('last_name', 'first_name')
|
|
|
|
|
|
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,
|
|
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,
|
|
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,
|
|
role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
|
|
).order_by('last_name', 'first_name')
|
|
|