Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reply feature for Doctor notes #2160

Merged
merged 32 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
62baf51
doctor notes reply
UdaySagar-Git May 13, 2024
c0311bf
bug fix
UdaySagar-Git May 13, 2024
73b233e
checks to validated same thread and consultation
UdaySagar-Git May 14, 2024
49b60e1
Merge branch 'develop' into doctor-notes-replies
UdaySagar-Git May 14, 2024
b19e9f5
fix conflict
UdaySagar-Git May 14, 2024
3aaca5f
lint fix
UdaySagar-Git May 14, 2024
3e56057
code review
UdaySagar-Git May 15, 2024
c3a48fa
Merge branch 'develop' into doctor-notes-replies
UdaySagar-Git May 15, 2024
17b4ba3
added ReplyToPatientNoteSerializer
UdaySagar-Git May 15, 2024
2035260
code review
UdaySagar-Git May 16, 2024
07b66b7
adds tests
UdaySagar-Git May 17, 2024
344e5c0
Merge branch 'develop' into doctor-notes-replies
rithviknishad May 18, 2024
1ad7e51
resolve migration conflicts
May 20, 2024
75187a6
code review
UdaySagar-Git May 21, 2024
ac0d5ee
Merge branch 'doctor-notes-replies' of https://github.com/UdaySagar-G…
UdaySagar-Git May 21, 2024
ee953cd
Merge branch 'develop' into doctor-notes-replies
UdaySagar-Git May 21, 2024
106e355
Merge branch 'develop' into doctor-notes-replies
rithviknishad May 23, 2024
08fd2df
rebase migrations
rithviknishad May 23, 2024
255a520
Update care/facility/api/viewsets/patient.py
sainak May 28, 2024
ef6f66a
lint
sainak May 28, 2024
ad49411
Merge remote-tracking branch 'origin/develop' into doctor-notes-replies
sainak May 28, 2024
e72e072
rebase migrations
sainak May 28, 2024
02567b1
Adds validation for "consultation" field
UdaySagar-Git Jul 3, 2024
15bf7aa
Merge branch 'develop' into doctor-notes-replies
UdaySagar-Git Jul 3, 2024
70ac2b0
fix lint
UdaySagar-Git Jul 3, 2024
1453191
Merge branch 'doctor-notes-replies' of https://github.com/UdaySagar-G…
UdaySagar-Git Jul 3, 2024
4fc9416
rebase migration
UdaySagar-Git Jul 3, 2024
386102f
fix lint
rithviknishad Jul 8, 2024
d95d93c
Merge remote-tracking branch 'origin/develop' into doctor-notes-replies
sainak Sep 21, 2024
a9e1a74
rebase migrations
sainak Sep 21, 2024
5b995d1
Merge branch 'develop' into doctor-notes-replies
vigneshhari Sep 22, 2024
7397756
Rebase migrations
vigneshhari Sep 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions care/facility/api/serializers/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,18 @@
)

def get_approved_claim_amount(self, patient):
if patient.last_consultation is not None:
claim = (
Claim.objects.filter(
Q(error_text="") | Q(error_text=None),
consultation__external_id=patient.last_consultation.external_id,
outcome="complete",
total_claim_amount__isnull=False,
)
.order_by("-modified_date")
.first()
)
return claim.total_claim_amount if claim is not None else None

Check failure on line 126 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (RET503)

care/facility/api/serializers/patient.py:115:9: RET503 Missing explicit `return` at the end of function able to return non-`None` value

class Meta:
model = PatientRegistration
Expand All @@ -136,7 +136,7 @@
"allergies",
"external_id",
)
read_only = TIMESTAMP_FIELDS + ("death_datetime",)

Check failure on line 139 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (RUF005)

care/facility/api/serializers/patient.py:139:21: RUF005 Consider `(*TIMESTAMP_FIELDS, "death_datetime")` instead of concatenation


class PatientContactDetailsSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -178,9 +178,9 @@

last_consultation = PatientConsultationSerializer(read_only=True)
facility_object = FacilitySerializer(source="facility", read_only=True)
# nearest_facility_object = FacilitySerializer(

Check failure on line 181 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:181:5: ERA001 Found commented-out code
# source="nearest_facility", read_only=True
# )

Check failure on line 183 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:183:5: ERA001 Found commented-out code

source = ChoiceField(
choices=PatientRegistration.SourceChoices,
Expand Down Expand Up @@ -223,23 +223,23 @@
"external_id",
)
include = ("contacted_patients",)
read_only = TIMESTAMP_FIELDS + (
"last_edited",
"created_by",
"is_active",
"death_datetime",
)

Check failure on line 231 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (RUF005)

care/facility/api/serializers/patient.py:226:21: RUF005 Consider iterable unpacking instead of concatenation

# def get_last_consultation(self, obj):
# last_consultation = PatientConsultation.objects.filter(patient=obj).last()

Check failure on line 234 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:234:5: ERA001 Found commented-out code
# if not last_consultation:
# return None

Check failure on line 236 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:236:5: ERA001 Found commented-out code
# return PatientConsultationSerializer(last_consultation).data

Check failure on line 237 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:237:5: ERA001 Found commented-out code

# def validate_facility(self, value):
# if value is not None and Facility.objects.filter(external_id=value).first() is None:
# raise serializers.ValidationError("facility not found")

Check failure on line 241 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:241:5: ERA001 Found commented-out code
# return value

Check failure on line 242 in care/facility/api/serializers/patient.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

Ruff (ERA001)

care/facility/api/serializers/patient.py:242:5: ERA001 Found commented-out code

def validate_countries_travelled(self, value):
if not value:
Expand Down Expand Up @@ -500,6 +500,21 @@
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)
UdaySagar-Git marked this conversation as resolved.
Show resolved Hide resolved

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 @@
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 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 @@
# 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"):
vigneshhari marked this conversation as resolved.
Show resolved Hide resolved
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 @@
"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