Skip to content

Commit

Permalink
Merge pull request #1979 from uktrade/uat
Browse files Browse the repository at this point in the history
PROD Release
  • Loading branch information
depsiatwal authored May 8, 2024
2 parents f90d6cb + 26a0f0d commit 4d784a0
Show file tree
Hide file tree
Showing 25 changed files with 779 additions and 137 deletions.
9 changes: 5 additions & 4 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions api/applications/tests/test_matching_denials.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,32 @@ def test_revoke_denial_success(self):
self.assertEqual(response["is_revoked"], True)
self.assertEqual(response["is_revoked_comment"], "This denial is no longer active")

def test_revoke_denial_active_success(self):
response = self.client.get(reverse("external_data:denial-list"), **self.gov_headers)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["count"], 4)

denials = response.json()["results"]

# pick one and revoke it
self.assertEqual(denials[0]["is_revoked"], False)
denialentity = models.DenialEntity.objects.get(pk=denials[0]["id"])
denialentity.denial.is_revoked = True
denialentity.denial.save()

response = self.client.patch(
reverse("external_data:denial-detail", kwargs={"pk": denials[0]["id"]}),
{
"is_revoked": False,
},
**self.gov_headers,
)

self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()
self.assertEqual(response["is_revoked"], False)
self.assertEqual(response["is_revoked_comment"], "")

def test_view_denial_notifications_on_the_application(self):
data = []
for index in range(10):
Expand Down
6 changes: 6 additions & 0 deletions api/applications/views/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
from api.workflow.flagging_rules_automation import apply_flagging_rules_to_case

from lite_routing.routing_rules_internal.routing_engine import run_routing_rules
from api.cases.libraries.finalise import remove_flags_on_finalisation, remove_flags_from_audit_trail


class ApplicationList(ListCreateAPIView):
Expand Down Expand Up @@ -450,6 +451,11 @@ def put(self, request, pk):

data = get_application_view_serializer(application)(application, context={"user_type": request.user.type}).data

# Remove needed flags when case is Withdrawn/Closed
if case_status.status in [CaseStatusEnum.WITHDRAWN, CaseStatusEnum.CLOSED]:
remove_flags_on_finalisation(application.get_case())
remove_flags_from_audit_trail(application.get_case())

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


Expand Down
18 changes: 18 additions & 0 deletions api/cases/libraries/finalise.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from api.cases.enums import AdviceType, CaseTypeSubTypeEnum, AdviceLevel
from api.cases.models import Advice, GoodCountryDecision
from api.applications.models import GoodOnApplication
from api.flags.models import Flag
from api.audit_trail.models import Audit


def get_required_decision_document_types(case):
Expand Down Expand Up @@ -40,3 +42,19 @@ def get_required_decision_document_types(case):
required_decisions.add(AdviceType.REFUSE)

return required_decisions


def remove_flags_on_finalisation(case):
flags_to_remove = Flag.objects.filter(remove_on_finalised=True)
case.flags.remove(*flags_to_remove)


def remove_flags_from_audit_trail(case):
flags_to_remove_ids = [str(flag.id) for flag in Flag.objects.filter(remove_on_finalised=True)]
audit_logs = Audit.objects.filter(target_object_id=case.id)

for flag_id in flags_to_remove_ids:
for audit_log in audit_logs:
payload = audit_log.payload
if flag_id in payload.get("added_flags_id", []) or flag_id in payload.get("removed_flags_id", []):
audit_log.delete()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import logging

from django.db import transaction

from django.core.management.base import BaseCommand
from api.cases.libraries.finalise import remove_flags_on_finalisation, remove_flags_from_audit_trail
from api.cases.models import Case
from api.staticdata.statuses.models import CaseStatus
from api.staticdata.statuses.enums import CaseStatusEnum

logger = logging.getLogger(__name__)


class Command(BaseCommand):
help = "Command to remove required flags from Cases and Audit Trails for the past cases"

def handle(self, *args, **options):
count = 0
statuses = [
CaseStatus.objects.get(status=CaseStatusEnum.WITHDRAWN),
CaseStatus.objects.get(status=CaseStatusEnum.FINALISED),
CaseStatus.objects.get(status=CaseStatusEnum.CLOSED),
]

cases = Case.objects.filter(status__in=statuses)

with transaction.atomic():
for case in cases:
remove_flags_on_finalisation(case)
remove_flags_from_audit_trail(case)
count += 1

