Skip to content

Commit

Permalink
Merge pull request #1805 from uktrade/LTD-4620_Create_mention_notific…
Browse files Browse the repository at this point in the history
…ation_for_case_worker

Create mention notification for case worker when exporter responds
  • Loading branch information
hnryjmes authored Feb 14, 2024
2 parents 2adcb1c + 24393c2 commit 6e5d039
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 35 deletions.
25 changes: 24 additions & 1 deletion api/cases/helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from datetime import timedelta

from api.audit_trail.enums import AuditType
from api.common.dates import is_bank_holiday, is_weekend
from api.cases.enums import CaseTypeReferenceEnum
from api.staticdata.statuses.enums import CaseStatusEnum
from api.users.models import GovUser, GovNotification
from api.users.models import BaseUser, GovUser, GovNotification
from api.users.enums import SystemUser


def get_assigned_to_user_case_ids(user: GovUser, queue_id=None):
Expand Down Expand Up @@ -81,3 +83,24 @@ def can_set_status(case, status):
def working_days_in_range(start_date, end_date):
dates_in_range = [start_date + timedelta(n) for n in range((end_date - start_date).days)]
return len([date for date in dates_in_range if (not is_bank_holiday(date) and not is_weekend(date))])


def create_system_mention(case, case_note_text, mention_user):
"""
Create a LITE system mention e.g. exporter responded to an ECJU query
"""
# to avoid circular import ImportError these must be imported here
from api.cases.models import CaseNote, CaseNoteMentions
from api.audit_trail import service as audit_trail_service

case_note = CaseNote(text=case_note_text, case=case, user=BaseUser.objects.get(id=SystemUser.id))
case_note.save()
case_note_mentions = CaseNoteMentions(user=mention_user, case_note=case_note)
case_note_mentions.save()
audit_payload = {
"mention_users": [f"{mention_user.full_name} ({mention_user.team.name})"],
"additional_text": case_note_text,
}
audit_trail_service.create_system_user_audit(
verb=AuditType.CREATED_CASE_NOTE_WITH_MENTIONS, action_object=case_note, target=case, payload=audit_payload
)
1 change: 0 additions & 1 deletion api/cases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from queryable_properties.managers import QueryablePropertiesManager
from queryable_properties.properties import queryable_property


from api.audit_trail.enums import AuditType
from api.cases.enums import (
AdviceType,
Expand Down
65 changes: 57 additions & 8 deletions api/cases/tests/test_case_ecju_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from api.audit_trail.serializers import AuditSerializer
from api.cases.enums import ECJUQueryType
from api.cases.models import EcjuQuery
from api.core.exceptions import NotFoundError
from api.compliance.tests.factories import ComplianceSiteCaseFactory
from api.licences.enums import LicenceStatus
from api.licences.tests.factories import StandardLicenceFactory
Expand All @@ -23,6 +24,7 @@
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
from test_helpers.clients import DataTestClient
from api.users.tests.factories import ExporterUserFactory
from api.cases.models import CaseNoteMentions

faker = Faker()

Expand Down Expand Up @@ -465,7 +467,7 @@ def _test_exporter_responds_to_query(self, add_documents, query_type):
query_response_url = reverse("cases:case_ecju_query", kwargs={"pk": case.id, "ecju_pk": ecju_query.id})
data = {"response": "Attached the requested documents"}
response = self.client.put(query_response_url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])

Expand Down Expand Up @@ -498,7 +500,7 @@ def test_caseworker_manually_closes_query(self):
self.assertEqual(1, BaseNotification.objects.filter(object_id=ecju_query.id).count())

response = self.client.put(query_response_url, data, **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])

Expand All @@ -520,7 +522,7 @@ def test_close_query_has_optional_response_exporter(self):

data = {"response": ""}
response = self.client.put(query_response_url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)

