221 lines
8.2 KiB
Python
221 lines
8.2 KiB
Python
"""
|
|
DRF Serializers for Appointments app.
|
|
"""
|
|
|
|
from rest_framework import serializers
|
|
from .models import Appointment, Provider, Room, Schedule, AppointmentReminder
|
|
from core.serializers import PatientListSerializer, ClinicSerializer
|
|
|
|
|
|
class ProviderSerializer(serializers.ModelSerializer):
|
|
"""Serializer for Provider model."""
|
|
|
|
user_name = serializers.CharField(source='user.get_full_name', read_only=True)
|
|
specialties_display = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Provider
|
|
fields = [
|
|
'id', 'user', 'user_name', 'specialties', 'specialties_display',
|
|
'is_available', 'max_daily_appointments'
|
|
]
|
|
read_only_fields = ['id']
|
|
|
|
def get_specialties_display(self, obj):
|
|
"""Return list of specialty names."""
|
|
return [clinic.name_en for clinic in obj.specialties.all()]
|
|
|
|
|
|
class RoomSerializer(serializers.ModelSerializer):
|
|
"""Serializer for Room model."""
|
|
|
|
clinic_name = serializers.CharField(source='clinic.name_en', read_only=True)
|
|
|
|
class Meta:
|
|
model = Room
|
|
fields = [
|
|
'id', 'name', 'room_number', 'clinic', 'clinic_name',
|
|
'is_available'
|
|
]
|
|
read_only_fields = ['id']
|
|
|
|
|
|
class ScheduleSerializer(serializers.ModelSerializer):
|
|
"""Serializer for Schedule model."""
|
|
|
|
provider_name = serializers.CharField(source='provider.user.get_full_name', read_only=True)
|
|
day_display = serializers.CharField(source='get_day_of_week_display', read_only=True)
|
|
|
|
class Meta:
|
|
model = Schedule
|
|
fields = [
|
|
'id', 'provider', 'provider_name', 'day_of_week', 'day_display',
|
|
'start_time', 'end_time', 'slot_duration', 'is_active'
|
|
]
|
|
read_only_fields = ['id']
|
|
|
|
|
|
class AppointmentListSerializer(serializers.ModelSerializer):
|
|
"""Lightweight serializer for appointment lists."""
|
|
|
|
patient_name = serializers.CharField(source='patient.get_full_name', read_only=True)
|
|
clinic_name = serializers.CharField(source='clinic.name_en', read_only=True)
|
|
provider_name = serializers.CharField(source='provider.user.get_full_name', read_only=True)
|
|
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = [
|
|
'id', 'appointment_number', 'patient', 'patient_name',
|
|
'clinic', 'clinic_name', 'provider', 'provider_name',
|
|
'scheduled_date', 'scheduled_time', 'duration',
|
|
'status', 'status_display'
|
|
]
|
|
read_only_fields = ['id', 'appointment_number']
|
|
|
|
|
|
class AppointmentDetailSerializer(serializers.ModelSerializer):
|
|
"""Detailed serializer for appointment detail views."""
|
|
|
|
patient = PatientListSerializer(read_only=True)
|
|
clinic = ClinicSerializer(read_only=True)
|
|
provider = ProviderSerializer(read_only=True)
|
|
room = RoomSerializer(read_only=True)
|
|
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
|
service_type_display = serializers.CharField(source='get_service_type_display', read_only=True)
|
|
confirmation_method_display = serializers.CharField(source='get_confirmation_method_display', read_only=True)
|
|
cancelled_by_name = serializers.CharField(source='cancelled_by.get_full_name', read_only=True)
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = [
|
|
'id', 'appointment_number', 'patient', 'clinic', 'provider', 'room',
|
|
'service_type', 'service_type_display',
|
|
'scheduled_date', 'scheduled_time', 'duration',
|
|
'status', 'status_display',
|
|
'confirmation_sent_at', 'confirmation_method', 'confirmation_method_display',
|
|
'arrival_at', 'start_at', 'end_at',
|
|
'reschedule_reason', 'reschedule_count',
|
|
'cancel_reason', 'cancelled_by', 'cancelled_by_name',
|
|
'notes', 'finance_cleared', 'consent_verified',
|
|
'created_at', 'updated_at'
|
|
]
|
|
read_only_fields = [
|
|
'id', 'appointment_number', 'created_at', 'updated_at',
|
|
'confirmation_sent_at', 'arrival_at', 'start_at', 'end_at'
|
|
]
|
|
|
|
|
|
class AppointmentCreateSerializer(serializers.ModelSerializer):
|
|
"""Serializer for creating appointments."""
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = [
|
|
'patient', 'clinic', 'provider', 'room',
|
|
'service_type', 'scheduled_date', 'scheduled_time',
|
|
'duration', 'notes'
|
|
]
|
|
|
|
def validate(self, data):
|
|
"""Validate appointment data."""
|
|
# Check provider availability
|
|
provider = data.get('provider')
|
|
scheduled_date = data.get('scheduled_date')
|
|
scheduled_time = data.get('scheduled_time')
|
|
|
|
if provider and scheduled_date and scheduled_time:
|
|
# Check if provider has schedule for this day
|
|
day_of_week = scheduled_date.weekday()
|
|
schedule_exists = Schedule.objects.filter(
|
|
provider=provider,
|
|
day_of_week=day_of_week,
|
|
is_active=True
|
|
).exists()
|
|
|
|
if not schedule_exists:
|
|
raise serializers.ValidationError(
|
|
f"Provider is not available on {scheduled_date.strftime('%A')}"
|
|
)
|
|
|
|
# Check for conflicting appointments
|
|
conflicts = Appointment.objects.filter(
|
|
provider=provider,
|
|
scheduled_date=scheduled_date,
|
|
scheduled_time=scheduled_time,
|
|
status__in=['BOOKED', 'CONFIRMED', 'ARRIVED', 'IN_PROGRESS']
|
|
).exists()
|
|
|
|
if conflicts:
|
|
raise serializers.ValidationError(
|
|
"This time slot is already booked"
|
|
)
|
|
|
|
return data
|
|
|
|
|
|
class AppointmentUpdateSerializer(serializers.ModelSerializer):
|
|
"""Serializer for updating appointments."""
|
|
|
|
class Meta:
|
|
model = Appointment
|
|
fields = [
|
|
'scheduled_date', 'scheduled_time', 'duration',
|
|
'room', 'notes'
|
|
]
|
|
|
|
|
|
class AppointmentStatusSerializer(serializers.Serializer):
|
|
"""Serializer for appointment status transitions."""
|
|
|
|
status = serializers.ChoiceField(choices=Appointment.Status.choices)
|
|
notes = serializers.CharField(required=False, allow_blank=True)
|
|
cancel_reason = serializers.CharField(required=False, allow_blank=True)
|
|
reschedule_reason = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
def validate(self, data):
|
|
"""Validate status transition."""
|
|
new_status = data.get('status')
|
|
|
|
if new_status == 'CANCELLED' and not data.get('cancel_reason'):
|
|
raise serializers.ValidationError(
|
|
"Cancel reason is required when cancelling an appointment"
|
|
)
|
|
|
|
if new_status == 'RESCHEDULED' and not data.get('reschedule_reason'):
|
|
raise serializers.ValidationError(
|
|
"Reschedule reason is required when rescheduling an appointment"
|
|
)
|
|
|
|
return data
|
|
|
|
|
|
class AppointmentReminderSerializer(serializers.ModelSerializer):
|
|
"""Serializer for AppointmentReminder model."""
|
|
|
|
appointment_number = serializers.CharField(source='appointment.appointment_number', read_only=True)
|
|
patient_name = serializers.CharField(source='appointment.patient.get_full_name', read_only=True)
|
|
reminder_type_display = serializers.CharField(source='get_reminder_type_display', read_only=True)
|
|
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
|
|
|
class Meta:
|
|
model = AppointmentReminder
|
|
fields = [
|
|
'id', 'appointment', 'appointment_number', 'patient_name',
|
|
'reminder_type', 'reminder_type_display',
|
|
'scheduled_for', 'sent_at',
|
|
'status', 'status_display', 'message'
|
|
]
|
|
read_only_fields = ['id', 'sent_at']
|
|
|
|
|
|
class CalendarSlotSerializer(serializers.Serializer):
|
|
"""Serializer for calendar time slots."""
|
|
|
|
date = serializers.DateField()
|
|
time = serializers.TimeField()
|
|
provider = ProviderSerializer()
|
|
room = RoomSerializer()
|
|
is_available = serializers.BooleanField()
|
|
appointment = AppointmentListSerializer(required=False, allow_null=True)
|