From f57cbc36c742f2279e00a21a65cf3e366d3311d7 Mon Sep 17 00:00:00 2001 From: Prafful Sharma Date: Mon, 23 Feb 2026 13:27:56 +0530 Subject: [PATCH 1/2] added option to unmark patient deceased --- care/emr/api/viewsets/patient.py | 9 ++++ care/emr/resources/patient/spec.py | 2 + care/emr/tests/test_patient_api.py | 63 ++++++++++++++++++++++++++ care/security/authorization/patient.py | 4 ++ 4 files changed, 78 insertions(+) diff --git a/care/emr/api/viewsets/patient.py b/care/emr/api/viewsets/patient.py index 8c5ec9f707..3390f3f4da 100644 --- a/care/emr/api/viewsets/patient.py +++ b/care/emr/api/viewsets/patient.py @@ -155,6 +155,15 @@ def perform_create(self, instance): def perform_update(self, instance): identifiers = instance._identifiers # noqa: SLF001 with transaction.atomic(): + if ( + instance.deceased_datetime is None + and self.get_object().deceased_datetime + and not AuthorizationController.call( + "can_unmark_deceased_patient", self.request.user + ) + ): + raise PermissionDenied(detail="Cannot mark deceased patient alive") + super().perform_update(instance) for identifier in identifiers: config = get_object_or_404( diff --git a/care/emr/resources/patient/spec.py b/care/emr/resources/patient/spec.py index 955b639530..4bac7fb6ad 100644 --- a/care/emr/resources/patient/spec.py +++ b/care/emr/resources/patient/spec.py @@ -187,6 +187,8 @@ def perform_extra_deserialization(self, is_update, obj): obj.year_of_birth = timezone.now().year - self.age elif self.date_of_birth: obj.year_of_birth = self.date_of_birth.year + if self.deceased_datetime is None: + obj.deceased_datetime = None if not self.pincode: obj.pincode = None diff --git a/care/emr/tests/test_patient_api.py b/care/emr/tests/test_patient_api.py index f9f48cbaae..2975ac76d6 100644 --- a/care/emr/tests/test_patient_api.py +++ b/care/emr/tests/test_patient_api.py @@ -206,6 +206,69 @@ def test_invalid_date_of_birth_and_death_date(self): self.assertEqual(error["type"], "validation_error") self.assertIn("Date of birth cannot be after the date of death", error["msg"]) + def test_update_deceased_patient_to_alive(self): + """Test that a superuser can set deceased_datetime back to None""" + geo_organization = self.create_organization(org_type="govt") + superuser = self.create_super_user() + role = self.create_role_with_permissions( + permissions=[ + PatientPermissions.can_create_patient.name, + PatientPermissions.can_write_patient.name, + PatientPermissions.can_list_patients.name, + ] + ) + self.attach_role_organization_user(geo_organization, superuser, role) + self.client.force_authenticate(user=superuser) + + # Create patient with deceased_datetime + deceased_time = care_now() - datetime.timedelta(days=2) + patient_data = self.generate_patient_data( + geo_organization=geo_organization.external_id, + deceased_datetime=deceased_time, + ) + response = self.client.post(self.base_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone(response.data["deceased_datetime"]) + patient_id = response.data["id"] + + # Update patient to set deceased_datetime to None + update_url = reverse("patient-detail", kwargs={"external_id": patient_id}) + patient_data["deceased_datetime"] = None + response = self.client.put(update_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNone(response.data["deceased_datetime"]) + + def test_non_superuser_cannot_unmark_deceased(self): + """Test that a non-superuser cannot set deceased_datetime back to None""" + geo_organization = self.create_organization(org_type="govt") + user = self.create_user() + role = self.create_role_with_permissions( + permissions=[ + PatientPermissions.can_create_patient.name, + PatientPermissions.can_write_patient.name, + PatientPermissions.can_list_patients.name, + ] + ) + self.attach_role_organization_user(geo_organization, user, role) + self.client.force_authenticate(user=user) + + # Create patient with deceased_datetime + deceased_time = care_now() - datetime.timedelta(days=2) + patient_data = self.generate_patient_data( + geo_organization=geo_organization.external_id, + deceased_datetime=deceased_time, + ) + response = self.client.post(self.base_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone(response.data["deceased_datetime"]) + patient_id = response.data["id"] + + # Attempt to set deceased_datetime to None should be denied + update_url = reverse("patient-detail", kwargs={"external_id": patient_id}) + patient_data["deceased_datetime"] = None + response = self.client.put(update_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_invalid_age_and_death_date(self): user = self.create_user() geo_organization = self.create_organization(org_type="govt") diff --git a/care/security/authorization/patient.py b/care/security/authorization/patient.py index 329c031334..2cf9e81d92 100644 --- a/care/security/authorization/patient.py +++ b/care/security/authorization/patient.py @@ -127,5 +127,9 @@ def get_filtered_patients(self, qs, user): | Q(users_cache__overlap=[user.id]) ) + def can_unmark_deceased_patient(self, user): + """Permission to reverse a patient's deceased status""" + return user.is_superuser + AuthorizationController.register_internal_controller(PatientAccess) From ba8b4fbd4d5c0a8b634930e7a4d7509048b15594 Mon Sep 17 00:00:00 2001 From: Prafful Sharma Date: Wed, 25 Feb 2026 14:06:11 +0530 Subject: [PATCH 2/2] fix conflicts --- care/emr/tests/test_patient_api.py | 56 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/care/emr/tests/test_patient_api.py b/care/emr/tests/test_patient_api.py index 5350657f5a..54ed8182ed 100644 --- a/care/emr/tests/test_patient_api.py +++ b/care/emr/tests/test_patient_api.py @@ -240,8 +240,35 @@ def test_update_deceased_patient_to_alive(self): def test_non_superuser_cannot_unmark_deceased(self): """Test that a non-superuser cannot set deceased_datetime back to None""" - geo_organization = self.create_organization(org_type="govt") user = self.create_user() + geo_organization = self.create_organization(org_type="govt") + role = self.create_role_with_permissions( + permissions=[ + PatientPermissions.can_create_patient.name, + PatientPermissions.can_write_patient.name, + PatientPermissions.can_list_patients.name, + ] + ) + self.attach_role_organization_user(geo_organization, user, role) + self.client.force_authenticate(user=user) + + # Create patient with deceased_datetime + deceased_time = care_now() - datetime.timedelta(days=2) + patient_data = self.generate_patient_data( + geo_organization=geo_organization.external_id, + deceased_datetime=deceased_time, + ) + response = self.client.post(self.base_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone(response.data["deceased_datetime"]) + patient_id = response.data["id"] + + # Attempt to set deceased_datetime to None should be denied + update_url = reverse("patient-detail", kwargs={"external_id": patient_id}) + patient_data["deceased_datetime"] = None + response = self.client.put(update_url, patient_data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_create_patient_with_future_deceased_datetime(self): user = self.create_user() geo_organization = self.create_organization(org_type="govt") @@ -279,33 +306,10 @@ def test_delete_patient_as_non_superuser(self): user = self.create_user() geo_organization = self.create_organization(org_type="govt") role = self.create_role_with_permissions( - permissions=[ - PatientPermissions.can_create_patient.name, - PatientPermissions.can_write_patient.name, - PatientPermissions.can_list_patients.name, - ] + permissions=[PatientPermissions.can_create_patient.name] ) self.attach_role_organization_user(geo_organization, user, role) self.client.force_authenticate(user=user) - - # Create patient with deceased_datetime - deceased_time = care_now() - datetime.timedelta(days=2) - patient_data = self.generate_patient_data( - geo_organization=geo_organization.external_id, - deceased_datetime=deceased_time, - ) - response = self.client.post(self.base_url, patient_data, format="json") - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertIsNotNone(response.data["deceased_datetime"]) - patient_id = response.data["id"] - - # Attempt to set deceased_datetime to None should be denied - update_url = reverse("patient-detail", kwargs={"external_id": patient_id}) - patient_data["deceased_datetime"] = None - response = self.client.put(update_url, patient_data, format="json") - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_invalid_age_and_death_date(self): patient_data = self.generate_patient_data( geo_organization=geo_organization.external_id ) @@ -315,6 +319,8 @@ def test_invalid_age_and_death_date(self): delete_url = reverse("patient-detail", kwargs={"external_id": patient_id}) response = self.client.delete(delete_url) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_invalid_age_and_death_date(self): user = self.create_user() geo_organization = self.create_organization(org_type="govt") role = self.create_role_with_permissions(