Skip to content

Commit

Permalink
Merge pull request #921 from uktrade/LTD-locations-updated
Browse files Browse the repository at this point in the history
Locations changes as per LTD-1372
  • Loading branch information
sekharpanja authored Feb 23, 2022
2 parents 4d4efa8 + b8d1a0b commit 57f5333
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 39 deletions.
49 changes: 37 additions & 12 deletions api/applications/creators.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
GoodOnApplication,
SiteOnApplication,
ExternalLocationOnApplication,
StandardApplication,
)
from api.cases.enums import CaseTypeSubTypeEnum
from api.core.helpers import str_to_bool
Expand All @@ -32,6 +33,27 @@ def _validate_locations(application, errors):
return errors


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)
and not getattr(application, "goodstype_category", None) == GoodsTypeCategory.CRYPTOGRAPHIC
)

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):
document_statuses = documents.values_list("safe", flat=True)

Expand Down Expand Up @@ -108,17 +130,20 @@ def _validate_end_user(draft, errors, is_mandatory, open_application=False):


def _validate_consignee(draft, errors, is_mandatory):
"""Checks there is an consignee (with a document if is_document_mandatory)"""

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]

"""
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


Expand Down Expand Up @@ -324,7 +349,7 @@ def _validate_exhibition_details(draft, errors):
def _validate_standard_licence(draft, errors):
"""Checks that a standard licence has all party types & goods"""

errors = _validate_locations(draft, errors)
errors = _validate_siel_locations(draft, errors)
errors = _validate_end_user(draft, errors, is_mandatory=True)
errors = _validate_consignee(draft, errors, is_mandatory=True)
errors = _validate_third_parties(draft, errors, is_mandatory=False)
Expand Down
11 changes: 0 additions & 11 deletions api/applications/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from elasticsearch_dsl import Search, Q
from elasticsearch.exceptions import NotFoundError

from api.applications.enums import ApplicationExportType
from api.applications.models import BaseApplication, GoodOnApplication
from api.applications.serializers.end_use_details import (
F680EndUseDetailsUpdateSerializer,
Expand Down Expand Up @@ -41,7 +40,6 @@
StandardApplicationViewSerializer,
)
from api.applications.serializers.good import GoodOnStandardLicenceSerializer
from api.applications.serializers.temporary_export_details import TemporaryExportDetailsUpdateSerializer
from api.cases.enums import CaseTypeSubTypeEnum, CaseTypeEnum, AdviceType, AdviceLevel
from api.core.exceptions import BadRequestError
from api.documents.models import Document
Expand Down Expand Up @@ -130,15 +128,6 @@ def get_application_end_use_details_update_serializer(application: BaseApplicati
)


def get_temp_export_details_update_serializer(export_type):
if export_type == ApplicationExportType.TEMPORARY:
return TemporaryExportDetailsUpdateSerializer
else:
raise BadRequestError(
{f"get_temp_export_details_update_serializer does " f"not support this export type: {export_type}"}
)


def validate_and_create_goods_on_licence(application_id, licence_id, data):
errors = {}
good_on_applications = (
Expand Down
28 changes: 28 additions & 0 deletions api/applications/migrations/0050_auto_20211210_1618.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.1.12 on 2021-12-10 16:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('applications', '0049_auto_20211206_1031'),
]

operations = [
migrations.AddField(
model_name='standardapplication',
name='goods_recipients',
field=models.TextField(choices=[('direct_to_end_user', 'Directly to the end-user'), ('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')], default=''),
),
migrations.AddField(
model_name='standardapplication',
name='goods_starting_point',
field=models.TextField(choices=[('GB', 'Great Britain'), ('NI', 'Northern Ireland')], default=''),
),
migrations.AlterField(
model_name='standardapplication',
name='export_type',
field=models.TextField(blank=True, choices=[('permanent', 'Permanent'), ('temporary', 'Temporary')], default=''),
),
]
19 changes: 19 additions & 0 deletions api/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ class Meta:
# Licence Applications
class StandardApplication(BaseApplication):
export_type = models.CharField(choices=ApplicationExportType.choices, default=None, max_length=50)
GB = "GB"
NI = "NI"
GOODS_STARTING_POINT_CHOICES = [
(GB, "Great Britain"),
(NI, "Northern Ireland"),
]
DIRECT_TO_END_USER = "direct_to_end_user"
VIA_CONSIGNEE = "via_consignee"
VIA_CONSIGNEE_AND_THIRD_PARTIES = "via_consignee_and_third_parties"

GOODS_RECIPIENTS_CHOICES = [
(DIRECT_TO_END_USER, "Directly to the end-user"),
(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"),
]

export_type = models.TextField(choices=ApplicationExportType.choices, blank=True, default="")
reference_number_on_information_form = models.CharField(blank=True, null=True, max_length=255)
have_you_been_informed = models.CharField(
choices=ApplicationExportLicenceOfficialType.choices,
Expand All @@ -201,6 +218,8 @@ class StandardApplication(BaseApplication):
trade_control_product_categories = SeparatedValuesField(
choices=TradeControlProductCategory.choices, blank=False, null=True, max_length=50
)
goods_recipients = models.TextField(choices=GOODS_RECIPIENTS_CHOICES, default="")
goods_starting_point = models.TextField(choices=GOODS_STARTING_POINT_CHOICES, default="")


class OpenApplication(BaseApplication):
Expand Down
15 changes: 11 additions & 4 deletions api/applications/serializers/standard_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class StandardApplicationViewSerializer(PartiesSerializerMixin, GenericApplicati
trade_control_product_categories = serializers.SerializerMethodField()
sanction_matches = serializers.SerializerMethodField()
is_amended = serializers.SerializerMethodField()
goods_starting_point = serializers.CharField()
goods_recipients = serializers.CharField()

class Meta:
model = StandardApplication
Expand Down Expand Up @@ -75,6 +77,8 @@ class Meta:
"trade_control_product_categories",
"sanction_matches",
"is_amended",
"goods_starting_point",
"goods_recipients",
)
)

Expand Down Expand Up @@ -133,10 +137,7 @@ def get_is_amended(self, instance):


class StandardApplicationCreateSerializer(GenericApplicationCreateSerializer):
export_type = KeyValueChoiceField(
choices=ApplicationExportType.choices,
error_messages={"required": strings.Applications.Generic.NO_EXPORT_TYPE},
)
export_type = KeyValueChoiceField(choices=ApplicationExportType.choices, required=False)
have_you_been_informed = KeyValueChoiceField(
choices=ApplicationExportLicenceOfficialType.choices,
error_messages={"required": strings.Goods.INFORMED},
Expand Down Expand Up @@ -194,15 +195,21 @@ def create(self, validated_data):


class StandardApplicationUpdateSerializer(GenericApplicationUpdateSerializer):
export_type = KeyValueChoiceField(choices=ApplicationExportType.choices, required=False)
goods_starting_point = serializers.CharField()
goods_recipients = serializers.CharField()
reference_number_on_information_form = CharField(max_length=100, required=False, allow_blank=True, allow_null=True)

class Meta:
model = StandardApplication
fields = GenericApplicationUpdateSerializer.Meta.fields + (
"export_type",
"have_you_been_informed",
"reference_number_on_information_form",
"is_shipped_waybill_or_lading",
"non_waybill_or_lading_route_details",
"goods_starting_point",
"goods_recipients",
)

def __init__(self, *args, **kwargs):
Expand Down
4 changes: 3 additions & 1 deletion api/applications/serializers/temporary_export_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def validate(self, data):
data, "temp_export_details", strings.Generic.TemporaryExportDetails.Error.TEMPORARY_EXPORT_DETAILS
)
is_temp_direct_control_value = validate_field(
data, "is_temp_direct_control", strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL
data,
"is_temp_direct_control",
strings.Generic.TemporaryExportDetails.Error.PRODUCTS_UNDER_DIRECT_CONTROL,
)

# Only validate temp_direct_control_details if its parent is_temp_direct_control is False
Expand Down
23 changes: 21 additions & 2 deletions api/applications/tests/test_create_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ def test_create_draft_standard_individual_export_application_successful(self):
self.assertEqual(response_data["id"], str(standard_application.id))
self.assertEqual(StandardApplication.objects.count(), 1)

def test_create_draft_standard_individual_export_application_empty_export_type_successful(self):
"""
Ensure we can create a new standard individual export application draft without the export_type field populated
"""
data = {
"name": "Test",
"application_type": CaseTypeReferenceEnum.SIEL,
"have_you_been_informed": ApplicationExportLicenceOfficialType.YES,
"reference_number_on_information_form": "123",
}

response = self.client.post(self.url, data, **self.exporter_headers)
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

standard_application = StandardApplication.objects.get()
self.assertEqual(response_data["id"], str(standard_application.id))
self.assertEqual(StandardApplication.objects.count(), 1)

def test_create_draft_exhibition_clearance_application_successful(self):
"""
Ensure we can create a new Exhibition Clearance draft object
Expand All @@ -63,7 +82,7 @@ def test_create_draft_exhibition_clearance_application_successful(self):

