HH/apps/integrations/api_serializers.py
2026-07-11 19:24:28 +03:00

530 lines
20 KiB
Python

"""
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
# ---------------------------------------------------------------------------
# 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 (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 = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
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."})
dept_name = data.get("department")
if dept_name:
from apps.organizations.models import Department
try:
data["department"] = Department.objects.get(
name__iexact=dept_name.strip(), hospital=hospital, status="active"
)
except Department.DoesNotExist:
raise serializers.ValidationError(
{"department": f"Department '{dept_name}' not found for this hospital."}
)
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 = 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."})
dept_name = data.get("department")
if dept_name:
from apps.organizations.models import Department
try:
data["department"] = Department.objects.get(
name__iexact=dept_name.strip(), hospital=hospital, status="active"
)
except Department.DoesNotExist:
raise serializers.ValidationError(
{"department": f"Department '{dept_name}' not found for this hospital."}
)
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)
reference_number = serializers.SerializerMethodField()
class Meta:
model = Appreciation
fields = [
"id",
"reference_number",
"message_en",
"status",
"hospital_name",
"is_anonymous",
"created_at",
"updated_at",
]
read_only_fields = fields
def get_reference_number(self, obj):
if obj.metadata and "reference_number" in obj.metadata:
return obj.metadata["reference_number"]
return None
# ---------------------------------------------------------------------------
# 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)
reference_number = serializers.SerializerMethodField()
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
def get_reference_number(self, obj):
if obj.metadata and "reference_number" in obj.metadata:
return obj.metadata["reference_number"]
return None
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_id = serializers.IntegerField(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_name = 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_id(self, value):
from apps.organizations.models import Hospital
try:
return Hospital.objects.get(id=value)
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 Staff
hospital = data.get("hospital_id")
doctor_id = data.get("doctor_id", "").strip()
if hospital and doctor_id:
staff = Staff.objects.filter(
hospital=hospital, employee_id=doctor_id
).first()
if not staff:
raise serializers.ValidationError(
{
"doctor_id": f"Doctor with employee ID '{doctor_id}' not found at this hospital."
}
)
data["_staff"] = staff
return data