4116 lines
146 KiB
Python
4116 lines
146 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
|
|
# })
|
|
#
|
|
|
|
|
|
# from django.shortcuts import render, redirect, get_object_or_404
|
|
# from django.contrib.auth.decorators import login_required, permission_required
|
|
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
|
# from django.contrib import messages
|
|
# from django.views.generic import (
|
|
# CreateView, UpdateView, DeleteView, DetailView, ListView, FormView
|
|
# )
|
|
# from django.urls import reverse_lazy, reverse
|
|
# from django.http import JsonResponse, HttpResponse
|
|
# from django.utils import timezone
|
|
# from django.db import transaction
|
|
# from django.core.mail import send_mail
|
|
# from django.conf import settings
|
|
# from django.db.models import Q, Count
|
|
# from viewflow.views import CreateProcessView, UpdateProcessView
|
|
# from datetime import datetime, timedelta
|
|
# import json
|
|
#
|
|
# from .models import Appointment, AppointmentConfirmation, Queue, TelemedicineSession
|
|
# from .forms import (
|
|
# AppointmentSchedulingForm, AppointmentConfirmationForm, QueueManagementForm,
|
|
# TelemedicineSetupForm, AppointmentRescheduleForm, AppointmentCancellationForm,
|
|
# AppointmentCheckInForm, BulkAppointmentForm
|
|
# )
|
|
# from .flows import AppointmentSchedulingFlow, AppointmentConfirmationFlow, QueueManagementFlow, TelemedicineSetupFlow
|
|
# from patients.models import Patient
|
|
#
|
|
#
|
|
# class AppointmentSchedulingView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
|
# """
|
|
# View for appointment scheduling workflow
|
|
# """
|
|
# model = Appointment
|
|
# form_class = AppointmentSchedulingForm
|
|
# template_name = 'appointments/appointment_scheduling.html'
|
|
# permission_required = 'appointments.can_schedule_appointments'
|
|
# flow_class = AppointmentSchedulingFlow
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['tenant'] = self.request.user.tenant
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# with transaction.atomic():
|
|
# # Create appointment
|
|
# appointment = form.save(commit=False)
|
|
# appointment.tenant = self.request.user.tenant
|
|
# appointment.scheduled_by = self.request.user
|
|
# appointment.status = 'scheduled'
|
|
# appointment.save()
|
|
#
|
|
# # Start appointment scheduling workflow
|
|
# process = self.flow_class.start.run(
|
|
# appointment=appointment,
|
|
# send_confirmation=form.cleaned_data.get('send_confirmation', True),
|
|
# send_reminder=form.cleaned_data.get('send_reminder', True),
|
|
# created_by=self.request.user
|
|
# )
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Appointment scheduled successfully for {appointment.patient.get_full_name()} '
|
|
# f'on {appointment.appointment_date} at {appointment.appointment_time}.'
|
|
# )
|
|
#
|
|
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['title'] = 'Schedule Appointment'
|
|
# context['breadcrumbs'] = [
|
|
# {'name': 'Home', 'url': reverse('core:dashboard')},
|
|
# {'name': 'Appointments', 'url': reverse('appointments:appointment_list')},
|
|
# {'name': 'Schedule Appointment', 'url': ''}
|
|
# ]
|
|
# context['available_slots'] = self.get_available_slots()
|
|
# return context
|
|
#
|
|
# def get_available_slots(self):
|
|
# """Get available appointment slots for the next 7 days"""
|
|
# slots = []
|
|
# today = timezone.now().date()
|
|
#
|
|
# for i in range(7):
|
|
# date = today + timedelta(days=i)
|
|
# day_slots = self.get_slots_for_date(date)
|
|
# if day_slots:
|
|
# slots.append({
|
|
# 'date': date,
|
|
# 'slots': day_slots
|
|
# })
|
|
#
|
|
# return slots
|
|
#
|
|
# def get_slots_for_date(self, date):
|
|
# """Get available slots for a specific date"""
|
|
# # This would implement slot availability logic
|
|
# return []
|
|
#
|
|
#
|
|
# class AppointmentConfirmationView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
|
# """
|
|
# View for appointment confirmation workflow
|
|
# """
|
|
# model = AppointmentConfirmation
|
|
# form_class = AppointmentConfirmationForm
|
|
# template_name = 'appointments/appointment_confirmation.html'
|
|
# permission_required = 'appointments.can_confirm_appointments'
|
|
# flow_class = AppointmentConfirmationFlow
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Create confirmation record
|
|
# confirmation = form.save(commit=False)
|
|
# confirmation.appointment = appointment
|
|
# confirmation.confirmed_by = self.request.user
|
|
# confirmation.confirmed_at = timezone.now()
|
|
# confirmation.save()
|
|
#
|
|
# # Update appointment status
|
|
# if form.cleaned_data.get('reschedule_requested'):
|
|
# appointment.status = 'reschedule_requested'
|
|
# appointment.save()
|
|
#
|
|
# # Start rescheduling process
|
|
# messages.info(
|
|
# self.request,
|
|
# 'Patient requested reschedule. Please process the reschedule request.'
|
|
# )
|
|
# return redirect('appointments:appointment_reschedule', pk=appointment.pk)
|
|
# else:
|
|
# appointment.status = 'confirmed'
|
|
# appointment.save()
|
|
#
|
|
# # Start confirmation workflow
|
|
# process = self.flow_class.start.run(
|
|
# appointment=appointment,
|
|
# confirmation=confirmation,
|
|
# created_by=self.request.user
|
|
# )
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Appointment confirmed for {appointment.patient.get_full_name()}.'
|
|
# )
|
|
#
|
|
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
# context['title'] = 'Confirm Appointment'
|
|
# return context
|
|
#
|
|
#
|
|
# class QueueManagementView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
|
# """
|
|
# View for queue management workflow
|
|
# """
|
|
# model = Queue
|
|
# form_class = QueueManagementForm
|
|
# template_name = 'appointments/queue_management.html'
|
|
# permission_required = 'appointments.can_manage_queue'
|
|
# flow_class = QueueManagementFlow
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Create or update queue entry
|
|
# queue_entry, created = Queue.objects.get_or_create(
|
|
# appointment=appointment,
|
|
# defaults={
|
|
# 'tenant': self.request.user.tenant,
|
|
# 'patient': appointment.patient,
|
|
# 'provider': appointment.provider,
|
|
# 'department': appointment.department,
|
|
# 'created_by': self.request.user
|
|
# }
|
|
# )
|
|
#
|
|
# # Update queue entry with form data
|
|
# for field in form.cleaned_data:
|
|
# if hasattr(queue_entry, field):
|
|
# setattr(queue_entry, field, form.cleaned_data[field])
|
|
#
|
|
# queue_entry.save()
|
|
#
|
|
# # Start queue management workflow
|
|
# process = self.flow_class.start.run(
|
|
# queue_entry=queue_entry,
|
|
# appointment=appointment,
|
|
# notify_patient=form.cleaned_data.get('notify_patient', True),
|
|
# created_by=self.request.user
|
|
# )
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Queue updated for {appointment.patient.get_full_name()}. '
|
|
# f'Position: {queue_entry.position}, Wait time: {queue_entry.estimated_wait_time} minutes.'
|
|
# )
|
|
#
|
|
# return redirect('appointments:queue_list')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
# context['title'] = 'Manage Queue'
|
|
# context['current_queue'] = self.get_current_queue()
|
|
# return context
|
|
#
|
|
# def get_current_queue(self):
|
|
# """Get current queue status"""
|
|
# return Queue.objects.filter(
|
|
# tenant=self.request.user.tenant,
|
|
# status='waiting'
|
|
# ).order_by('priority', 'created_at')
|
|
#
|
|
#
|
|
# class TelemedicineSetupView(LoginRequiredMixin, PermissionRequiredMixin, CreateProcessView):
|
|
# """
|
|
# View for telemedicine setup workflow
|
|
# """
|
|
# model = TelemedicineSession
|
|
# form_class = TelemedicineSetupForm
|
|
# template_name = 'appointments/telemedicine_setup.html'
|
|
# permission_required = 'appointments.can_setup_telemedicine'
|
|
# flow_class = TelemedicineSetupFlow
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Create telemedicine session
|
|
# session = form.save(commit=False)
|
|
# session.appointment = appointment
|
|
# session.tenant = self.request.user.tenant
|
|
# session.created_by = self.request.user
|
|
# session.save()
|
|
#
|
|
# # Update appointment type
|
|
# appointment.appointment_type = 'telemedicine'
|
|
# appointment.save()
|
|
#
|
|
# # Start telemedicine setup workflow
|
|
# process = self.flow_class.start.run(
|
|
# session=session,
|
|
# appointment=appointment,
|
|
# test_connection=form.cleaned_data.get('test_connection', True),
|
|
# send_instructions=form.cleaned_data.get('send_instructions', True),
|
|
# created_by=self.request.user
|
|
# )
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Telemedicine session setup completed for {appointment.patient.get_full_name()}. '
|
|
# f'Meeting details have been sent to the patient.'
|
|
# )
|
|
#
|
|
# return redirect('appointments:appointment_detail', pk=appointment.pk)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['appointment_id'])
|
|
# context['title'] = 'Setup Telemedicine'
|
|
# return context
|
|
#
|
|
#
|
|
# class AppointmentRescheduleView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|
# """
|
|
# View for appointment rescheduling
|
|
# """
|
|
# form_class = AppointmentRescheduleForm
|
|
# template_name = 'appointments/appointment_reschedule.html'
|
|
# permission_required = 'appointments.can_reschedule_appointments'
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
|
|
#
|
|
# def get_form_kwargs(self):
|
|
# kwargs = super().get_form_kwargs()
|
|
# kwargs['tenant'] = self.request.user.tenant
|
|
# kwargs['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
# return kwargs
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Store original appointment details
|
|
# original_date = appointment.appointment_date
|
|
# original_time = appointment.appointment_time
|
|
# original_provider = appointment.provider
|
|
#
|
|
# # Update appointment
|
|
# appointment.appointment_date = form.cleaned_data['new_date']
|
|
# appointment.appointment_time = form.cleaned_data['new_time']
|
|
# if form.cleaned_data.get('new_provider'):
|
|
# appointment.provider = form.cleaned_data['new_provider']
|
|
# appointment.status = 'rescheduled'
|
|
# appointment.save()
|
|
#
|
|
# # Log reschedule
|
|
# self.log_reschedule(appointment, original_date, original_time, original_provider, form.cleaned_data)
|
|
#
|
|
# # Send notifications
|
|
# if form.cleaned_data.get('notify_patient'):
|
|
# self.send_reschedule_notification(appointment, form.cleaned_data)
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Appointment rescheduled successfully. New date: {appointment.appointment_date}, '
|
|
# f'New time: {appointment.appointment_time}.'
|
|
# )
|
|
#
|
|
# return super().form_valid(form)
|
|
#
|
|
# def log_reschedule(self, appointment, original_date, original_time, original_provider, form_data):
|
|
# """Log reschedule details"""
|
|
# from core.models import AuditLogEntry
|
|
# AuditLogEntry.objects.create(
|
|
# tenant=appointment.tenant,
|
|
# user=self.request.user,
|
|
# event_type='APPOINTMENT_RESCHEDULE',
|
|
# action='UPDATE',
|
|
# object_type='Appointment',
|
|
# object_id=str(appointment.id),
|
|
# details={
|
|
# 'original_date': original_date.isoformat(),
|
|
# 'original_time': original_time.isoformat(),
|
|
# 'original_provider': original_provider.get_full_name() if original_provider else None,
|
|
# 'new_date': form_data['new_date'].isoformat(),
|
|
# 'new_time': form_data['new_time'].isoformat(),
|
|
# 'new_provider': form_data['new_provider'].get_full_name() if form_data.get('new_provider') else None,
|
|
# 'reason': form_data['reschedule_reason'],
|
|
# 'notes': form_data.get('notes', '')
|
|
# },
|
|
# ip_address=self.request.META.get('REMOTE_ADDR'),
|
|
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
|
|
# )
|
|
#
|
|
# def send_reschedule_notification(self, appointment, form_data):
|
|
# """Send reschedule notification to patient"""
|
|
# if appointment.patient.email:
|
|
# send_mail(
|
|
# subject='Appointment Rescheduled',
|
|
# message=f'Your appointment has been rescheduled to {appointment.appointment_date} at {appointment.appointment_time}.',
|
|
# from_email=settings.DEFAULT_FROM_EMAIL,
|
|
# recipient_list=[appointment.patient.email],
|
|
# fail_silently=True
|
|
# )
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
# context['title'] = 'Reschedule Appointment'
|
|
# return context
|
|
#
|
|
#
|
|
# class AppointmentCancellationView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|
# """
|
|
# View for appointment cancellation
|
|
# """
|
|
# form_class = AppointmentCancellationForm
|
|
# template_name = 'appointments/appointment_cancellation.html'
|
|
# permission_required = 'appointments.can_cancel_appointments'
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('appointments:appointment_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Update appointment status
|
|
# appointment.status = 'cancelled'
|
|
# appointment.cancelled_at = timezone.now()
|
|
# appointment.cancelled_by = self.request.user
|
|
# appointment.cancellation_reason = form.cleaned_data['cancellation_reason']
|
|
# appointment.cancellation_notes = form.cleaned_data.get('cancellation_notes', '')
|
|
# appointment.save()
|
|
#
|
|
# # Handle follow-up actions
|
|
# if form.cleaned_data.get('offer_reschedule'):
|
|
# self.offer_reschedule(appointment)
|
|
#
|
|
# if form.cleaned_data.get('notify_patient'):
|
|
# self.send_cancellation_notification(appointment, form.cleaned_data)
|
|
#
|
|
# if form.cleaned_data.get('refund_required'):
|
|
# self.process_refund(appointment)
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Appointment cancelled for {appointment.patient.get_full_name()}.'
|
|
# )
|
|
#
|
|
# return super().form_valid(form)
|
|
#
|
|
# def offer_reschedule(self, appointment):
|
|
# """Offer reschedule options to patient"""
|
|
# # This would implement reschedule offering logic
|
|
# pass
|
|
#
|
|
# def send_cancellation_notification(self, appointment, form_data):
|
|
# """Send cancellation notification to patient"""
|
|
# if appointment.patient.email:
|
|
# send_mail(
|
|
# subject='Appointment Cancelled',
|
|
# message=f'Your appointment on {appointment.appointment_date} has been cancelled. Reason: {form_data["cancellation_reason"]}',
|
|
# from_email=settings.DEFAULT_FROM_EMAIL,
|
|
# recipient_list=[appointment.patient.email],
|
|
# fail_silently=True
|
|
# )
|
|
#
|
|
# def process_refund(self, appointment):
|
|
# """Process refund if required"""
|
|
# # This would implement refund processing logic
|
|
# pass
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
# context['title'] = 'Cancel Appointment'
|
|
# return context
|
|
#
|
|
#
|
|
# class AppointmentCheckInView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|
# """
|
|
# View for appointment check-in
|
|
# """
|
|
# form_class = AppointmentCheckInForm
|
|
# template_name = 'appointments/appointment_checkin.html'
|
|
# permission_required = 'appointments.can_checkin_patients'
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('appointments:appointment_detail', kwargs={'pk': self.kwargs['pk']})
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
#
|
|
# with transaction.atomic():
|
|
# # Update appointment status
|
|
# appointment.status = 'checked_in'
|
|
# appointment.checked_in_at = timezone.now()
|
|
# appointment.checked_in_by = self.request.user
|
|
# appointment.arrival_time = form.cleaned_data['arrival_time']
|
|
# appointment.save()
|
|
#
|
|
# # Create queue entry if needed
|
|
# if not hasattr(appointment, 'queue_entry'):
|
|
# Queue.objects.create(
|
|
# appointment=appointment,
|
|
# tenant=appointment.tenant,
|
|
# patient=appointment.patient,
|
|
# provider=appointment.provider,
|
|
# department=appointment.department,
|
|
# queue_type='check_in',
|
|
# priority='normal',
|
|
# status='waiting',
|
|
# created_by=self.request.user
|
|
# )
|
|
#
|
|
# # Handle pre-visit tasks
|
|
# self.process_checkin_tasks(appointment, form.cleaned_data)
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'{appointment.patient.get_full_name()} checked in successfully.'
|
|
# )
|
|
#
|
|
# return super().form_valid(form)
|
|
#
|
|
# def process_checkin_tasks(self, appointment, form_data):
|
|
# """Process check-in tasks"""
|
|
# tasks = []
|
|
#
|
|
# if form_data.get('insurance_verified'):
|
|
# tasks.append('Insurance verified')
|
|
# if form_data.get('copay_collected'):
|
|
# tasks.append('Copay collected')
|
|
# if form_data.get('forms_completed'):
|
|
# tasks.append('Forms completed')
|
|
# if form_data.get('vitals_required'):
|
|
# tasks.append('Vitals required')
|
|
#
|
|
# # Log completed tasks
|
|
# if tasks:
|
|
# from core.models import AuditLogEntry
|
|
# AuditLogEntry.objects.create(
|
|
# tenant=appointment.tenant,
|
|
# user=self.request.user,
|
|
# event_type='APPOINTMENT_CHECKIN',
|
|
# action='UPDATE',
|
|
# object_type='Appointment',
|
|
# object_id=str(appointment.id),
|
|
# details={
|
|
# 'completed_tasks': tasks,
|
|
# 'special_needs': form_data.get('special_needs', '')
|
|
# },
|
|
# ip_address=self.request.META.get('REMOTE_ADDR'),
|
|
# user_agent=self.request.META.get('HTTP_USER_AGENT', '')
|
|
# )
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['appointment'] = get_object_or_404(Appointment, pk=self.kwargs['pk'])
|
|
# context['title'] = 'Check In Patient'
|
|
# return context
|
|
#
|
|
#
|
|
# class BulkAppointmentView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|
# """
|
|
# View for bulk appointment operations
|
|
# """
|
|
# form_class = BulkAppointmentForm
|
|
# template_name = 'appointments/bulk_appointment.html'
|
|
# permission_required = 'appointments.can_bulk_manage_appointments'
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('appointments:appointment_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# appointment_ids = form.cleaned_data['appointment_ids'].split(',')
|
|
# appointments = Appointment.objects.filter(
|
|
# id__in=appointment_ids,
|
|
# tenant=self.request.user.tenant
|
|
# )
|
|
#
|
|
# action = form.cleaned_data['action']
|
|
#
|
|
# with transaction.atomic():
|
|
# if action == 'confirm':
|
|
# self.bulk_confirm(appointments, form.cleaned_data)
|
|
# elif action == 'reschedule':
|
|
# self.bulk_reschedule(appointments, form.cleaned_data)
|
|
# elif action == 'cancel':
|
|
# self.bulk_cancel(appointments, form.cleaned_data)
|
|
# elif action == 'send_reminders':
|
|
# self.bulk_send_reminders(appointments, form.cleaned_data)
|
|
#
|
|
# messages.success(
|
|
# self.request,
|
|
# f'Bulk operation "{action}" completed for {appointments.count()} appointments.'
|
|
# )
|
|
#
|
|
# return super().form_valid(form)
|
|
#
|
|
# def bulk_confirm(self, appointments, form_data):
|
|
# """Bulk confirm appointments"""
|
|
# for appointment in appointments:
|
|
# appointment.status = 'confirmed'
|
|
# appointment.save()
|
|
#
|
|
# if form_data.get('notify_patients'):
|
|
# self.send_confirmation_notification(appointment)
|
|
#
|
|
# def bulk_reschedule(self, appointments, form_data):
|
|
# """Bulk reschedule appointments"""
|
|
# new_date = form_data.get('bulk_date')
|
|
# if new_date:
|
|
# for appointment in appointments:
|
|
# appointment.appointment_date = new_date
|
|
# appointment.status = 'rescheduled'
|
|
# appointment.save()
|
|
#
|
|
# if form_data.get('notify_patients'):
|
|
# self.send_reschedule_notification(appointment, form_data)
|
|
#
|
|
# def bulk_cancel(self, appointments, form_data):
|
|
# """Bulk cancel appointments"""
|
|
# for appointment in appointments:
|
|
# appointment.status = 'cancelled'
|
|
# appointment.cancelled_at = timezone.now()
|
|
# appointment.cancelled_by = self.request.user
|
|
# appointment.cancellation_reason = 'bulk_cancellation'
|
|
# appointment.cancellation_notes = form_data.get('bulk_reason', '')
|
|
# appointment.save()
|
|
#
|
|
# if form_data.get('notify_patients'):
|
|
# self.send_cancellation_notification(appointment, form_data)
|
|
#
|
|
# def bulk_send_reminders(self, appointments, form_data):
|
|
# """Bulk send reminders"""
|
|
# for appointment in appointments:
|
|
# if form_data.get('notify_patients'):
|
|
# self.send_reminder_notification(appointment)
|
|
#
|
|
# def send_confirmation_notification(self, appointment):
|
|
# """Send confirmation notification"""
|
|
# if appointment.patient.email:
|
|
# send_mail(
|
|
# subject='Appointment Confirmed',
|
|
# message=f'Your appointment on {appointment.appointment_date} at {appointment.appointment_time} has been confirmed.',
|
|
# from_email=settings.DEFAULT_FROM_EMAIL,
|
|
# recipient_list=[appointment.patient.email],
|
|
# fail_silently=True
|
|
# )
|
|
#
|
|
# def send_reminder_notification(self, appointment):
|
|
# """Send reminder notification"""
|
|
# if appointment.patient.email:
|
|
# send_mail(
|
|
# subject='Appointment Reminder',
|
|
# message=f'Reminder: You have an appointment on {appointment.appointment_date} at {appointment.appointment_time}.',
|
|
# from_email=settings.DEFAULT_FROM_EMAIL,
|
|
# recipient_list=[appointment.patient.email],
|
|
# fail_silently=True
|
|
# )
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['title'] = 'Bulk Appointment Operations'
|
|
# return context
|
|
#
|
|
#
|
|
# class AppointmentListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
# """
|
|
# View for listing appointments
|
|
# """
|
|
# model = Appointment
|
|
# template_name = 'appointments/appointment_list.html'
|
|
# context_object_name = 'appointments'
|
|
# permission_required = 'appointments.view_appointment'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = Appointment.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Apply filters
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(patient__first_name__icontains=search) |
|
|
# Q(patient__last_name__icontains=search) |
|
|
# Q(provider__first_name__icontains=search) |
|
|
# Q(provider__last_name__icontains=search) |
|
|
# Q(reason__icontains=search)
|
|
# )
|
|
#
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# date_from = self.request.GET.get('date_from')
|
|
# if date_from:
|
|
# queryset = queryset.filter(appointment_date__gte=date_from)
|
|
#
|
|
# date_to = self.request.GET.get('date_to')
|
|
# if date_to:
|
|
# queryset = queryset.filter(appointment_date__lte=date_to)
|
|
#
|
|
# provider = self.request.GET.get('provider')
|
|
# if provider:
|
|
# queryset = queryset.filter(provider_id=provider)
|
|
#
|
|
# return queryset.order_by('appointment_date', 'appointment_time')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['title'] = 'Appointments'
|
|
# context['providers'] = self.get_providers()
|
|
# context['search'] = self.request.GET.get('search', '')
|
|
# context['selected_status'] = self.request.GET.get('status', '')
|
|
# context['selected_provider'] = self.request.GET.get('provider', '')
|
|
# context['date_from'] = self.request.GET.get('date_from', '')
|
|
# context['date_to'] = self.request.GET.get('date_to', '')
|
|
# return context
|
|
#
|
|
# def get_providers(self):
|
|
# """Get providers for filter"""
|
|
# from django.contrib.auth.models import User
|
|
# return User.objects.filter(
|
|
# tenant=self.request.user.tenant,
|
|
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
|
|
# ).distinct()
|
|
#
|
|
#
|
|
# class AppointmentDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|
# """
|
|
# View for appointment details
|
|
# """
|
|
# model = Appointment
|
|
# template_name = 'appointments/appointment_detail.html'
|
|
# context_object_name = 'appointment'
|
|
# permission_required = 'appointments.view_appointment'
|
|
#
|
|
# def get_queryset(self):
|
|
# return Appointment.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# appointment = self.object
|
|
# context['title'] = f'Appointment - {appointment.patient.get_full_name()}'
|
|
# context['confirmation'] = getattr(appointment, 'confirmation', None)
|
|
# context['queue_entry'] = getattr(appointment, 'queue_entry', None)
|
|
# context['telemedicine_session'] = getattr(appointment, 'telemedicine_session', None)
|
|
# context['can_edit'] = self.request.user.has_perm('appointments.change_appointment')
|
|
# context['can_cancel'] = self.request.user.has_perm('appointments.can_cancel_appointments')
|
|
# context['can_reschedule'] = self.request.user.has_perm('appointments.can_reschedule_appointments')
|
|
# return context
|
|
#
|
|
#
|
|
# class QueueListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|
# """
|
|
# View for listing queue entries
|
|
# """
|
|
# model = Queue
|
|
# template_name = 'appointments/queue_list.html'
|
|
# context_object_name = 'queue_entries'
|
|
# permission_required = 'appointments.view_queue'
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = Queue.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Apply filters
|
|
# status = self.request.GET.get('status', 'waiting')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# department = self.request.GET.get('department')
|
|
# if department:
|
|
# queryset = queryset.filter(department_id=department)
|
|
#
|
|
# return queryset.order_by('priority', 'created_at')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context['title'] = 'Patient Queue'
|
|
# context['departments'] = self.get_departments()
|
|
# context['selected_status'] = self.request.GET.get('status', 'waiting')
|
|
# context['selected_department'] = self.request.GET.get('department', '')
|
|
# context['queue_stats'] = self.get_queue_stats()
|
|
# return context
|
|
#
|
|
# def get_departments(self):
|
|
# """Get departments for filter"""
|
|
# from core.models import Department
|
|
# return Department.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_queue_stats(self):
|
|
# """Get queue statistics"""
|
|
# return {
|
|
# 'total_waiting': Queue.objects.filter(
|
|
# tenant=self.request.user.tenant,
|
|
# status='waiting'
|
|
# ).count(),
|
|
# 'average_wait_time': 25, # Would be calculated
|
|
# 'longest_wait': 45 # Would be calculated
|
|
# }
|
|
#
|
|
#
|
|
# # AJAX Views
|
|
# @login_required
|
|
# @permission_required('appointments.view_appointment')
|
|
# def appointment_availability_ajax(request):
|
|
# """AJAX view to check appointment availability"""
|
|
# date = request.GET.get('date')
|
|
# provider_id = request.GET.get('provider_id')
|
|
#
|
|
# if not date or not provider_id:
|
|
# return JsonResponse({'success': False, 'message': 'Missing parameters'})
|
|
#
|
|
# try:
|
|
# from django.contrib.auth.models import User
|
|
# provider = User.objects.get(id=provider_id, tenant=request.user.tenant)
|
|
# appointment_date = datetime.strptime(date, '%Y-%m-%d').date()
|
|
#
|
|
# # Get existing appointments for the date
|
|
# existing_appointments = Appointment.objects.filter(
|
|
# provider=provider,
|
|
# appointment_date=appointment_date,
|
|
# status__in=['scheduled', 'confirmed', 'in_progress']
|
|
# )
|
|
#
|
|
# # Generate available slots
|
|
# available_slots = generate_available_slots(appointment_date, existing_appointments)
|
|
#
|
|
# return JsonResponse({
|
|
# 'success': True,
|
|
# 'slots': available_slots
|
|
# })
|
|
# except Exception as e:
|
|
# return JsonResponse({'success': False, 'message': str(e)})
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('appointments.view_patient')
|
|
# def patient_search_ajax(request):
|
|
# """AJAX view for patient search"""
|
|
# query = request.GET.get('q', '')
|
|
# if len(query) < 2:
|
|
# return JsonResponse({'patients': []})
|
|
#
|
|
# patients = Patient.objects.filter(
|
|
# tenant=request.user.tenant
|
|
# ).filter(
|
|
# Q(first_name__icontains=query) |
|
|
# Q(last_name__icontains=query) |
|
|
# Q(patient_id__icontains=query) |
|
|
# Q(phone_number__icontains=query)
|
|
# )[:10]
|
|
#
|
|
# patient_data = [
|
|
# {
|
|
# 'id': patient.id,
|
|
# 'name': patient.get_full_name(),
|
|
# 'patient_id': patient.patient_id,
|
|
# 'phone': patient.phone_number,
|
|
# 'email': patient.email
|
|
# }
|
|
# for patient in patients
|
|
# ]
|
|
#
|
|
# return JsonResponse({'patients': patient_data})
|
|
#
|
|
#
|
|
# @login_required
|
|
# @permission_required('appointments.can_manage_queue')
|
|
# def update_queue_position_ajax(request):
|
|
# """AJAX view to update queue position"""
|
|
# if request.method == 'POST':
|
|
# try:
|
|
# data = json.loads(request.body)
|
|
# queue_id = data.get('queue_id')
|
|
# new_position = data.get('new_position')
|
|
#
|
|
# queue_entry = Queue.objects.get(
|
|
# id=queue_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# queue_entry.position = new_position
|
|
# queue_entry.save()
|
|
#
|
|
# return JsonResponse({
|
|
# 'success': True,
|
|
# 'message': 'Queue position updated successfully.'
|
|
# })
|
|
# except Queue.DoesNotExist:
|
|
# return JsonResponse({
|
|
# 'success': False,
|
|
# 'message': 'Queue entry not found.'
|
|
# })
|
|
# except Exception as e:
|
|
# return JsonResponse({
|
|
# 'success': False,
|
|
# 'message': str(e)
|
|
# })
|
|
#
|
|
# return JsonResponse({'success': False, 'message': 'Invalid request.'})
|
|
#
|
|
#
|
|
# def generate_available_slots(date, existing_appointments):
|
|
# """Generate available appointment slots for a date"""
|
|
# slots = []
|
|
# start_time = datetime.strptime('09:00', '%H:%M').time()
|
|
# end_time = datetime.strptime('17:00', '%H:%M').time()
|
|
# slot_duration = 30 # minutes
|
|
#
|
|
# current_time = datetime.combine(date, start_time)
|
|
# end_datetime = datetime.combine(date, end_time)
|
|
#
|
|
# while current_time < end_datetime:
|
|
# slot_time = current_time.time()
|
|
#
|
|
# # Check if slot is available
|
|
# is_available = True
|
|
# for appointment in existing_appointments:
|
|
# appointment_start = datetime.combine(date, appointment.appointment_time)
|
|
# appointment_end = appointment_start + timedelta(minutes=appointment.duration)
|
|
#
|
|
# slot_start = current_time
|
|
# slot_end = current_time + timedelta(minutes=slot_duration)
|
|
#
|
|
# if slot_start < appointment_end and slot_end > appointment_start:
|
|
# is_available = False
|
|
# break
|
|
#
|
|
# if is_available:
|
|
# slots.append({
|
|
# 'time': slot_time.strftime('%H:%M'),
|
|
# 'available': True
|
|
# })
|
|
#
|
|
# current_time += timedelta(minutes=slot_duration)
|
|
#
|
|
# return slots
|
|
#
|