""" Appointments app views for hospital management system with comprehensive CRUD operations. """ from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.views.generic import ( TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView ) from django.http import JsonResponse from django.contrib import messages from django.db.models.functions import Now from django.db.models import Q, Count, Avg, Case, When, Value, DurationField, FloatField, F, ExpressionWrapper, IntegerField from django.utils import timezone from django.urls import reverse_lazy, reverse from django.core.paginator import Paginator from datetime import timedelta, datetime, time, date from .models import ( AppointmentRequest, SlotAvailability, WaitingQueue, QueueEntry, TelemedicineSession, AppointmentTemplate ) from .forms import ( AppointmentRequestForm, SlotAvailabilityForm, WaitingQueueForm, QueueEntryForm, TelemedicineSessionForm, AppointmentTemplateForm, AppointmentSearchForm, QueueSearchForm, SlotSearchForm ) from patients.models import PatientProfile from accounts.models import User from core.utils import AuditLogger # ============================================================================ # DASHBOARD VIEW # ============================================================================ class AppointmentDashboardView(LoginRequiredMixin, TemplateView): """ Appointment dashboard view. """ template_name = 'appointments/dashboard.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tenant = getattr(self.request, 'tenant', None) if tenant: today = timezone.now().date() now = timezone.now() # Today's appointments context['todays_appointments'] = AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today ).order_by('scheduled_datetime')[:10] # Active queues context['active_queues'] = WaitingQueue.objects.filter( tenant=tenant, is_active=True ).order_by('name') # Statistics context['stats'] = { 'total_appointments_today': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today ).count(), 'confirmed_appointments': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today, status='CONFIRMED' ).count(), 'active_queues_count': WaitingQueue.objects.filter( tenant=tenant, is_active=True ).count(), 'telemedicine_sessions': TelemedicineSession.objects.filter( appointment__tenant=tenant, appointment__scheduled_datetime__date=today ).count(), } return context # ============================================================================ # APPOINTMENT REQUEST VIEWS (RESTRICTED CRUD - Clinical Data) # ============================================================================ class AppointmentRequestListView(LoginRequiredMixin, ListView): """ List appointment requests. """ model = AppointmentRequest template_name = 'appointments/appointment_request_list.html' context_object_name = 'appointments' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentRequest.objects.none() queryset = AppointmentRequest.objects.filter(tenant=tenant) # Apply filters appointment_type = self.request.GET.get('appointment_type') if appointment_type: queryset = queryset.filter(appointment_type=appointment_type) status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) priority = self.request.GET.get('priority') if priority: queryset = queryset.filter(priority=priority) provider_id = self.request.GET.get('provider') if provider_id: queryset = queryset.filter(provider_id=provider_id) department = self.request.GET.get('department') if department: queryset = queryset.filter(department__icontains=department) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(scheduled_datetime__date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(scheduled_datetime__date__lte=date_to) is_telemedicine = self.request.GET.get('is_telemedicine') if is_telemedicine == 'true': queryset = queryset.filter(is_telemedicine=True) elif is_telemedicine == 'false': queryset = queryset.filter(is_telemedicine=False) search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(patient__first_name__icontains=search) | Q(patient__last_name__icontains=search) | Q(patient__patient_id__icontains=search) | Q(provider__first_name__icontains=search) | Q(provider__last_name__icontains=search) | Q(reason__icontains=search) | Q(department__icontains=search) ) return queryset.order_by('-scheduled_datetime') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search_form'] = AppointmentSearchForm( self.request.GET, user=self.request.user ) return context class AppointmentRequestDetailView(LoginRequiredMixin, DetailView): """ Display appointment request details. """ model = AppointmentRequest template_name = 'appointments/appointment_request_detail.html' context_object_name = 'appointment' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentRequest.objects.none() return AppointmentRequest.objects.filter(tenant=tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) appointment = self.get_object() # Get related data context['queue_entry'] = QueueEntry.objects.filter( appointment=appointment ).first() context['telemedicine_session'] = TelemedicineSession.objects.filter( appointment=appointment ).first() return context class AppointmentRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new appointment request. """ model = AppointmentRequest form_class = AppointmentRequestForm template_name = 'appointments/appointment_request_form.html' permission_required = 'appointments.add_appointmentrequest' success_url = reverse_lazy('appointments:appointment_request_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): # Set tenant form.instance.tenant = getattr(self.request, 'tenant', None) response = super().form_valid(form) # Log appointment creation AuditLogger.log_event( tenant=form.instance.tenant, event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Appointment', description=f'Created appointment: {self.object.patient} with {self.object.provider}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Appointment for {self.object.patient} created successfully.') return response class AppointmentRequestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update appointment request (limited fields after scheduling). """ model = AppointmentRequest form_class = AppointmentRequestForm template_name = 'appointments/appointment_request_form.html' permission_required = 'appointments.change_appointmentrequest' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentRequest.objects.none() return AppointmentRequest.objects.filter(tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:appointment_request_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log appointment update AuditLogger.log_event( tenant=self.object.tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Appointment', description=f'Updated appointment: {self.object.patient} with {self.object.provider}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Appointment for {self.object.patient} updated successfully.') return response class AppointmentRequestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """ Cancel appointment request. """ model = AppointmentRequest template_name = 'appointments/appointment_request_confirm_delete.html' permission_required = 'appointments.delete_appointmentrequest' success_url = reverse_lazy('appointments:appointment_request_list') def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentRequest.objects.none() return AppointmentRequest.objects.filter(tenant=tenant) def delete(self, request, *args, **kwargs): self.object = self.get_object() # Update status to cancelled instead of hard delete self.object.status = 'CANCELLED' self.object.save() # Log appointment cancellation AuditLogger.log_event( tenant=self.object.tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Cancel Appointment', description=f'Cancelled appointment: {self.object.patient} with {self.object.provider}', user=request.user, content_object=self.object, request=request ) messages.success(request, f'Appointment for {self.object.patient} cancelled successfully.') return redirect(self.success_url) # ============================================================================ # SLOT AVAILABILITY VIEWS (LIMITED CRUD - Operational Data) # ============================================================================ class SlotAvailabilityListView(LoginRequiredMixin, ListView): """ List slot availability. """ model = SlotAvailability template_name = 'appointments/slot_availability_list.html' context_object_name = 'slots' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return SlotAvailability.objects.none() queryset = SlotAvailability.objects.filter(provider__tenant=tenant) # Apply filters provider_id = self.request.GET.get('provider') if provider_id: queryset = queryset.filter(provider_id=provider_id) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(date__lte=date_to) availability = self.request.GET.get('availability') if availability == 'available': queryset = queryset.filter(is_available=True) elif availability == 'unavailable': queryset = queryset.filter(is_available=False) return queryset.order_by('date', 'start_time') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search_form'] = SlotSearchForm( self.request.GET, user=self.request.user ) return context class SlotAvailabilityDetailView(LoginRequiredMixin, DetailView): """ Display slot availability details. """ model = SlotAvailability template_name = 'appointments/slot_availability_detail.html' context_object_name = 'slot' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return SlotAvailability.objects.none() return SlotAvailability.objects.filter(provider__tenant=tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) slot = self.get_object() # Get appointments for this slot context['appointments'] = AppointmentRequest.objects.filter( provider=slot.provider, scheduled_datetime__date=slot.date, scheduled_datetime__time__gte=slot.start_time, scheduled_datetime__time__lt=slot.end_time ).order_by('scheduled_datetime') return context class SlotAvailabilityCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new slot availability. """ model = SlotAvailability form_class = SlotAvailabilityForm template_name = 'appointments/slot_availability_form.html' permission_required = 'appointments.add_slotavailability' success_url = reverse_lazy('appointments:slot_availability_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): response = super().form_valid(form) # Log slot creation AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Slot Availability', description=f'Created slot: {self.object.provider} on {self.object.date}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Slot availability for {self.object.provider} created successfully.') return response class SlotAvailabilityUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update slot availability. """ model = SlotAvailability form_class = SlotAvailabilityForm template_name = 'appointments/slot_availability_form.html' permission_required = 'appointments.change_slotavailability' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return SlotAvailability.objects.none() return SlotAvailability.objects.filter(provider__tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:slot_availability_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log slot update AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Slot Availability', description=f'Updated slot: {self.object.provider} on {self.object.date}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Slot availability for {self.object.provider} updated successfully.') return response class SlotAvailabilityDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """ Delete slot availability. """ model = SlotAvailability template_name = 'appointments/slot_availability_confirm_delete.html' permission_required = 'appointments.delete_slotavailability' success_url = reverse_lazy('appointments:slot_availability_list') def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return SlotAvailability.objects.none() return SlotAvailability.objects.filter(provider__tenant=tenant) def delete(self, request, *args, **kwargs): self.object = self.get_object() # Check if slot has appointments appointments_count = AppointmentRequest.objects.filter( provider=self.object.provider, scheduled_datetime__date=self.object.date, scheduled_datetime__time__gte=self.object.start_time, scheduled_datetime__time__lt=self.object.end_time, status__in=['SCHEDULED', 'CONFIRMED'] ).count() if appointments_count > 0: messages.error(request, f'Cannot delete slot with {appointments_count} scheduled appointments.') return redirect('appointments:slot_availability_detail', pk=self.object.pk) provider_name = str(self.object.provider) slot_date = self.object.date # Log slot deletion AuditLogger.log_event( tenant=getattr(request, 'tenant', None), event_type='DELETE', event_category='APPOINTMENT_MANAGEMENT', action='Delete Slot Availability', description=f'Deleted slot: {provider_name} on {slot_date}', user=request.user, content_object=self.object, request=request ) messages.success(request, f'Slot availability for {provider_name} on {slot_date} deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # WAITING QUEUE VIEWS (FULL CRUD - Operational Data) # ============================================================================ class WaitingQueueListView(LoginRequiredMixin, ListView): """ List waiting queues. """ model = WaitingQueue template_name = 'appointments/waiting_queue_list.html' context_object_name = 'queues' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return WaitingQueue.objects.none() queryset = WaitingQueue.objects.filter(tenant=tenant) # Apply filters queue_type = self.request.GET.get('queue_type') if queue_type: queryset = queryset.filter(queue_type=queue_type) status = self.request.GET.get('status') if status == 'active': queryset = queryset.filter(is_active=True) elif status == 'inactive': queryset = queryset.filter(is_active=False) department = self.request.GET.get('department') if department: queryset = queryset.filter(department__icontains=department) provider_id = self.request.GET.get('provider') if provider_id: queryset = queryset.filter(provider_id=provider_id) search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(description__icontains=search) | Q(department__icontains=search) ) return queryset.order_by('name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search_form'] = QueueSearchForm( self.request.GET, user=self.request.user ) return context class WaitingQueueDetailView(LoginRequiredMixin, DetailView): """ Display waiting queue details. """ model = WaitingQueue template_name = 'appointments/waiting_queue_detail.html' context_object_name = 'queue' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return WaitingQueue.objects.none() return WaitingQueue.objects.filter(tenant=tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) queue = self.get_object() # Get queue entries context['queue_entries'] = QueueEntry.objects.filter( queue=queue ).order_by('position', 'created_at') # Calculate statistics context['stats'] = { 'total_entries': QueueEntry.objects.filter(queue=queue).count(), 'waiting_entries': QueueEntry.objects.filter(queue=queue, status='WAITING').count(), 'in_progress_entries': QueueEntry.objects.filter(queue=queue, status='IN_PROGRESS').count(), 'average_wait_time': QueueEntry.objects.filter( queue=queue, status='COMPLETED' ).aggregate(avg_wait=Avg('actual_wait_time_minutes'))['avg_wait'] or 0, } return context class WaitingQueueCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new waiting queue. """ model = WaitingQueue form_class = WaitingQueueForm template_name = 'appointments/waiting_queue_form.html' permission_required = 'appointments.add_waitingqueue' success_url = reverse_lazy('appointments:waiting_queue_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): # Set tenant form.instance.tenant = getattr(self.request, 'tenant', None) response = super().form_valid(form) # Log queue creation AuditLogger.log_event( tenant=form.instance.tenant, event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Waiting Queue', description=f'Created queue: {self.object.name}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Waiting queue "{self.object.name}" created successfully.') return response class WaitingQueueUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update waiting queue. """ model = WaitingQueue form_class = WaitingQueueForm template_name = 'appointments/waiting_queue_form.html' permission_required = 'appointments.change_waitingqueue' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return WaitingQueue.objects.none() return WaitingQueue.objects.filter(tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:waiting_queue_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log queue update AuditLogger.log_event( tenant=self.object.tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Waiting Queue', description=f'Updated queue: {self.object.name}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Waiting queue "{self.object.name}" updated successfully.') return response class WaitingQueueDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """ Delete waiting queue. """ model = WaitingQueue template_name = 'appointments/waiting_queue_confirm_delete.html' permission_required = 'appointments.delete_waitingqueue' success_url = reverse_lazy('appointments:waiting_queue_list') def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return WaitingQueue.objects.none() return WaitingQueue.objects.filter(tenant=tenant) def delete(self, request, *args, **kwargs): self.object = self.get_object() # Check if queue has active entries active_entries = QueueEntry.objects.filter( queue=self.object, status__in=['WAITING', 'IN_PROGRESS'] ).count() if active_entries > 0: messages.error(request, f'Cannot delete queue with {active_entries} active entries.') return redirect('appointments:waiting_queue_detail', pk=self.object.pk) queue_name = self.object.name # Log queue deletion AuditLogger.log_event( tenant=getattr(request, 'tenant', None), event_type='DELETE', event_category='APPOINTMENT_MANAGEMENT', action='Delete Waiting Queue', description=f'Deleted queue: {queue_name}', user=request.user, content_object=self.object, request=request ) messages.success(request, f'Waiting queue "{queue_name}" deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # QUEUE ENTRY VIEWS (LIMITED CRUD - Operational Data) # ============================================================================ class QueueEntryListView(LoginRequiredMixin, ListView): """ List queue entries. """ model = QueueEntry template_name = 'appointments/queue_entry_list.html' context_object_name = 'queue_entries' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return QueueEntry.objects.none() queryset = QueueEntry.objects.filter(queue__tenant=tenant) # Apply filters queue_id = self.request.GET.get('queue_id') if queue_id: queryset = queryset.filter(queue_id=queue_id) status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) priority = self.request.GET.get('priority') if priority: queryset = queryset.filter(priority=priority) return queryset.order_by('queue__name', 'position', 'created_at') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tenant = getattr(self.request, 'tenant', None) if tenant: context['queues'] = WaitingQueue.objects.filter( tenant=tenant, is_active=True ).order_by('name') return context class QueueEntryDetailView(LoginRequiredMixin, DetailView): """ Display queue entry details. """ model = QueueEntry template_name = 'appointments/queue_entry_detail.html' context_object_name = 'queue_entry' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return QueueEntry.objects.none() return QueueEntry.objects.filter(queue__tenant=tenant) class QueueEntryCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new queue entry. """ model = QueueEntry form_class = QueueEntryForm template_name = 'appointments/queue_entry_form.html' permission_required = 'appointments.add_queueentry' success_url = reverse_lazy('appointments:queue_entry_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): # Set position last_position = QueueEntry.objects.filter( queue=form.instance.queue ).aggregate(max_position=Count('id'))['max_position'] or 0 form.instance.position = last_position + 1 response = super().form_valid(form) # Log queue entry creation AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Queue Entry', description=f'Added {self.object.patient} to queue: {self.object.queue.name}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Patient {self.object.patient} added to queue successfully.') return response class QueueEntryUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update queue entry. """ model = QueueEntry form_class = QueueEntryForm template_name = 'appointments/queue_entry_form.html' permission_required = 'appointments.change_queueentry' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return QueueEntry.objects.none() return QueueEntry.objects.filter(queue__tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:queue_entry_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log queue entry update AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Queue Entry', description=f'Updated queue entry for {self.object.patient}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Queue entry for {self.object.patient} updated successfully.') return response # ============================================================================ # TELEMEDICINE SESSION VIEWS (RESTRICTED CRUD - Clinical Data) # ============================================================================ class TelemedicineSessionListView(LoginRequiredMixin, ListView): """ List telemedicine sessions. """ model = TelemedicineSession template_name = 'appointments/telemedicine_session_list.html' context_object_name = 'sessions' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return TelemedicineSession.objects.none() queryset = TelemedicineSession.objects.filter(appointment__tenant=tenant) # Apply filters status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) platform = self.request.GET.get('platform') if platform: queryset = queryset.filter(platform=platform) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(start_time__date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(start_time__date__lte=date_to) return queryset.order_by('-start_time') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tenant = getattr(self.request, 'tenant', None) if tenant: context.update({ 'status_choices': TelemedicineSession.STATUS_CHOICES, 'platform_choices': TelemedicineSession.PLATFORM_CHOICES, }) return context class TelemedicineSessionDetailView(LoginRequiredMixin, DetailView): """ Display telemedicine session details. """ model = TelemedicineSession template_name = 'appointments/telemedicine_session_detail.html' context_object_name = 'session' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return TelemedicineSession.objects.none() return TelemedicineSession.objects.filter(appointment__tenant=tenant) class TelemedicineSessionCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new telemedicine session. """ model = TelemedicineSession form_class = TelemedicineSessionForm template_name = 'appointments/telemedicine_session_form.html' permission_required = 'appointments.add_telemedicinesession' success_url = reverse_lazy('appointments:telemedicine_session_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): response = super().form_valid(form) # Log session creation AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Telemedicine Session', description=f'Created telemedicine session for appointment: {self.object.appointment}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Telemedicine session created successfully.') return response class TelemedicineSessionUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update telemedicine session. """ model = TelemedicineSession form_class = TelemedicineSessionForm template_name = 'appointments/telemedicine_session_form.html' permission_required = 'appointments.change_telemedicinesession' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return TelemedicineSession.objects.none() return TelemedicineSession.objects.filter(appointment__tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:telemedicine_session_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log session update AuditLogger.log_event( tenant=getattr(self.request, 'tenant', None), event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Telemedicine Session', description=f'Updated telemedicine session: {self.object.appointment}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Telemedicine session updated successfully.') return response # ============================================================================ # APPOINTMENT TEMPLATE VIEWS (FULL CRUD - Master Data) # ============================================================================ class AppointmentTemplateListView(LoginRequiredMixin, ListView): """ List appointment templates. """ model = AppointmentTemplate template_name = 'appointments/appointment_template_list.html' context_object_name = 'templates' paginate_by = 25 def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentTemplate.objects.none() queryset = AppointmentTemplate.objects.filter(tenant=tenant) # Apply filters appointment_type = self.request.GET.get('appointment_type') if appointment_type: queryset = queryset.filter(appointment_type=appointment_type) department = self.request.GET.get('department') if department: queryset = queryset.filter(department__icontains=department) provider_id = self.request.GET.get('provider') if provider_id: queryset = queryset.filter(provider_id=provider_id) status = self.request.GET.get('status') if status == 'active': queryset = queryset.filter(is_active=True) elif status == 'inactive': queryset = queryset.filter(is_active=False) search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(description__icontains=search) | Q(department__icontains=search) ) return queryset.order_by('appointment_type', 'name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tenant = getattr(self.request, 'tenant', None) if tenant: context.update({ 'appointment_types': AppointmentTemplate.objects.filter( tenant=tenant ).values_list('appointment_type', flat=True).distinct(), 'providers': User.objects.filter( tenant=tenant, is_active=True, role__in=['DOCTOR', 'NURSE', 'SPECIALIST'] ).order_by('last_name', 'first_name'), }) return context class AppointmentTemplateDetailView(LoginRequiredMixin, DetailView): """ Display appointment template details. """ model = AppointmentTemplate template_name = 'appointments/appointment_template_detail.html' context_object_name = 'template' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentTemplate.objects.none() return AppointmentTemplate.objects.filter(tenant=tenant) class AppointmentTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ Create new appointment template. """ model = AppointmentTemplate form_class = AppointmentTemplateForm template_name = 'appointments/appointment_template_form.html' permission_required = 'appointments.add_appointmenttemplate' success_url = reverse_lazy('appointments:appointment_template_list') def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def form_valid(self, form): # Set tenant form.instance.tenant = getattr(self.request, 'tenant', None) response = super().form_valid(form) # Log template creation AuditLogger.log_event( tenant=form.instance.tenant, event_type='CREATE', event_category='APPOINTMENT_MANAGEMENT', action='Create Appointment Template', description=f'Created template: {self.object.name}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Appointment template "{self.object.name}" created successfully.') return response class AppointmentTemplateUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """ Update appointment template. """ model = AppointmentTemplate form_class = AppointmentTemplateForm template_name = 'appointments/appointment_template_form.html' permission_required = 'appointments.change_appointmenttemplate' def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentTemplate.objects.none() return AppointmentTemplate.objects.filter(tenant=tenant) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_success_url(self): return reverse('appointments:appointment_template_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): response = super().form_valid(form) # Log template update AuditLogger.log_event( tenant=self.object.tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Update Appointment Template', description=f'Updated template: {self.object.name}', user=self.request.user, content_object=self.object, request=self.request ) messages.success(self.request, f'Appointment template "{self.object.name}" updated successfully.') return response class AppointmentTemplateDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """ Delete appointment template. """ model = AppointmentTemplate template_name = 'appointments/appointment_template_confirm_delete.html' permission_required = 'appointments.delete_appointmenttemplate' success_url = reverse_lazy('appointments:appointment_template_list') def get_queryset(self): tenant = getattr(self.request, 'tenant', None) if not tenant: return AppointmentTemplate.objects.none() return AppointmentTemplate.objects.filter(tenant=tenant) def delete(self, request, *args, **kwargs): self.object = self.get_object() template_name = self.object.name # Log template deletion AuditLogger.log_event( tenant=getattr(request, 'tenant', None), event_type='DELETE', event_category='APPOINTMENT_MANAGEMENT', action='Delete Appointment Template', description=f'Deleted template: {template_name}', user=request.user, content_object=self.object, request=request ) messages.success(request, f'Appointment template "{template_name}" deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # HTMX VIEWS FOR REAL-TIME UPDATES # ============================================================================ @login_required def appointment_search(request): """ HTMX view for appointment search. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) search_query = request.GET.get('search', '') queryset = AppointmentRequest.objects.filter(tenant=tenant) if search_query: queryset = queryset.filter( Q(patient__first_name__icontains=search_query) | Q(patient__last_name__icontains=search_query) | Q(provider__first_name__icontains=search_query) | Q(provider__last_name__icontains=search_query) | Q(reason__icontains=search_query) ) appointments = queryset.order_by('-scheduled_datetime')[:20] return render(request, 'appointments/partials/appointment_list.html', { 'appointments': appointments }) @login_required def appointment_stats(request): """ HTMX view for appointment statistics. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) today = timezone.now().date() # Calculate appointment statistics stats = { 'total_appointments_today': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today ).count(), 'confirmed_appointments': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today, status='CONFIRMED' ).count(), 'completed_appointments': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today, status='COMPLETED' ).count(), 'cancelled_appointments': AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=today, status='CANCELLED' ).count(), 'telemedicine_sessions': TelemedicineSession.objects.filter( appointment__tenant=tenant, appointment__scheduled_datetime__date=today ).count(), 'active_queues': WaitingQueue.objects.filter( tenant=tenant, is_active=True ).count(), 'total_queue_entries': QueueEntry.objects.filter( queue__tenant=tenant, status='WAITING' ).count(), } return render(request, 'appointments/partials/appointment_stats.html', {'stats': stats}) @login_required def available_slots(request): """ HTMX view for available slots. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) provider_id = request.GET.get('provider_id') date_str = request.GET.get('date') if not provider_id or not date_str: return render(request, 'appointments/partials/available_slots.html', {'slots': []}) try: selected_date = datetime.strptime(date_str, '%Y-%m-%d').date() except ValueError: return render(request, 'appointments/partials/available_slots.html', {'slots': []}) # Get available slots for the provider on the selected date slots = SlotAvailability.objects.filter( provider_id=provider_id, provider__tenant=tenant, date=selected_date, is_available=True ).order_by('start_time') return render(request, 'appointments/partials/available_slots.html', { 'slots': slots, 'selected_date': selected_date }) @login_required def queue_status(request, queue_id): """ HTMX view for queue status. Shows queue entries plus aggregated stats with DB-side wait calculations. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) queue = get_object_or_404(WaitingQueue, id=queue_id, tenant=tenant) queue_entries = ( QueueEntry.objects .filter(queue=queue) .annotate( wait_duration=Case( When(status='WAITING', then=Now() - F('joined_at')), When(served_at__isnull=False, then=F('served_at') - F('joined_at')), default=Value(None), output_field=DurationField(), ), ) .annotate( wait_minutes=Case( When(wait_duration__isnull=False, then=ExpressionWrapper( F('wait_duration') / Value(timedelta(minutes=1)), output_field=FloatField() )), default=Value(None), output_field=FloatField(), ), waiting_rank=Case( When(status='WAITING', then=F('queue_position')), default=Value(None), output_field=IntegerField() ), ) .select_related('assigned_provider', 'patient', 'appointment') # adjust if you need more .order_by('queue_position', 'updated_at') ) # Aggregates & stats total_entries = queue_entries.count() waiting_entries = queue_entries.filter(status='WAITING').count() called_entries = queue_entries.filter(status='CALLED').count() in_service_entries = queue_entries.filter(status='IN_SERVICE').count() completed_entries = queue_entries.filter(status='COMPLETED').count() avg_completed_wait = ( queue_entries .filter(status='COMPLETED') .aggregate(avg_wait=Avg('wait_minutes')) .get('avg_wait') or 0 ) stats = { 'total_entries': total_entries, 'waiting_entries': waiting_entries, 'called_entries': called_entries, 'in_service_entries': in_service_entries, 'completed_entries': completed_entries, # Average from COMPLETED cases only (rounded to 1 decimal) 'average_wait_time_minutes': round(avg_completed_wait, 1), # Quick estimate based on queue config 'estimated_queue_wait_minutes': waiting_entries * queue.average_service_time_minutes, } return render(request, 'appointments/partials/queue_status.html', { 'queue': queue, 'queue_entries': queue_entries, # each has .wait_minutes 'stats': stats, }) @login_required def calendar_appointments(request): """ HTMX view for calendar appointments. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) date_str = request.GET.get('date') provider_id = request.GET.get('provider_id') if not date_str: date_str = timezone.now().date().strftime('%Y-%m-%d') try: selected_date = datetime.strptime(date_str, '%Y-%m-%d').date() except ValueError: selected_date = timezone.now().date() # Get appointments for the selected date queryset = AppointmentRequest.objects.filter( tenant=tenant, scheduled_datetime__date=selected_date ) if provider_id: queryset = queryset.filter(provider_id=provider_id) appointments = queryset.order_by('scheduled_datetime') return render(request, 'appointments/partials/calendar_appointments.html', { 'appointments': appointments, 'selected_date': selected_date }) # ============================================================================ # ACTION VIEWS FOR WORKFLOW OPERATIONS # ============================================================================ @login_required def confirm_appointment(request, pk): """ Confirm an appointment. """ tenant = getattr(request, 'tenant', None) if not tenant: messages.error(request, 'No tenant found.') return redirect('appointments:appointment_request_list') appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) appointment.status = 'CONFIRMED' appointment.save() # Log confirmation AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Confirm Appointment', description=f'Confirmed appointment: {appointment.patient} with {appointment.provider}', user=request.user, content_object=appointment, request=request ) messages.success(request, f'Appointment for {appointment.patient} confirmed successfully.') return redirect('appointments:appointment_request_detail', pk=pk) @login_required def start_appointment(request, pk): """ Start an appointment. """ tenant = getattr(request, 'tenant', None) if not tenant: messages.error(request, 'No tenant found.') return redirect('appointments:appointment_request_list') appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) appointment.status = 'IN_PROGRESS' appointment.actual_start_time = timezone.now() appointment.save() # Log start AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Start Appointment', description=f'Started appointment: {appointment.patient} with {appointment.provider}', user=request.user, content_object=appointment, request=request ) messages.success(request, f'Appointment for {appointment.patient} started successfully.') return redirect('appointments:appointment_request_detail', pk=pk) @login_required def complete_appointment(request, pk): """ Complete an appointment. """ tenant = getattr(request, 'tenant', None) if not tenant: messages.error(request, 'No tenant found.') return redirect('appointments:appointment_request_list') appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) appointment.status = 'COMPLETED' appointment.actual_end_time = timezone.now() appointment.save() # Log completion AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Complete Appointment', description=f'Completed appointment: {appointment.patient} with {appointment.provider}', user=request.user, content_object=appointment, request=request ) messages.success(request, f'Appointment for {appointment.patient} completed successfully.') return redirect('appointments:appointment_request_detail', pk=pk) @login_required def next_in_queue(request, queue_id): """ Call next patient in queue. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) queue = get_object_or_404(WaitingQueue, id=queue_id, tenant=tenant) # Get next waiting entry next_entry = QueueEntry.objects.filter( queue=queue, status='WAITING' ).order_by('position', 'created_at').first() if next_entry: next_entry.status = 'IN_PROGRESS' next_entry.called_at = timezone.now() next_entry.save() # Log queue progression AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Call Next in Queue', description=f'Called next patient: {next_entry.patient} in queue: {queue.name}', user=request.user, content_object=next_entry, request=request ) return JsonResponse({ 'status': 'success', 'patient': str(next_entry.patient), 'position': next_entry.queue_position }) else: return JsonResponse({'status': 'no_patients'}) @login_required def complete_queue_entry(request, pk): """ Complete a queue entry. """ tenant = getattr(request, 'tenant', None) if not tenant: return JsonResponse({'error': 'No tenant found'}, status=400) queue_entry = get_object_or_404( QueueEntry, pk=pk, queue__tenant=tenant ) queue_entry.status = 'COMPLETED' queue_entry.completed_at = timezone.now() # Calculate actual wait time if queue_entry.called_at: wait_time = (queue_entry.called_at - queue_entry.joined_at).total_seconds() / 60 queue_entry.actual_wait_time_minutes = int(wait_time) queue_entry.save() # Log completion AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Complete Queue Entry', description=f'Completed queue entry for: {queue_entry.patient}', user=request.user, content_object=queue_entry, request=request ) return JsonResponse({'status': 'completed'}) @login_required def start_telemedicine_session(request, pk): """ Start a telemedicine session. """ tenant = getattr(request, 'tenant', None) if not tenant: messages.error(request, 'No tenant found.') return redirect('appointments:telemedicine_session_list') session = get_object_or_404( TelemedicineSession, pk=pk, appointment__tenant=tenant ) session.status = 'IN_PROGRESS' session.start_time = timezone.now() session.save() # Update appointment status session.appointment.status = 'IN_PROGRESS' session.appointment.save() # Log session start AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='Start Telemedicine Session', description=f'Started telemedicine session for: {session.appointment}', user=request.user, content_object=session, request=request ) messages.success(request, f'Telemedicine session started successfully.') return redirect('appointments:telemedicine_session_detail', pk=pk) @login_required def end_telemedicine_session(request, pk): """ End a telemedicine session. """ tenant = getattr(request, 'tenant', None) if not tenant: messages.error(request, 'No tenant found.') return redirect('appointments:telemedicine_session_list') session = get_object_or_404( TelemedicineSession, pk=pk, appointment__tenant=tenant ) session.status = 'COMPLETED' session.end_time = timezone.now() session.save() # Update appointment status session.appointment.status = 'COMPLETED' session.appointment.save() # Log session end AuditLogger.log_event( tenant=tenant, event_type='UPDATE', event_category='APPOINTMENT_MANAGEMENT', action='End Telemedicine Session', description=f'Ended telemedicine session for: {session.appointment}', user=request.user, content_object=session, request=request ) messages.success(request, f'Telemedicine session ended successfully.') return redirect('appointments:telemedicine_session_detail', pk=pk) # Missing Views - Placeholder implementations class AppointmentListView(LoginRequiredMixin, ListView): """ List view for appointments. """ model = AppointmentRequest template_name = 'appointments/appointment_list.html' context_object_name = 'appointments' paginate_by = 20 def get_queryset(self): return AppointmentRequest.objects.filter( tenant=self.request.user.tenant ).order_by('-created_at') class AppointmentDetailView(LoginRequiredMixin, DetailView): """ Detail view for appointments. """ model = AppointmentRequest template_name = 'appointments/appointment_detail.html' context_object_name = 'appointment' def get_queryset(self): return AppointmentRequest.objects.filter( tenant=self.request.user.tenant ) class SchedulingCalendarView(LoginRequiredMixin, TemplateView): """ Calendar view for scheduling appointments. """ template_name = 'appointments/scheduling_calendar.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['appointments'] = AppointmentRequest.objects.filter( tenant=self.request.user.tenant, status='SCHEDULED' ) return context class QueueManagementView(LoginRequiredMixin, ListView): """ Queue management view for appointments. """ model = QueueEntry template_name = 'appointments/queue_management.html' context_object_name = 'queues' def get_queryset(self): return QueueEntry.objects.filter( appointment__tenant=self.request.user.tenant ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['queue'] = AppointmentRequest.objects.filter( tenant=self.request.user.tenant, status__in=['CONFIRMED', 'CHECKED_IN'] ).order_by('scheduled_datetime') return context class TelemedicineView(LoginRequiredMixin, TemplateView): """ Telemedicine appointments view. """ template_name = 'appointments/telemedicine.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['telemedicine_appointments'] = AppointmentRequest.objects.filter( tenant=self.request.user.tenant, appointment_type='TELEMEDICINE' ) return context def check_in_patient(request, appointment_id): """ Check in a patient for their appointment. """ appointment = get_object_or_404(AppointmentRequest, request_id=appointment_id, tenant=request.user.tenant ) appointment.status = 'CHECKED_IN' appointment.save() messages.success(request, f'Patient {appointment.patient} has been checked in.') return redirect('appointments:queue_management') def call_next_patient(request, queue_id): """ Call the next patient in the queue. """ # Mock implementation - in real system, this would manage actual queue messages.success(request, 'Next patient has been called.') return redirect('appointments:queue_management') def reschedule_appointment(request, appointment_id): """ Reschedule an appointment. """ appointment = get_object_or_404(AppointmentRequest, request_id=appointment_id, tenant=request.user.tenant ) if request.method == 'POST': new_date = request.POST.get('new_date') new_time = request.POST.get('new_time') if new_date and new_time: appointment.scheduled_date = new_date appointment.scheduled_time = new_time appointment.status = 'RESCHEDULED' appointment.save() messages.success(request, f'Appointment has been rescheduled to {new_date} at {new_time}.') return redirect('appointments:appointment_detail', pk=appointment_id) return render(request, 'appointments/reschedule_appointment.html', { 'appointment': appointment }) # # from django.shortcuts import render, get_object_or_404, redirect # from django.views.generic import ( # ListView, DetailView, CreateView, UpdateView, DeleteView # ) # from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin # from django.contrib.auth.decorators import login_required # from django.http import JsonResponse, HttpResponseForbidden # from django.urls import reverse, reverse_lazy # from django.utils import timezone # from django.utils.translation import gettext_lazy as _ # from django.contrib import messages # from django.db.models import Q, Count, Avg # from django.template.loader import render_to_string # from datetime import datetime, timedelta # # from .models import ( # AppointmentRequest, SlotAvailability, WaitingQueue, # QueueEntry, TelemedicineSession, AppointmentTemplate # ) # from .forms import ( # AppointmentRequestForm, SlotAvailabilityForm, WaitingQueueForm, # QueueEntryForm, TelemedicineSessionForm, AppointmentTemplateForm, # AppointmentSearchForm # ) # from patients.models import PatientProfile # from accounts.models import User # from core.utils import get_tenant_from_request # # # class TenantRequiredMixin: # """Mixin to require a tenant for views.""" # # def dispatch(self, request, *args, **kwargs): # """Check for tenant before dispatching.""" # self.tenant = get_tenant_from_request(request) # if not self.tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # return super().dispatch(request, *args, **kwargs) # # # # --- AppointmentRequest Views --- # # class AppointmentRequestListView(LoginRequiredMixin, TenantRequiredMixin, ListView): # """List view for appointment requests.""" # model = AppointmentRequest # template_name = 'appointments/appointment_list.html' # context_object_name = 'appointments' # paginate_by = 20 # # def get_queryset(self): # """Get filtered queryset based on search parameters.""" # queryset = AppointmentRequest.objects.filter(tenant=self.tenant) # # # Initialize the search form # self.form = AppointmentSearchForm(self.request.GET, tenant=self.tenant) # # # Apply filters if the form is valid # if self.form.is_valid(): # data = self.form.cleaned_data # # # Text search # if data.get('search'): # search_term = data['search'] # queryset = queryset.filter( # Q(patient__first_name__icontains=search_term) | # Q(patient__last_name__icontains=search_term) | # Q(provider__first_name__icontains=search_term) | # Q(provider__last_name__icontains=search_term) | # Q(reason__icontains=search_term) # ) # # # Status filter # if data.get('status'): # queryset = queryset.filter(status=data['status']) # # # Appointment type filter # if data.get('appointment_type'): # queryset = queryset.filter(appointment_type=data['appointment_type']) # # # Provider filter # if data.get('provider'): # queryset = queryset.filter(provider=data['provider']) # # # Department filter # if data.get('department'): # queryset = queryset.filter(department=data['department']) # # # Date range filters # if data.get('date_from'): # queryset = queryset.filter(scheduled_datetime__date__gte=data['date_from']) # # if data.get('date_to'): # queryset = queryset.filter(scheduled_datetime__date__lte=data['date_to']) # # # Default ordering # return queryset.select_related('patient', 'provider', 'department').order_by( # 'scheduled_datetime' # ) # # def get_context_data(self, **kwargs): # """Add search form and filters to context.""" # context = super().get_context_data(**kwargs) # context['form'] = self.form # # # Add filter summaries # context['active_filters'] = {} # if self.form.is_valid(): # data = self.form.cleaned_data # for field, value in data.items(): # if value: # if field == 'provider' and value: # context['active_filters'][field] = value.get_full_name() # elif field == 'department' and value: # context['active_filters'][field] = value.name # elif field in ['status', 'appointment_type'] and value: # # Get display value for choice fields # choices_dict = dict(getattr(AppointmentRequest, f"{field.upper()}_CHOICES", [])) # context['active_filters'][field] = choices_dict.get(value, value) # else: # context['active_filters'][field] = value # # # Add appointment counts by status # context['appointment_counts'] = { # 'total': AppointmentRequest.objects.filter(tenant=self.tenant).count(), # 'upcoming': AppointmentRequest.objects.filter( # tenant=self.tenant, # scheduled_datetime__gte=timezone.now(), # status__in=['SCHEDULED', 'CONFIRMED'] # ).count(), # 'today': AppointmentRequest.objects.filter( # tenant=self.tenant, # scheduled_datetime__date=timezone.now().date() # ).count(), # 'pending': AppointmentRequest.objects.filter( # tenant=self.tenant, # status='REQUESTED' # ).count(), # } # # return context # # # class AppointmentRequestDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView): # """Detail view for an appointment request.""" # model = AppointmentRequest # template_name = 'appointments/appointment_detail.html' # context_object_name = 'appointment' # # def get_queryset(self): # """Filter by tenant.""" # return AppointmentRequest.objects.filter( # tenant=self.tenant # ).select_related( # 'patient', 'provider', 'department', 'telemedicine_session', # 'template', 'requested_by', 'cancelled_by' # ) # # def get_context_data(self, **kwargs): # """Add additional context data.""" # context = super().get_context_data(**kwargs) # # # Add related queue entries # context['queue_entries'] = self.object.queue_entries.all() # # # Check if patient has other appointments # context['other_appointments'] = AppointmentRequest.objects.filter( # tenant=self.tenant, # patient=self.object.patient # ).exclude( # pk=self.object.pk # ).order_by('-scheduled_datetime')[:5] # # return context # # # class AppointmentRequestCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView): # """Create view for a new appointment request.""" # model = AppointmentRequest # form_class = AppointmentRequestForm # template_name = 'appointments/appointment_form.html' # permission_required = 'appointments.add_appointmentrequest' # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def get_initial(self): # """Pre-populate form with initial values.""" # initial = super().get_initial() # # # Pre-populate patient if provided in query params # patient_id = self.request.GET.get('patient_id') # if patient_id: # try: # patient = PatientProfile.objects.get( # tenant=self.tenant, # patient_id=patient_id # ) # initial['patient'] = patient # except PatientProfile.DoesNotExist: # pass # # # Pre-populate provider if provided in query params # provider_id = self.request.GET.get('provider_id') # if provider_id: # try: # provider = User.objects.get( # tenant=self.tenant, # id=provider_id # ) # initial['provider'] = provider # except User.DoesNotExist: # pass # # # Pre-populate date/time if provided # date_str = self.request.GET.get('date') # time_str = self.request.GET.get('time') # if date_str: # try: # if time_str: # # Combine date and time # date_time = datetime.strptime( # f"{date_str} {time_str}", # '%Y-%m-%d %H:%M' # ) # else: # # Just date, use default time (9:00 AM) # date_time = datetime.strptime( # f"{date_str} 09:00", # '%Y-%m-%d %H:%M' # ) # # # Convert to timezone-aware datetime # date_time = timezone.make_aware(date_time) # initial['scheduled_datetime'] = date_time # except ValueError: # pass # # # Set default status # initial['status'] = 'SCHEDULED' # # # Set default appointment type # initial['appointment_type'] = 'IN_PERSON' # # # Set default duration # initial['duration_minutes'] = 30 # # return initial # # def form_valid(self, form): # """Process the valid form.""" # # Set tenant # form.instance.tenant = self.tenant # # # Set requested_by to current user if not set # if not form.instance.requested_by: # form.instance.requested_by = self.request.user # # # Save the form # response = super().form_valid(form) # # # Log the creation # messages.success( # self.request, # _("Appointment for {} with {} has been scheduled for {}.").format( # form.instance.patient.get_full_name(), # form.instance.provider.get_full_name(), # form.instance.scheduled_datetime.strftime('%Y-%m-%d %H:%M') # ) # ) # # return response # # def get_success_url(self): # """Redirect to appointment detail view.""" # return reverse('appointments:appointment_detail', kwargs={'pk': self.object.pk}) # # # class AppointmentRequestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView): # """Update view for an appointment request.""" # model = AppointmentRequest # form_class = AppointmentRequestForm # template_name = 'appointments/appointment_form.html' # permission_required = 'appointments.change_appointmentrequest' # # def get_queryset(self): # """Filter by tenant.""" # return AppointmentRequest.objects.filter(tenant=self.tenant) # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def form_valid(self, form): # """Process the valid form.""" # # Check if status changed # old_status = AppointmentRequest.objects.get(pk=self.object.pk).status # new_status = form.cleaned_data.get('status') # # # Save the form # response = super().form_valid(form) # # # Add appropriate message # if old_status != new_status: # messages.success( # self.request, # _("Appointment status updated from {} to {}.").format( # dict(AppointmentRequest.STATUS_CHOICES).get(old_status, old_status), # dict(AppointmentRequest.STATUS_CHOICES).get(new_status, new_status), # ) # ) # else: # messages.success( # self.request, # _("Appointment updated successfully.") # ) # # return response # # def get_success_url(self): # """Redirect to appointment detail view.""" # return reverse('appointments:appointment_detail', kwargs={'pk': self.object.pk}) # # # class AppointmentRequestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView): # """Delete view for an appointment request.""" # model = AppointmentRequest # template_name = 'appointments/appointment_confirm_delete.html' # permission_required = 'appointments.delete_appointmentrequest' # success_url = reverse_lazy('appointments:appointment_list') # # def get_queryset(self): # """Filter by tenant.""" # return AppointmentRequest.objects.filter(tenant=self.tenant) # # def delete(self, request, *args, **kwargs): # """Override delete to add custom message.""" # self.object = self.get_object() # success_url = self.get_success_url() # # # Store patient name for message # patient_name = self.object.patient.get_full_name() if self.object.patient else "Unknown" # appointment_date = self.object.scheduled_datetime.strftime( # '%Y-%m-%d %H:%M') if self.object.scheduled_datetime else "Unscheduled" # # # Delete the object # self.object.delete() # # # Add success message # messages.success( # request, # _("Appointment for {} on {} has been deleted.").format( # patient_name, appointment_date # ) # ) # # return redirect(success_url) # # # # --- SlotAvailability Views --- # # class SlotAvailabilityListView(LoginRequiredMixin, TenantRequiredMixin, ListView): # """List view for provider availability slots.""" # model = SlotAvailability # template_name = 'appointments/slot_availability_list.html' # context_object_name = 'slots' # paginate_by = 20 # # def get_queryset(self): # """Filter by tenant and provider.""" # queryset = SlotAvailability.objects.filter(tenant=self.tenant) # # # Filter by provider if specified # provider_id = self.request.GET.get('provider_id') # if provider_id: # queryset = queryset.filter(provider_id=provider_id) # # # Filter by date range # date_from = self.request.GET.get('date_from') # if date_from: # try: # date_from = datetime.strptime(date_from, '%Y-%m-%d').date() # queryset = queryset.filter(start_time__date__gte=date_from) # except ValueError: # pass # # date_to = self.request.GET.get('date_to') # if date_to: # try: # date_to = datetime.strptime(date_to, '%Y-%m-%d').date() # queryset = queryset.filter(start_time__date__lte=date_to) # except ValueError: # pass # # # Default ordering # return queryset.select_related('provider').order_by('start_time') # # def get_context_data(self, **kwargs): # """Add filter context.""" # context = super().get_context_data(**kwargs) # # # Add provider filter # provider_id = self.request.GET.get('provider_id') # if provider_id: # try: # context['selected_provider'] = User.objects.get(id=provider_id) # except User.DoesNotExist: # pass # # # Add providers for filter dropdown # context['providers'] = User.objects.filter( # tenant=self.tenant, # role__in=['DOCTOR', 'NURSE', 'PHYSICIAN_ASSISTANT'] # ).order_by('first_name', 'last_name') # # # Add date filters # context['date_from'] = self.request.GET.get('date_from', '') # context['date_to'] = self.request.GET.get('date_to', '') # # return context # # # class SlotAvailabilityDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView): # """Detail view for a provider availability slot.""" # model = SlotAvailability # template_name = 'appointments/slot_availability_detail.html' # context_object_name = 'slot' # # def get_queryset(self): # """Filter by tenant.""" # return SlotAvailability.objects.filter(tenant=self.tenant).select_related('provider') # # def get_context_data(self, **kwargs): # """Add additional context data.""" # context = super().get_context_data(**kwargs) # # # Get appointments during this slot # context['appointments'] = self.object.get_appointments() # # return context # # # class SlotAvailabilityCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView): # """Create view for a new availability slot.""" # model = SlotAvailability # form_class = SlotAvailabilityForm # template_name = 'appointments/slot_availability_form.html' # permission_required = 'appointments.add_slotavailability' # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def get_initial(self): # """Pre-populate form with initial values.""" # initial = super().get_initial() # # # Pre-populate provider if provided in query params # provider_id = self.request.GET.get('provider_id') # if provider_id: # try: # provider = User.objects.get(tenant=self.tenant, id=provider_id) # initial['provider'] = provider # except User.DoesNotExist: # pass # # # Pre-populate date/time if provided # date_str = self.request.GET.get('date') # if date_str: # try: # # Parse date and set default times (9:00 AM - 5:00 PM) # date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() # start_time = timezone.make_aware(datetime.combine(date_obj, datetime.min.time().replace(hour=9))) # end_time = timezone.make_aware(datetime.combine(date_obj, datetime.min.time().replace(hour=17))) # # initial['start_time'] = start_time # initial['end_time'] = end_time # except ValueError: # pass # # # Set default availability # initial['is_available'] = True # # return initial # # def form_valid(self, form): # """Process the valid form.""" # # Set tenant # form.instance.tenant = self.tenant # # # Save the form # response = super().form_valid(form) # # # Add success message # messages.success( # self.request, # _("Availability slot for {} from {} to {} has been created.").format( # form.instance.provider.get_full_name(), # form.instance.start_time.strftime('%Y-%m-%d %H:%M'), # form.instance.end_time.strftime('%H:%M') # ) # ) # # return response # # def get_success_url(self): # """Redirect to slot list view.""" # return reverse('appointments:slot_availability_list') # # # class SlotAvailabilityUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView): # """Update view for an availability slot.""" # model = SlotAvailability # form_class = SlotAvailabilityForm # template_name = 'appointments/slot_availability_form.html' # permission_required = 'appointments.change_slotavailability' # # def get_queryset(self): # """Filter by tenant.""" # return SlotAvailability.objects.filter(tenant=self.tenant) # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def form_valid(self, form): # """Process the valid form.""" # # Save the form # response = super().form_valid(form) # # # Add success message # messages.success( # self.request, # _("Availability slot updated successfully.") # ) # # return response # # def get_success_url(self): # """Redirect to slot detail view.""" # return reverse('appointments:slot_availability_detail', kwargs={'pk': self.object.pk}) # # # class SlotAvailabilityDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView): # """Delete view for an availability slot.""" # model = SlotAvailability # template_name = 'appointments/slot_availability_confirm_delete.html' # permission_required = 'appointments.delete_slotavailability' # success_url = reverse_lazy('appointments:slot_availability_list') # # def get_queryset(self): # """Filter by tenant.""" # return SlotAvailability.objects.filter(tenant=self.tenant) # # def delete(self, request, *args, **kwargs): # """Override delete to add custom message.""" # self.object = self.get_object() # success_url = self.get_success_url() # # # Store information for message # provider_name = self.object.provider.get_full_name() if self.object.provider else "Unknown" # slot_date = self.object.start_time.strftime('%Y-%m-%d') # # # Delete the object # self.object.delete() # # # Add success message # messages.success( # request, # _("Availability slot for {} on {} has been deleted.").format( # provider_name, slot_date # ) # ) # # return redirect(success_url) # # # # --- WaitingQueue Views --- # # class WaitingQueueListView(LoginRequiredMixin, TenantRequiredMixin, ListView): # """List view for waiting queues.""" # model = WaitingQueue # template_name = 'appointments/waiting_queue_list.html' # context_object_name = 'queues' # # def get_queryset(self): # """Filter by tenant.""" # return WaitingQueue.objects.filter( # tenant=self.tenant # ).annotate( # waiting_count=Count('entries', filter=Q(entries__status='WAITING')) # ).select_related('department', 'provider').order_by('name') # # def get_context_data(self, **kwargs): # """Add department filter context.""" # context = super().get_context_data(**kwargs) # # # Add department filter # department_id = self.request.GET.get('department_id') # if department_id: # context['queues'] = context['queues'].filter(department_id=department_id) # try: # context['selected_department'] = department_id # except: # pass # # return context # # # class WaitingQueueDetailView(LoginRequiredMixin, TenantRequiredMixin, DetailView): # """Detail view for a waiting queue.""" # model = WaitingQueue # template_name = 'appointments/waiting_queue_detail.html' # context_object_name = 'queue' # # def get_queryset(self): # """Filter by tenant.""" # return WaitingQueue.objects.filter(tenant=self.tenant).select_related('department', 'provider') # # def get_context_data(self, **kwargs): # """Add entries to context.""" # context = super().get_context_data(**kwargs) # # # Get waiting entries # context['waiting_entries'] = self.object.entries.filter( # status='WAITING' # ).select_related('patient').order_by('priority', 'added_time') # # # Get called/in-progress entries # context['active_entries'] = self.object.entries.filter( # status__in=['CALLED', 'IN_PROGRESS'] # ).select_related('patient').order_by('called_time') # # # Get completed entries from today # today = timezone.now().date() # context['completed_entries'] = self.object.entries.filter( # status='COMPLETED', # completed_time__date=today # ).select_related('patient').order_by('-completed_time') # # # Get statistics # context['stats'] = { # 'avg_wait_time': self.object.entries.filter( # status__in=['COMPLETED', 'CALLED', 'IN_PROGRESS'], # wait_time__isnull=False # ).aggregate(avg=Avg('wait_time'))['avg'] or 0, # # 'today_count': self.object.entries.filter( # added_time__date=today # ).count(), # # 'completed_count': self.object.entries.filter( # status='COMPLETED', # completed_time__date=today # ).count(), # } # # # Add form for adding a new entry # context['entry_form'] = QueueEntryForm( # user=self.request.user, # initial={'queue': self.object, 'priority': 'ROUTINE'} # ) # # return context # # # class WaitingQueueCreateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, CreateView): # """Create view for a new waiting queue.""" # model = WaitingQueue # form_class = WaitingQueueForm # template_name = 'appointments/waiting_queue_form.html' # permission_required = 'appointments.add_waitingqueue' # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def form_valid(self, form): # """Process the valid form.""" # # Set tenant # form.instance.tenant = self.tenant # # # Save the form # response = super().form_valid(form) # # # Add success message # messages.success( # self.request, # _("Waiting queue '{}' has been created.").format( # form.instance.name # ) # ) # # return response # # def get_success_url(self): # """Redirect to queue detail view.""" # return reverse('appointments:waiting_queue_detail', kwargs={'pk': self.object.pk}) # # # class WaitingQueueUpdateView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, UpdateView): # """Update view for a waiting queue.""" # model = WaitingQueue # form_class = WaitingQueueForm # template_name = 'appointments/waiting_queue_form.html' # permission_required = 'appointments.change_waitingqueue' # # def get_queryset(self): # """Filter by tenant.""" # return WaitingQueue.objects.filter(tenant=self.tenant) # # def get_form_kwargs(self): # """Add the current user to form kwargs.""" # kwargs = super().get_form_kwargs() # kwargs['user'] = self.request.user # return kwargs # # def form_valid(self, form): # """Process the valid form.""" # # Save the form # response = super().form_valid(form) # # # Add success message # messages.success( # self.request, # _("Waiting queue '{}' has been updated.").format( # form.instance.name # ) # ) # # return response # # def get_success_url(self): # """Redirect to queue detail view.""" # return reverse('appointments:waiting_queue_detail', kwargs={'pk': self.object.pk}) # # # class WaitingQueueDeleteView(LoginRequiredMixin, PermissionRequiredMixin, TenantRequiredMixin, DeleteView): # """Delete view for a waiting queue.""" # model = WaitingQueue # template_name = 'appointments/waiting_queue_confirm_delete.html' # permission_required = 'appointments.delete_waitingqueue' # success_url = reverse_lazy('appointments:waiting_queue_list') # # def get_queryset(self): # """Filter by tenant.""" # return WaitingQueue.objects.filter(tenant=self.tenant) # # def delete(self, request, *args, **kwargs): # """Override delete to add custom message.""" # self.object = self.get_object() # success_url = self.get_success_url() # # # Store information for message # queue_name = self.object.name # # # Check if there are active entries # active_entries = self.object.entries.filter( # status__in=['WAITING', 'CALLED', 'IN_PROGRESS'] # ).exists() # # if active_entries: # messages.error( # request, # _("Cannot delete queue '{}' because it has active entries.").format(queue_name) # ) # return redirect('appointments:waiting_queue_detail', pk=self.object.pk) # # # Delete the object # self.object.delete() # # # Add success message # messages.success( # request, # _("Waiting queue '{}' has been deleted.").format(queue_name) # ) # # return redirect(success_url) # # # # --- Calendar Views --- # # @login_required # def calendar_view(request): # """View for the appointment calendar.""" # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # # Get providers for filter # providers = User.objects.filter( # tenant=tenant, # role__in=['DOCTOR', 'NURSE', 'PHYSICIAN_ASSISTANT'] # ).order_by('first_name', 'last_name') # # # Get selected date (default to today) # date_str = request.GET.get('date') # if not date_str: # date_str = timezone.now().date().strftime('%Y-%m-%d') # # try: # selected_date = datetime.strptime(date_str, '%Y-%m-%d').date() # except ValueError: # selected_date = timezone.now().date() # # # Get selected provider (optional) # provider_id = request.GET.get('provider_id') # selected_provider = None # if provider_id: # try: # selected_provider = User.objects.get(id=provider_id, tenant=tenant) # except User.DoesNotExist: # pass # # # Calendar navigation # prev_date = selected_date - timedelta(days=1) # next_date = selected_date + timedelta(days=1) # # # Week navigation # start_of_week = selected_date - timedelta(days=selected_date.weekday()) # week_dates = [start_of_week + timedelta(days=i) for i in range(7)] # # context = { # 'providers': providers, # 'selected_date': selected_date, # 'selected_provider': selected_provider, # 'prev_date': prev_date, # 'next_date': next_date, # 'week_dates': week_dates, # } # # return render(request, 'appointments/calendar.html', context) # # # @login_required # def calendar_appointments(request): # """ # HTMX view for calendar appointments. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # date_str = request.GET.get('date') # provider_id = request.GET.get('provider_id') # # if not date_str: # date_str = timezone.now().date().strftime('%Y-%m-%d') # # try: # selected_date = datetime.strptime(date_str, '%Y-%m-%d').date() # except ValueError: # selected_date = timezone.now().date() # # # Get appointments for the selected date # queryset = AppointmentRequest.objects.filter( # tenant=tenant, # scheduled_datetime__date=selected_date # ) # # if provider_id: # queryset = queryset.filter(provider_id=provider_id) # # appointments = queryset.select_related('patient', 'provider').order_by('scheduled_datetime') # # # Get time slots (30-minute intervals from 8:00 to 18:00) # start_hour = 8 # end_hour = 18 # time_slots = [] # # for hour in range(start_hour, end_hour): # for minute in [0, 30]: # time_slots.append({ # 'time': f"{hour:02d}:{minute:02d}", # 'datetime': datetime.combine(selected_date, datetime.min.time().replace(hour=hour, minute=minute)) # }) # # # Organize appointments by time slot # for slot in time_slots: # slot_time = timezone.make_aware(slot['datetime']) # slot_end = slot_time + timedelta(minutes=30) # # # Find appointments that overlap with this slot # slot['appointments'] = [ # appt for appt in appointments if ( # appt.scheduled_datetime < slot_end and # appt.scheduled_datetime + timedelta(minutes=appt.duration_minutes) > slot_time # ) # ] # # return render(request, 'appointments/partials/calendar_appointments.html', { # 'appointments': appointments, # 'selected_date': selected_date, # 'time_slots': time_slots # }) # # # @login_required # def provider_availability(request): # """ # HTMX view for provider availability. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # provider_id = request.GET.get('provider_id') # date_str = request.GET.get('date') # # if not provider_id: # return render(request, 'appointments/partials/provider_availability.html', { # 'availability': None, # 'provider': None, # 'selected_date': None # }) # # try: # provider = User.objects.get(id=provider_id, tenant=tenant) # except User.DoesNotExist: # return JsonResponse({'error': 'Provider not found'}, status=404) # # if not date_str: # date_str = timezone.now().date().strftime('%Y-%m-%d') # # try: # selected_date = datetime.strptime(date_str, '%Y-%m-%d').date() # except ValueError: # selected_date = timezone.now().date() # # # Get availability for the selected date # start_of_day = timezone.make_aware(datetime.combine(selected_date, datetime.min.time())) # end_of_day = timezone.make_aware(datetime.combine(selected_date, datetime.max.time())) # # # Regular availability slots # availability = SlotAvailability.objects.filter( # tenant=tenant, # provider=provider, # start_time__lt=end_of_day, # end_time__gt=start_of_day, # is_available=True # ).order_by('start_time') # # # Recurring availability (based on day of week) # day_of_week = selected_date.weekday() # 0-6, Monday is 0 # recurring_availability = SlotAvailability.objects.filter( # tenant=tenant, # provider=provider, # is_recurring=True, # day_of_week=day_of_week, # is_available=True # ) # # # Get appointments for this provider on this date # appointments = AppointmentRequest.objects.filter( # tenant=tenant, # provider=provider, # scheduled_datetime__date=selected_date, # status__in=['SCHEDULED', 'CONFIRMED', 'CHECKED_IN', 'IN_PROGRESS'] # ).select_related('patient').order_by('scheduled_datetime') # # return render(request, 'appointments/partials/provider_availability.html', { # 'availability': availability, # 'recurring_availability': recurring_availability, # 'appointments': appointments, # 'provider': provider, # 'selected_date': selected_date # }) # # # # --- HTMX Queue Management Views --- # # @login_required # def queue_status(request, queue_id): # """ # HTMX view for queue status updates. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # queue = get_object_or_404(WaitingQueue, queue_id=queue_id, tenant=tenant) # # # Get waiting entries # waiting_entries = queue.entries.filter( # status='WAITING' # ).select_related('patient').order_by('priority', 'added_time') # # # Get called/in-progress entries # active_entries = queue.entries.filter( # status__in=['CALLED', 'IN_PROGRESS'] # ).select_related('patient').order_by('called_time') # # return render(request, 'appointments/partials/queue_status.html', { # 'queue': queue, # 'waiting_entries': waiting_entries, # 'active_entries': active_entries # }) # # # @login_required # def add_to_queue(request, queue_id): # """ # HTMX view for adding a patient to the queue. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # queue = get_object_or_404(WaitingQueue, queue_id=queue_id, tenant=tenant) # # if request.method == 'POST': # form = QueueEntryForm(request.POST, user=request.user) # if form.is_valid(): # entry = form.save(commit=False) # entry.queue = queue # entry.save() # # return redirect('appointments:queue_status', queue_id=queue_id) # else: # form = QueueEntryForm( # user=request.user, # initial={'queue': queue, 'priority': 'ROUTINE'} # ) # # return render(request, 'appointments/partials/add_to_queue.html', { # 'form': form, # 'queue': queue # }) # # # @login_required # def call_next_patient(request, queue_id): # """ # HTMX view for calling the next patient from the queue. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # queue = get_object_or_404(WaitingQueue, queue_id=queue_id, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_queueentry'): # return HttpResponseForbidden("Permission denied") # # # Get the next patient # next_entry = queue.get_next_patient() # # if next_entry: # next_entry.mark_as_called() # # # Return the updated queue status # return redirect('appointments:queue_status', queue_id=queue_id) # else: # # No patients waiting # return render(request, 'appointments/partials/no_patients_waiting.html', { # 'queue': queue # }) # # # @login_required # def update_entry_status(request, entry_id, status): # """ # HTMX view for updating a queue entry status. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # entry = get_object_or_404(QueueEntry, entry_id=entry_id, queue__tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_queueentry'): # return HttpResponseForbidden("Permission denied") # # # Update the status # if status == 'called': # entry.mark_as_called() # elif status == 'in_progress': # entry.mark_as_in_progress() # elif status == 'completed': # entry.mark_as_completed() # elif status == 'no_show': # entry.mark_as_no_show() # elif status == 'cancelled': # entry.mark_as_cancelled() # elif status == 'removed': # entry.mark_as_removed() # # # Return the updated queue status # return redirect('appointments:queue_status', queue_id=entry.queue.queue_id) # # # # --- Action Views --- # # @login_required # def cancel_appointment(request, pk): # """ # View for cancelling an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_appointmentrequest'): # messages.error(request, _("You don't have permission to cancel appointments.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # if request.method == 'POST': # reason = request.POST.get('reason', '') # # # Cancel the appointment # appointment.cancel(request.user, reason) # # messages.success(request, _("Appointment has been cancelled.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # return render(request, 'appointments/cancel_appointment.html', { # 'appointment': appointment # }) # # # @login_required # def check_in_appointment(request, pk): # """ # View for checking in a patient for an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_appointmentrequest'): # messages.error(request, _("You don't have permission to check in patients.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Check if the appointment can be checked in # if appointment.status not in ['SCHEDULED', 'CONFIRMED']: # messages.error(request, _("This appointment cannot be checked in.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Check in the appointment # appointment.check_in() # # messages.success(request, _("Patient has been checked in.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # @login_required # def start_appointment(request, pk): # """ # View for starting an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_appointmentrequest'): # messages.error(request, _("You don't have permission to start appointments.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Check if the appointment can be started # if appointment.status != 'CHECKED_IN': # messages.error(request, _("This appointment cannot be started.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Start the appointment # appointment.start_appointment() # # messages.success(request, _("Appointment has been started.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # @login_required # def complete_appointment(request, pk): # """ # View for completing an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_appointmentrequest'): # messages.error(request, _("You don't have permission to complete appointments.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Check if the appointment can be completed # if appointment.status != 'IN_PROGRESS': # messages.error(request, _("This appointment cannot be marked as completed.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Complete the appointment # appointment.complete_appointment() # # messages.success(request, _("Appointment has been completed.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # @login_required # def reschedule_appointment(request, pk): # """ # View for rescheduling an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # messages.error(request, _("No tenant found. Please contact an administrator.")) # return redirect('core:dashboard') # # appointment = get_object_or_404(AppointmentRequest, pk=pk, tenant=tenant) # # # Check if the user has permission # if not request.user.has_perm('appointments.change_appointmentrequest'): # messages.error(request, _("You don't have permission to reschedule appointments.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # # Check if the appointment can be rescheduled # if appointment.status in ['COMPLETED', 'CANCELLED', 'NO_SHOW']: # messages.error(request, _("This appointment cannot be rescheduled.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # # if request.method == 'POST': # form = AppointmentRequestForm(request.POST, instance=appointment, user=request.user) # if form.is_valid(): # # Update the appointment # appointment = form.save() # # # Set status to RESCHEDULED if it was already scheduled # if appointment.status in ['SCHEDULED', 'CONFIRMED']: # appointment.status = 'RESCHEDULED' # appointment.save() # # messages.success(request, _("Appointment has been rescheduled.")) # return redirect('appointments:appointment_detail', pk=appointment.pk) # else: # form = AppointmentRequestForm(instance=appointment, user=request.user) # # return render(request, 'appointments/appointment_reschedule.html', { # 'form': form, # 'appointment': appointment # }) # # # # --- HTMX Views for Telemedicine --- # # @login_required # def create_telemedicine_session(request, appointment_id): # """ # HTMX view for creating a telemedicine session for an appointment. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # appointment = get_object_or_404( # AppointmentRequest, # appointment_id=appointment_id, # tenant=tenant # ) # # # Check if appointment is of type telemedicine # if appointment.appointment_type != 'TELEMEDICINE': # return JsonResponse({'error': 'Not a telemedicine appointment'}, status=400) # # # Check if a session already exists # if hasattr(appointment, 'telemedicine_session'): # return JsonResponse({'error': 'Session already exists'}, status=400) # # if request.method == 'POST': # form = TelemedicineSessionForm( # request.POST, # user=request.user, # appointment=appointment # ) # if form.is_valid(): # session = form.save(commit=False) # session.tenant = tenant # session.save() # # # Link session to appointment # appointment.telemedicine_session = session # appointment.save() # # return render(request, 'appointments/partials/telemedicine_session.html', { # 'session': session, # 'appointment': appointment # }) # else: # form = TelemedicineSessionForm( # user=request.user, # appointment=appointment, # initial={ # 'status': 'SCHEDULED', # 'scheduled_start_time': appointment.scheduled_datetime, # 'scheduled_end_time': appointment.scheduled_datetime + timedelta( # minutes=appointment.duration_minutes # ), # 'room_name': f"telehealth-{appointment.appointment_id.hex[:8]}", # 'is_recorded': False, # } # ) # # return render(request, 'appointments/partials/telemedicine_session_form.html', { # 'form': form, # 'appointment': appointment # }) # # # @login_required # def telemedicine_session_status(request, session_id): # """ # HTMX view for updating telemedicine session status. # """ # tenant = get_tenant_from_request(request) # if not tenant: # return JsonResponse({'error': 'No tenant found'}, status=400) # # session = get_object_or_404( # TelemedicineSession, # session_id=session_id, # tenant=tenant # ) # # # Get the requested action # action = request.POST.get('action') # # if action == 'provider_joined': # session.mark_provider_joined() # elif action == 'patient_joined': # session.mark_patient_joined() # elif action == 'end_session': # session.end_session() # elif action == 'cancel_session': # session.cancel_session() # # return render(request, 'appointments/partials/telemedicine_session.html', { # 'session': session, # 'appointment': session.appointment # }) #