def test_create_draft_gifting_clearance_application_successful(self):
"""
Ensure we can create a new Exhibition Clearance draft object
Ensure we can create a new Gifting Clearance draft object
"""
self.assertEqual(GiftingClearanceApplication.objects.count(), 0)

Expand All @@ -82,7 +101,7 @@ def test_create_draft_gifting_clearance_application_successful(self):

def test_create_draft_f680_clearance_application_successful(self):
"""
Ensure we can create a new Exhibition Clearance draft object
Ensure we can create a new F680 Clearance draft object
"""
self.assertEqual(F680ClearanceApplication.objects.count(), 0)

Expand Down
41 changes: 41 additions & 0 deletions api/applications/tests/test_edit_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,47 @@ def test_edit_unsubmitted_application_name_success(self):
# Unsubmitted (draft) applications should not create audit entries when edited
self.assertEqual(Audit.objects.count(), 0)

def test_edit_unsubmitted_application_export_type_success(self):
"""Test edit the application export_type of an unsubmitted application. An unsubmitted application
has the 'draft' status.
"""
application = self.create_draft_standard_application(self.organisation)
# export_type is set to permanent in create_draft_standard_application
self.assertEqual(application.export_type, "permanent")

url = reverse("applications:application", kwargs={"pk": application.id})
updated_at = application.updated_at

