1715 lines
65 KiB
Python
1715 lines
65 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'}),
|
|
'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}),
|
|
'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)
|
|
# patient is optional to receive; we don't render it, but can use if needed
|
|
# self._patient = kwargs.pop('patient', 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')
|
|
|
|
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.
|
|
"""
|
|
# 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',
|
|
'placeholder': '50',
|
|
'required': True
|
|
}),
|
|
'average_service_time_minutes': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': '1',
|
|
'placeholder': '30',
|
|
'required': True
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={
|
|
'class': 'form-check-input'
|
|
}),
|
|
}
|
|
|
|
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,
|
|
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,
|
|
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
|
|
)
|
|
|
|
# from django import forms
|
|
# from django.core.exceptions import ValidationError
|
|
# from django.utils import timezone
|
|
# from django.contrib.auth.models import User
|
|
# from crispy_forms.helper import FormHelper
|
|
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
|
|
# from crispy_forms.bootstrap import FormActions
|
|
# from datetime import datetime, timedelta
|
|
#
|
|
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
|
|
# from patients.models import Patient
|
|
# from core.models import Department
|
|
#
|
|
#
|
|
# class AppointmentSchedulingForm(forms.ModelForm):
|
|
# """
|
|
# Form for appointment scheduling
|
|
# """
|
|
# patient_search = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.TextInput(attrs={
|
|
# 'class': 'form-control',
|
|
# 'placeholder': 'Search patient by name, ID, or phone...',
|
|
# 'data-toggle': 'patient-search'
|
|
# })
|
|
# )
|
|
# preferred_time_1 = forms.TimeField(
|
|
# required=False,
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
# preferred_time_2 = forms.TimeField(
|
|
# required=False,
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
# preferred_time_3 = forms.TimeField(
|
|
# required=False,
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
# special_instructions = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# send_confirmation = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# send_reminder = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# class Meta:
|
|
# model = Appointment
|
|
# fields = [
|
|
# 'patient', 'provider', 'department', 'appointment_type',
|
|
# 'appointment_date', 'appointment_time', 'duration',
|
|
# 'urgency', 'reason', 'notes', 'special_instructions',
|
|
# 'send_confirmation', 'send_reminder'
|
|
# ]
|
|
# widgets = {
|
|
# 'patient': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'provider': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'department': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'appointment_type': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'appointment_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
# 'appointment_time': forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
|
# 'duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
# 'urgency': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'reason': forms.TextInput(attrs={'class': 'form-control'}),
|
|
# 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# }
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# tenant = kwargs.pop('tenant', None)
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# if tenant:
|
|
# self.fields['patient'].queryset = Patient.objects.filter(tenant=tenant)
|
|
# self.fields['provider'].queryset = User.objects.filter(
|
|
# tenant=tenant,
|
|
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
|
# )
|
|
# self.fields['department'].queryset = Department.objects.filter(tenant=tenant)
|
|
#
|
|
# # Set minimum date to today
|
|
# self.fields['appointment_date'].widget.attrs['min'] = timezone.now().date().isoformat()
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Patient Information',
|
|
# 'patient_search',
|
|
# 'patient'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Appointment Details',
|
|
# Row(
|
|
# Column('provider', css_class='form-group col-md-6 mb-0'),
|
|
# Column('department', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# Row(
|
|
# Column('appointment_type', css_class='form-group col-md-6 mb-0'),
|
|
# Column('urgency', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# Row(
|
|
# Column('appointment_date', css_class='form-group col-md-4 mb-0'),
|
|
# Column('appointment_time', css_class='form-group col-md-4 mb-0'),
|
|
# Column('duration', css_class='form-group col-md-4 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# 'reason',
|
|
# 'notes',
|
|
# 'special_instructions'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Preferred Times (Alternative Options)',
|
|
# Row(
|
|
# Column('preferred_time_1', css_class='form-group col-md-4 mb-0'),
|
|
# Column('preferred_time_2', css_class='form-group col-md-4 mb-0'),
|
|
# Column('preferred_time_3', css_class='form-group col-md-4 mb-0'),
|
|
# css_class='form-row'
|
|
# )
|
|
# ),
|
|
# Fieldset(
|
|
# 'Notifications',
|
|
# HTML('<div class="form-check">'),
|
|
# 'send_confirmation',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_send_confirmation">Send appointment confirmation</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'send_reminder',
|
|
# HTML('<label class="form-check-label" for="id_send_reminder">Send appointment reminder</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Schedule Appointment', css_class='btn btn-primary'),
|
|
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
# def clean(self):
|
|
# cleaned_data = super().clean()
|
|
# appointment_date = cleaned_data.get('appointment_date')
|
|
# appointment_time = cleaned_data.get('appointment_time')
|
|
# provider = cleaned_data.get('provider')
|
|
#
|
|
# if appointment_date and appointment_time:
|
|
# appointment_datetime = datetime.combine(appointment_date, appointment_time)
|
|
#
|
|
# # Check if appointment is in the past
|
|
# if appointment_datetime < timezone.now():
|
|
# raise ValidationError('Appointment cannot be scheduled in the past.')
|
|
#
|
|
# # Check provider availability
|
|
# if provider and self.check_provider_conflict(provider, appointment_datetime):
|
|
# raise ValidationError('Provider is not available at the selected time.')
|
|
#
|
|
# return cleaned_data
|
|
#
|
|
# def check_provider_conflict(self, provider, appointment_datetime):
|
|
# """Check if provider has conflicting appointments"""
|
|
# duration = self.cleaned_data.get('duration', 30)
|
|
# end_time = appointment_datetime + timedelta(minutes=duration)
|
|
#
|
|
# conflicts = Appointment.objects.filter(
|
|
# provider=provider,
|
|
# appointment_date=appointment_datetime.date(),
|
|
# status__in=['scheduled', 'confirmed', 'in_progress']
|
|
# ).exclude(id=self.instance.id if self.instance else None)
|
|
#
|
|
# for conflict in conflicts:
|
|
# conflict_start = datetime.combine(conflict.appointment_date, conflict.appointment_time)
|
|
# conflict_end = conflict_start + timedelta(minutes=conflict.duration)
|
|
#
|
|
# if (appointment_datetime < conflict_end and end_time > conflict_start):
|
|
# return True
|
|
#
|
|
# return False
|
|
#
|
|
#
|
|
# class AppointmentConfirmationForm(forms.ModelForm):
|
|
# """
|
|
# Form for appointment confirmation
|
|
# """
|
|
# confirmation_method = forms.ChoiceField(
|
|
# choices=[
|
|
# ('phone', 'Phone Call'),
|
|
# ('email', 'Email'),
|
|
# ('sms', 'SMS'),
|
|
# ('in_person', 'In Person'),
|
|
# ('online', 'Online Portal')
|
|
# ],
|
|
# required=True,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# contact_attempts = forms.IntegerField(
|
|
# initial=1,
|
|
# min_value=1,
|
|
# max_value=5,
|
|
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
|
# )
|
|
# confirmation_notes = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# reschedule_requested = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# new_preferred_date = forms.DateField(
|
|
# required=False,
|
|
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
# )
|
|
# new_preferred_time = forms.TimeField(
|
|
# required=False,
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
#
|
|
# class Meta:
|
|
# model = AppointmentConfirmation
|
|
# fields = [
|
|
# 'confirmation_method', 'contact_attempts', 'confirmation_notes',
|
|
# 'reschedule_requested', 'new_preferred_date', 'new_preferred_time'
|
|
# ]
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# appointment = kwargs.pop('appointment', None)
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.appointment = appointment
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Confirmation Details',
|
|
# Row(
|
|
# Column('confirmation_method', css_class='form-group col-md-6 mb-0'),
|
|
# Column('contact_attempts', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# 'confirmation_notes'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Reschedule Request',
|
|
# HTML('<div class="form-check">'),
|
|
# 'reschedule_requested',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_reschedule_requested">Patient requested reschedule</label>'),
|
|
# HTML('</div>'),
|
|
# Row(
|
|
# Column('new_preferred_date', css_class='form-group col-md-6 mb-0'),
|
|
# Column('new_preferred_time', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# )
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Confirm Appointment', css_class='btn btn-primary'),
|
|
# HTML(
|
|
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
# def clean(self):
|
|
# cleaned_data = super().clean()
|
|
# reschedule_requested = cleaned_data.get('reschedule_requested')
|
|
# new_preferred_date = cleaned_data.get('new_preferred_date')
|
|
# new_preferred_time = cleaned_data.get('new_preferred_time')
|
|
#
|
|
# if reschedule_requested:
|
|
# if not new_preferred_date:
|
|
# raise ValidationError('New preferred date is required when reschedule is requested.')
|
|
# if not new_preferred_time:
|
|
# raise ValidationError('New preferred time is required when reschedule is requested.')
|
|
#
|
|
# return cleaned_data
|
|
#
|
|
#
|
|
# class QueueManagementForm(forms.ModelForm):
|
|
# """
|
|
# Form for queue management
|
|
# """
|
|
# estimated_wait_time = forms.IntegerField(
|
|
# required=False,
|
|
# widget=forms.NumberInput(attrs={'class': 'form-control'})
|
|
# )
|
|
# priority_adjustment = forms.ChoiceField(
|
|
# choices=[
|
|
# ('none', 'No Change'),
|
|
# ('increase', 'Increase Priority'),
|
|
# ('decrease', 'Decrease Priority')
|
|
# ],
|
|
# required=False,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# queue_notes = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# notify_patient = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# class Meta:
|
|
# model = Queue
|
|
# fields = [
|
|
# 'queue_type', 'priority', 'estimated_wait_time',
|
|
# 'priority_adjustment', 'queue_notes', 'notify_patient'
|
|
# ]
|
|
# widgets = {
|
|
# 'queue_type': forms.Select(attrs={'class': 'form-control'}),
|
|
# 'priority': forms.Select(attrs={'class': 'form-control'})
|
|
# }
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Queue Configuration',
|
|
# Row(
|
|
# Column('queue_type', css_class='form-group col-md-6 mb-0'),
|
|
# Column('priority', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# Row(
|
|
# Column('estimated_wait_time', css_class='form-group col-md-6 mb-0'),
|
|
# Column('priority_adjustment', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# 'queue_notes'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Notifications',
|
|
# HTML('<div class="form-check">'),
|
|
# 'notify_patient',
|
|
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of queue status</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Update Queue', css_class='btn btn-primary'),
|
|
# HTML('<a href="{% url \'appointments:queue_list\' %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
#
|
|
# class TelemedicineSetupForm(forms.ModelForm):
|
|
# """
|
|
# Form for telemedicine session setup
|
|
# """
|
|
# platform = forms.ChoiceField(
|
|
# choices=[
|
|
# ('zoom', 'Zoom'),
|
|
# ('teams', 'Microsoft Teams'),
|
|
# ('webex', 'Cisco Webex'),
|
|
# ('meet', 'Google Meet'),
|
|
# ('custom', 'Custom Platform')
|
|
# ],
|
|
# required=True,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# test_connection = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# send_instructions = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# backup_phone = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
|
# )
|
|
# technical_support_contact = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.TextInput(attrs={'class': 'form-control'})
|
|
# )
|
|
# session_recording = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# class Meta:
|
|
# model = TelemedicineSession
|
|
# fields = [
|
|
# 'platform', 'meeting_url', 'meeting_id', 'meeting_password',
|
|
# 'test_connection', 'send_instructions', 'backup_phone',
|
|
# 'technical_support_contact', 'session_recording'
|
|
# ]
|
|
# widgets = {
|
|
# 'meeting_url': forms.URLInput(attrs={'class': 'form-control'}),
|
|
# 'meeting_id': forms.TextInput(attrs={'class': 'form-control'}),
|
|
# 'meeting_password': forms.TextInput(attrs={'class': 'form-control'})
|
|
# }
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# appointment = kwargs.pop('appointment', None)
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.appointment = appointment
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Platform Configuration',
|
|
# 'platform',
|
|
# 'meeting_url',
|
|
# Row(
|
|
# Column('meeting_id', css_class='form-group col-md-6 mb-0'),
|
|
# Column('meeting_password', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# )
|
|
# ),
|
|
# Fieldset(
|
|
# 'Support Information',
|
|
# Row(
|
|
# Column('backup_phone', css_class='form-group col-md-6 mb-0'),
|
|
# Column('technical_support_contact', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# )
|
|
# ),
|
|
# Fieldset(
|
|
# 'Session Options',
|
|
# HTML('<div class="form-check">'),
|
|
# 'test_connection',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_test_connection">Test connection before appointment</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'send_instructions',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_send_instructions">Send setup instructions to patient</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'session_recording',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_session_recording">Record session (with consent)</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Setup Telemedicine', css_class='btn btn-primary'),
|
|
# HTML(
|
|
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
# def clean_meeting_url(self):
|
|
# meeting_url = self.cleaned_data.get('meeting_url')
|
|
# platform = self.cleaned_data.get('platform')
|
|
#
|
|
# if platform and meeting_url:
|
|
# # Validate URL format based on platform
|
|
# if platform == 'zoom' and 'zoom.us' not in meeting_url:
|
|
# raise ValidationError('Invalid Zoom meeting URL format.')
|
|
# elif platform == 'teams' and 'teams.microsoft.com' not in meeting_url:
|
|
# raise ValidationError('Invalid Microsoft Teams meeting URL format.')
|
|
# elif platform == 'webex' and 'webex.com' not in meeting_url:
|
|
# raise ValidationError('Invalid Webex meeting URL format.')
|
|
# elif platform == 'meet' and 'meet.google.com' not in meeting_url:
|
|
# raise ValidationError('Invalid Google Meet URL format.')
|
|
#
|
|
# return meeting_url
|
|
#
|
|
#
|
|
# class AppointmentRescheduleForm(forms.Form):
|
|
# """
|
|
# Form for appointment rescheduling
|
|
# """
|
|
# new_date = forms.DateField(
|
|
# required=True,
|
|
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
# )
|
|
# new_time = forms.TimeField(
|
|
# required=True,
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
# new_provider = forms.ModelChoiceField(
|
|
# queryset=None,
|
|
# required=False,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# reschedule_reason = forms.ChoiceField(
|
|
# choices=[
|
|
# ('patient_request', 'Patient Request'),
|
|
# ('provider_unavailable', 'Provider Unavailable'),
|
|
# ('emergency', 'Emergency'),
|
|
# ('equipment_issue', 'Equipment Issue'),
|
|
# ('other', 'Other')
|
|
# ],
|
|
# required=True,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# notes = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# notify_patient = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# tenant = kwargs.pop('tenant', None)
|
|
# appointment = kwargs.pop('appointment', None)
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# if tenant:
|
|
# self.fields['new_provider'].queryset = User.objects.filter(
|
|
# tenant=tenant,
|
|
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
|
# )
|
|
#
|
|
# # Set minimum date to today
|
|
# self.fields['new_date'].widget.attrs['min'] = timezone.now().date().isoformat()
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'New Appointment Details',
|
|
# Row(
|
|
# Column('new_date', css_class='form-group col-md-6 mb-0'),
|
|
# Column('new_time', css_class='form-group col-md-6 mb-0'),
|
|
# css_class='form-row'
|
|
# ),
|
|
# 'new_provider'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Reschedule Information',
|
|
# 'reschedule_reason',
|
|
# 'notes'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Notifications',
|
|
# HTML('<div class="form-check">'),
|
|
# 'notify_patient',
|
|
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of reschedule</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Reschedule Appointment', css_class='btn btn-primary'),
|
|
# HTML(
|
|
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
#
|
|
# class AppointmentCancellationForm(forms.Form):
|
|
# """
|
|
# Form for appointment cancellation
|
|
# """
|
|
# cancellation_reason = forms.ChoiceField(
|
|
# choices=[
|
|
# ('patient_request', 'Patient Request'),
|
|
# ('provider_unavailable', 'Provider Unavailable'),
|
|
# ('patient_no_show', 'Patient No Show'),
|
|
# ('emergency', 'Emergency'),
|
|
# ('equipment_failure', 'Equipment Failure'),
|
|
# ('other', 'Other')
|
|
# ],
|
|
# required=True,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# cancellation_notes = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# offer_reschedule = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# notify_patient = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# refund_required = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Cancellation Details',
|
|
# 'cancellation_reason',
|
|
# 'cancellation_notes'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Follow-up Actions',
|
|
# HTML('<div class="form-check">'),
|
|
# 'offer_reschedule',
|
|
# HTML(
|
|
# '<label class="form-check-label" for="id_offer_reschedule">Offer to reschedule appointment</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'notify_patient',
|
|
# HTML('<label class="form-check-label" for="id_notify_patient">Notify patient of cancellation</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'refund_required',
|
|
# HTML('<label class="form-check-label" for="id_refund_required">Refund required</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Cancel Appointment', css_class='btn btn-danger'),
|
|
# HTML(
|
|
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Back</a>')
|
|
# )
|
|
# )
|
|
#
|
|
#
|
|
# class AppointmentCheckInForm(forms.Form):
|
|
# """
|
|
# Form for appointment check-in
|
|
# """
|
|
# arrival_time = forms.TimeField(
|
|
# initial=timezone.now().time(),
|
|
# widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'})
|
|
# )
|
|
# insurance_verified = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# copay_collected = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# forms_completed = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# vitals_required = forms.BooleanField(
|
|
# required=False,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
# special_needs = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2})
|
|
# )
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# Fieldset(
|
|
# 'Check-in Information',
|
|
# 'arrival_time',
|
|
# 'special_needs'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Pre-visit Tasks',
|
|
# HTML('<div class="form-check">'),
|
|
# 'insurance_verified',
|
|
# HTML('<label class="form-check-label" for="id_insurance_verified">Insurance verified</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'copay_collected',
|
|
# HTML('<label class="form-check-label" for="id_copay_collected">Copay collected</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'forms_completed',
|
|
# HTML('<label class="form-check-label" for="id_forms_completed">Forms completed</label>'),
|
|
# HTML('</div>'),
|
|
# HTML('<div class="form-check">'),
|
|
# 'vitals_required',
|
|
# HTML('<label class="form-check-label" for="id_vitals_required">Vitals required</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Check In Patient', css_class='btn btn-primary'),
|
|
# HTML(
|
|
# '<a href="{% url \'appointments:appointment_detail\' appointment.id %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|
|
#
|
|
# class BulkAppointmentForm(forms.Form):
|
|
# """
|
|
# Form for bulk appointment operations
|
|
# """
|
|
# action = forms.ChoiceField(
|
|
# choices=[
|
|
# ('confirm', 'Confirm Appointments'),
|
|
# ('reschedule', 'Reschedule Appointments'),
|
|
# ('cancel', 'Cancel Appointments'),
|
|
# ('send_reminders', 'Send Reminders')
|
|
# ],
|
|
# required=True,
|
|
# widget=forms.Select(attrs={'class': 'form-control'})
|
|
# )
|
|
# appointment_ids = forms.CharField(
|
|
# widget=forms.HiddenInput()
|
|
# )
|
|
# bulk_date = forms.DateField(
|
|
# required=False,
|
|
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
# )
|
|
# bulk_reason = forms.CharField(
|
|
# required=False,
|
|
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
# )
|
|
# notify_patients = forms.BooleanField(
|
|
# required=False,
|
|
# initial=True,
|
|
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
# )
|
|
#
|
|
# def __init__(self, *args, **kwargs):
|
|
# super().__init__(*args, **kwargs)
|
|
#
|
|
# self.helper = FormHelper()
|
|
# self.helper.layout = Layout(
|
|
# 'appointment_ids',
|
|
# Fieldset(
|
|
# 'Bulk Operation',
|
|
# 'action',
|
|
# 'bulk_date',
|
|
# 'bulk_reason'
|
|
# ),
|
|
# Fieldset(
|
|
# 'Notifications',
|
|
# HTML('<div class="form-check">'),
|
|
# 'notify_patients',
|
|
# HTML('<label class="form-check-label" for="id_notify_patients">Notify patients</label>'),
|
|
# HTML('</div>')
|
|
# ),
|
|
# FormActions(
|
|
# Submit('submit', 'Execute Bulk Operation', css_class='btn btn-primary'),
|
|
# HTML('<a href="{% url \'appointments:appointment_list\' %}" class="btn btn-secondary">Cancel</a>')
|
|
# )
|
|
# )
|
|
#
|