response_ecju_query = response.json()["ecju_query"]
self.assertIsNone(response_ecju_query["response"])
Expand Down Expand Up @@ -550,7 +552,7 @@ def test_caseworker_manually_closes_query_exporter_responds_raises_error(self):
data = {"response": "exporter provided details"}

response = self.client.put(query_response_url, data, **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])

Expand All @@ -567,7 +569,7 @@ def test_caseworker_manually_closes_query_already_closed_raises_error(self):
data = {"response": "exporter provided details"}

response = self.client.put(query_response_url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])

Expand All @@ -594,7 +596,7 @@ def test_exporter_cannot_respond_to_same_ecju_query_twice(self):
url = reverse("cases:case_ecju_query", kwargs={"pk": case.id, "ecju_pk": ecju_query.id})
data = {"response": "Additional details included"}
response = self.client.put(url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])

Expand Down Expand Up @@ -630,7 +632,7 @@ def test_exporter_cannot_add_documents_to_closed_query(self):
query_response_url = reverse("cases:case_ecju_query", kwargs={"pk": case.id, "ecju_pk": ecju_query.id})
data = {"response": "Attached the requested documents"}
response = self.client.put(query_response_url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])
self.assertEqual(len(response["documents"]), 1)
Expand Down Expand Up @@ -659,7 +661,7 @@ def test_exporter_cannot_delete_documents_of_closed_query(self):
url = reverse("cases:case_ecju_query", kwargs={"pk": case.id, "ecju_pk": ecju_query.id})
data = {"response": "Additional details included"}
response = self.client.put(url, data, **self.exporter_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()["ecju_query"]
self.assertEqual(response["response"], data["response"])
self.assertEqual(len(response["documents"]), 1)
Expand All @@ -675,3 +677,50 @@ def test_exporter_cannot_delete_documents_of_closed_query(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()
self.assertIsNotNone(response["document"]["id"])

@parameterized.expand(["this is some response text", ""])
def test_exporter_responding_to_query_creates_case_note_mention_for_caseworker(self, response_text):
case = self.create_standard_application_case(self.organisation)

# caseworker raises a query
url = reverse("cases:case_ecju_queries", kwargs={"pk": case.id})
question_text = "this is the question text"
data = {"question": question_text, "query_type": ECJUQueryType.ECJU}

response = self.client.post(url, data, **self.gov_headers)
response_data = response.json()
ecju_query = EcjuQuery.objects.get(case=case)

self.assertFalse(ecju_query.is_query_closed)
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
self.assertEqual(response_data["ecju_query_id"], str(ecju_query.id))
self.assertEqual(question_text, ecju_query.question)
self.assertIsNone(ecju_query.response)

# exporter responds to the query
url = reverse("cases:case_ecju_query", kwargs={"pk": case.id, "ecju_pk": ecju_query.id})
data = {"response": response_text}

response = self.client.put(url, data, **self.exporter_headers)
ecju_query = EcjuQuery.objects.get(case=case)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(ecju_query.is_query_closed)

# check case note mention is created
case_note_mentions = CaseNoteMentions.objects.first()
case_note = case_note_mentions.case_note
audit_object = Audit.objects.first()

expected_gov_user = ecju_query.raised_by_user
expected_exporter_user = ecju_query.responded_by_user
expected_mention_users_text = f"{expected_gov_user.full_name} ({expected_gov_user.team.name})"
expected_case_note_text = f"{expected_exporter_user.get_full_name()} has responded to a query."
expected_audit_payload = {
"mention_users": [expected_mention_users_text],
"additional_text": expected_case_note_text,
}

self.assertEqual(case_note_mentions.user, expected_gov_user)
self.assertEqual(case_note.text, expected_case_note_text)
self.assertEqual(audit_object.payload, expected_audit_payload)
55 changes: 30 additions & 25 deletions api/cases/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from rest_framework.views import APIView

from api.applications.models import GoodOnApplication
from api.users.models import BaseNotification
from api.users.models import BaseNotification, ExporterUser
from api.applications.serializers.advice import (
CountersignAdviceSerializer,
CountryWithFlagsSerializer,
Expand All @@ -27,6 +27,7 @@
)
from api.cases.generated_documents.models import GeneratedCaseDocument
from api.cases.generated_documents.serializers import AdviceDocumentGovSerializer
from api.cases.helpers import create_system_mention
from api.cases.libraries.advice import group_advice
from api.cases.libraries.finalise import get_required_decision_document_types
from api.cases.libraries.get_case import get_case, get_case_document
Expand Down Expand Up @@ -606,52 +607,56 @@ def get(self, request, pk, ecju_pk):

def put(self, request, pk, ecju_pk):
"""
If not validate only Will update the ecju query instance, with a response, and return the data details.
If validate only, this will return if the data is acceptable or not.
Update an ECJU query to be closed
"""

ecju_query = get_ecju_query(ecju_pk)
if ecju_query.response:
return JsonResponse(
data={"errors": f"Responding to closed {ecju_query.get_query_type_display()} is not allowed"},
status=status.HTTP_400_BAD_REQUEST,
)

is_govuser = hasattr(request.user, "govuser")
is_govuser_request = hasattr(request.user, "govuser")
is_blank_response = not bool(request.data.get("response"))

# response is required only when a govuser closes a query
if is_govuser and is_blank_response:
if is_govuser_request and is_blank_response:
return JsonResponse(
data={"errors": "Enter a reason why you are closing the query"}, status=status.HTTP_400_BAD_REQUEST
)

data = {"responded_by_user": str(request.user.pk)}

if request.data.get("response"):
data.update({"response": request.data["response"]})

serializer = EcjuQueryUserResponseSerializer(instance=ecju_query, data=data, partial=True)

if serializer.is_valid():
if "validate_only" not in request.data or not request.data["validate_only"]:
serializer.save()
# Delete any notifications against this query
ecju_query_type = ContentType.objects.get_for_model(EcjuQuery)
BaseNotification.objects.filter(object_id=ecju_pk, content_type=ecju_query_type).delete()
serializer.save()

# If the user is a Govuser query is manually being closed by a caseworker
query_verb = AuditType.ECJU_QUERY_MANUALLY_CLOSED if is_govuser else AuditType.ECJU_QUERY_RESPONSE
audit_trail_service.create(
actor=request.user,
verb=query_verb,
action_object=serializer.instance,
target=serializer.instance.case,
payload={"ecju_response": data.get("response")},
# Delete any notifications against this query
ecju_query_type = ContentType.objects.get_for_model(EcjuQuery)
BaseNotification.objects.filter(object_id=ecju_pk, content_type=ecju_query_type).delete()

# If the user is a govuser then query is manually being closed by a case worker
query_verb = AuditType.ECJU_QUERY_MANUALLY_CLOSED if is_govuser_request else AuditType.ECJU_QUERY_RESPONSE
audit_trail_service.create(
actor=request.user,
verb=query_verb,
action_object=serializer.instance,
target=serializer.instance.case,
payload={"ecju_response": data.get("response")},
)

# If an exporter responds to a query, create a mention notification
# for the case worker that lets them know the query has been responded to
if not is_govuser_request:
exporter_user_full_name = ExporterUser.objects.get(baseuser_ptr_id=request.user.pk).full_name
create_system_mention(
case=ecju_query.case,
case_note_text=f"{exporter_user_full_name} has responded to a query.",
mention_user=ecju_query.raised_by_user,
)
return JsonResponse(data={"ecju_query": serializer.data}, status=status.HTTP_201_CREATED)
else:
return JsonResponse(data={}, status=status.HTTP_200_OK)

return JsonResponse(data={"ecju_query": serializer.data}, status=status.HTTP_200_OK)

return JsonResponse(data={"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

Expand Down

0 comments on commit 6e5d039

Please sign in to comment.