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 391271c4ee..54ed8182ed 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""" + 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") @@ -243,11 +306,7 @@ 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) @@ -260,6 +319,8 @@ def test_delete_patient_as_non_superuser(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( 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)