Skip to content

Commit

Permalink
Reply feature for Doctor notes (#2160)
Browse files Browse the repository at this point in the history
Reply feature for Doctor notes  (#2160)

---------

Co-authored-by: Rithvik Nishad <[email protected]>
Co-authored-by: Khavin Shankar <[email protected]>
Co-authored-by: rithviknishad <[email protected]>
Co-authored-by: Aakash Singh <[email protected]>
Co-authored-by: Vignesh Hari <[email protected]>
Co-authored-by: vigneshhari <[email protected]>
  • Loading branch information
7 people authored Sep 22, 2024
1 parent 15debe1 commit ebc03d9
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 1 deletion.
39 changes: 39 additions & 0 deletions care/facility/api/serializers/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,21 @@ class Meta:
exclude = ("patient_note",)


class ReplyToPatientNoteSerializer(serializers.ModelSerializer):
id = serializers.CharField(source="external_id", read_only=True)
created_by_object = UserBaseMinimumSerializer(source="created_by", read_only=True)

class Meta:
model = PatientNotes
fields = (
"id",
"created_by_object",
"created_date",
"user_type",
"note",
)


class PatientNotesSerializer(serializers.ModelSerializer):
id = serializers.CharField(source="external_id", read_only=True)
facility = FacilityBasicInfoSerializer(read_only=True)
Expand All @@ -515,6 +530,13 @@ class PatientNotesSerializer(serializers.ModelSerializer):
thread = serializers.ChoiceField(
choices=PatientNoteThreadChoices, required=False, allow_null=False
)
reply_to = ExternalIdSerializerField(
queryset=PatientNotes.objects.all(),
write_only=True,
required=False,
allow_null=True,
)
reply_to_object = ReplyToPatientNoteSerializer(source="reply_to", read_only=True)

def validate_empty_values(self, data):
if not data.get("note", "").strip():
Expand All @@ -524,6 +546,10 @@ def validate_empty_values(self, data):
def create(self, validated_data):
if "thread" not in validated_data:
raise serializers.ValidationError({"thread": "This field is required"})
if "consultation" not in validated_data:
raise serializers.ValidationError(
{"consultation": "This field is required"}
)
user_type = User.REVERSE_TYPE_MAP[validated_data["created_by"].user_type]
# If the user is a doctor and the note is being created in the home facility
# then the user type is doctor else it is a remote specialist
Expand All @@ -536,6 +562,17 @@ def create(self, validated_data):
# If the user is not a doctor then the user type is the same as the user type
validated_data["user_type"] = user_type

if validated_data.get("reply_to"):
reply_to_note = validated_data["reply_to"]
if reply_to_note.thread != validated_data["thread"]:
raise serializers.ValidationError(
"Reply to note should be in the same thread"
)
if reply_to_note.consultation != validated_data.get("consultation"):
raise serializers.ValidationError(
"Reply to note should be in the same consultation"
)

user = self.context["request"].user
note = validated_data.get("note")
with transaction.atomic():
Expand Down Expand Up @@ -584,6 +621,8 @@ class Meta:
"modified_date",
"last_edited_by",
"last_edited_date",
"reply_to",
"reply_to_object",
)
read_only_fields = (
"id",
Expand Down
4 changes: 3 additions & 1 deletion care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,9 @@ class PatientNotesViewSet(
):
queryset = (
PatientNotes.objects.all()
.select_related("facility", "patient", "created_by")
.select_related(
"facility", "patient", "created_by", "reply_to", "reply_to__created_by"
)
.order_by("-created_date")
)
lookup_field = "external_id"
Expand Down
25 changes: 25 additions & 0 deletions care/facility/migrations/0463_patientnotes_reply_to.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 5.1.1 on 2024-09-22 17:36

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("facility", "0462_facilityhubspoke"),
]

operations = [
migrations.AddField(
model_name="patientnotes",
name="reply_to",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="replies",
to="facility.patientnotes",
),
),
]
7 changes: 7 additions & 0 deletions care/facility/models/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,13 @@ class PatientNotes(FacilityBaseModel, ConsultationRelatedPermissionMixin):
db_index=True,
default=PatientNoteThreadChoices.DOCTORS,
)
reply_to = models.ForeignKey(
"self",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="replies",
)
note = models.TextField(default="", blank=True)

def get_related_consultation(self):
Expand Down
43 changes: 43 additions & 0 deletions care/facility/tests/test_patient_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ExpectedPatientNoteKeys(Enum):
LAST_EDITED_DATE = "last_edited_date"
THREAD = "thread"
USER_TYPE = "user_type"
REPLY_TO_OBJECT = "reply_to_object"


class ExpectedFacilityKeys(Enum):
Expand Down Expand Up @@ -234,6 +235,48 @@ def test_patient_notes(self):
[item.value for item in ExpectedCreatedByObjectKeys],
)

def test_patient_note_with_reply(self):
patient = self.patient
note = "How is the patient"
created_by = self.user

data = {
"facility": patient.facility or self.facility,
"note": note,
"thread": PatientNoteThreadChoices.DOCTORS,
}
self.client.force_authenticate(user=created_by)
response = self.client.post(
f"/api/v1/patient/{patient.external_id}/notes/", data=data
)
reply_data = {
"facility": patient.facility or self.facility,
"note": "Patient is doing fine",
"thread": PatientNoteThreadChoices.DOCTORS,
"reply_to": response.json()["id"],
}
reply_response = self.client.post(
f"/api/v1/patient/{patient.external_id}/notes/", data=reply_data
)

# Ensure the reply is created successfully
self.assertEqual(reply_response.status_code, status.HTTP_201_CREATED)

# Ensure the reply is posted on same thread
self.assertEqual(reply_response.json()["thread"], response.json()["thread"])

# Posting reply in other thread should fail
reply_response = self.client.post(
f"/api/v1/patient/{patient.external_id}/notes/",
{
"facility": patient.facility or self.facility,
"note": "Patient is doing fine",
"thread": PatientNoteThreadChoices.NURSES,
"reply_to": response.json()["id"],
},
)
self.assertEqual(reply_response.status_code, status.HTTP_400_BAD_REQUEST)

def test_patient_note_edit(self):
patientId = self.patient.external_id
notes_list_response = self.client.get(
Expand Down

0 comments on commit ebc03d9

Please sign in to comment.