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 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_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 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)
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_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): """ 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 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 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, )
def can_set_status(case, status): """ Returns true or false depending on different case conditions """ from api.compliance.models import ComplianceVisitCase from api.compliance.helpers import compliance_visit_case_complete reference_type = case.case_type.reference if reference_type == CaseTypeReferenceEnum.COMP_SITE and status not in CaseStatusEnum.compliance_site_statuses: return False elif reference_type == CaseTypeReferenceEnum.COMP_VISIT and status not in CaseStatusEnum.compliance_visit_statuses: return False if case.case_type.reference == CaseTypeReferenceEnum.COMP_VISIT and CaseStatusEnum.is_terminal( status): comp_case = ComplianceVisitCase.objects.get(id=case.id) if not compliance_visit_case_complete(comp_case): return False if reference_type == CaseTypeReferenceEnum.CRE and status not in [ CaseStatusEnum.CLOSED, CaseStatusEnum.SUBMITTED, CaseStatusEnum.RESUBMITTED, ]: return False return True