2025-11-23 00:19:26 +00:00
|
|
|
from rest_framework import generics, status
|
|
|
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
|
|
|
from rest_framework.response import Response
|
2025-11-24 13:29:07 +00:00
|
|
|
from rest_framework.permissions import IsAuthenticated, AllowAny,IsAdminUser
|
2025-11-23 00:19:26 +00:00
|
|
|
from django.utils import timezone
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
from .models import AdminWeeklyAvailability, AppointmentRequest
|
|
|
|
|
from .serializers import (
|
|
|
|
|
AdminWeeklyAvailabilitySerializer,
|
|
|
|
|
AppointmentRequestSerializer,
|
|
|
|
|
AppointmentRequestCreateSerializer,
|
|
|
|
|
AppointmentScheduleSerializer,
|
|
|
|
|
AppointmentRejectSerializer
|
|
|
|
|
)
|
|
|
|
|
from .email_service import EmailService
|
2025-11-23 13:55:04 +00:00
|
|
|
from users.models import CustomUser
|
2025-11-24 13:29:07 +00:00
|
|
|
from django.db.models import Count, Q
|
|
|
|
|
|
2025-11-23 00:19:26 +00:00
|
|
|
|
|
|
|
|
class AdminAvailabilityView(generics.RetrieveUpdateAPIView):
|
2025-11-24 13:29:07 +00:00
|
|
|
permission_classes = [IsAuthenticated, IsAdminUser]
|
2025-11-23 00:19:26 +00:00
|
|
|
serializer_class = AdminWeeklyAvailabilitySerializer
|
|
|
|
|
|
|
|
|
|
def get_object(self):
|
|
|
|
|
obj, created = AdminWeeklyAvailability.objects.get_or_create(
|
|
|
|
|
defaults={'available_days': []}
|
|
|
|
|
)
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
class AppointmentRequestListView(generics.ListAPIView):
|
|
|
|
|
serializer_class = AppointmentRequestSerializer
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
queryset = AppointmentRequest.objects.all()
|
|
|
|
|
|
|
|
|
|
if self.request.user.is_staff:
|
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
|
|
return queryset.filter(email=self.request.user.email)
|
|
|
|
|
|
|
|
|
|
class AppointmentRequestCreateView(generics.CreateAPIView):
|
2025-11-23 13:55:04 +00:00
|
|
|
permission_classes = [IsAuthenticated]
|
2025-11-23 00:19:26 +00:00
|
|
|
queryset = AppointmentRequest.objects.all()
|
|
|
|
|
serializer_class = AppointmentRequestCreateSerializer
|
|
|
|
|
|
|
|
|
|
def perform_create(self, serializer):
|
|
|
|
|
availability = AdminWeeklyAvailability.objects.first()
|
|
|
|
|
if availability:
|
|
|
|
|
available_days = availability.available_days
|
|
|
|
|
preferred_dates = serializer.validated_data['preferred_dates']
|
|
|
|
|
|
|
|
|
|
for date_str in preferred_dates:
|
|
|
|
|
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
|
|
|
if date_obj.weekday() not in available_days:
|
|
|
|
|
from rest_framework.exceptions import ValidationError
|
|
|
|
|
raise ValidationError(f'Date {date_str} is not available for appointments.')
|
|
|
|
|
|
|
|
|
|
appointment = serializer.save()
|
|
|
|
|
EmailService.send_admin_notification(appointment)
|
|
|
|
|
|
|
|
|
|
class AppointmentRequestDetailView(generics.RetrieveAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
queryset = AppointmentRequest.objects.all()
|
|
|
|
|
serializer_class = AppointmentRequestSerializer
|
|
|
|
|
lookup_field = 'pk'
|
|
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class ScheduleAppointmentView(generics.GenericAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated, IsAdminUser]
|
|
|
|
|
serializer_class = AppointmentScheduleSerializer
|
|
|
|
|
queryset = AppointmentRequest.objects.all()
|
|
|
|
|
lookup_field = 'pk'
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
def post(self, request, pk):
|
|
|
|
|
appointment = self.get_object()
|
|
|
|
|
|
|
|
|
|
if appointment.status != 'pending_review':
|
|
|
|
|
return Response(
|
|
|
|
|
{'error': 'Only pending review appointments can be scheduled.'},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
serializer = self.get_serializer(data=request.data)
|
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
|
|
2025-11-23 00:19:26 +00:00
|
|
|
appointment.schedule_appointment(
|
|
|
|
|
datetime_obj=serializer.validated_data['scheduled_datetime'],
|
|
|
|
|
duration=serializer.validated_data['scheduled_duration']
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
EmailService.send_appointment_scheduled(appointment)
|
|
|
|
|
|
|
|
|
|
response_serializer = AppointmentRequestSerializer(appointment)
|
|
|
|
|
return Response({
|
|
|
|
|
**response_serializer.data,
|
|
|
|
|
'message': 'Appointment scheduled successfully. Jitsi meeting created.',
|
|
|
|
|
'jitsi_meeting_created': appointment.has_jitsi_meeting
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class RejectAppointmentView(generics.GenericAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
serializer_class = AppointmentRejectSerializer
|
|
|
|
|
queryset = AppointmentRequest.objects.all()
|
|
|
|
|
lookup_field = 'pk'
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
def post(self, request, pk):
|
|
|
|
|
appointment = self.get_object()
|
|
|
|
|
|
|
|
|
|
if appointment.status != 'pending_review':
|
|
|
|
|
return Response(
|
|
|
|
|
{'error': 'Only pending appointments can be rejected'},
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
serializer = self.get_serializer(data=request.data)
|
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
|
|
|
|
|
|
appointment.reject_appointment(
|
|
|
|
|
serializer.validated_data.get('rejection_reason', '')
|
2025-11-23 00:19:26 +00:00
|
|
|
)
|
|
|
|
|
EmailService.send_appointment_rejected(appointment)
|
2025-11-24 13:29:07 +00:00
|
|
|
|
|
|
|
|
response_serializer = AppointmentRequestSerializer(appointment)
|
|
|
|
|
return Response(response_serializer.data)
|
2025-11-23 00:19:26 +00:00
|
|
|
|
|
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class AvailableDatesView(generics.GenericAPIView):
|
|
|
|
|
permission_classes = [AllowAny]
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
def get(self, request):
|
|
|
|
|
availability = AdminWeeklyAvailability.objects.first()
|
|
|
|
|
if not availability:
|
|
|
|
|
return Response([])
|
|
|
|
|
|
|
|
|
|
available_days = availability.available_days
|
|
|
|
|
today = timezone.now().date()
|
|
|
|
|
available_dates = []
|
|
|
|
|
|
|
|
|
|
for i in range(1, 31):
|
|
|
|
|
date = today + timedelta(days=i)
|
|
|
|
|
if date.weekday() in available_days:
|
|
|
|
|
available_dates.append(date.strftime('%Y-%m-%d'))
|
|
|
|
|
|
|
|
|
|
return Response(available_dates)
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class UserAppointmentsView(generics.ListAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
serializer_class = AppointmentRequestSerializer
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
def get_queryset(self):
|
|
|
|
|
return AppointmentRequest.objects.filter(
|
|
|
|
|
email=self.request.user.email
|
|
|
|
|
).order_by('-created_at')
|
2025-11-23 00:19:26 +00:00
|
|
|
|
2025-11-23 23:06:17 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class AppointmentStatsView(generics.GenericAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated, IsAdminUser]
|
|
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
|
total = AppointmentRequest.objects.count()
|
|
|
|
|
users = CustomUser.objects.filter(is_staff=False).count()
|
|
|
|
|
pending = AppointmentRequest.objects.filter(status='pending_review').count()
|
|
|
|
|
scheduled = AppointmentRequest.objects.filter(status='scheduled').count()
|
|
|
|
|
rejected = AppointmentRequest.objects.filter(status='rejected').count()
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'total_requests': total,
|
|
|
|
|
'pending_review': pending,
|
|
|
|
|
'scheduled': scheduled,
|
|
|
|
|
'rejected': rejected,
|
|
|
|
|
'users': users,
|
|
|
|
|
'completion_rate': round((scheduled / total * 100), 2) if total > 0 else 0
|
|
|
|
|
})
|
2025-11-23 23:06:17 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
class UserAppointmentStatsView(generics.GenericAPIView):
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
2025-11-23 23:06:17 +00:00
|
|
|
|
2025-11-24 13:29:07 +00:00
|
|
|
def get(self, request):
|
|
|
|
|
stats = AppointmentRequest.objects.filter(
|
|
|
|
|
email=request.user.email
|
|
|
|
|
).aggregate(
|
|
|
|
|
total=Count('id'),
|
|
|
|
|
pending=Count('id', filter=Q(status='pending_review')),
|
|
|
|
|
scheduled=Count('id', filter=Q(status='scheduled')),
|
|
|
|
|
rejected=Count('id', filter=Q(status='rejected')),
|
|
|
|
|
completed=Count('id', filter=Q(status='completed'))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
total = stats['total']
|
|
|
|
|
scheduled = stats['scheduled']
|
|
|
|
|
completion_rate = round((scheduled / total * 100), 2) if total > 0 else 0
|
|
|
|
|
|
|
|
|
|
return Response({
|
|
|
|
|
'total_requests': total,
|
|
|
|
|
'pending_review': stats['pending'],
|
|
|
|
|
'scheduled': scheduled,
|
|
|
|
|
'rejected': stats['rejected'],
|
|
|
|
|
'completed': stats['completed'],
|
|
|
|
|
'completion_rate': completion_rate
|
|
|
|
|
})
|