2025-08-12 13:33:25 +03:00

3161 lines
109 KiB
Python

"""
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
# })
#