Marwan Alwali 610e165e17 update
2025-09-04 19:19:52 +03:00

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