agdar/appointments/forms.py
2025-11-02 14:35:35 +03:00

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