def inner(request, *args, **kwargs): application_status = _get_application(request, kwargs).values_list("status__status", flat=True)[0] if is_editable and application_status in CaseStatusEnum.read_only_statuses(): return JsonResponse( data={ "errors": { "non_field_errors": [ strings.Applications.Generic.INVALID_OPERATION_FOR_READ_ONLY_CASE_ERROR ] } }, status=status.HTTP_400_BAD_REQUEST, ) if is_major_editable and application_status not in CaseStatusEnum.major_editable_statuses(): return JsonResponse( data={ "errors": { "non_field_errors": [ strings.Applications.Generic.INVALID_OPERATION_FOR_NON_DRAFT_OR_MAJOR_EDIT_CASE_ERROR ] } }, status=status.HTTP_400_BAD_REQUEST, ) return func(request, *args, **kwargs)
def submit_application(application: BaseApplication, user: ExporterUser = None): if not user: user = UserOrganisationRelationship.objects.filter( organisation_id=application.organisation_id).first().user application.submitted_at = timezone.localtime() application.sla_remaining_days = get_application_target_sla( application.case_type.sub_type) application.status = get_case_status_by_status( CaseStatusEnum.SUBMITTED) application.save() if application.case_type.sub_type in [ CaseTypeSubTypeEnum.STANDARD, CaseTypeSubTypeEnum.OPEN ]: set_case_flags_on_submitted_standard_or_open_application( application) add_goods_flags_to_submitted_application(application) apply_flagging_rules_to_case(application) audit_trail_service.create( actor=user.baseuser_ptr, verb=AuditType.UPDATED_STATUS, target=application.get_case(), payload={ "status": { "new": CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED), "old": CaseStatusEnum.get_text(CaseStatusEnum.DRAFT), } }, ) return application
def get_case_statuses(read_only): """ Get a list of the case statuses that are read-only. """ if read_only: return CaseStatusEnum.read_only_statuses() else: return [ status for status, value in CaseStatusEnum.choices if not CaseStatusEnum.is_read_only(status) ]
def test_standard_application_declaration_submit_success(self, upload_bytes_file_func, html_to_pdf_func): upload_bytes_file_func.return_value = None html_to_pdf_func.return_value = None self.draft.agreed_to_foi = True self.draft.save() self.assertEqual(self.draft.status.status, CaseStatusEnum.DRAFT) data = { "submit_declaration": "True", "agreed_to_declaration": "True", "agreed_to_foi": "False", "foi_reason": "Lorem ipsum", } url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) response = self.client.put(url, data, **self.exporter_headers) self.assertEqual(response.status_code, status.HTTP_200_OK) case = Case.objects.get(id=self.draft.id) self.assertIsNotNone(case.submitted_at) self.assertNotEqual(case.status.status, CaseStatusEnum.DRAFT) self.assertFalse(case.status.is_terminal) self.assertEqual(case.baseapplication.agreed_to_foi, False) self.assertEqual(case.baseapplication.foi_reason, "Lorem ipsum") self.assertEqual(case.submitted_by, self.exporter_user) self.assertTrue(UUID(SystemFlags.ENFORCEMENT_CHECK_REQUIRED) in case.flags.values_list("id", flat=True)) for good_on_application in GoodOnApplication.objects.filter(application=case): self.assertEqual(good_on_application.good.status, GoodStatus.SUBMITTED) case_status_audits = Audit.objects.filter(target_object_id=case.id, verb=AuditType.UPDATED_STATUS).values_list( "payload", flat=True ) self.assertIn( { "status": { "new": CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED), "old": CaseStatusEnum.get_text(CaseStatusEnum.DRAFT), } }, case_status_audits, ) # Asserting that the 'Application Form' has been autogenerated on submission of the application html_to_pdf_func.assert_called_once() upload_bytes_file_func.assert_called_once() self.assertEqual( CaseDocument.objects.filter( name__contains=AutoGeneratedDocuments.APPLICATION_FORM, type=CaseDocumentState.AUTO_GENERATED, safe=True, case=case, visible_to_exporter=False, ).count(), 1, )
def change_status(self, user, status: CaseStatus, note: Optional[str] = ""): """ Sets the status for the case, runs validation on various parameters, creates audit entries and also runs flagging and automation rules """ from api.cases.helpers import can_set_status from api.audit_trail import service as audit_trail_service from api.applications.libraries.application_helpers import can_status_be_set_by_gov_user from api.workflow.automation import run_routing_rules from api.workflow.flagging_rules_automation import apply_flagging_rules_to_case from api.licences.helpers import update_licence_status old_status = self.status.status # Only allow the final decision if the user has the MANAGE_FINAL_ADVICE permission if status.status == CaseStatusEnum.FINALISED: assert_user_has_permission( user.govuser, GovPermissions.MANAGE_LICENCE_FINAL_ADVICE) if not can_set_status(self, status.status): raise ValidationError({"status": [strings.Statuses.BAD_STATUS]}) if not can_status_be_set_by_gov_user( user.govuser, old_status, status.status, is_mod=False): raise ValidationError({"status": ["Status cannot be set by user"]}) self.status = status self.save() # Update licence status if applicable case status change update_licence_status(self, status.status) if CaseStatusEnum.is_terminal( old_status) and not CaseStatusEnum.is_terminal( self.status.status): apply_flagging_rules_to_case(self) audit_trail_service.create( actor=user, verb=AuditType.UPDATED_STATUS, target=self, payload={ "status": { "new": CaseStatusEnum.get_text(self.status.status), "old": old_status }, "additional_text": note, }, ) if old_status != self.status.status: run_routing_rules(case=self, keep_status=True)
def create_submitted_audit(request: Request, application: HmrcQuery, old_status: str) -> None: audit_trail_service.create( actor=request.user, verb=AuditType.UPDATED_STATUS, target=application.get_case(), payload={ "status": { "new": CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED), "old": CaseStatusEnum.get_text(old_status), } }, ignore_case_status=True, send_notification=False, )
def test_gov_set_status_for_all_except_applicant_editing_and_finalised_success(self, case_status): if case_status == CaseStatusEnum.REVOKED: self.standard_application.licences.add( self.create_licence(self.standard_application, status=LicenceStatus.ISSUED) ) data = {"status": case_status} with mock.patch("gov_notify.service.client") as mock_notify_client: response = self.client.put(self.url, data=data, **self.gov_headers) self.standard_application.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(self.standard_application.status, get_case_status_by_status(case_status)) if CaseStatusEnum.is_terminal(case_status): mock_notify_client.send_email.assert_called_with( email_address=self.standard_application.submitted_by.email, template_id=TemplateType.APPLICATION_STATUS.template_id, data={ "case_reference": self.standard_application.reference_code, "application_reference": self.standard_application.name, "link": f"{settings.EXPORTER_BASE_URL}/applications/{self.standard_application.pk}", }, )
def put(self, request, pk): """ Respond to a control list classification.""" assert_user_has_permission(request.user.govuser, constants.GovPermissions.RESPOND_PV_GRADING) query = get_exporter_query(pk) if CaseStatusEnum.is_terminal(query.status.status): return JsonResponse( data={"errors": [strings.Applications.Generic.TERMINAL_CASE_CANNOT_PERFORM_OPERATION_ERROR]}, status=status.HTTP_400_BAD_REQUEST, ) data = request.data pv_grading_good_serializer = PVGradingResponseSerializer(data=data) if pv_grading_good_serializer.is_valid(): if not str_to_bool(data.get("validate_only")): pv_grading = pv_grading_good_serializer.save() self.update_query_and_good(query, data, pv_grading) self.generate_audit_trail(request.user, query) # Send a notification to the user for user_relationship in UserOrganisationRelationship.objects.filter(organisation=query.organisation): user_relationship.send_notification(content_object=query, case=query) return JsonResponse( data={"pv_grading_query": pv_grading_good_serializer.data}, status=status.HTTP_200_OK, ) return JsonResponse(data={"pv_grading_query": data}, status=status.HTTP_200_OK) return JsonResponse(data={"errors": pv_grading_good_serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
def can_status_be_set_by_gov_user(user: GovUser, original_status: str, new_status: str, is_mod: bool) -> bool: """ Check that a status can be set by a gov user. Gov users can not set a case's status to `Applicant editing`. They also cannot set a case's status to `Finalised` or open a closed case without additional permissions. """ if new_status == CaseStatusEnum.APPLICANT_EDITING: return False elif CaseStatusEnum.is_terminal( original_status) and not assert_user_has_permission( user, GovPermissions.REOPEN_CLOSED_CASES): return False if new_status == CaseStatusEnum.FINALISED: if is_mod: if not assert_user_has_permission( user, GovPermissions.MANAGE_CLEARANCE_FINAL_ADVICE): return False else: if not assert_user_has_permission( user, GovPermissions.MANAGE_LICENCE_FINAL_ADVICE): return False return True
def test_exporter_set_application_status_surrendered_success(self, mock_notify_client): self.standard_application.status = get_case_status_by_status(CaseStatusEnum.FINALISED) self.standard_application.save() self.create_licence(self.standard_application, status=LicenceStatus.ISSUED) surrendered_status = get_case_status_by_status("surrendered") data = {"status": CaseStatusEnum.SURRENDERED} response = self.client.put(self.url, data=data, **self.exporter_headers) response_data = response.json()["data"] self.standard_application.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response_data["status"], {"key": surrendered_status.status, "value": CaseStatusEnum.get_text(surrendered_status.status)}, ) self.assertEqual(self.standard_application.status, get_case_status_by_status(CaseStatusEnum.SURRENDERED)) mock_notify_client.send_email.assert_called_with( email_address=self.standard_application.submitted_by.email, template_id=TemplateType.APPLICATION_STATUS.template_id, data={ "case_reference": self.standard_application.reference_code, "application_reference": self.standard_application.name, "link": f"{settings.EXPORTER_BASE_URL}/applications/{self.standard_application.pk}", }, )
def test_rules_rerun_when_no_rules_are_applied_then_case_status_is_changed_and_audited( self): self.routing_rule_1.delete() self.case.queues.set([self.other_queue.id]) response = self.client.put(self.url, {}, **self.gov_headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.case.refresh_from_db() # Case has been removed from queues self.assertEqual(self.case.queues.count(), 0) # Assert case status changed to all applicable statuses in correct order (ignoring initial submission status) exclude_payload = { "status": { "new": CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED), "old": CaseStatusEnum.get_text(CaseStatusEnum.DRAFT), } } actual_status_changes = (Audit.objects.filter( target_object_id=self.case.id, verb=AuditType.UPDATED_STATUS).exclude( payload=exclude_payload).order_by("created_at")) applicable_status_changes = CaseStatus.objects.filter( workflow_sequence__isnull=False, workflow_sequence__gt=CaseStatus.objects.get( status=CaseStatusEnum.SUBMITTED).workflow_sequence, workflow_sequence__lte=CaseStatus.objects.get( status=CaseStatusEnum.UNDER_FINAL_REVIEW).workflow_sequence, ).order_by("workflow_sequence") self.assertEqual(actual_status_changes.count(), applicable_status_changes.count()) for index in range(len(applicable_status_changes)): self.assertEqual( actual_status_changes[index].payload["status"]["new"], CaseStatusEnum.get_text( applicable_status_changes[index].status), ) # Assert the case status was finally set to Under Final Review (the last applicable status) self.assertEqual(self.case.status.status, CaseStatusEnum.UNDER_FINAL_REVIEW)
def can_status_be_set_by_exporter_user(original_status: str, new_status: str) -> bool: """ Check that a status can be set by an exporter user. Exporter users cannot withdraw an application that is already in a terminal state and they cannot set an application to `Applicant editing` if the application is read only. """ if new_status == CaseStatusEnum.WITHDRAWN: if CaseStatusEnum.is_terminal(original_status): return False elif new_status == CaseStatusEnum.SURRENDERED: if original_status != CaseStatusEnum.FINALISED: return False elif CaseStatusEnum.is_read_only( original_status) or new_status != CaseStatusEnum.APPLICANT_EDITING: return False return True
def test_exp_set_application_status_to_submitted_when_previously_applicant_editing_success( self, upload_bytes_file_func, html_to_pdf_func ): upload_bytes_file_func.return_value = None html_to_pdf_func.return_value = None standard_application = self.create_draft_standard_application(self.organisation) self.submit_application(standard_application) standard_application.status = get_case_status_by_status(CaseStatusEnum.APPLICANT_EDITING) standard_application.save() previous_submitted_at = standard_application.submitted_at data = {"submit_declaration": True, "agreed_to_declaration": True, "agreed_to_foi": True, "foi_reason": ""} url = reverse("applications:application_submit", kwargs={"pk": standard_application.id}) response = self.client.put(url, data, **self.exporter_headers) standard_application.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotEqual( standard_application.status.status, CaseStatusEnum.APPLICANT_EDITING, ) self.assertFalse(standard_application.status.is_terminal) self.assertNotEqual(standard_application.submitted_at, previous_submitted_at) self.assertEqual(standard_application.agreed_to_foi, True) case_status_audits = Audit.objects.filter( target_object_id=standard_application.id, verb=AuditType.UPDATED_STATUS ).values_list("payload", flat=True) self.assertIn( { "status": { "new": CaseStatusEnum.get_text(CaseStatusEnum.SUBMITTED), "old": CaseStatusEnum.get_text(CaseStatusEnum.APPLICANT_EDITING), } }, case_status_audits, ) html_to_pdf_func.assert_called_once() upload_bytes_file_func.assert_called_once()
def test_application_in_state_major_editable_success(self): application = self.create_standard_application_case(self.organisation) application.status = CaseStatus.objects.get( status=CaseStatusEnum.major_editable_statuses()[0]) application.save() @application_in_state(is_major_editable=True) def a_view(request, *args, **kwargs): return HttpResponse() resp = a_view(request=None, pk=application.pk) self.assertEqual(resp.status_code, status.HTTP_200_OK)
def save(self, *args, **kwargs): if CaseStatusEnum.is_terminal(self.status.status): self.case_officer = None if self.pk: self.queues.clear() CaseAssignment.objects.filter(case=self).delete() if not self.reference_code and self.status != get_case_status_by_status( CaseStatusEnum.DRAFT): self.reference_code = generate_reference_code(self) super(Case, self).save(*args, **kwargs)
class CaseNotesExporterCreateTests(DataTestClient): def setUp(self): super().setUp() self.standard_application = self.create_draft_standard_application( self.organisation) self.case = self.submit_application(self.standard_application) self.url = reverse("cases:case_notes", kwargs={"pk": self.case.id}) self.data = {"text": "Days of brutalism"} def test_create_case_note_successful(self): response = self.client.post(self.url, data=self.data, **self.exporter_headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(CaseNote.objects.count(), 1) self.assertEqual(CaseNote.objects.get().text, self.data.get("text")) self.assertEqual(CaseNote.objects.get().is_visible_to_exporter, True) @parameterized.expand([ [{}], # Empty data [{ "text": "" }], # Empty text field [{ "text": "🍌" }], # Less than two character minimum [{ "text": "🍌" * 2201 }], # More than two thousand, two hundred character maximum ]) def test_create_case_note_failure(self, data): response = self.client.post(self.url, data=data, **self.exporter_headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(CaseNote.objects.count(), 0) @parameterized.expand(CaseStatusEnum.terminal_statuses()) def test_create_case_note_case_terminal_state_failure_exporter_user( self, terminal_status): self.standard_application.status = get_case_status_by_status( terminal_status) self.standard_application.save() response = self.client.post(self.url, data=self.data, **self.exporter_headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_application_in_state_editable_failure(self): application = self.create_standard_application_case(self.organisation) application_status = CaseStatusEnum.read_only_statuses()[0] application.status = CaseStatus.objects.get(status=application_status) application.save() @application_in_state(is_editable=True) def a_view(request, *args, **kwargs): return HttpResponse() resp = a_view(request=None, pk=application.pk) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertTrue(strings.Applications.Generic. INVALID_OPERATION_FOR_READ_ONLY_CASE_ERROR in resp.content.decode("utf-8"))
def convert_status(status): converted = CaseStatusEnum.get_value(status) return converted if converted else status
def put(self, request, pk): """ Respond to a control list classification.""" assert_user_has_permission(request.user.govuser, constants.GovPermissions.REVIEW_GOODS) query = get_exporter_query(pk) if CaseStatusEnum.is_terminal(query.status.status): return JsonResponse( data={"errors": [strings.Applications.Generic.TERMINAL_CASE_CANNOT_PERFORM_OPERATION_ERROR]}, status=status.HTTP_400_BAD_REQUEST, ) data = request.data clc_good_serializer = ClcControlGoodSerializer(query.good, data=data) if clc_good_serializer.is_valid(): if not str_to_bool(data.get("validate_only")): previous_control_list_entries = list( query.good.control_list_entries.values_list("rating", flat=True) ) or [strings.Goods.GOOD_NO_CONTROL_CODE] clc_good_serializer.save() query.clc_responded = True query.save() if clc_good_serializer.validated_data.get("control_list_entries"): values = clc_good_serializer.validated_data["control_list_entries"] new_control_list_entries = [clc.rating for clc in values] else: new_control_list_entries = [strings.Goods.GOOD_NO_CONTROL_CODE] if new_control_list_entries != previous_control_list_entries: audit_trail_service.create( actor=request.user, verb=AuditType.GOOD_REVIEWED, action_object=query.good, target=query.get_case(), payload={ "good_name": query.good.description, "old_control_list_entry": previous_control_list_entries, "new_control_list_entry": new_control_list_entries, }, ) flag = Flag.objects.get(id=SystemFlags.GOOD_CLC_QUERY_ID) query.good.flags.remove(flag) query.good.status = GoodStatus.VERIFIED query.good.save() apply_flagging_rules_to_case(query) audit_trail_service.create( actor=request.user, verb=AuditType.CLC_RESPONSE, action_object=query.good, target=query.get_case(), ) # Send a notification to the user for user_relationship in UserOrganisationRelationship.objects.filter(organisation=query.organisation): user_relationship.send_notification(content_object=query, case=query) return JsonResponse( data={"control_list_classification_query": clc_good_serializer.data}, status=status.HTTP_200_OK ) return JsonResponse(data={"control_list_classification_query": data}, status=status.HTTP_200_OK) return JsonResponse(data={"errors": clc_good_serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
def get_status(self, instance): return { "key": instance.status.status, "value": CaseStatusEnum.get_text(instance.status.status) }
def is_editable(self): return not CaseStatusEnum.is_read_only(self.status.status)
class CreateCaseAdviceTests(DataTestClient): def setUp(self): super().setUp() self.standard_application = self.create_draft_standard_application( self.organisation) self.good = self.standard_application.goods.first().good self.standard_case = self.submit_application(self.standard_application) team_2 = Team(name="2") team_3 = Team(name="3") team_2.save() team_3.save() role = Role(name="team_level") role.permissions.set([ constants.GovPermissions.MANAGE_LICENCE_FINAL_ADVICE.name, constants.GovPermissions.MANAGE_TEAM_ADVICE.name, constants.GovPermissions.MANAGE_TEAM_CONFIRM_OWN_ADVICE.name, constants.GovPermissions.MANAGE_LICENCE_FINAL_ADVICE.name, constants.GovPermissions.MANAGE_TEAM_ADVICE.name, ]) role.save() self.gov_user.role = role self.gov_user.save() self.gov_user_2 = GovUserFactory(baseuser_ptr__email="*****@*****.**", team=team_2, role=role) self.gov_user_3 = GovUserFactory(baseuser_ptr__email="*****@*****.**", team=team_3, role=role) self.standard_case_url = reverse("cases:case_final_advice", kwargs={"pk": self.standard_case.id}) def test_advice_is_concatenated_when_final_advice_first_created(self): """ Final advice is created on first call """ self.create_advice(self.gov_user, self.standard_case, "end_user", AdviceType.PROVISO, AdviceLevel.TEAM) self.create_advice(self.gov_user_2, self.standard_case, "end_user", AdviceType.PROVISO, AdviceLevel.TEAM) self.create_advice(self.gov_user, self.standard_case, "good", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.TEAM) self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.TEAM) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"] self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response_data), 6) end_user, good = None, None for data in response_data: if data.get("end_user"): end_user = data.get("type").get("key") elif data.get("good"): good = data.get("type").get("key") self.assertEqual(end_user, AdviceType.PROVISO) self.assertEqual(good, AdviceType.NO_LICENCE_REQUIRED) def test_create_conflicting_final_advice_shows_all_fields(self): """ The type should show conflicting if there are conflicting types in the advice on a single object """ self.create_advice(self.gov_user, self.standard_case, "good", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.TEAM) self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.REFUSE, AdviceLevel.TEAM) self.create_advice(self.gov_user_3, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.TEAM) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"][0] self.assertEqual(response_data.get("type").get("key"), "conflicting") self.assertEqual(response_data.get("proviso"), "I am easy to proviso") self.assertCountEqual(["1a", "1b", "1c"], response_data["denial_reasons"]) def test_create_final_advice_same_advice_type_different_pv_gradings(self): """ Same advice types, different pv gradings """ inputs = [ (self.gov_user, PvGrading.UK_OFFICIAL), (self.gov_user_2, PvGrading.UK_OFFICIAL_SENSITIVE), (self.gov_user_3, PvGrading.NATO_CONFIDENTIAL), ] for user, pv_grading in inputs: self.create_advice(user, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.TEAM, pv_grading) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"] self.assertEqual(response_data[0].get("type").get("key"), "proviso") self.assertEqual(response_data[0].get("proviso"), "I am easy to proviso") pv_gradings = Advice.objects.get( id=response_data[0]["id"]).collated_pv_grading self.assertIn("\n-------\n", pv_gradings) for _, pv_grading in inputs: self.assertIn(PvGrading.to_str(pv_grading), pv_gradings) def test_create_final_advice_same_advice_type_same_pv_gradings(self): """ Same advice types, same pv gradings """ pv_grading = PvGrading.OCCAR_CONFIDENTIAL inputs = [self.gov_user, self.gov_user_2, self.gov_user_3] for user in inputs: self.create_advice(user, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.TEAM, pv_grading) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"] self.assertEqual(response_data[0].get("type").get("key"), "proviso") self.assertEqual(response_data[0].get("proviso"), "I am easy to proviso") pv_gradings = Advice.objects.get( id=response_data[0]["id"]).collated_pv_grading self.assertNotIn("\n-------\n", pv_gradings) self.assertIn(PvGrading.to_str(pv_grading), pv_gradings) def test_create_conflicting_final_advice_different_advice_type_same_pv_gradings( self): """ Different advice types, same pv gradings """ pv_grading = PvGrading.UK_OFFICIAL inputs = [ (self.gov_user, AdviceType.PROVISO), (self.gov_user_2, AdviceType.REFUSE), (self.gov_user_3, AdviceType.APPROVE), ] for user, advice_type in inputs: self.create_advice(user, self.standard_case, "good", advice_type, AdviceLevel.TEAM, pv_grading) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"] self.assertEqual(response_data[0].get("type").get("key"), "conflicting") pv_gradings = Advice.objects.get( id=response_data[0]["id"]).collated_pv_grading self.assertNotIn("\n-------\n", pv_gradings) self.assertIn(PvGrading.to_str(pv_grading), pv_gradings) def test_create_conflicting_final_advice_different_advice_type_different_pv_gradings( self): """ Different advice types, different pv gradings """ inputs = [ (self.gov_user, AdviceType.PROVISO, PvGrading.UK_OFFICIAL), (self.gov_user_2, AdviceType.REFUSE, PvGrading.UK_OFFICIAL_SENSITIVE), (self.gov_user_3, AdviceType.APPROVE, PvGrading.NATO_CONFIDENTIAL), ] for user, advice_type, pv_grading in inputs: self.create_advice(user, self.standard_case, "good", advice_type, AdviceLevel.TEAM, pv_grading) response = self.client.get(self.standard_case_url, **self.gov_headers) response_data = response.json()["advice"] self.assertEqual(response_data[0].get("type").get("key"), "conflicting") pv_gradings = Advice.objects.get( id=response_data[0]["id"]).collated_pv_grading self.assertIn("\n-------\n", pv_gradings) for _, _, pv_grading in inputs: self.assertIn(PvGrading.to_str(pv_grading), pv_gradings) # Normal restrictions on team advice items @parameterized.expand([ [AdviceType.APPROVE], [AdviceType.PROVISO], [AdviceType.REFUSE], [AdviceType.NO_LICENCE_REQUIRED], [AdviceType.NOT_APPLICABLE], ]) def test_create_end_user_case_final_advice(self, advice_type): """ Tests that a gov user can create an approval/proviso/refuse/nlr/not_applicable piece of team level advice for an end user """ data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": advice_type, "end_user": str(self.standard_application.end_user.party.id), } if advice_type == AdviceType.PROVISO: data["proviso"] = "I am easy to proviso" if advice_type == AdviceType.REFUSE: data["denial_reasons"] = ["1a", "1b", "1c"] response = self.client.post(self.standard_case_url, **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertIsNotNone(Advice.objects.get()) def test_user_cannot_create_final_advice_without_permissions(self): """ Tests that the permissions are required to perform final level actions """ self.gov_user.role.permissions.set([]) self.gov_user.save() response = self.client.get(self.standard_case_url, **self.gov_headers) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) response = self.client.post(self.standard_case_url, **self.gov_headers) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) response = self.client.delete(self.standard_case_url, **self.gov_headers) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_cannot_submit_user_level_advice_if_final_advice_exists_on_that_case( self): """ Logically blocks the submission of lower tier advice if higher tier advice exists """ FinalAdviceFactory(user=self.gov_user_2, case=self.standard_case, good=self.good) data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } response = self.client.post(reverse( "cases:user_advice", kwargs={"pk": self.standard_case.id}), **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_can_submit_user_level_advice_if_final_advice_has_been_cleared_for_that_team_on_that_case( self, ): """ No residual data is left to block lower tier advice being submitted after a clear """ self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.USER) self.client.delete(self.standard_case_url, **self.gov_headers) data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } response = self.client.post(reverse( "cases:user_advice", kwargs={"pk": self.standard_case.id}), **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_cannot_submit_team_level_advice_if_final_advice_exists_for_that_team_on_that_case( self, ): """ Logically blocks the submission of lower tier advice if higher tier advice exists """ self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.FINAL) data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } response = self.client.post(reverse( "cases:team_advice", kwargs={"pk": self.standard_case.id}), **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_can_submit_team_level_advice_if_final_advice_has_been_cleared_for_that_team_on_that_case( self, ): """ No residual data is left to block lower tier advice being submitted after a clear """ self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.TEAM) self.client.delete(self.standard_case_url, **self.gov_headers) data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } response = self.client.post(reverse( "cases:team_advice", kwargs={"pk": self.standard_case.id}), **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_and_delete_audit_trail_is_created_when_the_appropriate_actions_take_place( self, ): """ Audit trail is created when clearing or combining advice """ self.create_advice(self.gov_user, self.standard_case, "end_user", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.TEAM) self.create_advice(self.gov_user_2, self.standard_case, "good", AdviceType.REFUSE, AdviceLevel.TEAM) self.create_advice(self.gov_user_3, self.standard_case, "good", AdviceType.PROVISO, AdviceLevel.TEAM) self.client.get(self.standard_case_url, **self.gov_headers) self.client.delete(self.standard_case_url, **self.gov_headers) response = self.client.get( reverse("cases:activity", kwargs={"pk": self.standard_case.id}), **self.gov_headers) self.assertEqual(len(response.json()["activity"]), 3) def test_creating_final_advice_does_not_overwrite_user_level_advice_or_team_level_advice( self, ): """ Because of the shared parent class, make sure the parent class "save" method is overridden by the child class """ self.create_advice(self.gov_user, self.standard_case, "end_user", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.USER) self.create_advice(self.gov_user, self.standard_case, "end_user", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.TEAM) self.create_advice(self.gov_user, self.standard_case, "end_user", AdviceType.NO_LICENCE_REQUIRED, AdviceLevel.FINAL) self.client.get(self.standard_case_url, **self.gov_headers) self.assertEqual(Advice.objects.count(), 3) @parameterized.expand(CaseStatusEnum.terminal_statuses()) def test_cannot_create_final_advice_on_case_in_terminal_state( self, terminal_status): self.standard_application.status = get_case_status_by_status( terminal_status) self.standard_application.save() data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } response = self.client.post(self.standard_case_url, **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_get_entity_from_final_advice_model(self): data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "end_user": str(self.standard_application.end_user.party.id), } self.client.post(self.standard_case_url, **self.gov_headers, data=[data]) advice_object = Advice.objects.get( entity_id=self.standard_application.end_user.party.id) self.assertEqual(str(advice_object.end_user.id), data["end_user"]) self.assertEqual(advice_object.entity, advice_object.end_user) entity_field = Advice.ENTITY_FIELDS.copy() entity_field.remove("end_user") for field in entity_field: self.assertIsNone(getattr(advice_object, field, None)) def test_updating_final_advice_removes_draft_decision_documents(self): good = self.standard_application.goods.first().good FinalAdviceFactory( user=self.gov_user, team=self.team, case=self.standard_case, good=good, type=AdviceType.APPROVE, ) template = self.create_letter_template( case_types=[self.standard_case.case_type], decisions=[Decision.objects.get(name=AdviceType.APPROVE)], ) self.create_generated_case_document(self.standard_case, template, advice_type=AdviceType.APPROVE, visible_to_exporter=False) data = { "text": "I Am Easy to Find", "note": "I Am Easy to Find", "type": AdviceType.APPROVE, "good": str(good.id), } response = self.client.post(self.standard_case_url, **self.gov_headers, data=[data]) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertFalse( GeneratedCaseDocument.objects.filter( case=self.standard_case).exists())
class GoodsVerifiedTestsStandardApplication(DataTestClient): def setUp(self): super().setUp() self.report_summary = self.create_picklist_item( "Report Summary", self.team, PicklistType.REPORT_SUMMARY, PickListStatus.ACTIVE) self.good_1 = GoodFactory( organisation=self.organisation, flags=[FlagFactory(level=FlagLevels.GOOD, team=self.team)]) self.good_2 = GoodFactory(organisation=self.organisation) role = Role(name="review_goods") role.permissions.set([constants.GovPermissions.REVIEW_GOODS.name]) role.save() self.gov_user.role = role self.gov_user.save() self.application = self.create_draft_standard_application( organisation=self.organisation) self.good_on_application_1 = GoodOnApplication.objects.create( good=self.good_1, application=self.application, quantity=10, unit=Units.NAR, value=500) self.good_on_application_2 = GoodOnApplication.objects.create( good=self.good_2, application=self.application, quantity=10, unit=Units.NAR, value=500) self.case = self.submit_application(self.application) self.url = reverse_lazy("goods:control_list_entries", kwargs={"case_pk": self.case.id}) def test_verify_multiple_goods(self): """ Post multiple goods to the endpoint, and check that the control code is updated for both """ data = { "objects": [self.good_1.pk, self.good_2.pk], "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "control_list_entries": ["ML1a"], "is_good_controlled": True, } response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, status.HTTP_200_OK) verified_good_1 = Good.objects.get(pk=self.good_1.pk) verified_good_2 = Good.objects.get(pk=self.good_2.pk) self.assertEqual(verified_good_1.control_list_entries.get().rating, "ML1a") self.assertEqual(verified_good_2.control_list_entries.get().rating, "ML1a") def test_verify_multiple_goods_NLR(self): """ Post multiple goods to the endpoint, and check that the control code is not set if good is not controlled """ data = { "objects": [self.good_1.pk, self.good_2.pk], "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "control_list_entries": ["ML1a"], "is_good_controlled": False, } response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, status.HTTP_200_OK) self.good_1.refresh_from_db() self.good_2.refresh_from_db() self.assertEqual(self.good_1.control_list_entries.count(), 1) self.assertEqual(self.good_2.control_list_entries.count(), 1) def test_invalid_good_pk(self): # given one of the good pk is invalid data = { "objects": [self.team.pk, self.good_1.pk], "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "is_good_controlled": False, "control_list_entries": ["ML1b"], } # when I review the goods response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, 200) # then the valid good is updated verified_good = Good.objects.get(pk=self.good_1.pk) self.assertEqual(verified_good.control_list_entries.count(), 1) @parameterized.expand([ ( # legacy where frontend doesn't send is_precedent { "comment": "I Am Easy to Find", "is_good_controlled": False, "control_list_entries": [], }, False, ), ( # precedent = False { "comment": "I Am Easy to Find", "is_good_controlled": False, "control_list_entries": [], "is_precedent": False, }, False, ), ( # precedent = True { "comment": "I Am Easy to Find", "is_good_controlled": False, "control_list_entries": [], "is_precedent": True, }, True, ), ]) def test_is_precedent_is_set(self, input, expected_is_precedent): defaults = { "objects": [self.good_1.pk], "report_summary": self.report_summary.pk, } data = {**defaults, **input} # when I review the goods response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, 200) # then the good_on_application is updated verified_good_on_application = GoodOnApplication.objects.get( pk=self.good_on_application_1.pk) self.assertEqual(verified_good_on_application.is_precedent, expected_is_precedent) def test_standard_invalid_control_list_entries(self): """ Post multiple goods to the endpoint, and that a bad request is returned, and that flags is not updated """ data = { "objects": [self.good_1.pk, self.good_2.pk], "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "is_good_controlled": True, "control_list_entries": ["invalid"], } response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) # since it has an invalid control code, flags should not be removed verified_good = Good.objects.get(pk=self.good_1.pk) self.assertTrue(is_not_verified_flag_set_on_good(verified_good)) def test_standard_controlled_good_empty_control_list_entries(self): """ Post multiple goods, with an blank control_list_entries and is controlled, for a 400 response, and no update of goods """ data = { "objects": [self.good_1.pk, self.good_2.pk], "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "is_good_controlled": True, "control_list_entries": [], } response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, 200) def test_user_cannot_review_good_without_permissions(self): """ Tests that the right level of permissions are required by a gov user to review a good. """ # create a second user to adopt the super user role as it will # overwritten otherwise if we try and remove the role from the first valid_user = GovUserFactory( baseuser_ptr__email="*****@*****.**", baseuser_ptr__first_name="John", baseuser_ptr__last_name="Smith", team=self.team, role=self.super_user_role, ) valid_user.save() self.gov_user.role = self.default_role self.gov_user.save() response = self.client.post(self.url, **self.gov_headers) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @parameterized.expand(CaseStatusEnum.terminal_statuses()) def test_cannot_set_control_list_entries_when_application_in_terminal_state( self, terminal_status): self.application.status = get_case_status_by_status(terminal_status) self.application.save() data = { "objects": self.good_1.pk, "comment": "I Am Easy to Find", "report_summary": self.report_summary.pk, "control_list_entries": "ML1a", "is_good_controlled": "yes", } response = self.client.post(self.url, data, **self.gov_headers) self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
case=case).exists(): CaseAssignment( user=rule.user, queue=rule.queue, case=case).save(audit_user=system_user) team_rule_tier = rule.tier rules_have_been_applied = True break # If no rules have been applied, we wish to either move to the next status, or break loop if keep_status is True # or the next status is terminal if not rules_have_been_applied: next_status = get_next_status_in_workflow_sequence(case) if next_status and not next_status.is_terminal and not keep_status: old_status = case.status case.status = next_status case.save() audit_trail_service.create( actor=system_user, verb=AuditType.UPDATED_STATUS, target=case, payload={ "status": { "new": CaseStatusEnum.get_text(next_status.status), "old": CaseStatusEnum.get_text(old_status.status), } }, ) else: rules_have_been_applied = True
def apply_flagging_rule_to_all_open_cases(flagging_rule: FlaggingRule): """ Takes a flagging rule and creates a relationship between it's flag and objects that meet match conditions """ if flagging_rule.status == FlagStatuses.ACTIVE and flagging_rule.flag.status == FlagStatuses.ACTIVE: # Flagging rules should only be applied to open cases draft_and_terminal_statuses = [ CaseStatusEnum.DRAFT, *CaseStatusEnum.terminal_statuses() ] open_cases = Case.objects.exclude( status__status__in=draft_and_terminal_statuses) # Apply the flagging rule to different entities depending on the rule's level if flagging_rule.level == FlagLevels.CASE: # Add flag to all open Cases open_cases = open_cases.filter( case_type__reference__in=flagging_rule.matching_values ).values_list("id", flat=True) flagging_rule.flag.cases.add(*open_cases) elif flagging_rule.level == FlagLevels.GOOD: clc_entries_of_groups = [] for group in flagging_rule.matching_groups: child_entries = get_clc_child_nodes(group) clc_entries_of_groups.extend(child_entries) matching_values = flagging_rule.matching_values + clc_entries_of_groups # excluded_values contain individual entries and groups excluded_values = [] for rating in flagging_rule.excluded_values: entries = get_clc_child_nodes(rating) excluded_values.extend(entries) # Add flag to all Goods on open Goods Queries goods_in_query = GoodsQuery.objects.filter( good__control_list_entries__rating__in=matching_values ).exclude(status__status__in=draft_and_terminal_statuses) if excluded_values: # exclusion entries - goods that doesn't contain given control list entries goods_in_query = goods_in_query.exclude( good__control_list_entries__rating__in=excluded_values) if flagging_rule.is_for_verified_goods_only: goods_in_query = goods_in_query.filter( good__status=GoodStatus.VERIFIED) goods_in_query = goods_in_query.values_list("good_id", flat=True) flagging_rule.flag.goods.add(*goods_in_query) # Add flag to all Goods Types goods_types = GoodsType.objects.filter( application_id__in=open_cases, control_list_entries__rating__in=matching_values) if excluded_values: goods_types = goods_types.exclude( application_id__in=open_cases, control_list_entries__rating__in=excluded_values) goods_types = goods_types.values_list("id", flat=True) flagging_rule.flag.goods_type.add(*goods_types) # Add flag to all open Applications goods = GoodOnApplication.objects.filter( application_id__in=open_cases, good__control_list_entries__rating__in=matching_values) if excluded_values: goods = goods.exclude( application_id__in=open_cases, good__control_list_entries__rating__in=excluded_values) if flagging_rule.is_for_verified_goods_only: goods = goods.filter(good__status=GoodStatus.VERIFIED) goods = goods.values_list("good_id", flat=True) flagging_rule.flag.goods.add(*goods) elif flagging_rule.level == FlagLevels.DESTINATION: # Add flag to all End Users on open End User Advisory Queries end_users = (EndUserAdvisoryQuery.objects.filter( end_user__country_id__in=flagging_rule.matching_values ).exclude( status__status__in=draft_and_terminal_statuses).values_list( "end_user_id", flat=True)) flagging_rule.flag.parties.add(*end_users) # Add flag to all Parties on open Applications parties = PartyOnApplication.objects.filter( application_id__in=open_cases, party__country_id__in=flagging_rule.matching_values ).values_list("party_id", flat=True) flagging_rule.flag.parties.add(*parties) countries = Country.objects.filter( id__in=flagging_rule.matching_values).values_list("id", flat=True) flagging_rule.flag.countries.add(*countries)
def get_status_value_from_case_status_enum(case_status): if CaseStatusEnum.is_system_status(case_status): return case_status return [x for x in CaseStatusEnum.choices if x[0] == case_status][0][1]
def post(self, request, case_pk): if CaseStatusEnum.is_terminal(self.application.status.status): return JsonResponse( data={ "errors": { "error": [ strings.Applications.Generic. TERMINAL_CASE_CANNOT_PERFORM_OPERATION_ERROR ] } }, status=status.HTTP_400_BAD_REQUEST, ) case = get_case(case_pk) for good in self.get_queryset(): serializer = self.get_serializer(good) serializer.is_valid(raise_exception=True) old_control_list_entries = list( good.control_list_entries.values_list("rating", flat=True)) old_is_controlled = good.is_good_controlled serializer.save() if "control_list_entries" in serializer.data or "is_good_controlled" in serializer.data: new_control_list_entries = [ item.rating for item in serializer.validated_data["control_list_entries"] ] new_is_controlled = serializer.validated_data[ "is_good_controlled"] if new_control_list_entries != old_control_list_entries or new_is_controlled != old_is_controlled: if isinstance(good, GoodsType): good.flags.clear() else: good.good.flags.clear() default_control = [strings.Goods.GOOD_NO_CONTROL_CODE] audit_trail_service.create( actor=request.user, verb=AuditType.GOOD_REVIEWED, action_object=good, target=case, payload={ "good_name": good.description, "new_control_list_entry": new_control_list_entries or default_control, "old_control_list_entry": old_control_list_entries or default_control, "old_is_good_controlled": "Yes" if old_is_controlled else "No", "new_is_good_controlled": "Yes" if new_is_controlled else "No", "additional_text": serializer.validated_data["comment"], "is_precedent": serializer.validated_data.get( "is_precedent", False), }, ) apply_good_flagging_rules_for_case(case) return JsonResponse(data={}, status=status.HTTP_200_OK)
def get_case_status_list() -> List[Dict]: return CaseStatusEnum.as_list()
def test_apply_flagging_rule_to_open_cases(self, case_status): if case_status == CaseStatusEnum.DRAFT: case = self.create_draft_standard_application(self.organisation) else: case = self.create_standard_application_case(self.organisation) case.status = get_case_status_by_status(case_status) case.save() flag = self.create_flag(case.case_type.reference, FlagLevels.CASE, self.team) flagging_rule = self.create_flagging_rule(FlagLevels.CASE, self.team, flag, [case.case_type.reference]) apply_flagging_rule_to_all_open_cases(flagging_rule) case.refresh_from_db() if CaseStatusEnum.is_terminal(case_status) or case_status == CaseStatusEnum.DRAFT: self.assertNotIn(flag, case.flags.all()) else: self.assertIn(flag, case.flags.all()) def test_apply_verified_goods_only_flagging_rule_to_open_cases_failure(self): """ Test flag not applied to good when flagging rule is for verified goods only. """ case = self.create_standard_application_case(self.organisation) flag = self.create_flag("good flag", FlagLevels.GOOD, self.team) flagging_rule = self.create_flagging_rule( FlagLevels.GOOD, self.team, flag, [case.case_type.reference], is_for_verified_goods_only=True ) good = GoodOnApplication.objects.filter(application_id=case.id).first().good apply_flagging_rule_to_all_open_cases(flagging_rule)
def put(self, request, pk): application = get_application(pk) is_licence_application = application.case_type.sub_type != CaseTypeSubTypeEnum.EXHIBITION data = deepcopy(request.data) if data["status"] == CaseStatusEnum.FINALISED: return JsonResponse( data={ "errors": [ strings.Applications.Generic.Finalise.Error. SET_FINALISED ] }, status=status.HTTP_400_BAD_REQUEST, ) if not can_set_status(application, data["status"]): raise ValidationError({"status": [strings.Statuses.BAD_STATUS]}) if hasattr(request.user, "exporteruser"): if get_request_user_organisation_id( request) != application.organisation.id: raise PermissionDenied() if not can_status_be_set_by_exporter_user( application.status.status, data["status"]): return JsonResponse( data={ "errors": [ strings.Applications.Generic.Finalise.Error. EXPORTER_SET_STATUS ] }, status=status.HTTP_400_BAD_REQUEST, ) else: if not can_status_be_set_by_gov_user( request.user.govuser, application.status.status, data["status"], is_licence_application): return JsonResponse( data={ "errors": [ strings.Applications.Generic.Finalise.Error. GOV_SET_STATUS ] }, status=status.HTTP_400_BAD_REQUEST, ) update_licence_status(application, data["status"]) case_status = get_case_status_by_status(data["status"]) data["status"] = str(case_status.pk) old_status = application.status serializer = get_application_update_serializer(application) serializer = serializer(application, data=data, partial=True) if not serializer.is_valid(): return JsonResponse(data={"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST) application = serializer.save() if CaseStatusEnum.is_terminal( old_status.status) and not CaseStatusEnum.is_terminal( application.status.status): # we reapply flagging rules if the status is reopened from a terminal state apply_flagging_rules_to_case(application) audit_trail_service.create( actor=request.user, verb=AuditType.UPDATED_STATUS, target=application.get_case(), payload={ "status": { "new": CaseStatusEnum.get_text(case_status.status), "old": CaseStatusEnum.get_text(old_status.status), }, "additional_text": data.get("note"), }, ) # Case routing rules if old_status != application.status: run_routing_rules(case=application, keep_status=True) if CaseStatusEnum.is_terminal(application.status.status): gov_notify_service.send_email( email_address=application.submitted_by.email, template_type=TemplateType.APPLICATION_STATUS, data=ApplicationStatusEmailData( case_reference=application.reference_code, application_reference=application.name, link= f"{settings.EXPORTER_BASE_URL}/applications/{application.id}", ), ) data = get_application_view_serializer(application)( application, context={ "user_type": request.user.type }).data if application.case_type.sub_type == CaseTypeSubTypeEnum.OPEN: data["destinations"] = get_destinations( application.id, user_type=request.user.type) return JsonResponse( data={"data": data}, status=status.HTTP_200_OK, )