326 lines
12 KiB
Python
326 lines
12 KiB
Python
"""
|
|
Appointments forms for the Tenhal Multidisciplinary Healthcare Platform.
|
|
|
|
This module contains forms for appointment booking, confirmation, rescheduling, etc.
|
|
"""
|
|
|
|
from django import forms
|
|
from django.utils.translation import gettext_lazy as _
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import Layout, Fieldset, Row, Column, Submit, HTML
|
|
|
|
from .models import Appointment, Provider, Room, Schedule
|
|
|
|
|
|
class AppointmentBookingForm(forms.ModelForm):
|
|
"""
|
|
Form for booking new appointments.
|
|
"""
|
|
|
|
# Define common service types
|
|
SERVICE_TYPE_CHOICES = [
|
|
('', _('Select a service type')),
|
|
('consultation', _('Consultation')),
|
|
('follow_up', _('Follow-up Visit')),
|
|
('assessment', _('Assessment')),
|
|
('therapy_session', _('Therapy Session')),
|
|
('evaluation', _('Evaluation')),
|
|
('intervention', _('Intervention')),
|
|
('screening', _('Screening')),
|
|
('parent_training', _('Parent Training')),
|
|
('group_session', _('Group Session')),
|
|
('other', _('Other')),
|
|
]
|
|
|
|
service_type = forms.ChoiceField(
|
|
choices=SERVICE_TYPE_CHOICES,
|
|
widget=forms.Select(attrs={'class': 'form-control'}),
|
|
label=_('Service Type')
|
|
)
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = [
|
|
'patient', 'clinic', 'provider', 'room',
|
|
'service_type', 'scheduled_date', 'scheduled_time', 'duration',
|
|
'finance_cleared', 'consent_verified',
|
|
'notes',
|
|
]
|
|
widgets = {
|
|
'patient': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}),
|
|
'clinic': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select clinic'}),
|
|
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
|
|
'room': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select room'}),
|
|
'scheduled_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'scheduled_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'duration': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}),
|
|
'finance_cleared': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'consent_verified': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
Fieldset(
|
|
_('Appointment Details'),
|
|
'patient',
|
|
Row(
|
|
Column('clinic', css_class='form-group col-md-6 mb-0'),
|
|
Column('provider', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
Row(
|
|
Column('service_type', css_class='form-group col-md-6 mb-0'),
|
|
Column('room', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
Row(
|
|
Column('scheduled_date', css_class='form-group col-md-4 mb-0'),
|
|
Column('scheduled_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'
|
|
),
|
|
'notes',
|
|
),
|
|
Submit('submit', _('Book Appointment'), css_class='btn btn-primary')
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
provider = cleaned_data.get('provider')
|
|
clinic = cleaned_data.get('clinic')
|
|
|
|
# Validate provider belongs to clinic
|
|
if provider and clinic:
|
|
if not provider.specialties.filter(id=clinic.id).exists():
|
|
raise forms.ValidationError(
|
|
_('Selected provider does not work in this clinic.')
|
|
)
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class AppointmentConfirmForm(forms.Form):
|
|
"""
|
|
Form for confirming appointments.
|
|
"""
|
|
|
|
confirmation_method = forms.ChoiceField(
|
|
label=_('Confirmation Method'),
|
|
choices=Appointment.ConfirmationMethod.choices,
|
|
widget=forms.RadioSelect(attrs={'class': 'form-check-input'})
|
|
)
|
|
notes = forms.CharField(
|
|
required=False,
|
|
label=_('Notes'),
|
|
widget=forms.Textarea(attrs={'rows': 2, 'class': 'form-control'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'confirmation_method',
|
|
'notes',
|
|
Submit('submit', _('Confirm Appointment'), css_class='btn btn-success')
|
|
)
|
|
|
|
|
|
class AppointmentRescheduleForm(forms.ModelForm):
|
|
"""
|
|
Form for rescheduling appointments.
|
|
"""
|
|
|
|
reschedule_reason = forms.CharField(
|
|
label=_('Reason for Rescheduling'),
|
|
widget=forms.Textarea(attrs={'rows': 3, 'class': 'form-control'})
|
|
)
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = ['scheduled_date', 'scheduled_time', 'reschedule_reason']
|
|
widgets = {
|
|
'scheduled_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'scheduled_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
}
|
|
labels = {
|
|
'scheduled_date': _('New Date'),
|
|
'scheduled_time': _('New Time'),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
Row(
|
|
Column('scheduled_date', css_class='form-group col-md-6 mb-0'),
|
|
Column('scheduled_time', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
'reschedule_reason',
|
|
Submit('submit', _('Reschedule'), css_class='btn btn-warning')
|
|
)
|
|
|
|
|
|
class AppointmentCancelForm(forms.Form):
|
|
"""
|
|
Form for cancelling appointments.
|
|
"""
|
|
|
|
cancel_reason = forms.CharField(
|
|
label=_('Reason for Cancellation'),
|
|
widget=forms.Textarea(attrs={'rows': 3, 'class': 'form-control'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'cancel_reason',
|
|
Submit('submit', _('Cancel Appointment'), css_class='btn btn-danger')
|
|
)
|
|
|
|
|
|
class AppointmentArrivalForm(forms.Form):
|
|
"""
|
|
Form for marking patient arrival.
|
|
"""
|
|
|
|
notes = forms.CharField(
|
|
required=False,
|
|
label=_('Notes'),
|
|
widget=forms.Textarea(attrs={'rows': 2, 'class': 'form-control'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'notes',
|
|
Submit('submit', _('Mark as Arrived'), css_class='btn btn-info')
|
|
)
|
|
|
|
|
|
class AppointmentSearchForm(forms.Form):
|
|
"""
|
|
Form for searching appointments.
|
|
"""
|
|
|
|
search_query = forms.CharField(
|
|
required=False,
|
|
label=_('Search'),
|
|
widget=forms.TextInput(attrs={
|
|
'placeholder': _('Patient name, MRN, Appointment #...'),
|
|
'class': 'form-control'
|
|
})
|
|
)
|
|
clinic = forms.ModelChoiceField(
|
|
required=False,
|
|
label=_('Clinic'),
|
|
queryset=None, # Will be set in __init__
|
|
widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select clinic'})
|
|
)
|
|
provider = forms.ModelChoiceField(
|
|
required=False,
|
|
label=_('Provider'),
|
|
queryset=None, # Will be set in __init__
|
|
widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'})
|
|
)
|
|
status = forms.ChoiceField(
|
|
required=False,
|
|
label=_('Status'),
|
|
choices=[('', _('All'))] + list(Appointment.Status.choices),
|
|
widget=forms.Select(attrs={'class': 'form-control'})
|
|
)
|
|
date_from = forms.DateField(
|
|
required=False,
|
|
label=_('From Date'),
|
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
|
|
)
|
|
date_to = forms.DateField(
|
|
required=False,
|
|
label=_('To Date'),
|
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
tenant = kwargs.pop('tenant', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if tenant:
|
|
from core.models import Clinic
|
|
self.fields['clinic'].queryset = Clinic.objects.filter(tenant=tenant, is_active=True)
|
|
self.fields['provider'].queryset = Provider.objects.filter(tenant=tenant, is_available=True)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'get'
|
|
self.helper.layout = Layout(
|
|
Row(
|
|
Column('search_query', css_class='form-group col-md-3 mb-0'),
|
|
Column('clinic', css_class='form-group col-md-2 mb-0'),
|
|
Column('provider', css_class='form-group col-md-2 mb-0'),
|
|
Column('status', css_class='form-group col-md-2 mb-0'),
|
|
Column('date_from', css_class='form-group col-md-1.5 mb-0'),
|
|
Column('date_to', css_class='form-group col-md-1.5 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
Submit('search', _('Search'), css_class='btn btn-primary')
|
|
)
|
|
|
|
|
|
class ProviderScheduleForm(forms.ModelForm):
|
|
"""
|
|
Form for managing provider schedules.
|
|
"""
|
|
|
|
class Meta:
|
|
model = Schedule
|
|
fields = ['provider', 'day_of_week', 'start_time', 'end_time', 'slot_duration', 'is_active']
|
|
widgets = {
|
|
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
|
|
'day_of_week': forms.Select(attrs={'class': 'form-control'}),
|
|
'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
|
'slot_duration': forms.NumberInput(attrs={'class': 'form-control', 'min': '15', 'step': '15'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'provider',
|
|
Row(
|
|
Column('day_of_week', css_class='form-group col-md-4 mb-0'),
|
|
Column('start_time', css_class='form-group col-md-3 mb-0'),
|
|
Column('end_time', css_class='form-group col-md-3 mb-0'),
|
|
Column('slot_duration', css_class='form-group col-md-2 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
'is_active',
|
|
Submit('submit', _('Save Schedule'), css_class='btn btn-primary')
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
start_time = cleaned_data.get('start_time')
|
|
end_time = cleaned_data.get('end_time')
|
|
|
|
if start_time and end_time and start_time >= end_time:
|
|
raise forms.ValidationError(_('End time must be after start time.'))
|
|
|
|
return cleaned_data
|
|
|
|
|
|
# Alias for backward compatibility with views
|
|
AppointmentForm = AppointmentBookingForm
|
|
RescheduleForm = AppointmentRescheduleForm
|