From 6bee13f42bb18ae14b8b7f627cd362cfe7a8e8e3 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 25 Mar 2024 12:06:49 +0000 Subject: [PATCH 01/13] Add basic validator to validate an application for submission --- api/applications/validators.py | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 api/applications/validators.py diff --git a/api/applications/validators.py b/api/applications/validators.py new file mode 100644 index 0000000000..fb731c7cea --- /dev/null +++ b/api/applications/validators.py @@ -0,0 +1,53 @@ +from api.applications.enums import ApplicationExportType +from api.cases.enums import CaseTypeSubTypeEnum +from api.parties.models import PartyDocument +from api.parties.enums import PartyType + +from lite_content.lite_api import strings + + +def siel_end_user_validator(application): + errors = {"end_user": []} + + # perform validation checks + + return errors + + +class BaseApplicationValidator: + config = {} + + def __init__(self, application): + self.application = application + + def validate(self): + all_errors = {} + for _, func in self.config.items(): + errors = func(self.application) + all_errors = {**errors, **all_errors} + + return all_errors + + +class StandardApplicationValidator(BaseApplicationValidator): + config = { + "end_user": siel_end_user_validator, + # more to follow + } + + +class ApplicationValidator: + def __init__(self, application): + self.application = application + + def validate(self): + errors = {} + if not self.application: + raise ValueError("Invalid application") + + if self.application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD: + errors = StandardApplicationValidator(self.application).validate() + else: + raise NotImplementedError("Only SIEL applications are supported") + + return errors From c3714dd62dbc17fb545c3cbbc5e0d5357a6a98d0 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 25 Mar 2024 15:56:34 +0000 Subject: [PATCH 02/13] Update validator to validate end-user details --- api/applications/creators.py | 1 - api/applications/models.py | 6 +++++ api/applications/validators.py | 43 ++++++++++++++++------------------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/api/applications/creators.py b/api/applications/creators.py index aa2a111d49..a7c7ff5818 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -19,7 +19,6 @@ def _validate_siel_locations(application, errors): not SiteOnApplication.objects.filter(application=application).exists() and not ExternalLocationOnApplication.objects.filter(application=application).exists() and not getattr(application, "have_goods_departed", False) - and not getattr(application, "goodstype_category", None) == GoodsTypeCategory.CRYPTOGRAPHIC ) new_locations_invalid = ( diff --git a/api/applications/models.py b/api/applications/models.py index 3abd357210..5a9566f712 100644 --- a/api/applications/models.py +++ b/api/applications/models.py @@ -16,6 +16,7 @@ ) from api.appeals.models import Appeal from api.applications.managers import BaseApplicationManager +from api.applications.validators import StandardApplicationValidator from api.audit_trail.models import Audit, AuditType from api.audit_trail import service as audit_trail_service from api.cases.enums import CaseTypeEnum @@ -265,6 +266,7 @@ class StandardApplication(BaseApplication): (VIA_CONSIGNEE, "To an end-user via a consignee"), (VIA_CONSIGNEE_AND_THIRD_PARTIES, "To an end-user via a consignee, with additional third parties"), ] + validator_class = StandardApplicationValidator export_type = models.TextField(choices=ApplicationExportType.choices, blank=True, default="") reference_number_on_information_form = models.CharField(blank=True, null=True, max_length=255) @@ -302,6 +304,10 @@ class StandardApplication(BaseApplication): f1686_approval_date = models.DateField(blank=False, null=True) other_security_approval_details = models.TextField(default=None, blank=True, null=True) + def validate(self): + validator = self.validator_class(self) + return validator.validate() + class ApplicationDocument(Document): application = models.ForeignKey(BaseApplication, on_delete=models.CASCADE) diff --git a/api/applications/validators.py b/api/applications/validators.py index fb731c7cea..7d54ad9a5f 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -1,17 +1,31 @@ from api.applications.enums import ApplicationExportType from api.cases.enums import CaseTypeSubTypeEnum from api.parties.models import PartyDocument -from api.parties.enums import PartyType +from api.parties.enums import PartyType, PartyDocumentType from lite_content.lite_api import strings def siel_end_user_validator(application): - errors = {"end_user": []} - - # perform validation checks - - return errors + error = None + if not application.end_user: + error = "To submit the application, add an end user" + + party = application.end_user.party + breakpoint() + documents_qs = PartyDocument.objects.filter( + party=party, type=PartyDocumentType.END_USER_UNDERTAKING_DOCUMENT + ).values_list("safe", flat=True) + if documents_qs.exists(): + if None in documents_qs: + error = "We're still processing the end user document. Please submit again" + elif False in documents_qs: + error = "To submit the application, attach a document that does not contain a virus to the end user" + else: + if not party.end_user_document_available and not party.end_user_document_missing_reason: + error = "To submit the application, attach a document to the end user" + + return {"end_user": [error]} if error else None class BaseApplicationValidator: @@ -34,20 +48,3 @@ class StandardApplicationValidator(BaseApplicationValidator): "end_user": siel_end_user_validator, # more to follow } - - -class ApplicationValidator: - def __init__(self, application): - self.application = application - - def validate(self): - errors = {} - if not self.application: - raise ValueError("Invalid application") - - if self.application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD: - errors = StandardApplicationValidator(self.application).validate() - else: - raise NotImplementedError("Only SIEL applications are supported") - - return errors From c7b6edb12a1496544017f948139d4ec76e239d35 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 25 Mar 2024 16:33:18 +0000 Subject: [PATCH 03/13] Add locations validator for SIEL applications --- api/applications/validators.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/api/applications/validators.py b/api/applications/validators.py index 7d54ad9a5f..bfe139f732 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -1,4 +1,5 @@ from api.applications.enums import ApplicationExportType +from api.applications.models import SiteOnApplication from api.cases.enums import CaseTypeSubTypeEnum from api.parties.models import PartyDocument from api.parties.enums import PartyType, PartyDocumentType @@ -6,6 +7,25 @@ from lite_content.lite_api import strings +def siel_locations_validator(application): + error = None + + export_type_choices = [item[0] for item in ApplicationExportType.choices] + starting_point_choices = [item[0] for item in application.GOODS_STARTING_POINT_CHOICES] + recipient_choices = [item[0] for item in application.GOODS_RECIPIENTS_CHOICES] + + if not ( + SiteOnApplication.objects.filter(application=application).exists() + and application.export_type in export_type_choices + and application.goods_starting_point in starting_point_choices + and application.goods_recipients in recipient_choices + and application.is_shipped_waybill_or_lading is not None + ): + error = "To submit the application, add a product location" + + return {"location": [error]} if error else None + + def siel_end_user_validator(application): error = None if not application.end_user: @@ -45,6 +65,6 @@ def validate(self): class StandardApplicationValidator(BaseApplicationValidator): config = { + "location": siel_locations_validator, "end_user": siel_end_user_validator, - # more to follow } From b7d915faab776f8ec0823c4541c1413d7402f704 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 25 Mar 2024 17:35:28 +0000 Subject: [PATCH 04/13] Add validator to check if security approvals are completed or not --- api/applications/validators.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/api/applications/validators.py b/api/applications/validators.py index bfe139f732..b1883e3078 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -1,5 +1,4 @@ from api.applications.enums import ApplicationExportType -from api.applications.models import SiteOnApplication from api.cases.enums import CaseTypeSubTypeEnum from api.parties.models import PartyDocument from api.parties.enums import PartyType, PartyDocumentType @@ -8,6 +7,8 @@ def siel_locations_validator(application): + from api.applications.models import SiteOnApplication + error = None export_type_choices = [item[0] for item in ApplicationExportType.choices] @@ -23,7 +24,7 @@ def siel_locations_validator(application): ): error = "To submit the application, add a product location" - return {"location": [error]} if error else None + return error def siel_end_user_validator(application): @@ -32,7 +33,6 @@ def siel_end_user_validator(application): error = "To submit the application, add an end user" party = application.end_user.party - breakpoint() documents_qs = PartyDocument.objects.filter( party=party, type=PartyDocumentType.END_USER_UNDERTAKING_DOCUMENT ).values_list("safe", flat=True) @@ -45,7 +45,13 @@ def siel_end_user_validator(application): if not party.end_user_document_available and not party.end_user_document_missing_reason: error = "To submit the application, attach a document to the end user" - return {"end_user": [error]} if error else None + return error + + +def siel_security_approvals_validator(application): + error = "To submit the application, complete the 'Do you have a security approval?' section" + + return error if application.is_mod_security_approved is None else None class BaseApplicationValidator: @@ -56,9 +62,11 @@ def __init__(self, application): def validate(self): all_errors = {} - for _, func in self.config.items(): - errors = func(self.application) - all_errors = {**errors, **all_errors} + for entity, func in self.config.items(): + error = func(self.application) + if error: + entity_errors = {entity: [error]} + all_errors = {**entity_errors, **all_errors} return all_errors @@ -67,4 +75,5 @@ class StandardApplicationValidator(BaseApplicationValidator): config = { "location": siel_locations_validator, "end_user": siel_end_user_validator, + "security_approvals": siel_security_approvals_validator, } From 4bcafc18726b46b5d7d28b31705d87d97e1e1e70 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Thu, 16 May 2024 21:01:02 +0100 Subject: [PATCH 05/13] Add goods and other validators for SIEL applications --- api/applications/validators.py | 127 +++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/api/applications/validators.py b/api/applications/validators.py index b1883e3078..d9d722ae1e 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -1,9 +1,9 @@ +from django.db.models import Q + from api.applications.enums import ApplicationExportType -from api.cases.enums import CaseTypeSubTypeEnum +from api.goods.models import GoodDocument from api.parties.models import PartyDocument -from api.parties.enums import PartyType, PartyDocumentType - -from lite_content.lite_api import strings +from api.parties.enums import PartyDocumentType def siel_locations_validator(application): @@ -48,12 +48,124 @@ def siel_end_user_validator(application): return error +def siel_consignee_validator(application): + from api.applications.models import StandardApplication + + error = None + if application.goods_recipients not in [ + StandardApplication.VIA_CONSIGNEE, + StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, + ]: + return error + + if not application.consignee: + return "To submit the application, add a consignee" + + party = application.consignee.party + documents_qs = PartyDocument.objects.filter(party=party).values_list("safe", flat=True) + if documents_qs.exists(): + if None in documents_qs: + error = "We're still processing the consignee document. Please submit again" + elif False in documents_qs: + error = "To submit the application, attach a document that does not contain a virus to the end user" + + return error + + +def siel_third_parties_validator(application): + """If there are third parties and they added any documents check if they are all valid""" + error = None + + if application.third_parties.count() == 0: + return error + + for third_party_on_application in application.third_parties.all(): + party = third_party_on_application.party + documents_qs = PartyDocument.objects.filter(party=party).values_list("safe", flat=True) + if documents_qs.exists(): + if None in documents_qs: + return "We're still processing the third party document. Please submit again" + elif False in documents_qs: + return "To submit the application, attach a document that does not contain a virus to the end user" + + +def siel_ultimate_end_users_validator(application): + """If ultimate end users are required and they added any documents check if they are all valid""" + error = None + + ultimate_end_user_required = application.goods.filter( + Q(is_good_incorporated=True) | Q(is_onward_incorporated=True) + ).exists() + + if ultimate_end_user_required and application.ultimate_end_users.count() == 0: + error = "To submit the application, add an ultimate end-user" + else: + end_user_id = application.end_user.party.id + # We make sure that an ultimate end user is not also the end user + if end_user_id in list(application.ultimate_end_users.values_list("id", flat=True)): + error = "To submit the application, an ultimate end-user cannot be the same as the end user" + + return error + + def siel_security_approvals_validator(application): error = "To submit the application, complete the 'Do you have a security approval?' section" return error if application.is_mod_security_approved is None else None +def siel_goods_validator(application): + + if application.goods.count() == 0: + return "To submit the application, add a product" + + goods = application.goods.values_list("good", flat=True) + document_statuses = GoodDocument.objects.filter(good__in=goods).values_list("safe", flat=True) + + # If safe field value is None, then the document hasn't been virus scanned yet + if not all(item is not None for item in document_statuses): + return "We're still processing a good document. Please submit again" + + # If safe is False, the file contains a virus + if not all(document_statuses): + return "To submit the application, attach a document that does not contain a virus to goods" + + +def siel_end_use_details_validator(application): + if ( + application.is_military_end_use_controls is None + or application.is_informed_wmd is None + or application.is_suspected_wmd is None + or application.is_eu_military is None + or not application.intended_end_use + or (application.is_eu_military and application.is_compliant_limitations_eu is None) + ): + return "To submit the application, complete the 'End use details' section" + + return None + + +def siel_route_of_goods_validator(application): + if application.is_shipped_waybill_or_lading is None: + return "To submit the application, complete the 'Route of products' section" + + return None + + +def siel_temporary_export_details_validator(application): + if application.export_type == ApplicationExportType.PERMANENT: + return None + + if ( + not application.temp_export_details + or application.is_temp_direct_control is None + or application.proposed_return_date is None + ): + return "To submit the application, add temporary export details" + + return None + + class BaseApplicationValidator: config = {} @@ -75,5 +187,12 @@ class StandardApplicationValidator(BaseApplicationValidator): config = { "location": siel_locations_validator, "end_user": siel_end_user_validator, + "consignee": siel_consignee_validator, + "third_parties_documents": siel_third_parties_validator, + "ultimate_end_user_documents": siel_ultimate_end_users_validator, "security_approvals": siel_security_approvals_validator, + "goods": siel_goods_validator, + "end_use_details": siel_end_use_details_validator, + "route_of_goods": siel_route_of_goods_validator, + "temporary_export_details": siel_temporary_export_details_validator, } From 6609026a8d9e2d85213bb40c90898fc80396c8bc Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Fri, 17 May 2024 14:26:04 +0100 Subject: [PATCH 06/13] Refactor StandardApplication validators functions --- api/applications/creators.py | 24 +------------- api/applications/tests/factories.py | 2 ++ .../tests/test_standard_application_submit.py | 1 + api/applications/validators.py | 31 ++++++++++--------- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/api/applications/creators.py b/api/applications/creators.py index 358dd275ca..fbd9e5be49 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -256,23 +256,6 @@ def _validate_goods(draft, errors, is_mandatory): return errors -def _validate_standard_licence(draft, errors): - """Checks that a standard licence has all party types & goods""" - - errors = _validate_siel_locations(draft, errors) - errors = _validate_end_user(draft, errors, is_mandatory=True) - errors = _validate_security_approvals(draft, errors, is_mandatory=True) - errors = _validate_consignee(draft, errors, is_mandatory=True) - errors = _validate_third_parties(draft, errors, is_mandatory=False) - errors = _validate_goods(draft, errors, is_mandatory=True) - errors = _validate_ultimate_end_users(draft, errors, is_mandatory=True) - errors = _validate_end_use_details(draft, errors, draft.case_type.sub_type) - errors = _validate_route_of_goods(draft, errors) - errors = _validate_temporary_export_details(draft, errors) - - return errors - - def _validate_route_of_goods(draft, errors): if ( draft.is_shipped_waybill_or_lading is None @@ -302,12 +285,7 @@ def _validate_additional_documents(draft, errors): def validate_application_ready_for_submission(application): errors = {} - # Perform additional validation and append errors if found - if application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD: - _validate_standard_licence(application, errors) - else: - errors["unsupported_application"] = ["You can only validate a supported application type"] - + errors = application.validate() errors = _validate_additional_documents(application, errors) return errors diff --git a/api/applications/tests/factories.py b/api/applications/tests/factories.py index 67f2b3b129..fb427cd689 100644 --- a/api/applications/tests/factories.py +++ b/api/applications/tests/factories.py @@ -44,6 +44,8 @@ class StandardApplicationFactory(factory.django.DjangoModelFactory): is_shipped_waybill_or_lading = True non_waybill_or_lading_route_details = None is_mod_security_approved = False + goods_starting_point = StandardApplication.GB + goods_recipients = StandardApplication.DIRECT_TO_END_USER submitted_by = factory.SubFactory(ExporterUserFactory) class Meta: diff --git a/api/applications/tests/test_standard_application_submit.py b/api/applications/tests/test_standard_application_submit.py index e0b08afc7c..4af4d16674 100644 --- a/api/applications/tests/test_standard_application_submit.py +++ b/api/applications/tests/test_standard_application_submit.py @@ -112,6 +112,7 @@ def test_submit_standard_application_without_end_user_document_success(self): PartyDocument.objects.filter(party=self.draft.end_user.party).delete() party = Party.objects.get(id=self.draft.end_user.party_id) party.end_user_document_available = False + party.end_user_document_missing_reason = "not applicable" party.save() url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) diff --git a/api/applications/validators.py b/api/applications/validators.py index d9d722ae1e..67f56b4638 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -9,33 +9,32 @@ def siel_locations_validator(application): from api.applications.models import SiteOnApplication - error = None - export_type_choices = [item[0] for item in ApplicationExportType.choices] starting_point_choices = [item[0] for item in application.GOODS_STARTING_POINT_CHOICES] recipient_choices = [item[0] for item in application.GOODS_RECIPIENTS_CHOICES] - if not ( - SiteOnApplication.objects.filter(application=application).exists() - and application.export_type in export_type_choices - and application.goods_starting_point in starting_point_choices - and application.goods_recipients in recipient_choices - and application.is_shipped_waybill_or_lading is not None + if ( + not SiteOnApplication.objects.filter(application=application).exists() + and application.export_type not in export_type_choices + and application.goods_starting_point not in starting_point_choices + and application.goods_recipients not in recipient_choices + and application.is_shipped_waybill_or_lading is None ): - error = "To submit the application, add a product location" + return "To submit the application, add a product location" - return error + return None def siel_end_user_validator(application): error = None + if application.export_type == ApplicationExportType.TEMPORARY: + return None + if not application.end_user: - error = "To submit the application, add an end user" + return "To submit the application, add an end user" party = application.end_user.party - documents_qs = PartyDocument.objects.filter( - party=party, type=PartyDocumentType.END_USER_UNDERTAKING_DOCUMENT - ).values_list("safe", flat=True) + documents_qs = PartyDocument.objects.filter(party=party).values_list("safe", flat=True) if documents_qs.exists(): if None in documents_qs: error = "We're still processing the end user document. Please submit again" @@ -86,12 +85,14 @@ def siel_third_parties_validator(application): if None in documents_qs: return "We're still processing the third party document. Please submit again" elif False in documents_qs: - return "To submit the application, attach a document that does not contain a virus to the end user" + return "To submit the application, attach a document that does not contain a virus to the third party" def siel_ultimate_end_users_validator(application): """If ultimate end users are required and they added any documents check if they are all valid""" error = None + if not application.end_user: + return "To submit the application, add an end user" ultimate_end_user_required = application.goods.filter( Q(is_good_incorporated=True) | Q(is_onward_incorporated=True) From 153992119267e7bcb682843febb239342497de79 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Fri, 17 May 2024 15:16:40 +0100 Subject: [PATCH 07/13] Fix linter error and raise not implemented error if no validators are defined --- api/applications/models.py | 3 +++ api/applications/validators.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/applications/models.py b/api/applications/models.py index 1b39fa4172..4701b1799f 100644 --- a/api/applications/models.py +++ b/api/applications/models.py @@ -248,6 +248,9 @@ def set_appealed(self, appeal, exporter_user): self.set_sub_status(CaseSubStatusIdEnum.UNDER_APPEAL__APPEAL_RECEIVED) self.add_to_queue(Queue.objects.get(id=QueuesEnum.LU_APPEALS)) + def validate(self): + raise NotImplementedError("Validator to validate application attributes is not implemented") + # Licence Applications class StandardApplication(BaseApplication): diff --git a/api/applications/validators.py b/api/applications/validators.py index 67f56b4638..0ee0a0c197 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -3,7 +3,6 @@ from api.applications.enums import ApplicationExportType from api.goods.models import GoodDocument from api.parties.models import PartyDocument -from api.parties.enums import PartyDocumentType def siel_locations_validator(application): From a229adb34e800685d65e59a499e3a3cb8012b073 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Tue, 4 Jun 2024 16:08:38 +0100 Subject: [PATCH 08/13] Delete unused validation helper functions --- api/applications/creators.py | 234 +---------------------------------- 1 file changed, 1 insertion(+), 233 deletions(-) diff --git a/api/applications/creators.py b/api/applications/creators.py index fbd9e5be49..344e137f51 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -1,39 +1,6 @@ -from django.db.models import Q - -from api.applications.enums import ApplicationExportType, GoodsTypeCategory -from api.applications.models import ( - ApplicationDocument, - GoodOnApplication, - SiteOnApplication, - ExternalLocationOnApplication, - StandardApplication, -) -from api.cases.enums import CaseTypeSubTypeEnum +from api.applications.models import ApplicationDocument from api.core.helpers import str_to_bool -from api.goods.models import GoodDocument from lite_content.lite_api import strings -from api.parties.models import PartyDocument -from api.parties.enums import PartyType - - -def _validate_siel_locations(application, errors): - old_locations_invalid = ( - not SiteOnApplication.objects.filter(application=application).exists() - and not ExternalLocationOnApplication.objects.filter(application=application).exists() - and not getattr(application, "have_goods_departed", False) - ) - - new_locations_invalid = ( - not getattr(application, "export_type", False) - and not getattr(application, "goods_recipients", False) - and not getattr(application, "goods_starting_point", False) - and getattr(application, "is_shipped_waybill_or_lading") is None - ) - - if old_locations_invalid and new_locations_invalid: - errors["location"] = [strings.Applications.Generic.NO_LOCATION_SET] - - return errors def _get_document_errors(documents, processing_error, virus_error): @@ -48,152 +15,6 @@ def _get_document_errors(documents, processing_error, virus_error): return virus_error -def check_party_document(party, is_mandatory): - """ - Checks for existence of and status of document (if it is mandatory) and return any errors - """ - documents_qs = PartyDocument.objects.filter(party=party).values_list("safe", flat=True) - if not documents_qs.exists(): - # End-user document is mandatory but we are providing an option to not upload - # if there is a valid reason - if party.type == PartyType.END_USER and party.end_user_document_available is False: - return None - - if is_mandatory: - return getattr(strings.Applications.Standard, f"NO_{party.type.upper()}_DOCUMENT_SET") - else: - return None - - if None in documents_qs: - return getattr(strings.Applications.Standard, f"{party.type.upper()}_DOCUMENT_PROCESSING") - elif False in documents_qs: - return getattr(strings.Applications.Standard, f"{party.type.upper()}_DOCUMENT_INFECTED") - - return None - - -def check_parties_documents(parties, is_mandatory=True): - """Check a given list of parties all have documents if is_mandatory. Also checks all documents are safe""" - - for poa in parties: - error = check_party_document(poa.party, is_mandatory) - if error: - return error - return None - - -def check_party_error(party, object_not_found_error, is_mandatory, is_document_mandatory=True): - """Check a given party exists and has a document if is_document_mandatory""" - - if is_mandatory and not party: - return object_not_found_error - elif party: - document_error = check_party_document(party, is_document_mandatory) - if document_error: - return document_error - - -def _validate_end_user(draft, errors, is_mandatory, open_application=False): - """Validates end user. If a document is mandatory, this is also validated.""" - - # Document is only mandatory if application is standard permanent or HMRC query - is_document_mandatory = ( - draft.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD - and draft.export_type == ApplicationExportType.PERMANENT - ) or draft.case_type.sub_type == CaseTypeSubTypeEnum.HMRC - - end_user_errors = check_party_error( - draft.end_user.party if draft.end_user else None, - object_not_found_error=strings.Applications.Standard.NO_END_USER_SET, - is_mandatory=is_mandatory, - is_document_mandatory=is_document_mandatory, - ) - if end_user_errors: - errors["end_user"] = [end_user_errors] - - return errors - - -def _validate_consignee(draft, errors, is_mandatory): - """ - Checks there is an consignee if goods_recipients is set to VIA_CONSIGNEE or VIA_CONSIGNEE_AND_THIRD_PARTIES - (with a document if is_document_mandatory) - """ - # This logic includes old style applications where the goods_recipients field will be "" - if draft.goods_recipients != StandardApplication.DIRECT_TO_END_USER: - consignee_errors = check_party_error( - draft.consignee.party if draft.consignee else None, - object_not_found_error=strings.Applications.Standard.NO_CONSIGNEE_SET, - is_mandatory=is_mandatory, - is_document_mandatory=False, - ) - if consignee_errors: - errors["consignee"] = [consignee_errors] - return errors - - -def _validate_security_approvals(draft, errors, is_mandatory): - """Checks there are security approvals for the draft""" - if is_mandatory: - if draft.is_mod_security_approved is None: - errors["security_approvals"] = [ - "To submit the application, complete the 'Do you have a security approval?' section" - ] - return errors - - -def _validate_ultimate_end_users(draft, errors, is_mandatory): - """ - Checks all ultimate end users have documents if is_mandatory is True. - Also checks that at least one ultimate_end_user is present if there is an incorporated good - """ - # Document is always optional even if there are incorporated goods - ultimate_end_user_documents_error = check_parties_documents(draft.ultimate_end_users.all(), is_mandatory=False) - if ultimate_end_user_documents_error: - errors["ultimate_end_user_documents"] = [ultimate_end_user_documents_error] - - if is_mandatory: - ultimate_end_user_required = GoodOnApplication.objects.filter( - Q(application=draft), Q(is_good_incorporated=True) | Q(is_onward_incorporated=True) - ).exists() - - if ultimate_end_user_required: - if len(draft.ultimate_end_users.values_list()) == 0: - errors["ultimate_end_users"] = ["To submit the application, add an ultimate end-user"] - else: - # We make sure that an ultimate end user is not also the end user - for ultimate_end_user in draft.ultimate_end_users.values_list("id", flat=True): - if "end_user" not in errors and str(ultimate_end_user) == str(draft.end_user.party.id): - errors["ultimate_end_users"] = [ - "To submit the application, an ultimate end-user cannot be the same as the end user" - ] - - return errors - - -def _validate_end_use_details(draft, errors, application_type): - if application_type in [CaseTypeSubTypeEnum.STANDARD, CaseTypeSubTypeEnum.OPEN]: - if ( - draft.is_military_end_use_controls is None - or draft.is_informed_wmd is None - or draft.is_suspected_wmd is None - or not draft.intended_end_use - ) and not getattr(draft, "goodstype_category", None) == GoodsTypeCategory.CRYPTOGRAPHIC: - errors["end_use_details"] = [strings.Applications.Generic.NO_END_USE_DETAILS] - - if application_type == CaseTypeSubTypeEnum.STANDARD: - if draft.is_eu_military is None: - errors["end_use_details"] = [strings.Applications.Generic.NO_END_USE_DETAILS] - elif draft.is_eu_military and draft.is_compliant_limitations_eu is None: - errors["end_use_details"] = [strings.Applications.Generic.NO_END_USE_DETAILS] - - elif application_type == CaseTypeSubTypeEnum.F680: - if not draft.intended_end_use: - errors["end_use_details"] = [strings.Applications.Generic.NO_END_USE_DETAILS] - - return errors - - def _validate_agree_to_declaration(request, errors): """Checks the exporter has agreed to the T&Cs of the licence""" @@ -212,59 +33,6 @@ def _validate_agree_to_declaration(request, errors): return errors -def _validate_temporary_export_details(draft, errors): - if ( - draft.case_type.sub_type in [CaseTypeSubTypeEnum.STANDARD, CaseTypeSubTypeEnum.OPEN] - and draft.export_type == ApplicationExportType.TEMPORARY - ): - if not draft.temp_export_details or draft.is_temp_direct_control is None or draft.proposed_return_date is None: - errors["temporary_export_details"] = [strings.Applications.Generic.NO_TEMPORARY_EXPORT_DETAILS] - - return errors - - -def _validate_third_parties(draft, errors, is_mandatory): - """Checks all third parties have documents if is_mandatory is True""" - - third_parties_documents_error = check_parties_documents(draft.third_parties.all(), is_mandatory) - if third_parties_documents_error: - errors["third_parties_documents"] = [third_parties_documents_error] - - return errors - - -def _validate_goods(draft, errors, is_mandatory): - """Checks Goods""" - - goods_on_application = GoodOnApplication.objects.filter(application=draft) - - if is_mandatory: - if not goods_on_application: - errors["goods"] = [strings.Applications.Standard.NO_GOODS_SET] - - # Check goods documents - if goods_on_application.exists(): - goods = goods_on_application.values_list("good", flat=True) - document_errors = _get_document_errors( - GoodDocument.objects.filter(good__in=goods), - processing_error=strings.Applications.Standard.GOODS_DOCUMENT_PROCESSING, - virus_error=strings.Applications.Standard.GOODS_DOCUMENT_INFECTED, - ) - if document_errors: - errors["goods"] = [document_errors] - - return errors - - -def _validate_route_of_goods(draft, errors): - if ( - draft.is_shipped_waybill_or_lading is None - and not getattr(draft, "goodstype_category", None) == GoodsTypeCategory.CRYPTOGRAPHIC - ): - errors["route_of_goods"] = [strings.Applications.Generic.NO_ROUTE_OF_GOODS] - return errors - - def _validate_additional_documents(draft, errors): """Validate additional documents""" documents = ApplicationDocument.objects.filter(application=draft) From e7f3dc607221d90858dd08fdba9c1e279ae6b2cd Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Wed, 5 Jun 2024 11:51:59 +0100 Subject: [PATCH 09/13] Update test data Goods location is currently set in StandardApplicationFactory so update expected test data accordingly --- api/cases/tests/test_good_precedents_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/cases/tests/test_good_precedents_view.py b/api/cases/tests/test_good_precedents_view.py index 40eb642209..d974cf5c86 100644 --- a/api/cases/tests/test_good_precedents_view.py +++ b/api/cases/tests/test_good_precedents_view.py @@ -138,7 +138,7 @@ def test_get_with_matching_precedents(self): "%Y-%m-%dT%H:%M:%S.%f" )[:-3] + "Z", - "goods_starting_point": "", + "goods_starting_point": "GB", "regime_entries": [ { "name": "Wassenaar Arrangement", From e71affa05889b22619f7c0927bcf73bbf12038a9 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Thu, 6 Jun 2024 13:18:54 +0100 Subject: [PATCH 10/13] Add/update tests to fix coverage issues --- api/applications/tests/factories.py | 28 +++- .../tests/test_standard_application_submit.py | 156 ++++++++++++------ api/applications/validators.py | 7 +- api/parties/tests/factories.py | 2 + 4 files changed, 132 insertions(+), 61 deletions(-) diff --git a/api/applications/tests/factories.py b/api/applications/tests/factories.py index 5a6f589a93..930004c0e0 100644 --- a/api/applications/tests/factories.py +++ b/api/applications/tests/factories.py @@ -15,7 +15,7 @@ from api.staticdata.statuses.models import CaseStatus from api.goods.tests.factories import GoodFactory from api.organisations.tests.factories import OrganisationFactory, SiteFactory -from api.parties.tests.factories import PartyFactory +from api.parties.tests.factories import ConsigneeFactory, EndUserFactory, PartyFactory, ThirdPartyFactory from api.users.tests.factories import ExporterUserFactory, GovUserFactory from api.staticdata.control_list_entries.helpers import get_control_list_entry from api.staticdata.regimes.helpers import get_regime_entry @@ -171,3 +171,29 @@ class SanctionMatchFactory(factory.django.DjangoModelFactory): class Meta: model = SanctionMatch + + +class DraftStandardApplicationFactory(StandardApplicationFactory): + goods_starting_point = StandardApplication.GB + goods_recipients = StandardApplication.VIA_CONSIGNEE + + @classmethod + def _create(cls, model_class, *args, **kwargs): + obj = model_class(*args, **kwargs) + obj.status = get_case_status_by_status(CaseStatusEnum.DRAFT) + obj.save() + + GoodOnApplicationFactory(application=obj, good=GoodFactory(organisation=obj.organisation)) + + PartyOnApplicationFactory(application=obj, party=EndUserFactory(organisation=obj.organisation)) + + if kwargs["goods_recipients"] in [ + StandardApplication.VIA_CONSIGNEE, + StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, + ]: + PartyOnApplicationFactory(application=obj, party=ConsigneeFactory(organisation=obj.organisation)) + + if kwargs["goods_recipients"] == StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES: + PartyOnApplicationFactory(application=obj, party=ThirdPartyFactory(organisation=obj.organisation)) + + return obj diff --git a/api/applications/tests/test_standard_application_submit.py b/api/applications/tests/test_standard_application_submit.py index 4af4d16674..2d5e85088a 100644 --- a/api/applications/tests/test_standard_application_submit.py +++ b/api/applications/tests/test_standard_application_submit.py @@ -7,6 +7,7 @@ from api.applications.enums import ApplicationExportType from api.applications.models import SiteOnApplication, GoodOnApplication, PartyOnApplication, StandardApplication +from api.applications.tests.factories import DraftStandardApplicationFactory from api.audit_trail.enums import AuditType from api.audit_trail.models import Audit from api.cases.enums import CaseTypeEnum, CaseDocumentState @@ -18,6 +19,7 @@ from lite_content.lite_api import strings from api.parties.enums import PartyType from api.parties.models import Party, PartyDocument +from api.parties.tests.factories import PartyDocumentFactory from api.staticdata.statuses.enums import CaseStatusEnum from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status from api.staticdata.trade_control.enums import TradeControlActivity, TradeControlProductCategory @@ -95,45 +97,45 @@ def test_submit_standard_application_with_no_new_or_old_location_info_failure(se status_code=status.HTTP_400_BAD_REQUEST, ) - def test_submit_standard_application_without_end_user_failure(self): - self.draft.delete_party(PartyOnApplication.objects.get(application=self.draft, party__type=PartyType.END_USER)) - - url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) - - response = self.client.put(url, **self.exporter_headers) - - self.assertContains( - response, - text=strings.Applications.Standard.NO_END_USER_SET, - status_code=status.HTTP_400_BAD_REQUEST, - ) - def test_submit_standard_application_without_end_user_document_success(self): - PartyDocument.objects.filter(party=self.draft.end_user.party).delete() - party = Party.objects.get(id=self.draft.end_user.party_id) + application = DraftStandardApplicationFactory(organisation=self.organisation) + party = Party.objects.get(id=application.end_user.party_id) party.end_user_document_available = False party.end_user_document_missing_reason = "not applicable" party.save() - url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + url = reverse("applications:application_submit", kwargs={"pk": application.id}) response = self.client.put(url, **self.exporter_headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_submit_standard_application_without_consignee_failure(self): - self.draft.delete_party(self.draft.consignee) - self.draft.goods_recipients = StandardApplication.VIA_CONSIGNEE - self.draft.save() + def test_submit_without_end_user_document_missing_reason_fail(self): + application = DraftStandardApplicationFactory(organisation=self.organisation) - url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + url = reverse("applications:application_submit", kwargs={"pk": application.id}) response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + errors = response.json()["errors"] + self.assertEqual(errors["end_user"][0], "To submit the application, attach a document to the end user") - self.assertContains( - response, - text=strings.Applications.Standard.NO_CONSIGNEE_SET, - status_code=status.HTTP_400_BAD_REQUEST, - ) + @parameterized.expand( + [ + (PartyType.END_USER, ["To submit the application, add an end user"]), + (PartyType.CONSIGNEE, ["To submit the application, add a consignee"]), + ] + ) + def test_submit_standard_application_without_party_failure(self, party_type, expected_errors): + application = DraftStandardApplicationFactory(organisation=self.organisation) + application.parties.filter(party__type=party_type).delete() + + url = reverse("applications:application_submit", kwargs={"pk": application.id}) + + response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + errors = response.json()["errors"] + self.assertEqual(errors[party_type], expected_errors) def test_submit_standard_application_direct_end_user_without_consignee_success(self): self.draft.delete_party(self.draft.consignee) @@ -147,16 +149,16 @@ def test_submit_standard_application_direct_end_user_without_consignee_success(s self.assertEqual(response.status_code, status.HTTP_200_OK) def test_submit_standard_application_without_consignee_document_success(self): - # Consignee document is optional - PartyDocument.objects.filter(party=self.draft.consignee.party).delete() - url = reverse("applications:application_submit", kwargs={"pk": self.draft.id}) + application = DraftStandardApplicationFactory(organisation=self.organisation) + party_on_application = application.parties.filter(party__type=PartyType.END_USER).first() - response = self.client.put(url, **self.exporter_headers) + # only Consignee document is optional + PartyDocumentFactory(party=party_on_application.party, safe=True) - self.assertNotContains( - response, - text=strings.Applications.Standard.NO_CONSIGNEE_DOCUMENT_SET, - ) + url = reverse("applications:application_submit", kwargs={"pk": application.id}) + + response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_200_OK) def test_submit_standard_application_without_good_failure(self): GoodOnApplication.objects.get(application=self.draft).delete() @@ -227,6 +229,16 @@ def test_submit_draft_with_incorporated_good_and_without_ultimate_end_user_docum text="To submit the application, attach a document to the ultimate end-users", ) + def test_submit_draft_without_third_parties_success(self): + application = DraftStandardApplicationFactory(organisation=self.organisation) + party_on_application = application.parties.filter(party__type=PartyType.END_USER).first() + PartyDocumentFactory(party=party_on_application.party, safe=True) + + url = reverse("applications:application_submit", kwargs={"pk": application.id}) + + response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_submit_draft_without_third_party_documents_success(self): third_party = PartyOnApplication.objects.get(application=self.draft, party__type=PartyType.THIRD_PARTY).party PartyDocument.objects.filter(party=third_party).delete() @@ -236,29 +248,65 @@ def test_submit_draft_without_third_party_documents_success(self): self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_status_code_post_with_untested_document_failure(self): - draft = self.create_draft_standard_application(self.organisation, safe_document=None) - url = reverse("applications:application_submit", kwargs={"pk": draft.id}) - - response = self.client.put(url, **self.exporter_headers) - - self.assertContains( - response, - text=strings.Applications.Standard.END_USER_DOCUMENT_PROCESSING, - status_code=status.HTTP_400_BAD_REQUEST, - ) - - def test_status_code_post_with_infected_document_failure(self): - draft = self.create_draft_standard_application(self.organisation, safe_document=False) - url = reverse("applications:application_submit", kwargs={"pk": draft.id}) + @parameterized.expand( + [ + ( + StandardApplication.DIRECT_TO_END_USER, + PartyType.END_USER, + PartyType.END_USER, + None, + ["We're still processing the end user document. Please submit again"], + ), + ( + StandardApplication.DIRECT_TO_END_USER, + PartyType.END_USER, + PartyType.END_USER, + False, + ["To submit the application, attach a document that does not contain a virus to the end user"], + ), + ( + StandardApplication.VIA_CONSIGNEE, + PartyType.CONSIGNEE, + PartyType.CONSIGNEE, + None, + ["We're still processing the consignee document. Please submit again"], + ), + ( + StandardApplication.VIA_CONSIGNEE, + PartyType.CONSIGNEE, + PartyType.CONSIGNEE, + False, + ["To submit the application, attach a document that does not contain a virus to the consignee"], + ), + ( + StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, + PartyType.THIRD_PARTY, + "third_parties_documents", + None, + ["We're still processing the third party document. Please submit again"], + ), + ( + StandardApplication.VIA_CONSIGNEE_AND_THIRD_PARTIES, + PartyType.THIRD_PARTY, + "third_parties_documents", + False, + ["To submit the application, attach a document that does not contain a virus to the third party"], + ), + ] + ) + def test_application_with_infected_party_document_failure( + self, recipients, party_type, field_name, safe_document, expected_errors + ): + application = DraftStandardApplicationFactory(organisation=self.organisation, goods_recipients=recipients) + party_on_application = application.parties.filter(party__type=party_type).first() + PartyDocumentFactory(party=party_on_application.party, safe=safe_document) + url = reverse("applications:application_submit", kwargs={"pk": application.id}) response = self.client.put(url, **self.exporter_headers) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + errors = response.json()["errors"] - self.assertContains( - response, - text=strings.Applications.Standard.END_USER_DOCUMENT_INFECTED, - status_code=status.HTTP_400_BAD_REQUEST, - ) + self.assertEqual(errors[field_name], expected_errors) def test_submit_standard_application_with_unprocessed_additional_documents_failure(self): self.create_application_document(self.draft, safe=None) diff --git a/api/applications/validators.py b/api/applications/validators.py index 0ee0a0c197..79232a6e1f 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -65,7 +65,7 @@ def siel_consignee_validator(application): if None in documents_qs: error = "We're still processing the consignee document. Please submit again" elif False in documents_qs: - error = "To submit the application, attach a document that does not contain a virus to the end user" + error = "To submit the application, attach a document that does not contain a virus to the consignee" return error @@ -99,11 +99,6 @@ def siel_ultimate_end_users_validator(application): if ultimate_end_user_required and application.ultimate_end_users.count() == 0: error = "To submit the application, add an ultimate end-user" - else: - end_user_id = application.end_user.party.id - # We make sure that an ultimate end user is not also the end user - if end_user_id in list(application.ultimate_end_users.values_list("id", flat=True)): - error = "To submit the application, an ultimate end-user cannot be the same as the end user" return error diff --git a/api/parties/tests/factories.py b/api/parties/tests/factories.py index 5b9683bc9b..005029cb0d 100644 --- a/api/parties/tests/factories.py +++ b/api/parties/tests/factories.py @@ -18,6 +18,8 @@ class Meta: class PartyDocumentFactory(factory.django.DjangoModelFactory): + s3_key = factory.Faker("name") + class Meta: model = PartyDocument From b768b399d78490062bf08c3ae398b0552d6bc763 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 9 Dec 2024 12:55:56 +0000 Subject: [PATCH 11/13] Move the import into the class to avoid circular imports --- api/applications/models.py | 3 ++- api/applications/tests/test_models.py | 5 +++-- api/applications/validators.py | 18 +----------------- api/core/model_mixins.py | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/api/applications/models.py b/api/applications/models.py index 6ca452f5bb..c78cb5a426 100644 --- a/api/applications/models.py +++ b/api/applications/models.py @@ -17,7 +17,6 @@ from api.appeals.models import Appeal from api.applications.exceptions import AmendmentError from api.applications.managers import BaseApplicationManager -from api.applications.validators import StandardApplicationValidator from api.applications.libraries.application_helpers import create_submitted_audit from api.audit_trail.models import AuditType from api.audit_trail import service as audit_trail_service @@ -278,6 +277,8 @@ def create_amendment(self, user): # Licence Applications class StandardApplication(BaseApplication, Clonable): + from api.applications.validators import StandardApplicationValidator + GB = "GB" NI = "NI" GOODS_STARTING_POINT_CHOICES = [ diff --git a/api/applications/tests/test_models.py b/api/applications/tests/test_models.py index ba797065cd..706062563e 100644 --- a/api/applications/tests/test_models.py +++ b/api/applications/tests/test_models.py @@ -43,7 +43,7 @@ CaseStatusEnum, ) from api.users.enums import SystemUser -from api.users.models import ExporterUser +from api.users.models import BaseUser, ExporterUser from api.users.tests.factories import BaseUserFactory @@ -603,7 +603,8 @@ def test_clone_with_party_override(self): @pytest.mark.requires_transactions class TestStandardApplicationRaceConditions(TransactionTestCase): def test_create_amendment_race_condition_success(self): - BaseUserFactory(id=SystemUser.id) + if not BaseUser.objects.filter(id=SystemUser.id).exists(): + BaseUserFactory(id=SystemUser.id) original_application = StandardApplicationFactory() diff --git a/api/applications/validators.py b/api/applications/validators.py index 79232a6e1f..96bf694ac5 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -1,6 +1,7 @@ from django.db.models import Q from api.applications.enums import ApplicationExportType +from api.core.model_mixins import BaseApplicationValidator from api.goods.models import GoodDocument from api.parties.models import PartyDocument @@ -161,23 +162,6 @@ def siel_temporary_export_details_validator(application): return None -class BaseApplicationValidator: - config = {} - - def __init__(self, application): - self.application = application - - def validate(self): - all_errors = {} - for entity, func in self.config.items(): - error = func(self.application) - if error: - entity_errors = {entity: [error]} - all_errors = {**entity_errors, **all_errors} - - return all_errors - - class StandardApplicationValidator(BaseApplicationValidator): config = { "location": siel_locations_validator, diff --git a/api/core/model_mixins.py b/api/core/model_mixins.py index daa0af6fbf..e237a7bbe9 100644 --- a/api/core/model_mixins.py +++ b/api/core/model_mixins.py @@ -42,3 +42,20 @@ class Trackable: def get_history(self, field): raise NotImplementedError() + + +class BaseApplicationValidator: + config = {} + + def __init__(self, application): + self.application = application + + def validate(self): + all_errors = {} + for entity, func in self.config.items(): + error = func(self.application) + if error: + entity_errors = {entity: [error]} + all_errors = {**entity_errors, **all_errors} + + return all_errors From cee13c6d391e4f8d5bb525349df96ec5bc4caf95 Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Mon, 9 Dec 2024 17:31:08 +0000 Subject: [PATCH 12/13] Correct an error message --- api/applications/creators.py | 10 ---------- api/applications/tests/factories.py | 1 - api/applications/tests/test_models.py | 3 +-- api/applications/validators.py | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/api/applications/creators.py b/api/applications/creators.py index 99d455d129..346ab805e9 100644 --- a/api/applications/creators.py +++ b/api/applications/creators.py @@ -61,13 +61,3 @@ def validate_application_ready_for_submission(application): def build_document_processing_error_message(document_type_description): return f"We are still processing {document_type_description} document. Try submitting again in a few minutes." - - -def get_document_type_description_from_party_type(party_type): - document_type_description = { - PartyType.CONSIGNEE: "a consignee", - PartyType.END_USER: "an end-user", - PartyType.THIRD_PARTY: "a third party", - PartyType.ULTIMATE_END_USER: "an ultimate end-user", - } - return document_type_description[party_type] diff --git a/api/applications/tests/factories.py b/api/applications/tests/factories.py index b321f86e46..cfbbbfc927 100644 --- a/api/applications/tests/factories.py +++ b/api/applications/tests/factories.py @@ -22,7 +22,6 @@ from api.goods.tests.factories import GoodFactory from api.parties.tests.factories import ConsigneeFactory, EndUserFactory, PartyFactory, ThirdPartyFactory from api.organisations.tests.factories import OrganisationFactory, SiteFactory, ExternalLocationFactory -from api.parties.tests.factories import ConsigneeFactory, EndUserFactory, PartyFactory, ThirdPartyFactory from api.users.tests.factories import ExporterUserFactory, GovUserFactory from api.staticdata.units.enums import Units from api.staticdata.control_list_entries.helpers import get_control_list_entry diff --git a/api/applications/tests/test_models.py b/api/applications/tests/test_models.py index 706062563e..96d0f2f979 100644 --- a/api/applications/tests/test_models.py +++ b/api/applications/tests/test_models.py @@ -33,7 +33,7 @@ GoodOnApplicationFactory, PartyOnApplicationFactory, ) -from api.users.models import GovUser, ExporterUser +from api.users.models import BaseUser, GovUser, ExporterUser from api.goods.tests.factories import FirearmFactory from api.organisations.tests.factories import OrganisationFactory from api.staticdata.control_list_entries.models import ControlListEntry @@ -43,7 +43,6 @@ CaseStatusEnum, ) from api.users.enums import SystemUser -from api.users.models import BaseUser, ExporterUser from api.users.tests.factories import BaseUserFactory diff --git a/api/applications/validators.py b/api/applications/validators.py index 96bf694ac5..4760cfd3f4 100644 --- a/api/applications/validators.py +++ b/api/applications/validators.py @@ -120,7 +120,7 @@ def siel_goods_validator(application): # If safe field value is None, then the document hasn't been virus scanned yet if not all(item is not None for item in document_statuses): - return "We're still processing a good document. Please submit again" + return "We are still processing a good document. Try submitting again in a few minutes." # If safe is False, the file contains a virus if not all(document_statuses): From 83f21be97eb60da3afd620a9f53bb0b0b1899ded Mon Sep 17 00:00:00 2001 From: Arun Siluvery Date: Tue, 10 Dec 2024 09:40:24 +0000 Subject: [PATCH 13/13] Fix sorting issue Current code sorting the results based on first key in the results but for one of the test this is same for all rows so randomly it sorts by different order and causing the test to fail. Update the test to sort by all keys so we have a consistent order. --- api/data_workspace/v2/tests/bdd/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/data_workspace/v2/tests/bdd/conftest.py b/api/data_workspace/v2/tests/bdd/conftest.py index e052c8471c..4bf5c3e915 100644 --- a/api/data_workspace/v2/tests/bdd/conftest.py +++ b/api/data_workspace/v2/tests/bdd/conftest.py @@ -9,6 +9,7 @@ from django.urls import reverse from freezegun import freeze_time from moto import mock_aws +from operator import itemgetter from pytest_bdd import ( given, parsers, @@ -522,8 +523,8 @@ def check_rows(client, parse_table, unpage_data, table_name, rows): for row in parsed_rows[1:]: expected_data.append({key: value for key, value in zip(keys, row)}) expected_data = cast_to_types(expected_data, table_metadata["fields"]) - actual_data = sorted(actual_data, key=lambda item, keys=keys: item[keys[0]]) - expected_data = sorted(expected_data, key=lambda item, keys=keys: item[keys[0]]) + actual_data = sorted(actual_data, key=itemgetter(*keys)) + expected_data = sorted(expected_data, key=itemgetter(*keys)) assert actual_data == expected_data