logging.info("Successfully adjusted %s cases.", cases.count())
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from django.core.management import call_command
from parameterized import parameterized

from test_helpers.clients import DataTestClient

from api.flags.models import Flag
from api.users.models import BaseUser
from api.users.enums import UserType
from api.audit_trail.enums import AuditType

from api.audit_trail import service as audit_trail_service

from api.cases.tests.factories import FinalAdviceFactory
from api.cases.enums import AdviceType
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.models import CaseStatus
from api.audit_trail.models import Audit


class TestCommand(DataTestClient):
def setUp(self):
super().setUp()
self.application = self.create_standard_application_case(self.organisation)
self.original_flag_id = self.application.flags.first().id

# Add Flag to test with removal after Finalise
self.test_flag = Flag.objects.all().first()
self.test_flag.remove_on_finalised = True
self.test_flag.save()
self.application.flags.add(self.test_flag)

user = BaseUser(email="[email protected]", first_name="John", last_name="Smith", type=UserType.SYSTEM)

self.case = self.application.get_case()

self.test_audit = audit_trail_service.create(
actor=user,
verb=AuditType.ADD_FLAGS,
target=self.case,
payload={
"added_flags": [self.test_flag.name],
"additional_text": "test",
"added_flags_id": [str(self.test_flag.id)],
},
)

FinalAdviceFactory(user=self.gov_user, case=self.application, type=AdviceType.APPROVE)

@parameterized.expand(
case_status for case_status in [CaseStatusEnum.FINALISED, CaseStatusEnum.WITHDRAWN, CaseStatusEnum.CLOSED]
)
def test_call_command_removes_flags_and_audits_from_cases(self, case_status):
status_var = CaseStatus.objects.get(status=case_status)
self.case.status = status_var
self.case.save()

audit_queryset = Audit.objects.filter(target_object_id=self.case.id)

self.assertEqual(self.case.flags.count(), 2)
self.assertEqual(audit_queryset.count(), 2)

call_command("removes_flags_and_audits_from_cases")

audit_queryset = Audit.objects.filter(target_object_id=self.case.id)

self.assertNotIn(self.test_flag, self.case.flags.all())
self.assertEqual(audit_queryset.count(), 1)
self.assertNotIn(self.test_audit, audit_queryset)
1 change: 1 addition & 0 deletions api/cases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ def save(self, *args, **kwargs):
case=self.case,
good=self.good,
user=self.user,
team=self.user.team,
level=AdviceLevel.USER,
goods_type=self.goods_type,
country=self.country,
Expand Down
22 changes: 22 additions & 0 deletions api/cases/tests/test_edit_advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ def test_edit_standard_case_advice_twice_only_shows_once(self):
# Assert that there's only one piece of advice
self.assertEqual(Advice.objects.count(), 1)

def test_edit_standard_case_advice_same_user_on_different_teams_saves_for_each_team(self):
"""
Tests that a gov user can create two pieces of advice on the same
case item (be that a good or destination) as long as they change team
"""
data = {
"type": AdviceType.APPROVE,
"text": "I Am Easy to Find",
"note": "I Am Easy to Find",
"country": "GB",
"level": AdviceLevel.USER,
}

self.client.post(self.url, **self.gov_headers, data=[data])

self.gov_user.team = Team.objects.exclude(pk=self.gov_user.team.pk).first()
self.gov_user.save()
self.assertIsNotNone(self.gov_user.team)
self.client.post(self.url, **self.gov_headers, data=[data])

self.assertEqual(Advice.objects.count(), 2)

