From 558ff311f9123bda55386fa4df760c763cfe2726 Mon Sep 17 00:00:00 2001 From: AdJez Date: Tue, 28 Jan 2025 14:10:35 +0100 Subject: [PATCH] Send email to assignees at report publishing --- src/backend/partaj/core/email.py | 27 +- src/backend/partaj/core/receivers.py | 12 +- .../core/test_api_referral_publish_answer.py | 528 ------------------ .../test_api_referralreport_publishreport.py | 6 +- 4 files changed, 33 insertions(+), 540 deletions(-) delete mode 100644 src/backend/tests/partaj/core/test_api_referral_publish_answer.py diff --git a/src/backend/partaj/core/email.py b/src/backend/partaj/core/email.py index bcd28ccbe5..90702c1566 100644 --- a/src/backend/partaj/core/email.py +++ b/src/backend/partaj/core/email.py @@ -214,7 +214,9 @@ def send_referral_answered_to_users(cls, referral, published_by): cls.send(data) @classmethod - def send_referral_answered_to_unit_owners(cls, referral, published_by): + def send_referral_answered_to_unit_owners_and_assignees( + cls, referral, published_by + ): """ Send the "referral answered" email to the units'owner when an answer is added to a referral. @@ -223,10 +225,27 @@ def send_referral_answered_to_unit_owners(cls, referral, published_by): "REFERRAL_ANSWERED_UNIT_OWNER_TEMPLATE_ID" ] + for assignee in referral.assignees.exclude(id=published_by.id): + link_path = FrontendLink.unit_referral_detail_answer(referral=referral.id) + + data = { + "params": { + "answer_sender": published_by.get_full_name(), + "case_number": referral.id, + "link_to_referral": f"{cls.location}{link_path}", + "title": referral.title or referral.object, + }, + "replyTo": cls.reply_to, + "templateId": template_unit_owner_id, + "to": [{"email": assignee.email}], + } + cls.send(data) + for unit in referral.units.all(): contacts = unit.members.filter( unitmembership__role=UnitMembershipRole.OWNER - ) + ).exclude(id=published_by.id) + # Get the path to the referral detail view from the requester's "my referrals" view link_path = FrontendLink.unit_referral_detail_answer(referral=referral.id) @@ -245,7 +264,7 @@ def send_referral_answered_to_unit_owners(cls, referral, published_by): cls.send(data) @classmethod - def send_referral_answered_to_created_by(cls, referral, version): + def send_referral_answered_to_published_by(cls, referral, published_by): """ Send the "referral answered" email to the response owner when an answer is added to a referral. @@ -261,7 +280,7 @@ def send_referral_answered_to_created_by(cls, referral, version): }, "replyTo": cls.reply_to, "templateId": template_created_by_id, - "to": [{"email": version.created_by.email}], + "to": [{"email": published_by.email}], } cls.send(data) diff --git a/src/backend/partaj/core/receivers.py b/src/backend/partaj/core/receivers.py index d561ac3e31..136db7b383 100644 --- a/src/backend/partaj/core/receivers.py +++ b/src/backend/partaj/core/receivers.py @@ -336,8 +336,8 @@ def answer_published(sender, referral, published_answer, published_by, **kwargs) # Notify the requester by sending them an email Mailer.send_referral_answered_to_users(published_by=published_by, referral=referral) - # Notify the unit owner by sending them an email - Mailer.send_referral_answered_to_unit_owners( + # Notify the unit owner and assignees by sending them an email + Mailer.send_referral_answered_to_unit_owners_and_assignees( published_by=published_by, referral=referral ) @@ -422,12 +422,14 @@ def report_published(sender, referral, version, published_by, **kwargs): Mailer.send_referral_answered_to_users(published_by=published_by, referral=referral) # Notify the unit'owner by sending them an email - Mailer.send_referral_answered_to_unit_owners( + Mailer.send_referral_answered_to_unit_owners_and_assignees( published_by=published_by, referral=referral ) - # Notify the response'owner by sending them an email - Mailer.send_referral_answered_to_created_by(referral=referral, version=version) + # Notify the response sender by sending them an email + Mailer.send_referral_answered_to_published_by( + referral=referral, published_by=published_by + ) if referral.units.filter(kdb_export=False): capture_message( diff --git a/src/backend/tests/partaj/core/test_api_referral_publish_answer.py b/src/backend/tests/partaj/core/test_api_referral_publish_answer.py deleted file mode 100644 index 478f2a4cbc..0000000000 --- a/src/backend/tests/partaj/core/test_api_referral_publish_answer.py +++ /dev/null @@ -1,528 +0,0 @@ -import uuid -from unittest import mock - -from django.conf import settings -from django.test import TestCase - -from rest_framework.authtoken.models import Token - -from partaj.core import factories, models - - -@mock.patch("partaj.core.email.Mailer.send") -class ReferralApiPublishAnswerTestCase(TestCase): - """ - Test API routes and actions related to the Referral "publish_answer" endpoint. - """ - - def test_publish_referral_answer_by_anonymous_user(self, mock_mailer_send): - """ - Anonymous users cannot publish an answer for a referral. - """ - referral = factories.ReferralFactory(state=models.ReferralState.PROCESSING) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - - response = self.client.post( - f"/api/referrals/{referral.id}/publish_answer/", - {"answer": answer.id}, - ) - self.assertEqual(response.status_code, 401) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.PROCESSING) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_by_random_logged_in_user(self, mock_mailer_send): - """ - Any random logged in user cannot publish an answer for a referral. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.PROCESSING) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - - response = self.client.post( - f"/api/referrals/{referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - self.assertEqual(response.status_code, 403) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.PROCESSING) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_by_linked_user(self, mock_mailer_send): - """ - The referral's creator cannot publish a draft answer themselves. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory( - state=models.ReferralState.PROCESSING, post__users=[user] - ) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - - response = self.client.post( - f"/api/referrals/{referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - self.assertEqual(response.status_code, 403) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.PROCESSING) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_by_linked_unit_member(self, mock_mailer_send): - """ - Members of the linked unit can publish a draft answer for a referral. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.PROCESSING) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - unit_owner = factories.UnitMembershipFactory( - role=models.UnitMembershipRole.OWNER, unit=referral.units.get() - ).user - - attachment_1 = factories.ReferralAnswerAttachmentFactory() - attachment_1.referral_answers.add(answer) - attachment_2 = factories.ReferralAnswerAttachmentFactory() - attachment_2.referral_answers.add(answer) - answer.refresh_from_db() - self.assertEqual(answer.attachments.count(), 2) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["state"], models.ReferralState.ANSWERED) - self.assertEqual(response.json()["answers"][0]["content"], answer.content) - self.assertEqual( - response.json()["answers"][0]["state"], models.ReferralAnswerState.PUBLISHED - ) - self.assertEqual( - len(response.json()["answers"][0]["attachments"]), - 2, - ) - self.assertEqual(response.json()["answers"][1]["content"], answer.content) - self.assertEqual( - response.json()["answers"][1]["state"], models.ReferralAnswerState.DRAFT - ) - # Make sure the published answer was added to the related draft - published_answer = models.ReferralAnswer.objects.get( - id=response.json()["answers"][0]["id"] - ) - answer.refresh_from_db() - self.assertEqual(answer.published_answer, published_answer) - self.assertEqual(published_answer.attachments.count(), 2) - # An activity was created for this published answer - self.assertEqual( - str( - models.ReferralActivity.objects.get( - verb=models.ReferralActivityVerb.ANSWERED - ).item_content_object.id - ), - response.json()["answers"][0]["id"], - ) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.ANSWERED) - - self.assertEqual(mock_mailer_send.call_count, 2) - self.assertEqual( - tuple(mock_mailer_send.call_args_list[0]), - ( - ( # args - { - "params": { - "answer_sender": user.get_full_name(), - "case_number": referral.id, - "link_to_referral": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/answer", - "link_to_referral_message": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/messages", - "referral_topic_name": referral.topic.name, - }, - "replyTo": { - "email": settings.CONTACT_EMAIL, - "name": "Partaj", - }, - "templateId": settings.SENDINBLUE[ - "REFERRAL_ANSWERED_REQUESTERS_TEMPLATE_ID" - ], - "to": [{"email": referral.users.first().email}], - }, - ), - {}, # kwargs - ), - ) - self.assertEqual( - tuple(mock_mailer_send.call_args_list[1]), - ( - ( # args - { - "params": { - "answer_sender": user.get_full_name(), - "case_number": referral.id, - "link_to_referral": ( - f"https://partaj/app/unit/referral-detail/{referral.id}/answer" - ), - "title": referral.object, - }, - "replyTo": { - "email": settings.CONTACT_EMAIL, - "name": "Partaj", - }, - "templateId": settings.SENDINBLUE[ - "REFERRAL_ANSWERED_UNIT_OWNER_TEMPLATE_ID" - ], - "to": [{"email": unit_owner.email}], - }, - ), - {}, # kwargs - ), - ) - - def test_publish_referral_with_title_filled_answer_by_linked_unit_member( - self, mock_mailer_send - ): - """ - Members of the linked unit can publish a draft answer for a referral with title filled. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory( - state=models.ReferralState.PROCESSING, title="Titre de le DAJ" - ) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - unit_owner = factories.UnitMembershipFactory( - role=models.UnitMembershipRole.OWNER, unit=referral.units.get() - ).user - - attachment_1 = factories.ReferralAnswerAttachmentFactory() - attachment_1.referral_answers.add(answer) - attachment_2 = factories.ReferralAnswerAttachmentFactory() - attachment_2.referral_answers.add(answer) - answer.refresh_from_db() - self.assertEqual(answer.attachments.count(), 2) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["state"], models.ReferralState.ANSWERED) - self.assertEqual(response.json()["answers"][0]["content"], answer.content) - self.assertEqual( - response.json()["answers"][0]["state"], models.ReferralAnswerState.PUBLISHED - ) - self.assertEqual( - len(response.json()["answers"][0]["attachments"]), - 2, - ) - self.assertEqual(response.json()["answers"][1]["content"], answer.content) - self.assertEqual( - response.json()["answers"][1]["state"], models.ReferralAnswerState.DRAFT - ) - # Make sure the published answer was added to the related draft - published_answer = models.ReferralAnswer.objects.get( - id=response.json()["answers"][0]["id"] - ) - answer.refresh_from_db() - self.assertEqual(answer.published_answer, published_answer) - self.assertEqual(published_answer.attachments.count(), 2) - # An activity was created for this published answer - self.assertEqual( - str( - models.ReferralActivity.objects.get( - verb=models.ReferralActivityVerb.ANSWERED - ).item_content_object.id - ), - response.json()["answers"][0]["id"], - ) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.ANSWERED) - - self.assertEqual(mock_mailer_send.call_count, 2) - self.assertEqual( - tuple(mock_mailer_send.call_args_list[0]), - ( - ( # args - { - "params": { - "answer_sender": user.get_full_name(), - "case_number": referral.id, - "link_to_referral": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/answer", - "link_to_referral_message": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/messages", - "referral_topic_name": referral.topic.name, - }, - "replyTo": { - "email": settings.CONTACT_EMAIL, - "name": "Partaj", - }, - "templateId": settings.SENDINBLUE[ - "REFERRAL_ANSWERED_REQUESTERS_TEMPLATE_ID" - ], - "to": [{"email": referral.users.first().email}], - }, - ), - {}, # kwargs - ), - ) - self.assertEqual( - tuple(mock_mailer_send.call_args_list[1]), - ( - ( # args - { - "params": { - "answer_sender": user.get_full_name(), - "case_number": referral.id, - "link_to_referral": ( - f"https://partaj/app/unit/referral-detail/{referral.id}/answer" - ), - "title": referral.title, - }, - "replyTo": { - "email": settings.CONTACT_EMAIL, - "name": "Partaj", - }, - "templateId": settings.SENDINBLUE[ - "REFERRAL_ANSWERED_UNIT_OWNER_TEMPLATE_ID" - ], - "to": [{"email": unit_owner.email}], - }, - ), - {}, # kwargs - ), - ) - - def test_publish_nonexistent_referral_answer_by_linked_unit_member( - self, mock_mailer_send - ): - """ - When a user (like a unit member) attempts to publish an answer that does not exist, - they receive an error with an appropriate message. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.PROCESSING) - referral.units.get().members.add(user) - some_uuid = uuid.uuid4() - self.assertEqual(models.ReferralAnswer.objects.count(), 0) - - response = self.client.post( - f"/api/referrals/{referral.id}/publish_answer/", - {"answer": some_uuid}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json()["errors"], [f"answer {some_uuid} does not exist"] - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 0) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.PROCESSING) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_from_in_validation_state(self, mock_mailer_send): - """ - A referral in the IN_VALIDATION state can go through the publish answer transition. - """ - user = factories.UserFactory() - - referral = factories.ReferralFactory(state=models.ReferralState.IN_VALIDATION) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["state"], models.ReferralState.ANSWERED) - self.assertEqual(response.json()["answers"][0]["content"], answer.content) - self.assertEqual( - response.json()["answers"][0]["state"], models.ReferralAnswerState.PUBLISHED - ) - self.assertEqual(response.json()["answers"][1]["content"], answer.content) - self.assertEqual( - response.json()["answers"][1]["state"], models.ReferralAnswerState.DRAFT - ) - # Make sure the published answer was added to the related draft - published_answer = models.ReferralAnswer.objects.get( - id=response.json()["answers"][0]["id"] - ) - answer.refresh_from_db() - self.assertEqual(answer.published_answer, published_answer) - # An activity was created for this published answer - self.assertEqual( - str( - models.ReferralActivity.objects.get( - verb=models.ReferralActivityVerb.ANSWERED - ).item_content_object.id - ), - response.json()["answers"][0]["id"], - ) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.ANSWERED) - mock_mailer_send.assert_called_with( - { - "params": { - "answer_sender": user.get_full_name(), - "case_number": referral.id, - "link_to_referral": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/answer", - "link_to_referral_message": f"https://partaj/app/sent-referrals/referral-detail/{referral.id}/messages", - "referral_topic_name": referral.topic.name, - }, - "replyTo": {"email": settings.CONTACT_EMAIL, "name": "Partaj"}, - "templateId": settings.SENDINBLUE[ - "REFERRAL_ANSWERED_REQUESTERS_TEMPLATE_ID" - ], - "to": [{"email": referral.users.first().email}], - } - ) - - def test_publish_referral_answer_from_received_state(self, mock_mailer_send): - """ - A referral in the RECEIVED state cannot go through the publish answer transition. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.RECEIVED) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json(), - {"errors": ["Transition PUBLISH_ANSWER not allowed from state received."]}, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.RECEIVED) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_from_assigned_state(self, mock_mailer_send): - """ - A referral in the ASSIGNED state cannot go through the publish answer transition. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.ASSIGNED) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json(), - {"errors": ["Transition PUBLISH_ANSWER not allowed from state assigned."]}, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.ASSIGNED) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_from_answered_state(self, mock_mailer_send): - """ - A referral in the ANSWERED state cannot go through the publish answer transition. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.ANSWERED) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json(), - {"errors": ["Transition PUBLISH_ANSWER not allowed from state answered."]}, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.ANSWERED) - mock_mailer_send.assert_not_called() - - def test_publish_referral_answer_from_closed_state(self, mock_mailer_send): - """ - A referral in the CLOSED state cannot go through the publish answer transition. - """ - user = factories.UserFactory() - referral = factories.ReferralFactory(state=models.ReferralState.CLOSED) - answer = factories.ReferralAnswerFactory( - referral=referral, - state=models.ReferralAnswerState.DRAFT, - ) - referral.units.get().members.add(user) - - response = self.client.post( - f"/api/referrals/{answer.referral.id}/publish_answer/", - {"answer": answer.id}, - HTTP_AUTHORIZATION=f"Token {Token.objects.get_or_create(user=user)[0]}", - ) - - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json(), - {"errors": ["Transition PUBLISH_ANSWER not allowed from state closed."]}, - ) - self.assertEqual(models.ReferralAnswer.objects.count(), 1) - self.assertEqual(models.ReferralActivity.objects.count(), 0) - referral.refresh_from_db() - self.assertEqual(referral.state, models.ReferralState.CLOSED) - mock_mailer_send.assert_not_called() diff --git a/src/backend/tests/partaj/core/test_api_referralreport_publishreport.py b/src/backend/tests/partaj/core/test_api_referralreport_publishreport.py index 5bf4d1f6fa..d1158ef8e9 100644 --- a/src/backend/tests/partaj/core/test_api_referralreport_publishreport.py +++ b/src/backend/tests/partaj/core/test_api_referralreport_publishreport.py @@ -25,7 +25,7 @@ def test_referralreport_publish_report_by_linked_unit_user(self, mock_mailer_sen """ Test - Non-last version author unit members can nevertheless publish a report - - 2 mails are sent during publishment + - 2 mails are sent during publishing - Referral changes its state to ANSWERED - Comment, publication date, final_version are saved into the report - Note is created with received status, referral title as object and no R unit duplication @@ -58,11 +58,11 @@ def test_referralreport_publish_report_by_linked_unit_user(self, mock_mailer_sen "urgency_explanation": "la justification de l'urgence", } - asker_token = Token.objects.get_or_create(user=requester_1)[0] + requester_1_token = Token.objects.get_or_create(user=requester_1)[0] self.client.post( f"/api/referrals/{referral.id}/send/", form_data, - HTTP_AUTHORIZATION=f"Token {asker_token}", + HTTP_AUTHORIZATION=f"Token {requester_1_token}", ) created_referral = models.Referral.objects.get(id=referral.id)