""" External API serializers for public-facing create/retrieve operations. """ from rest_framework import serializers from apps.complaints.models import Complaint, Inquiry from apps.observations.models import Observation from apps.appreciation.models import Appreciation, AppreciationStatus, AppreciationVisibility from apps.feedback.models import Feedback, FeedbackType, FeedbackStatus, FeedbackCategory from apps.physicians.models import PhysicianIndividualRating # --------------------------------------------------------------------------- # Complaint # --------------------------------------------------------------------------- class ExternalComplaintCreateSerializer(serializers.Serializer): """Public-facing fields for creating a complaint via external API.""" # Contact contact_name = serializers.CharField(max_length=200, required=True) contact_phone = serializers.CharField(max_length=20, required=True) contact_email = serializers.EmailField(required=False, allow_blank=True, default="") relation_to_patient = serializers.ChoiceField( choices=[("patient", "Patient"), ("relative", "Relative")], required=False, allow_blank=True, default="", ) # Patient patient_name = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") national_id = serializers.CharField(max_length=50, required=False, allow_blank=True, default="") incident_date = serializers.DateField(required=False, allow_null=True, default=None) # Hospital context hospital = serializers.CharField(max_length=200, required=True) # Location hierarchy location_type = serializers.ChoiceField( choices=[("OP", "Outpatient"), ("IP", "Inpatient"), ("ER", "Emergency"), ("GENERAL", "General")], required=False, allow_blank=True, default="", ) area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") department_code = serializers.CharField(max_length=200, required=True) section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") # Complaint content title = serializers.CharField(max_length=500, required=True) description = serializers.CharField(required=True) expected_result = serializers.CharField(required=False, allow_blank=True, default="") def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") def validate(self, data): hospital = data.get("hospital") if hospital: area_name = data.get("area") if area_name: from apps.organizations.models import Area try: data["area"] = Area.objects.get( name_en__iexact=area_name.strip(), hospital=hospital, status="active" ) except Area.DoesNotExist: raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."}) department_code = data.get("department_code") if department_code: from apps.organizations.models import Department try: data["department"] = Department.objects.get( code__iexact=department_code.strip(), hospital=hospital, status="active" ) except Department.DoesNotExist: raise serializers.ValidationError( {"department_code": f"Department with code '{department_code}' not found for this hospital."} ) else: data["department"] = None section_name = data.get("section") if section_name: department = data.get("department") if not department: raise serializers.ValidationError({"section": "Department is required to look up section."}) from apps.organizations.models import Section try: data["section"] = Section.objects.get( name_en__iexact=section_name.strip(), department=department, status="active" ) except Section.DoesNotExist: raise serializers.ValidationError( {"section": f"Section '{section_name}' not found for this department."} ) return data class ExternalComplaintRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving a complaint.""" hospital_name = serializers.CharField(source="hospital.name", read_only=True) class Meta: model = Complaint fields = [ "id", "reference_number", "title", "description", "status", "severity", "priority", "contact_name", "contact_phone", "contact_email", "relation_to_patient", "patient_name", "incident_date", "expected_result", "resolution", "satisfaction", "hospital_name", "created_at", "updated_at", ] read_only_fields = fields # --------------------------------------------------------------------------- # Inquiry # --------------------------------------------------------------------------- class ExternalInquiryCreateSerializer(serializers.Serializer): """Public-facing fields for creating an inquiry via external API.""" # Contact contact_name = serializers.CharField(max_length=200, required=True) contact_phone = serializers.CharField(max_length=20, required=True) contact_email = serializers.EmailField(required=False, allow_blank=True, default="") # Hospital context hospital = serializers.CharField(max_length=200, required=True) # Location hierarchy (all optional) location_type = serializers.ChoiceField( choices=[("OP", "Outpatient"), ("IP", "Inpatient"), ("ER", "Emergency"), ("GENERAL", "General")], required=False, allow_blank=True, default="", ) area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") department_code = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") # Inquiry content subject = serializers.CharField(max_length=500, required=True) message = serializers.CharField(required=True) category = serializers.ChoiceField( choices=[ ("appointment", "Appointment"), ("billing", "Billing"), ("medical_records", "Medical Records"), ("general", "General Information"), ("other", "Other"), ], required=False, default="general", ) def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") def validate(self, data): hospital = data.get("hospital") if hospital: area_name = data.get("area") if area_name: from apps.organizations.models import Area try: data["area"] = Area.objects.get( name_en__iexact=area_name.strip(), hospital=hospital, status="active" ) except Area.DoesNotExist: raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."}) department_code = data.get("department_code") if department_code: from apps.organizations.models import Department try: data["department"] = Department.objects.get( code__iexact=department_code.strip(), hospital=hospital, status="active" ) except Department.DoesNotExist: raise serializers.ValidationError( {"department_code": f"Department with code '{department_code}' not found for this hospital."} ) else: data["department"] = None section_name = data.get("section") if section_name: department = data.get("department") if not department: raise serializers.ValidationError({"section": "Department is required to look up section."}) from apps.organizations.models import Section try: data["section"] = Section.objects.get( name_en__iexact=section_name.strip(), department=department, status="active" ) except Section.DoesNotExist: raise serializers.ValidationError( {"section": f"Section '{section_name}' not found for this department."} ) return data class ExternalInquiryRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving an inquiry.""" hospital_name = serializers.CharField(source="hospital.name", read_only=True) class Meta: model = Inquiry fields = [ "id", "reference_number", "subject", "message", "category", "status", "contact_name", "contact_phone", "contact_email", "hospital_name", "created_at", "updated_at", ] read_only_fields = fields # --------------------------------------------------------------------------- # Observation # --------------------------------------------------------------------------- class ExternalObservationCreateSerializer(serializers.Serializer): """Public-facing fields for creating an observation via external API.""" # Hospital context hospital = serializers.CharField(max_length=200, required=True) # Classification category = serializers.UUIDField(required=False, allow_null=True, default=None) # Content title = serializers.CharField(max_length=300, required=False, allow_blank=True, default="") description = serializers.CharField(required=True) severity = serializers.ChoiceField( choices=[("low", "Low"), ("medium", "Medium"), ("high", "High"), ("critical", "Critical")], required=False, default="medium", ) # Location and timing location_text = serializers.CharField(max_length=500, required=False, allow_blank=True, default="") incident_datetime = serializers.DateTimeField(required=False, allow_null=True, default=None) # Reporter info (all optional - anonymous supported) contact_name = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") contact_phone = serializers.CharField(max_length=20, required=False, allow_blank=True, default="") contact_email = serializers.EmailField(required=False, allow_blank=True, default="") reporter_staff_id = serializers.CharField(max_length=50, required=False, allow_blank=True, default="") # Patient info patient_file_number = serializers.CharField(max_length=100, required=False, allow_blank=True, default="") def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") def validate_category(self, value): if value is None: return None from apps.observations.models import ObservationCategory try: return ObservationCategory.objects.get(id=value) except ObservationCategory.DoesNotExist: raise serializers.ValidationError("Observation category not found.") class ExternalObservationRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving an observation.""" reference_number = serializers.CharField(source="tracking_code", read_only=True) hospital_name = serializers.CharField(source="hospital.name", read_only=True, default=None) category_name = serializers.CharField(source="category.name", read_only=True, default=None) contact_name = serializers.CharField(source="reporter_name", read_only=True) contact_phone = serializers.CharField(source="reporter_phone", read_only=True) contact_email = serializers.CharField(source="reporter_email", read_only=True) class Meta: model = Observation fields = [ "id", "reference_number", "title", "description", "severity", "status", "location_text", "incident_datetime", "contact_name", "contact_phone", "contact_email", "reporter_staff_id", "patient_file_number", "hospital_name", "category_name", "created_at", "updated_at", ] read_only_fields = fields # --------------------------------------------------------------------------- # Appreciation # --------------------------------------------------------------------------- class ExternalAppreciationCreateSerializer(serializers.Serializer): """Public-facing fields for creating an appreciation via external API.""" # Contact contact_name = serializers.CharField(max_length=200, required=True) contact_phone = serializers.CharField(max_length=20, required=True) # Content message = serializers.CharField(required=True) # Hospital context hospital = serializers.CharField(max_length=200, required=True) def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") class ExternalAppreciationRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving an appreciation.""" hospital_name = serializers.CharField(source="hospital.name", read_only=True) class Meta: model = Appreciation fields = [ "id", "reference_number", "message_en", "status", "hospital_name", "is_anonymous", "created_at", "updated_at", ] read_only_fields = fields # --------------------------------------------------------------------------- # Suggestion (Feedback) # --------------------------------------------------------------------------- class ExternalSuggestionCreateSerializer(serializers.Serializer): """Public-facing fields for creating a suggestion via external API.""" # Contact contact_name = serializers.CharField(max_length=200, required=True) contact_phone = serializers.CharField(max_length=20, required=True) # Content title = serializers.CharField(max_length=500, required=False, allow_blank=True, default="") message = serializers.CharField(required=True) category = serializers.ChoiceField( choices=[ ("clinical_care", "Clinical Care"), ("staff_service", "Staff Service"), ("facility", "Facility"), ("communication", "Communication"), ("appointment", "Appointment"), ("billing", "Billing"), ("food_service", "Food Service"), ("cleanliness", "Cleanliness"), ("technology", "Technology"), ("general", "General"), ("other", "Other"), ], required=False, default="general", ) rating = serializers.IntegerField(min_value=1, max_value=5, required=False, allow_null=True, default=None) # Hospital context hospital = serializers.CharField(max_length=200, required=True) def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") class ExternalSuggestionRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving a suggestion.""" hospital_name = serializers.CharField(source="hospital.name", read_only=True) class Meta: model = Feedback fields = [ "id", "reference_number", "title", "message", "category", "rating", "status", "feedback_type", "sentiment", "contact_name", "contact_phone", "hospital_name", "created_at", "updated_at", ] read_only_fields = fields class ExternalComplaintSatisfactionSerializer(serializers.Serializer): satisfaction = serializers.ChoiceField( choices=[ ("satisfied", "Satisfied"), ("neutral", "Neutral"), ("dissatisfied", "Dissatisfied"), ("no_response", "No Response"), ], required=True, ) # --------------------------------------------------------------------------- # Doctor Rating # --------------------------------------------------------------------------- class ExternalDoctorRatingCreateSerializer(serializers.Serializer): hospital = serializers.CharField(max_length=200, required=True) doctor_id = serializers.CharField(max_length=50, required=True) doctor_name = serializers.CharField(max_length=300, required=False, allow_blank=True, default="") rating = serializers.IntegerField(min_value=1, max_value=5, required=True) feedback = serializers.CharField(required=False, allow_blank=True, default="") rating_date = serializers.DateField(required=False, allow_null=True, default=None) patient_uhid = serializers.CharField(max_length=100, required=False, allow_blank=True, default="") patient_name = serializers.CharField(max_length=300, required=False, allow_blank=True, default="") patient_type = serializers.ChoiceField( choices=["IP", "OP", "ER", "DC"], required=False, allow_blank=True, default="", ) department_code = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") admit_date = serializers.DateField(required=False, allow_null=True, default=None) discharge_date = serializers.DateField(required=False, allow_null=True, default=None) def validate_hospital(self, value): from apps.organizations.models import Hospital try: return Hospital.objects.get(name__iexact=value.strip()) except Hospital.DoesNotExist: raise serializers.ValidationError("Hospital not found.") def validate_doctor_id(self, value): return value.strip() def validate(self, data): from apps.organizations.models import Department, Staff from apps.physicians.adapter import DoctorRatingAdapter hospital = data.get("hospital") doctor_id = data.get("doctor_id", "").strip() # Resolve department code → human-readable name (the rating model stores # a text department_name; HIS sends a stable department_code instead). department_code = data.get("department_code", "").strip() data["department_name"] = "" dept = None if department_code and hospital: dept = Department.objects.filter( code__iexact=department_code, hospital=hospital, status="active" ).first() if not dept: raise serializers.ValidationError( {"department_code": f"Department with code '{department_code}' not found for this hospital."} ) data["department_name"] = dept.name_en or dept.name if hospital and doctor_id: doctor_name = (data.get("doctor_name") or "").strip() # 1. Canonical multi-strategy lookup (employee_id → license_number → name). staff = DoctorRatingAdapter.find_staff_by_doctor_id( doctor_id=doctor_id, hospital=hospital, doctor_name=doctor_name ) # 2. Auto-create the Staff if not found and we have a name to populate # the required first_name/last_name fields. if not staff: if not doctor_name: raise serializers.ValidationError( { "doctor_name": ( "Staff not found for this doctor_id; provide doctor_name " "to auto-create the staff record." ) } ) name_parts = doctor_name.split() first_name = name_parts[0] last_name = name_parts[-1] if len(name_parts) > 1 else name_parts[0] staff, created = Staff.objects.get_or_create( employee_id=doctor_id, defaults={ "hospital": hospital, "first_name": first_name, "last_name": last_name, "name": doctor_name, "staff_type": Staff.StaffType.PHYSICIAN, "job_title": "Physician", "physician": True, "status": "active", "department": dept, }, ) data["_staff_created"] = created data["_staff"] = staff return data class ExternalDoctorRatingRetrieveSerializer(serializers.ModelSerializer): """Read-only serializer for retrieving a doctor rating.""" hospital_name = serializers.CharField(source="hospital.name", read_only=True) staff_id = serializers.CharField(read_only=True, default=None) doctor_name_display = serializers.SerializerMethodField() class Meta: model = PhysicianIndividualRating fields = [ "id", "staff_id", "hospital_name", "source", "source_reference", "doctor_id", "doctor_name", "doctor_name_raw", "doctor_name_display", "department_name", "patient_uhid", "patient_name", "patient_type", "admit_date", "discharge_date", "rating", "feedback", "rating_date", "is_aggregated", "aggregated_at", "created_at", "updated_at", ] read_only_fields = fields def get_doctor_name_display(self, obj): return obj.doctor_name or obj.doctor_name_raw or ""