def test_edit_standard_case_advice_updates_denial_reasons(self):
data = {
"type": AdviceType.REFUSE,
Expand Down
78 changes: 78 additions & 0 deletions api/cases/tests/test_finalise_advice.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import pytest
from unittest import mock
from django.urls import reverse
from api.audit_trail.enums import AuditType
from api.flags.models import Flag
from api.users.enums import UserType
from api.users.models import BaseUser
from rest_framework import status
from parameterized import parameterized

from api.audit_trail.models import Audit
from api.cases.enums import AdviceType, CaseTypeEnum
Expand All @@ -12,6 +18,8 @@
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.models import CaseStatus
from test_helpers.clients import DataTestClient
from api.audit_trail import service as audit_trail_service
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status


class RefuseAdviceTests(DataTestClient):
Expand Down Expand Up @@ -121,6 +129,29 @@ class ApproveAdviceTests(DataTestClient):
def setUp(self):
super().setUp()
self.application = self.create_standard_application_case(self.organisation)
self.original_flag_id = self.application.flags.first().id

# Add Flag to test with removal after Finalise
self.test_flag = Flag.objects.all().first()
self.test_flag.remove_on_finalised = True
self.test_flag.save()
self.application.flags.add(self.test_flag)

user = BaseUser(email="[email protected]", first_name="John", last_name="Smith", type=UserType.SYSTEM)

case = self.application.get_case()

self.test_audit = audit_trail_service.create(
actor=user,
verb=AuditType.ADD_FLAGS,
target=case,
payload={
"added_flags": [self.test_flag.name],
"additional_text": "test",
"added_flags_id": [str(self.test_flag.id)],
},
)

self.url = reverse("cases:finalise", kwargs={"pk": self.application.id})
FinalAdviceFactory(user=self.gov_user, case=self.application, type=AdviceType.APPROVE)
self.template = self.create_letter_template(
Expand Down Expand Up @@ -151,3 +182,50 @@ def test_approve_standard_application_success(
send_exporter_notifications_func.assert_called()

assert case.sub_status.name == "Approved"

@mock.patch("api.cases.views.views.notify_exporter_licence_issued")
@mock.patch("api.cases.generated_documents.models.GeneratedCaseDocument.send_exporter_notifications")
def test_finalised_standard_application_with_flags_removed(
self,
send_exporter_notifications_func,
mock_notify_exporter_licence_issued,
):
self.gov_user.role.permissions.set([GovPermissions.MANAGE_LICENCE_FINAL_ADVICE.name])
self.create_generated_case_document(self.application, self.template, advice_type=AdviceType.APPROVE)

self.assertEqual(self.application.flags.count(), 2)

response = self.client.put(self.url, data={}, **self.gov_headers)
self.application.refresh_from_db()

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(self.application.status, CaseStatus.objects.get(status=CaseStatusEnum.FINALISED))
for document in GeneratedCaseDocument.objects.filter(advice_type__isnull=False):
self.assertTrue(document.visible_to_exporter)

# Make sure Flag was removed and Audit trail was removed
case = get_case(self.application.id)
self.assertNotIn(self.test_flag, case.flags.all())

audit_queryset = Audit.objects.filter(target_object_id=case.id)
self.assertNotIn(self.test_audit, audit_queryset)

@parameterized.expand(case_status for case_status in [CaseStatusEnum.WITHDRAWN, CaseStatusEnum.CLOSED])
def test_standard_application_remove_audit_and_flag_with_statuses(self, case_status):
url = reverse("applications:manage_status", kwargs={"pk": self.application.id})
data = {"status": case_status}

self.assertEqual(self.application.flags.count(), 2)

response = self.client.put(url, data=data, **self.gov_headers)
self.application.refresh_from_db()

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.application.status, get_case_status_by_status(case_status))

# Make sure Flag was removed and Audit trail was removed
case = get_case(self.application.id)
self.assertNotIn(self.test_flag, case.flags.all())

audit_queryset = Audit.objects.filter(target_object_id=case.id)
self.assertNotIn(self.test_audit, audit_queryset)
10 changes: 9 additions & 1 deletion api/cases/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
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.finalise import (
get_required_decision_document_types,
remove_flags_on_finalisation,
remove_flags_from_audit_trail,
)
from api.cases.libraries.get_case import get_case, get_case_document
from api.cases.libraries.get_destination import get_destination
from api.cases.libraries.get_ecju_queries import get_ecju_query
Expand Down Expand Up @@ -977,6 +981,10 @@ def put(self, request, pk):
case.save()
logging.info("Case status is now finalised")

# Remove Flags and related Audits when Finalising
remove_flags_on_finalisation(case)
remove_flags_from_audit_trail(case)

decisions = required_decisions.copy()

if AdviceType.REFUSE in decisions:
Expand Down
1 change: 0 additions & 1 deletion api/data_workspace/tests/test_external_data_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def test_denial_view(self):
"country",
"item_list_codes",
"item_description",
"consignee_name",
"end_use",
"data",
"is_revoked",
Expand Down
Loading

0 comments on commit 4d784a0

Please sign in to comment.