""" 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')), ] # Appointment type selection APPOINTMENT_TYPE_CHOICES = [ ('single', _('Single Session')), ('package', _('Use Package')), ] appointment_type = forms.ChoiceField( choices=APPOINTMENT_TYPE_CHOICES, widget=forms.RadioSelect(attrs={'class': 'form-check-input'}), label=_('Appointment Type'), initial='single', required=False ) # Package selection (shown when appointment_type = 'package') package_purchase = forms.ModelChoiceField( queryset=None, # Will be set in __init__ required=False, label=_('Select Package'), widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select a package'}), help_text=_('Select an active package with remaining sessions') ) 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): patient = kwargs.pop('patient', None) tenant = kwargs.pop('tenant', None) super().__init__(*args, **kwargs) # Package selection will be handled via modal # Set empty queryset initially - will be populated dynamically from finance.models import PackagePurchase self.fields['package_purchase'].queryset = PackagePurchase.objects.none() # Store patient and tenant for later use self.patient = patient self.tenant = tenant self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( Fieldset( _('Appointment Type'), 'appointment_type', ), Fieldset( _('Package Selection'), 'package_purchase', css_id='packageSection', css_class='d-none' ), 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 # ============================================================================ # Session Forms (Group Session Support) # ============================================================================ class GroupSessionCreateForm(forms.ModelForm): """ Form for creating a new group session. """ SERVICE_TYPE_CHOICES = [ ('', _('Select a service type')), ('group_therapy', _('Group Therapy')), ('group_assessment', _('Group Assessment')), ('parent_training', _('Parent Training Group')), ('social_skills', _('Social Skills Group')), ('behavior_management', _('Behavior Management Group')), ('other', _('Other')), ] service_type = forms.ChoiceField( choices=SERVICE_TYPE_CHOICES, widget=forms.Select(attrs={'class': 'form-control'}), label=_('Service Type') ) class Meta: from .models import Session model = Session fields = [ 'provider', 'clinic', 'room', 'service_type', 'scheduled_date', 'scheduled_time', 'duration', 'max_capacity', 'group_notes' ] widgets = { 'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}), 'clinic': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select clinic'}), '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', 'value': '60'}), 'max_capacity': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '20', 'value': '8'}), 'group_notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': 'Shared notes for the entire group session'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( Fieldset( _('Group Session Details'), Row( Column('provider', css_class='form-group col-md-6 mb-0'), Column('clinic', 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' ), Row( Column('max_capacity', css_class='form-group col-md-12 mb-0'), css_class='form-row' ), 'group_notes', ), Submit('submit', _('Create Group Session'), css_class='btn btn-primary') ) def clean_max_capacity(self): """Validate capacity is between 1 and 20.""" capacity = self.cleaned_data.get('max_capacity') if capacity and (capacity < 1 or capacity > 20): raise forms.ValidationError(_('Capacity must be between 1 and 20 patients')) return capacity class AddPatientToSessionForm(forms.Form): """ Form for adding a patient to an existing session. """ patient = forms.ModelChoiceField( queryset=None, # Will be set in __init__ label=_('Patient'), widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}) ) individual_notes = forms.CharField( required=False, label=_('Individual Notes'), widget=forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': 'Notes specific to this patient'}) ) def __init__(self, *args, **kwargs): tenant = kwargs.pop('tenant', None) session = kwargs.pop('session', None) super().__init__(*args, **kwargs) if tenant: from core.models import Patient # Get patients not already in this session queryset = Patient.objects.filter(tenant=tenant) if session: # Exclude patients already enrolled enrolled_patient_ids = session.participants.filter( status__in=['BOOKED', 'CONFIRMED', 'ARRIVED', 'ATTENDED'] ).values_list('patient_id', flat=True) queryset = queryset.exclude(id__in=enrolled_patient_ids) self.fields['patient'].queryset = queryset self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( 'patient', 'individual_notes', Submit('submit', _('Add Patient to Session'), css_class='btn btn-success') ) class SessionParticipantStatusForm(forms.Form): """ Form for updating participant status. """ STATUS_CHOICES = [ ('CONFIRMED', _('Confirm')), ('ARRIVED', _('Mark as Arrived')), ('ATTENDED', _('Mark as Attended')), ('NO_SHOW', _('Mark as No-Show')), ('CANCELLED', _('Cancel')), ] status = forms.ChoiceField( choices=STATUS_CHOICES, label=_('Action'), widget=forms.Select(attrs={'class': 'form-control'}) ) 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( 'status', 'notes', Submit('submit', _('Update Status'), css_class='btn btn-primary') ) class SessionSearchForm(forms.Form): """ Form for searching sessions. """ search_query = forms.CharField( required=False, label=_('Search'), widget=forms.TextInput(attrs={ 'placeholder': _('Session number, provider name...'), 'class': 'form-control' }) ) session_type = forms.ChoiceField( required=False, label=_('Session Type'), choices=[('', _('All'))] + [('INDIVIDUAL', _('Individual')), ('GROUP', _('Group'))], widget=forms.Select(attrs={'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')), ('SCHEDULED', _('Scheduled')), ('IN_PROGRESS', _('In Progress')), ('COMPLETED', _('Completed')), ('CANCELLED', _('Cancelled'))], 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('session_type', css_class='form-group col-md-2 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-1 mb-0'), Column('date_from', css_class='form-group col-md-1 mb-0'), Column('date_to', css_class='form-group col-md-1 mb-0'), css_class='form-row' ), Submit('search', _('Search'), css_class='btn btn-primary') ) class GroupSessionNotesForm(forms.ModelForm): """ Form for editing group session notes. """ class Meta: from .models import Session model = Session fields = ['group_notes'] widgets = { 'group_notes': forms.Textarea(attrs={'rows': 5, 'class': 'form-control'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( 'group_notes', Submit('submit', _('Save Notes'), css_class='btn btn-primary') )