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