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

Create mention notification for case worker when exporter responds #1805

Merged
merged 12 commits into from
Feb 14, 2024
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh I hate inline imports they can lead to all kind of problems.
how about moving to common or in it's own helper class ?

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)
hnryjmes marked this conversation as resolved.
Show resolved Hide resolved
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
Loading