diff --git a/api/applications/serializers/standard_application.py b/api/applications/serializers/standard_application.py index 9d995a15a..6643d749e 100644 --- a/api/applications/serializers/standard_application.py +++ b/api/applications/serializers/standard_application.py @@ -1,3 +1,4 @@ +from django.db.models import Q from rest_framework import serializers from rest_framework.fields import CharField @@ -17,10 +18,13 @@ from api.applications.serializers.good import GoodOnApplicationViewSerializer from api.licences.serializers.view_licence import CaseLicenceViewSerializer from api.applications.serializers.serializer_helper import validate_field +from api.audit_trail.enums import AuditType +from api.audit_trail.models import Audit from api.cases.enums import CaseTypeEnum from api.core.serializers import KeyValueChoiceField from api.licences.models import Licence from lite_content.lite_api import strings +from api.staticdata.statuses.enums import CaseStatusEnum from api.staticdata.trade_control.enums import TradeControlProductCategory, TradeControlActivity @@ -34,6 +38,7 @@ class StandardApplicationViewSerializer(PartiesSerializerMixin, GenericApplicati trade_control_activity = serializers.SerializerMethodField() trade_control_product_categories = serializers.SerializerMethodField() sanction_matches = serializers.SerializerMethodField() + is_amended = serializers.SerializerMethodField() class Meta: model = StandardApplication @@ -69,6 +74,7 @@ class Meta: "trade_control_activity", "trade_control_product_categories", "sanction_matches", + "is_amended", ) ) @@ -99,6 +105,32 @@ def get_denial_matches(self, instance): denial_matches = DenialMatchOnApplication.objects.filter(application=instance, denial__is_revoked=False) return DenialMatchOnApplicationViewSerializer(denial_matches, many=True).data + def get_is_amended(self, instance): + """Determines whether an application is major/minor edited using Audit logs + and returns True if either of the amends are done, False otherwise""" + audit_qs = Audit.objects.filter(target_object_id=instance.id) + is_reference_name_updated = audit_qs.filter(verb=AuditType.UPDATED_APPLICATION_NAME).exists() + is_product_removed = audit_qs.filter(verb=AuditType.REMOVE_GOOD_FROM_APPLICATION).exists() + app_letter_ref_updated = audit_qs.filter( + Q( + verb__in=[ + AuditType.ADDED_APPLICATION_LETTER_REFERENCE, + AuditType.UPDATE_APPLICATION_LETTER_REFERENCE, + AuditType.REMOVED_APPLICATION_LETTER_REFERENCE, + ] + ) + ) + # in case of doing major edits then the status is set as "Applicant editing" + # Here we are detecting the transition from "Submitted" -> "Applicant editing" + for item in audit_qs.filter(verb=AuditType.UPDATED_STATUS): + status = item.payload["status"] + if status["old"] == CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED) and status[ + "new" + ] == CaseStatusEnum.get_text(CaseStatusEnum.APPLICANT_EDITING): + return True + + return any([is_reference_name_updated, app_letter_ref_updated, is_product_removed]) + class StandardApplicationCreateSerializer(GenericApplicationCreateSerializer): export_type = KeyValueChoiceField( diff --git a/api/applications/tests/test_application_status.py b/api/applications/tests/test_application_status.py index 58efad2eb..3f97b5a33 100644 --- a/api/applications/tests/test_application_status.py +++ b/api/applications/tests/test_application_status.py @@ -5,6 +5,7 @@ from parameterized import parameterized from rest_framework import status +from api.audit_trail.models import AuditType, Audit from api.cases.models import CaseAssignment from gov_notify.enums import TemplateType from api.licences.enums import LicenceStatus @@ -57,6 +58,9 @@ def test_exporter_set_application_status_applicant_editing_when_in_editable_stat self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(self.standard_application.status, get_case_status_by_status(CaseStatusEnum.APPLICANT_EDITING)) + audit_event = Audit.objects.first() + self.assertEqual(audit_event.verb, AuditType.UPDATED_STATUS) + self.assertEqual(audit_event.payload, {"status": {"new": "Applicant editing", "old": "Submitted"}}) def test_exporter_set_application_status_withdrawn_when_application_not_terminal_success(self): self.submit_application(self.standard_application) diff --git a/api/applications/tests/test_edit_application.py b/api/applications/tests/test_edit_application.py index df37aff65..1a5452637 100644 --- a/api/applications/tests/test_edit_application.py +++ b/api/applications/tests/test_edit_application.py @@ -56,6 +56,7 @@ def test_edit_application_name_in_editable_status_success(self, editable_status) self.assertEqual(application.name, self.data["name"]) self.assertNotEqual(application.updated_at, updated_at) self.assertEqual(audit_qs.count(), 2) + self.assertEqual(audit_object.verb, AuditType.UPDATED_APPLICATION_NAME) self.assertEqual(audit_object.payload, {"new_name": self.data["name"], "old_name": old_name}) @parameterized.expand(get_case_statuses(read_only=True)) diff --git a/api/applications/tests/test_removing_goods.py b/api/applications/tests/test_removing_goods.py index d53d38d80..27b14c2c6 100644 --- a/api/applications/tests/test_removing_goods.py +++ b/api/applications/tests/test_removing_goods.py @@ -5,6 +5,7 @@ from api.applications.libraries.case_status_helpers import get_case_statuses from api.applications.models import GoodOnApplication +from api.audit_trail.models import Audit, AuditType from api.flags.enums import SystemFlags from api.goods.enums import GoodStatus from api.goods.models import Good, FirearmGoodDetails @@ -88,6 +89,8 @@ def test_remove_a_good_from_application_success_when_good_is_on_multiple_applica self.assertEqual( Good.objects.get(pk=good_on_application2.good.pk).status, GoodStatus.SUBMITTED, ) + audit_event = Audit.objects.first() + self.assertEqual(audit_event.verb, AuditType.REMOVE_GOOD_FROM_APPLICATION) def test_remove_a_good_that_does_not_exist_from_draft(self): """ diff --git a/api/data_workspace/tests/test_application_views.py b/api/data_workspace/tests/test_application_views.py index a8f68b29d..551cf5c4e 100644 --- a/api/data_workspace/tests/test_application_views.py +++ b/api/data_workspace/tests/test_application_views.py @@ -70,6 +70,7 @@ def test_dw_standard_application_views(self): "destinations", "denial_matches", "licence", + "is_amended", ) for key in expected_keys: self.assertTrue(key in actual_keys)