response = self.client.put(url, {"export_type": "temporary"}, **self.exporter_headers)

application.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(application.export_type, "temporary")
self.assertGreater(application.updated_at, updated_at)
# Unsubmitted (draft) applications should not create audit entries when edited
self.assertEqual(Audit.objects.count(), 0)

def test_edit_unsubmitted_application_locations_success(self):
application = self.create_draft_standard_application(self.organisation)

url = reverse("applications:application", kwargs={"pk": application.id})
updated_at = application.updated_at

data = {
"goods_starting_point": "GB",
"goods_recipients": "via_consignee",
}

response = self.client.put(url, data, **self.exporter_headers)

application.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(application.goods_starting_point, "GB")
self.assertEqual(application.goods_recipients, "via_consignee")
self.assertGreater(application.updated_at, updated_at)
# Unsubmitted (draft) applications should not create audit entries when edited
self.assertEqual(Audit.objects.count(), 0)

@parameterized.expand(get_case_statuses(read_only=False))
def test_edit_application_name_in_editable_status_success(self, editable_status):
old_name = "Old Name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_perform_action_on_non_temporary_export_type_standard_applications_failu
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["errors"],
["{'get_temp_export_details_update_serializer does not support this export type: permanent'}"],
{"temp_export_details": ["Cannot update temporary export details for a permanent export type"]},
)

def test_perform_action_on_non_open_or_standard_applications_failure(self):
Expand Down
Loading

0 comments on commit 57f5333

Please sign in to comment.