""" External API views for complaints, inquiries, observations, appreciations, and suggestions. All endpoints require X-API-Key header authentication. """ import logging from django.utils import timezone from rest_framework import generics, status from rest_framework.response import Response from rest_framework.views import APIView from apps.complaints.models import Complaint, ComplaintSourceType, ComplaintUpdate, Inquiry from apps.core.services import AuditService from apps.observations.models import Observation from apps.appreciation.models import Appreciation, AppreciationStatus, AppreciationVisibility from apps.feedback.models import Feedback, FeedbackType, FeedbackStatus, FeedbackCategory from .api_serializers import ( ExternalAppreciationCreateSerializer, ExternalAppreciationRetrieveSerializer, ExternalComplaintCreateSerializer, ExternalComplaintRetrieveSerializer, ExternalComplaintSatisfactionSerializer, ExternalDoctorRatingCreateSerializer, ExternalInquiryCreateSerializer, ExternalInquiryRetrieveSerializer, ExternalObservationCreateSerializer, ExternalObservationRetrieveSerializer, ExternalSuggestionCreateSerializer, ExternalSuggestionRetrieveSerializer, ) from .authentication import ExternalAPIKeyAuthentication logger = logging.getLogger("apps.integrations") # --------------------------------------------------------------------------- # Base mixin # --------------------------------------------------------------------------- class ExternalAPIBase: """Shared behaviour for all external API views.""" authentication_classes = [ExternalAPIKeyAuthentication] permission_classes = [] entity_name = None # Override in subclass def check_entity_access(self): """Ensure the API key is allowed to access this entity.""" api_key = getattr(self.request, "api_key", None) if api_key and not api_key.can_access(self.entity_name): return Response( {"detail": f"This API key does not have access to {self.entity_name}."}, status=status.HTTP_403_FORBIDDEN, ) return None def filter_queryset_by_hospital(self, qs): """Scope query to the API key's hospital (if set).""" api_key = getattr(self.request, "api_key", None) if api_key and api_key.hospital: hospital_field = self._get_hospital_field() if hospital_field: qs = qs.filter(**{hospital_field: api_key.hospital}) return qs def _get_hospital_field(self): """Return the ORM field name for hospital on the model.""" return "hospital" def apply_list_filters(self, qs, request): """Apply date range and status filters from query params.""" created_from = request.query_params.get("created_from", "").strip() created_to = request.query_params.get("created_to", "").strip() updated_from = request.query_params.get("updated_from", "").strip() updated_to = request.query_params.get("updated_to", "").strip() status_val = request.query_params.get("status", "").strip() if created_from: try: qs = qs.filter(created_at__gte=self._parse_date(created_from, start=True)) except (ValueError, TypeError): pass if created_to: try: qs = qs.filter(created_at__lte=self._parse_date(created_to, start=False)) except (ValueError, TypeError): pass if updated_from: try: qs = qs.filter(updated_at__gte=self._parse_date(updated_from, start=True)) except (ValueError, TypeError): pass if updated_to: try: qs = qs.filter(updated_at__lte=self._parse_date(updated_to, start=False)) except (ValueError, TypeError): pass if status_val: qs = qs.filter(status=status_val) return qs def _parse_date(self, value, start=True): """Parse a date string. If date-only, start=True → 00:00, start=False → 23:59:59.""" from datetime import datetime from django.utils import timezone as tz for fmt in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M", "%Y-%m-%d"): try: dt = datetime.strptime(value, fmt) if fmt == "%Y-%m-%d" and not start: dt = dt.replace(hour=23, minute=59, second=59) return tz.make_aware(dt) if tz.is_naive(dt) else dt except ValueError: continue raise ValueError(f"Invalid date format: {value}") # --------------------------------------------------------------------------- # Complaints # --------------------------------------------------------------------------- class ExternalComplaintCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/complaints/ Create a complaint via external API. """ entity_name = "complaints" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalComplaintCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data # Enforce hospital scoping api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and data["hospital"] != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) # Reference number generated by Complaint.save() (unified CMP-YYYYMM-NNNN) complaint = Complaint.objects.create( patient=None, hospital=data["hospital"], department=data.get("department"), section=data.get("section"), area=data.get("area"), location_type=data.get("location_type", ""), title=data["title"], description=data["description"], severity="medium", priority="medium", status="open", complaint_source_type=ComplaintSourceType.EXTERNAL, contact_name=data["contact_name"], contact_phone=data["contact_phone"], contact_email=data.get("contact_email", ""), relation_to_patient=data.get("relation_to_patient", ""), patient_name=data.get("patient_name", ""), national_id=data.get("national_id", ""), incident_date=data.get("incident_date"), expected_result=data.get("expected_result", ""), metadata={"source": "external_api"}, ) ComplaintUpdate.objects.create( complaint=complaint, update_type="note", message="Complaint submitted via external API.", ) reference_number = complaint.reference_number # Trigger background tasks try: from apps.complaints.tasks import analyze_complaint_with_ai, notify_staff_new_item analyze_complaint_with_ai.delay(str(complaint.id)) notify_staff_new_item.delay("complaint", str(complaint.id)) except Exception: pass AuditService.log_event( event_type="external_complaint_created", description=f"Complaint created via external API: {reference_number}", content_object=complaint, metadata={"reference": reference_number, "source": "external_api"}, ) return Response( { "success": True, "reference_number": reference_number, "status": complaint.status, }, status=status.HTTP_201_CREATED, ) class ExternalComplaintRetrieveView(ExternalAPIBase, APIView): """ GET /api/v1/external/complaints/ GET /api/v1/external/complaints// List complaints (scoped to hospital) or retrieve a single complaint by reference number. """ entity_name = "complaints" def get(self, request, reference_number=None): access_err = self.check_entity_access() if access_err: return access_err if reference_number: return self._retrieve(request, reference_number) return self._list(request) def _retrieve(self, request, reference_number): qs = Complaint.all_objects.filter(reference_number=reference_number) qs = self.filter_queryset_by_hospital(qs) complaint = qs.first() if not complaint: return Response({"detail": "Complaint not found."}, status=status.HTTP_404_NOT_FOUND) ser = ExternalComplaintRetrieveSerializer(complaint) return Response(ser.data) def _list(self, request): qs = Complaint.all_objects.all() qs = self.filter_queryset_by_hospital(qs) qs = self.apply_list_filters(qs, request) page_size = min(int(request.query_params.get("page_size", 20)), 100) page = int(request.query_params.get("page", 1)) offset = (page - 1) * page_size total = qs.count() items = qs[offset : offset + page_size] ser = ExternalComplaintRetrieveSerializer(items, many=True) return Response( { "count": total, "page": page, "page_size": page_size, "results": ser.data, } ) class ExternalComplaintSatisfactionView(ExternalAPIBase, APIView): """ PATCH /api/v1/external/complaints//satisfaction/ Set patient satisfaction for a complaint. """ entity_name = "complaints" def patch(self, request, reference_number): access_err = self.check_entity_access() if access_err: return access_err qs = Complaint.all_objects.filter(reference_number=reference_number) qs = self.filter_queryset_by_hospital(qs) complaint = qs.first() if not complaint: return Response({"detail": "Complaint not found."}, status=status.HTTP_404_NOT_FOUND) if not complaint.resolution: return Response( {"detail": "Cannot set satisfaction before a resolution is recorded."}, status=status.HTTP_400_BAD_REQUEST, ) ser = ExternalComplaintSatisfactionSerializer(data=request.data) ser.is_valid(raise_exception=True) complaint.satisfaction = ser.validated_data["satisfaction"] complaint.satisfaction_set_at = timezone.now() complaint.save(update_fields=["satisfaction", "satisfaction_set_at", "updated_at"]) AuditService.log_event( event_type="external_complaint_satisfaction_set", description=f"Satisfaction set to '{complaint.satisfaction}' via external API for {reference_number}", content_object=complaint, metadata={"reference": reference_number, "satisfaction": complaint.satisfaction, "source": "external_api"}, ) return Response( { "success": True, "reference_number": complaint.reference_number, "satisfaction": complaint.satisfaction, } ) # --------------------------------------------------------------------------- # Inquiries # --------------------------------------------------------------------------- class ExternalInquiryCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/inquiries/ Create an inquiry via external API. """ entity_name = "inquiries" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalInquiryCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and data["hospital"] != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) import uuid from datetime import datetime today = datetime.now().strftime("%Y%m%d") random_suffix = str(uuid.uuid4().int)[:6] reference_number = f"INQ-{today}-{random_suffix}" inquiry = Inquiry.objects.create( patient=None, hospital=data["hospital"], department=data.get("department"), section=data.get("section"), area=data.get("area"), location_type=data.get("location_type", ""), subject=data["subject"], message=data["message"], category=data.get("category", "general"), contact_name=data["contact_name"], contact_phone=data["contact_phone"], contact_email=data.get("contact_email", ""), status="open", reference_number=reference_number, is_outgoing=False, metadata={"source": "external_api"}, ) try: from apps.complaints.tasks import analyze_inquiry_with_ai, notify_staff_new_item analyze_inquiry_with_ai.delay(str(inquiry.id)) notify_staff_new_item.delay("inquiry", str(inquiry.id)) except Exception: pass AuditService.log_event( event_type="external_inquiry_created", description=f"Inquiry created via external API: {reference_number}", content_object=inquiry, metadata={"reference": reference_number, "source": "external_api"}, ) return Response( { "success": True, "reference_number": reference_number, "status": inquiry.status, }, status=status.HTTP_201_CREATED, ) class ExternalInquiryRetrieveView(ExternalAPIBase, APIView): """ GET /api/v1/external/inquiries/ GET /api/v1/external/inquiries// """ entity_name = "inquiries" def get(self, request, reference_number=None): access_err = self.check_entity_access() if access_err: return access_err if reference_number: return self._retrieve(request, reference_number) return self._list(request) def _retrieve(self, request, reference_number): qs = Inquiry.all_objects.filter(reference_number=reference_number) qs = self.filter_queryset_by_hospital(qs) inquiry = qs.first() if not inquiry: return Response({"detail": "Inquiry not found."}, status=status.HTTP_404_NOT_FOUND) ser = ExternalInquiryRetrieveSerializer(inquiry) return Response(ser.data) def _list(self, request): qs = Inquiry.all_objects.all() qs = self.filter_queryset_by_hospital(qs) qs = self.apply_list_filters(qs, request) page_size = min(int(request.query_params.get("page_size", 20)), 100) page = int(request.query_params.get("page", 1)) offset = (page - 1) * page_size total = qs.count() items = qs[offset : offset + page_size] ser = ExternalInquiryRetrieveSerializer(items, many=True) return Response( { "count": total, "page": page, "page_size": page_size, "results": ser.data, } ) # --------------------------------------------------------------------------- # Observations # --------------------------------------------------------------------------- class ExternalObservationCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/observations/ Create an observation via external API. """ entity_name = "observations" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalObservationCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and data["hospital"] != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) from django.utils import timezone as tz observation = Observation.objects.create( hospital=data["hospital"], category=data.get("category"), title=data.get("title", ""), description=data["description"], severity=data.get("severity", "medium"), location_text=data.get("location_text", ""), incident_datetime=data.get("incident_datetime") or tz.now(), reporter_staff_id=data.get("reporter_staff_id", ""), reporter_name=data.get("contact_name", ""), reporter_phone=data.get("contact_phone", ""), reporter_email=data.get("contact_email", ""), patient_file_number=data.get("patient_file_number", ""), source="other", status="new", metadata={"source": "external_api"}, ) try: from apps.complaints.tasks import notify_staff_new_item notify_staff_new_item.delay("observation", str(observation.id)) except Exception: pass AuditService.log_event( event_type="external_observation_created", description=f"Observation created via external API: {observation.tracking_code}", content_object=observation, metadata={"reference_number": observation.tracking_code, "source": "external_api"}, ) return Response( { "success": True, "reference_number": observation.tracking_code, "status": observation.status, }, status=status.HTTP_201_CREATED, ) class ExternalObservationRetrieveView(ExternalAPIBase, APIView): """ GET /api/v1/external/observations/ GET /api/v1/external/observations// """ entity_name = "observations" def get(self, request, tracking_code=None): access_err = self.check_entity_access() if access_err: return access_err if tracking_code: return self._retrieve(request, tracking_code) return self._list(request) def _retrieve(self, request, tracking_code): qs = Observation.all_objects.filter(tracking_code=tracking_code) qs = self.filter_queryset_by_hospital(qs) observation = qs.first() if not observation: return Response({"detail": "Observation not found."}, status=status.HTTP_404_NOT_FOUND) ser = ExternalObservationRetrieveSerializer(observation) return Response(ser.data) def _list(self, request): qs = Observation.all_objects.all() qs = self.filter_queryset_by_hospital(qs) qs = self.apply_list_filters(qs, request) page_size = min(int(request.query_params.get("page_size", 20)), 100) page = int(request.query_params.get("page", 1)) offset = (page - 1) * page_size total = qs.count() items = qs[offset : offset + page_size] ser = ExternalObservationRetrieveSerializer(items, many=True) return Response( { "count": total, "page": page, "page_size": page_size, "results": ser.data, } ) # --------------------------------------------------------------------------- # Appreciations # --------------------------------------------------------------------------- class ExternalAppreciationCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/appreciations/ Create an appreciation via external API. """ entity_name = "appreciations" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalAppreciationCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and data["hospital"] != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) appreciation = Appreciation.objects.create( hospital=data["hospital"], message_en=data["message"], message_ar="", category=None, is_anonymous=False, status=AppreciationStatus.DRAFT, visibility=AppreciationVisibility.PUBLIC, metadata={ "source": "external_api", "submitted_by_name": data["contact_name"], "submitted_by_phone": data["contact_phone"], }, ) try: from apps.complaints.tasks import notify_staff_new_item notify_staff_new_item.delay("appreciation", str(appreciation.id)) except Exception: pass AuditService.log_event( event_type="external_appreciation_created", description=f"Appreciation created via external API", content_object=appreciation, metadata={"source": "external_api"}, ) return Response( { "success": True, "reference_number": appreciation.reference_number, "status": appreciation.status, }, status=status.HTTP_201_CREATED, ) class ExternalAppreciationRetrieveView(ExternalAPIBase, APIView): """ GET /api/v1/external/appreciations/ GET /api/v1/external/appreciations// """ entity_name = "appreciations" def get(self, request, reference_number=None): access_err = self.check_entity_access() if access_err: return access_err if reference_number: return self._retrieve(request, reference_number) return self._list(request) def _retrieve(self, request, reference_number): qs = Appreciation.all_objects.filter(reference_number=reference_number) qs = self.filter_queryset_by_hospital(qs) appreciation = qs.first() if not appreciation: return Response({"detail": "Appreciation not found."}, status=status.HTTP_404_NOT_FOUND) ser = ExternalAppreciationRetrieveSerializer(appreciation) return Response(ser.data) def _list(self, request): qs = Appreciation.all_objects.all() qs = self.filter_queryset_by_hospital(qs) qs = self.apply_list_filters(qs, request) page_size = min(int(request.query_params.get("page_size", 20)), 100) page = int(request.query_params.get("page", 1)) offset = (page - 1) * page_size total = qs.count() items = qs[offset : offset + page_size] ser = ExternalAppreciationRetrieveSerializer(items, many=True) return Response( { "count": total, "page": page, "page_size": page_size, "results": ser.data, } ) # --------------------------------------------------------------------------- # Suggestions (Feedback) # --------------------------------------------------------------------------- class ExternalSuggestionCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/suggestions/ Create a suggestion via external API. """ entity_name = "suggestions" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalSuggestionCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and data["hospital"] != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) category_map = { "general": FeedbackCategory.OTHER, "clinical_care": FeedbackCategory.CLINICAL_CARE, "facility": FeedbackCategory.FACILITY, "staff_service": FeedbackCategory.STAFF_SERVICE, "communication": FeedbackCategory.COMMUNICATION, "technology": FeedbackCategory.TECHNOLOGY, "food_service": FeedbackCategory.FOOD_SERVICE, "appointment": FeedbackCategory.APPOINTMENT, "other": FeedbackCategory.OTHER, } title = data.get("title") or data["message"][:100] feedback = Feedback.objects.create( hospital=data["hospital"], feedback_type=FeedbackType.SUGGESTION, title=title, message=data["message"], category=category_map.get(data.get("category", "general"), FeedbackCategory.OTHER), contact_name=data["contact_name"], contact_phone=data["contact_phone"], rating=data.get("rating"), is_anonymous=False, status=FeedbackStatus.SUBMITTED, metadata={ "source": "external_api", "suggestion_area": data.get("category", "general"), }, ) try: from apps.feedback.tasks import analyze_suggestion_with_ai from apps.complaints.tasks import notify_staff_new_item analyze_suggestion_with_ai.delay(str(feedback.id)) notify_staff_new_item.delay("suggestion", str(feedback.id)) except Exception: pass AuditService.log_event( event_type="external_suggestion_created", description=f"Suggestion created via external API", content_object=feedback, metadata={"source": "external_api"}, ) return Response( { "success": True, "reference_number": feedback.reference_number, "status": feedback.status, }, status=status.HTTP_201_CREATED, ) class ExternalSuggestionRetrieveView(ExternalAPIBase, APIView): """ GET /api/v1/external/suggestions/ GET /api/v1/external/suggestions// """ entity_name = "suggestions" def get(self, request, reference_number=None): access_err = self.check_entity_access() if access_err: return access_err if reference_number: return self._retrieve(request, reference_number) return self._list(request) def _retrieve(self, request, reference_number): qs = Feedback.all_objects.filter(reference_number=reference_number) qs = self.filter_queryset_by_hospital(qs) feedback = qs.first() if not feedback: return Response({"detail": "Suggestion not found."}, status=status.HTTP_404_NOT_FOUND) ser = ExternalSuggestionRetrieveSerializer(feedback) return Response(ser.data) def _list(self, request): qs = Feedback.all_objects.all() qs = self.filter_queryset_by_hospital(qs) qs = self.apply_list_filters(qs, request) page_size = min(int(request.query_params.get("page_size", 20)), 100) page = int(request.query_params.get("page", 1)) offset = (page - 1) * page_size total = qs.count() items = qs[offset : offset + page_size] ser = ExternalSuggestionRetrieveSerializer(items, many=True) return Response( { "count": total, "page": page, "page_size": page_size, "results": ser.data, } ) # --------------------------------------------------------------------------- # Lookup / Dropdown endpoints # --------------------------------------------------------------------------- class ExternalHospitalsListView(ExternalAPIBase, APIView): """ GET /api/v1/external/hospitals/ List all active hospitals. Used by external integrations to build dropdowns. """ entity_name = "lookup" def get(self, request): from apps.organizations.models import Hospital qs = Hospital.objects.filter(status="active").order_by("name") results = [ { "id": str(h.id), "name": h.name, "code": h.code, } for h in qs ] return Response({"count": len(results), "results": results}) class ExternalLocationTypesView(ExternalAPIBase, APIView): """ GET /api/v1/external/location-types/ Static list of location types. """ entity_name = "lookup" def get(self, request): results = [ {"value": "OP", "label": "Outpatient", "label_ar": "خارجي"}, {"value": "IP", "label": "Inpatient", "label_ar": "تنويم"}, {"value": "ER", "label": "Emergency", "label_ar": "طوارئ"}, {"value": "GENERAL", "label": "General", "label_ar": "عام"}, ] return Response({"count": len(results), "results": results}) class ExternalAreasListView(ExternalAPIBase, APIView): """ GET /api/v1/external/areas/?hospital=&location_type= List areas for a hospital, optionally filtered by location_type. """ entity_name = "lookup" def get(self, request): from apps.organizations.models import Area, Hospital hospital_name = request.query_params.get("hospital", "").strip() location_type = request.query_params.get("location_type", "").strip() if not hospital_name: return Response( {"detail": "hospital query parameter is required."}, status=status.HTTP_400_BAD_REQUEST, ) try: hospital = Hospital.objects.get(name__iexact=hospital_name, status="active") except Hospital.DoesNotExist: return Response({"detail": "Hospital not found."}, status=status.HTTP_404_NOT_FOUND) qs = Area.objects.filter(hospital=hospital, status="active") if location_type: qs = qs.filter(location_type=location_type) qs = qs.order_by("name_en") results = [ { "id": str(a.id), "name": a.name_en, "name_ar": a.name_ar, "code": a.code, } for a in qs ] return Response({"count": len(results), "results": results}) class ExternalDepartmentsListView(ExternalAPIBase, APIView): """ GET /api/v1/external/departments/?hospital= List departments for a hospital. """ entity_name = "lookup" def get(self, request): from apps.organizations.models import Department, Hospital hospital_name = request.query_params.get("hospital", "").strip() if not hospital_name: return Response( {"detail": "hospital query parameter is required."}, status=status.HTTP_400_BAD_REQUEST, ) try: hospital = Hospital.objects.get(name__iexact=hospital_name, status="active") except Hospital.DoesNotExist: return Response({"detail": "Hospital not found."}, status=status.HTTP_404_NOT_FOUND) qs = Department.objects.filter(hospital=hospital, status="active").order_by("name") results = [ { "id": str(d.id), "name": d.name, "name_en": d.name_en, "name_ar": d.name_ar, "code": d.code, } for d in qs ] return Response({"count": len(results), "results": results}) class ExternalSectionsListView(ExternalAPIBase, APIView): """ GET /api/v1/external/sections/?hospital=&department= List sections for a department. """ entity_name = "lookup" def get(self, request): from apps.organizations.models import Department, Hospital, Section hospital_name = request.query_params.get("hospital", "").strip() department_name = request.query_params.get("department", "").strip() if not hospital_name or not department_name: return Response( {"detail": "hospital and department query parameters are required."}, status=status.HTTP_400_BAD_REQUEST, ) try: hospital = Hospital.objects.get(name__iexact=hospital_name, status="active") except Hospital.DoesNotExist: return Response({"detail": "Hospital not found."}, status=status.HTTP_404_NOT_FOUND) try: department = Department.objects.get( name__iexact=department_name, hospital=hospital, status="active" ) except Department.DoesNotExist: return Response({"detail": "Department not found."}, status=status.HTTP_404_NOT_FOUND) qs = Section.objects.filter(department=department, status="active").order_by("name_en") results = [ { "id": str(s.id), "name": s.name_en, "name_ar": s.name_ar, "code": s.code, } for s in qs ] return Response({"count": len(results), "results": results}) # --------------------------------------------------------------------------- # Doctor Ratings # --------------------------------------------------------------------------- class ExternalDoctorRatingCreateView(ExternalAPIBase, APIView): """ POST /api/v1/external/doctor-ratings/ Submit a patient rating for a doctor via external API. Requires doctor_id (employee_id) that exists in the Staff table for the given hospital. """ entity_name = "doctor_ratings" def post(self, request): access_err = self.check_entity_access() if access_err: return access_err ser = ExternalDoctorRatingCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data hospital = data["hospital_id"] staff = data.pop("_staff") api_key = getattr(request, "api_key", None) if api_key and api_key.hospital and hospital != api_key.hospital: return Response( {"detail": "Hospital does not match API key scope."}, status=status.HTTP_403_FORBIDDEN, ) from django.utils import timezone as tz from apps.physicians.models import PhysicianIndividualRating rating_date = data.get("rating_date") if rating_date: from datetime import datetime as _dt rating_dt = tz.make_aware(_dt.combine(rating_date, _dt.min.time())) else: rating_dt = tz.now() doctor_name_raw = data.get("doctor_name", "") or staff.get_full_name() rating = PhysicianIndividualRating.objects.create( staff=staff, hospital=hospital, source="his_api", doctor_name_raw=doctor_name_raw, doctor_id=data["doctor_id"], doctor_name=data.get("doctor_name", ""), rating=data["rating"], feedback=data.get("feedback", ""), rating_date=rating_dt, patient_uhid=data.get("patient_uhid", "") or None, patient_name=data.get("patient_name", "") or None, patient_type=data.get("patient_type", "") or None, department_name=data.get("department_name", ""), admit_date=data.get("admit_date"), discharge_date=data.get("discharge_date"), metadata={"source": "external_api"}, ) if not staff.physician: staff.physician = True staff.save(update_fields=["physician"]) AuditService.log_event( event_type="external_doctor_rating_created", description=f"Doctor rating created via external API: {staff.get_full_name()} - {data['rating']}/5", content_object=rating, metadata={ "staff_id": str(staff.id), "doctor_id": data["doctor_id"], "rating": data["rating"], "hospital_id": str(hospital.id), "source": "external_api", }, ) return Response( { "success": True, "rating_id": str(rating.id), "staff_id": str(staff.id), "doctor_name": staff.get_full_name(), "rating": data["rating"], }, status=status.HTTP_201_CREATED, )