""" 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 import Q, Count, Avg 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. """ 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 queue entries queue_entries = QueueEntry.objects.filter( queue=queue ).order_by('position', 'created_at') # Calculate statistics stats = { 'total_entries': queue_entries.count(), 'waiting_entries': queue_entries.filter(status='WAITING').count(), 'in_progress_entries': queue_entries.filter(status='IN_PROGRESS').count(), 'average_wait_time': queue_entries.filter( status='COMPLETED' ).aggregate(avg_wait=Avg('actual_wait_time_minutes'))['avg_wait'] or 0, } return render(request, 'appointments/partials/queue_status.html', { 'queue': queue, 'queue_entries': queue_entries, '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.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, TemplateView): """ Queue management view for appointments. """ template_name = 'appointments/queue_management.html' 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 # }) #