diff --git a/care/abdm/api/serializers/health_facility.py b/care/abdm/api/serializers/health_facility.py index b624047266..336f348584 100644 --- a/care/abdm/api/serializers/health_facility.py +++ b/care/abdm/api/serializers/health_facility.py @@ -5,6 +5,7 @@ class HealthFacilitySerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) + registered = serializers.BooleanField(read_only=True) class Meta: model = HealthFacility diff --git a/care/abdm/api/viewsets/health_facility.py b/care/abdm/api/viewsets/health_facility.py index 66acc4e741..5fb41b5b4d 100644 --- a/care/abdm/api/viewsets/health_facility.py +++ b/care/abdm/api/viewsets/health_facility.py @@ -1,4 +1,6 @@ -from django.shortcuts import get_object_or_404 +from celery import shared_task +from dry_rest_permissions.generics import DRYPermissions +from rest_framework.decorators import action from rest_framework.mixins import ( CreateModelMixin, ListModelMixin, @@ -15,6 +17,36 @@ from care.utils.queryset.facility import get_facility_queryset +@shared_task +def register_health_facility_as_service(facility_external_id): + health_facility = HealthFacility.objects.filter( + facility__external_id=facility_external_id + ).first() + + if not health_facility: + return False + + if health_facility.registered: + return True + + response = Bridge().add_update_service( + { + "id": health_facility.hf_id, + "name": health_facility.facility.name, + "type": "HIP", + "active": True, + "alias": ["CARE_HIP"], + } + ) + + if response.status_code == 200: + health_facility.registered = True + health_facility.save() + return True + + return False + + class HealthFacilityViewSet( GenericViewSet, CreateModelMixin, @@ -25,7 +57,7 @@ class HealthFacilityViewSet( serializer_class = HealthFacilitySerializer model = HealthFacility queryset = HealthFacility.objects.all() - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, DRYPermissions) lookup_field = "facility__external_id" def get_queryset(self): @@ -33,40 +65,17 @@ def get_queryset(self): facilities = get_facility_queryset(self.request.user) return queryset.filter(facility__in=facilities) - def get_facility(self, facility_external_id): - facilities = get_facility_queryset(self.request.user) - return get_object_or_404(facilities.filter(external_id=facility_external_id)) - - def link_health_facility(self, hf_id, facility_id): - facility = self.get_facility(facility_id) - return Bridge().add_update_service( - { - "id": hf_id, - "name": facility.name, - "type": "HIP", - "active": True, - "alias": ["CARE_HIP"], - } - ) - - def create(self, request, *args, **kwargs): - if ( - self.link_health_facility( - request.data["hf_id"], request.data["facility"] - ).status_code - == 200 - ): - return super().create(request, *args, **kwargs) + @action(detail=True, methods=["POST"]) + def register_service(self, request, facility__external_id): + registered = register_health_facility_as_service(facility__external_id) - return Response({"message": "Error linking health facility"}, status=400) + return Response({"registered": registered}) - def update(self, request, *args, **kwargs): - if ( - self.link_health_facility( - request.data["hf_id"], kwargs["facility__external_id"] - ).status_code - == 200 - ): - return super().update(request, *args, **kwargs) + def perform_create(self, serializer): + instance = serializer.save() + register_health_facility_as_service.delay(instance.facility.external_id) - return Response({"message": "Error linking health facility"}, status=400) + def perform_update(self, serializer): + serializer.validated_data["registered"] = False + instance = serializer.save() + register_health_facility_as_service.delay(instance.facility.external_id) diff --git a/care/abdm/migrations/0010_healthfacility_registered.py b/care/abdm/migrations/0010_healthfacility_registered.py new file mode 100644 index 0000000000..5a5d753925 --- /dev/null +++ b/care/abdm/migrations/0010_healthfacility_registered.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.2 on 2023-09-05 06:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("abdm", "0009_healthfacility"), + ] + + operations = [ + migrations.AddField( + model_name="healthfacility", + name="registered", + field=models.BooleanField(default=False), + ), + ] diff --git a/care/abdm/models.py b/care/abdm/models.py index eafd5af1f7..e02bfad1b6 100644 --- a/care/abdm/models.py +++ b/care/abdm/models.py @@ -4,6 +4,7 @@ from django.db import models +from care.abdm.permissions import HealthFacilityPermissions from care.utils.models.base import BaseModel @@ -37,8 +38,9 @@ def __str__(self): return self.abha_number -class HealthFacility(BaseModel): +class HealthFacility(BaseModel, HealthFacilityPermissions): hf_id = models.CharField(max_length=50, unique=True) + registered = models.BooleanField(default=False) facility = models.OneToOneField( "facility.Facility", on_delete=models.PROTECT, to_field="external_id" ) diff --git a/care/abdm/permissions.py b/care/abdm/permissions.py new file mode 100644 index 0000000000..f1ccd0045e --- /dev/null +++ b/care/abdm/permissions.py @@ -0,0 +1,29 @@ +from care.facility.models.mixins.permissions.base import BasePermissionMixin +from care.users.models import User + + +class HealthFacilityPermissions(BasePermissionMixin): + """ + Permissions for HealthFacilityViewSet + """ + + def has_object_read_permission(self, request): + return self.facility.has_object_read_permission(request) + + def has_object_write_permission(self, request): + allowed_user_types = [ + User.TYPE_VALUE_MAP["WardAdmin"], + User.TYPE_VALUE_MAP["LocalBodyAdmin"], + User.TYPE_VALUE_MAP["DistrictAdmin"], + User.TYPE_VALUE_MAP["StateAdmin"], + ] + return request.user.is_superuser or ( + request.user.user_type in allowed_user_types + and self.facility.has_object_write_permission(request) + ) + + def has_object_update_permission(self, request): + return self.has_object_write_permission(request) + + def has_object_destroy_permission(self, request): + return self.has_object_write_permission(request) diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 4178ab75cd..32a77ee55c 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -361,10 +361,14 @@ def validate(self, attrs): ) from care.facility.static_data.icd11 import ICDDiseases + final_diagnosis = [] + provisional_diagnosis = [] + if "icd11_diagnoses" in validated: for diagnosis in validated["icd11_diagnoses"]: try: ICDDiseases.by.id[diagnosis] + final_diagnosis.append(diagnosis) except BaseException: raise ValidationError( { @@ -378,6 +382,7 @@ def validate(self, attrs): for diagnosis in validated["icd11_provisional_diagnoses"]: try: ICDDiseases.by.id[diagnosis] + provisional_diagnosis.append(diagnosis) except BaseException: raise ValidationError( { @@ -386,6 +391,41 @@ def validate(self, attrs): ] } ) + + if ( + "icd11_principal_diagnosis" in validated + and validated.get("suggestion") != SuggestionChoices.DD + ): + if len(final_diagnosis): + if validated["icd11_principal_diagnosis"] not in final_diagnosis: + raise ValidationError( + { + "icd11_principal_diagnosis": [ + "Principal Diagnosis must be one of the Final Diagnosis" + ] + } + ) + elif len(provisional_diagnosis): + if validated["icd11_principal_diagnosis"] not in provisional_diagnosis: + raise ValidationError( + { + "icd11_principal_diagnosis": [ + "Principal Diagnosis must be one of the Provisional Diagnosis" + ] + } + ) + else: + raise ValidationError( + { + "icd11_diagnoses": [ + "Atleast one diagnosis is required for final diagnosis" + ], + "icd11_provisional_diagnoses": [ + "Atleast one diagnosis is required for provisional diagnosis" + ], + } + ) + return validated @@ -525,10 +565,11 @@ def create(self, validated_data): class PatientConsultationIDSerializer(serializers.ModelSerializer): consultation_id = serializers.UUIDField(source="external_id", read_only=True) patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) + bed_id = serializers.UUIDField(source="current_bed.bed.external_id", read_only=True) class Meta: model = PatientConsultation - fields = ("consultation_id", "patient_id") + fields = ("consultation_id", "patient_id", "bed_id") class EmailDischargeSummarySerializer(serializers.Serializer): diff --git a/care/facility/api/serializers/prescription.py b/care/facility/api/serializers/prescription.py index 61ac5d0a16..4af84080ea 100644 --- a/care/facility/api/serializers/prescription.py +++ b/care/facility/api/serializers/prescription.py @@ -58,6 +58,23 @@ def validate(self, attrs): MedibaseMedicine, external_id=attrs["medicine"] ) + if not self.instance: + if Prescription.objects.filter( + consultation__external_id=self.context["request"].parser_context[ + "kwargs" + ]["consultation_external_id"], + medicine=attrs["medicine"], + discontinued=False, + ).exists(): + raise serializers.ValidationError( + { + "medicine": ( + "This medicine is already prescribed to this patient. " + "Please discontinue the existing prescription to prescribe again." + ) + } + ) + if attrs.get("is_prn"): if not attrs.get("indicator"): raise serializers.ValidationError( diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index b294b8dc52..c18d353bd5 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -103,7 +103,7 @@ def discontinue(self, request, *args, **kwargs): "discontinued_reason", None ) prescription_obj.save() - return Response({}, status=status.HTTP_201_CREATED) + return Response({}, status=status.HTTP_200_OK) @extend_schema(tags=["prescriptions"]) @action( diff --git a/care/facility/migrations/0383_patientconsultation_icd11_principal_diagnosis.py b/care/facility/migrations/0383_patientconsultation_icd11_principal_diagnosis.py new file mode 100644 index 0000000000..75b53735d9 --- /dev/null +++ b/care/facility/migrations/0383_patientconsultation_icd11_principal_diagnosis.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.2 on 2023-09-04 09:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0382_assetservice_remove_asset_last_serviced_on_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="patientconsultation", + name="icd11_principal_diagnosis", + field=models.CharField(blank=True, default="", max_length=100, null=True), + ), + ] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index ed5f875ab7..bc259921b1 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -64,6 +64,9 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): icd11_diagnoses = ArrayField( models.CharField(max_length=100), default=list, blank=True, null=True ) + icd11_principal_diagnosis = models.CharField( + max_length=100, default="", blank=True, null=True + ) symptoms = MultiSelectField( choices=SYMPTOM_CHOICES, default=1, diff --git a/care/facility/tests/test_prescriptions_api.py b/care/facility/tests/test_prescriptions_api.py new file mode 100644 index 0000000000..869120d114 --- /dev/null +++ b/care/facility/tests/test_prescriptions_api.py @@ -0,0 +1,61 @@ +from rest_framework import status + +from care.facility.models import MedibaseMedicine +from care.utils.tests.test_base import TestBase + + +class PrescriptionsApiTestCase(TestBase): + def setUp(self) -> None: + super().setUp() + self.medicine = MedibaseMedicine.objects.first() + + self.normal_prescription_data = { + "medicine": self.medicine.external_id, + "prescription_type": "REGULAR", + "dosage": "1 mg", + "frequency": "OD", + "is_prn": False, + } + + def test_create_normal_prescription(self): + consultation = self.create_consultation() + response = self.client.post( + f"/api/v1/consultation/{consultation.external_id}/prescriptions/", + self.normal_prescription_data, + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_prescribe_duplicate_active_medicine_and_discontinue(self): + """ + 1. Creates a prescription with Medicine A + 2. Attempts to create another prescription with Medicine A (expecting failure) + 3. Discontinues the first prescription + 4. Re-attempts to create another prescription with Medicine A (expecting success) + """ + consultation = self.create_consultation() + res = self.client.post( + f"/api/v1/consultation/{consultation.external_id}/prescriptions/", + self.normal_prescription_data, + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + discontinue_prescription_id = res.data["id"] + + res = self.client.post( + f"/api/v1/consultation/{consultation.external_id}/prescriptions/", + self.normal_prescription_data, + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + res = self.client.post( + f"/api/v1/consultation/{consultation.external_id}/prescriptions/{discontinue_prescription_id}/discontinue/", + { + "discontinued_reason": "Test Reason", + }, + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + + res = self.client.post( + f"/api/v1/consultation/{consultation.external_id}/prescriptions/", + self.normal_prescription_data, + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) diff --git a/care/facility/utils/reports/discharge_summary.py b/care/facility/utils/reports/discharge_summary.py index bf5f2ed27d..072ca43266 100644 --- a/care/facility/utils/reports/discharge_summary.py +++ b/care/facility/utils/reports/discharge_summary.py @@ -56,6 +56,11 @@ def get_discharge_summary_data(consultation: PatientConsultation): provisional_diagnosis = get_icd11_diagnoses_objects_by_ids( consultation.icd11_provisional_diagnoses ) + principal_diagnosis = get_icd11_diagnoses_objects_by_ids( + [consultation.icd11_principal_diagnosis] + if consultation.icd11_principal_diagnosis + else [] + ) investigations = InvestigationValue.objects.filter( Q(consultation=consultation.id) & (Q(value__isnull=False) | Q(notes__isnull=False)) @@ -94,6 +99,7 @@ def get_discharge_summary_data(consultation: PatientConsultation): "hcx": hcx, "diagnosis": diagnosis, "provisional_diagnosis": provisional_diagnosis, + "principal_diagnosis": principal_diagnosis, "consultation": consultation, "prescriptions": prescriptions, "prn_prescriptions": prn_prescriptions, diff --git a/care/templates/reports/patient_discharge_summary_pdf.html b/care/templates/reports/patient_discharge_summary_pdf.html index fca8b51c1a..a2263c25a4 100644 --- a/care/templates/reports/patient_discharge_summary_pdf.html +++ b/care/templates/reports/patient_discharge_summary_pdf.html @@ -247,6 +247,39 @@

{% endif %} + {% if principal_diagnosis %} +

+ Principal Diagnosis (as per ICD-11 recommended by WHO): +

+
+ + + + + + + + + {% for disease in principal_diagnosis %} + + + + + {% endfor %} + +
+ ID + + Name +
+ {{disease.id}} + + {{disease.label}} +
+
+ {% endif %} + {% if medical_history %}

diff --git a/config/api_router.py b/config/api_router.py index 6c7415dbdb..33b612d8d1 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -223,9 +223,9 @@ # ABDM endpoints if settings.ENABLE_ABDM: router.register("abdm/healthid", ABDMHealthIDViewSet, basename="abdm-healthid") - router.register( - "abdm/health_facility", HealthFacilityViewSet, basename="abdm-healthfacility" - ) +router.register( + "abdm/health_facility", HealthFacilityViewSet, basename="abdm-healthfacility" +) app_name = "api" urlpatterns = [