From 1ce8fdceea9fb9b00788e586662f8d871a292e22 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Thu, 7 Dec 2023 23:39:50 +0530 Subject: [PATCH] made patient notes consultation specific (#1727) * made patient notes consultation specific * link existing patient notes to consultations * update migrations --------- Co-authored-by: Bhavik Agarwal --- care/facility/api/serializers/patient.py | 7 ++++ care/facility/api/viewsets/patient.py | 7 ++++ .../0398_patientnotes_consultation.py | 23 ++++++++++++ ...0399_populate_patientnotes_consultation.py | 37 +++++++++++++++++++ care/facility/models/patient.py | 3 ++ care/facility/tests/test_patient_api.py | 25 +++++++++++-- care/utils/tests/test_utils.py | 5 ++- 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 care/facility/migrations/0398_patientnotes_consultation.py create mode 100644 care/facility/migrations/0399_populate_patientnotes_consultation.py diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index eef65c7893..6e9aa27e50 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -475,6 +475,12 @@ def save(self, **kwargs): class PatientNotesSerializer(serializers.ModelSerializer): facility = FacilityBasicInfoSerializer(read_only=True) created_by_object = UserBaseMinimumSerializer(source="created_by", read_only=True) + consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), + required=False, + allow_null=True, + read_only=True, + ) def validate_empty_values(self, data): if not data.get("note", "").strip(): @@ -501,6 +507,7 @@ class Meta: fields = ( "note", "facility", + "consultation", "created_by_object", "user_type", "created_date", diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 2601d8af8c..2f3caeb08a 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -626,6 +626,10 @@ def list(self, request, *args, **kwargs): return super(PatientSearchViewSet, self).list(request, *args, **kwargs) +class PatientNotesFilterSet(filters.FilterSet): + consultation = filters.CharFilter(field_name="consultation__external_id") + + class PatientNotesViewSet( ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet ): @@ -636,6 +640,8 @@ class PatientNotesViewSet( ) serializer_class = PatientNotesSerializer permission_classes = (IsAuthenticated, DRYPermissions) + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = PatientNotesFilterSet def get_queryset(self): user = self.request.user @@ -671,6 +677,7 @@ def perform_create(self, serializer): instance = serializer.save( facility=patient.facility, patient=patient, + consultation=patient.last_consultation, created_by=self.request.user, ) diff --git a/care/facility/migrations/0398_patientnotes_consultation.py b/care/facility/migrations/0398_patientnotes_consultation.py new file mode 100644 index 0000000000..635b37b5fd --- /dev/null +++ b/care/facility/migrations/0398_patientnotes_consultation.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2023-10-18 12:18 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0397_truncate_discharge_time"), + ] + + operations = [ + migrations.AddField( + model_name="patientnotes", + name="consultation", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="facility.patientconsultation", + ), + ), + ] diff --git a/care/facility/migrations/0399_populate_patientnotes_consultation.py b/care/facility/migrations/0399_populate_patientnotes_consultation.py new file mode 100644 index 0000000000..99b9f22e99 --- /dev/null +++ b/care/facility/migrations/0399_populate_patientnotes_consultation.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.7 on 2023-11-23 12:42 + +from django.db import migrations, models + + +def link_patient_notes_to_consultation(apps, schema_editor): + PatientConsultation = apps.get_model("facility", "PatientConsultation") + PatientNotes = apps.get_model("facility", "PatientNotes") + + consultations = PatientConsultation.objects.order_by("created_date").filter( + patient__in=models.Subquery(PatientNotes.objects.values("patient_id")) + ) + + for consultation in consultations: + notes = PatientNotes.objects.order_by("created_date").filter( + patient_id=consultation.patient_id, + created_date__gte=consultation.created_date, + ) + if consultation.discharge_reason: + notes = notes.filter( + created_date__lte=consultation.modified_date, + ) + + notes.update(consultation=consultation) + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0398_patientnotes_consultation"), + ] + + operations = [ + migrations.RunPython( + code=link_patient_notes_to_consultation, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 4590b67f90..0852d392b5 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -714,6 +714,9 @@ class PatientNotes(FacilityBaseModel, ConsultationRelatedPermissionMixin): patient = models.ForeignKey( PatientRegistration, on_delete=models.PROTECT, null=False, blank=False ) + consultation = models.ForeignKey( + PatientConsultation, on_delete=models.PROTECT, null=True, blank=True + ) facility = models.ForeignKey( Facility, on_delete=models.PROTECT, null=False, blank=False ) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 493a43f24b..9272f8fd55 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -10,6 +10,7 @@ class ExpectedPatientNoteKeys(Enum): NOTE = "note" FACILITY = "facility" + CONSULTATION = "consultation" CREATED_BY_OBJECT = "created_by_object" CREATED_DATE = "created_date" USER_TYPE = "user_type" @@ -97,6 +98,14 @@ def setUpTestData(cls) -> None: "doctor2", cls.district, home_facility=cls.facility2, user_type=15 ) cls.patient = cls.create_patient(cls.district, cls.facility) + cls.consultation = cls.create_consultation( + patient_no="IP5678", + patient=cls.patient, + facility=cls.facility, + created_by=cls.user, + suggestion="A", + admission_date=now(), + ) def setUp(self): super().setUp() @@ -120,18 +129,28 @@ def create_patient_note( def test_patient_notes(self): patientId = self.patient.external_id - response = self.client.get(f"/api/v1/patient/{patientId}/notes/") + response = self.client.get( + f"/api/v1/patient/{patientId}/notes/?consultation={self.consultation.external_id}" + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.json()["results"], list) - # Test user_type field if user is not from same facility as patient + results = response.json()["results"] + + # Test if all notes are from same consultation as requested + self.assertEqual( + str(self.consultation.external_id), + [note["consultation"] for note in results][0], + ) + + # Test created_by_local_user field if user is not from same facility as patient data2 = response.json()["results"][0] user_type_content2 = data2["user_type"] self.assertEqual(user_type_content2, "RemoteSpecialist") # Ensure only necessary data is being sent and no extra data - data = response.json()["results"][1] + data = results[1] self.assertCountEqual( data.keys(), [item.value for item in ExpectedPatientNoteKeys] diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index 1f6ceff22e..bf2b560893 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -326,7 +326,10 @@ def create_consultation( } ) data.update(kwargs) - return PatientConsultation.objects.create(**data) + consultation = PatientConsultation.objects.create(**data) + patient.last_consultation = consultation + patient.save() + return consultation @classmethod def create_asset_location(cls, facility: Facility, **kwargs) -> AssetLocation: