api/applications/views/applications.py
from copy import deepcopy
from uuid import UUID
from django.db import transaction
from django.db.models import F, Q
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.timezone import now
from rest_framework import status
from rest_framework.exceptions import PermissionDenied, ValidationError, ParseError
from rest_framework.generics import (
CreateAPIView,
ListAPIView,
ListCreateAPIView,
RetrieveAPIView,
RetrieveUpdateDestroyAPIView,
UpdateAPIView,
)
from rest_framework.views import APIView
from api.appeals.models import Appeal
from api.appeals.serializers import AppealSerializer
from api.applications import constants
from api.applications.creators import validate_application_ready_for_submission, _validate_agree_to_declaration
from api.applications.helpers import (
get_application_create_serializer,
get_application_view_serializer,
get_application_update_serializer,
validate_and_create_goods_on_licence,
auto_match_sanctions,
)
from api.applications.libraries.application_helpers import (
optional_str_to_bool,
can_status_be_set_by_gov_user,
create_submitted_audit,
check_user_can_set_status,
)
from api.applications.libraries.case_status_helpers import submit_application
from api.applications.libraries.edit_applications import (
save_and_audit_have_you_been_informed_ref,
set_case_flags_on_submitted_standard_application,
)
from api.applications.libraries.get_applications import get_application
from api.applications.libraries.goods_on_applications import add_goods_flags_to_submitted_application
from api.applications.libraries.licence import get_default_duration
from api.applications.models import (
BaseApplication,
SiteOnApplication,
GoodOnApplication,
ExternalLocationOnApplication,
PartyOnApplication,
StandardApplication,
)
from api.applications.notify import notify_exporter_case_opened_for_editing
from api.applications.serializers.generic_application import (
GenericApplicationListSerializer,
GenericApplicationCopySerializer,
)
from api.applications.serializers.standard_application import StandardApplicationRequiresSerialNumbersSerializer
from api.audit_trail import service as audit_trail_service
from api.audit_trail.enums import AuditType
from api.cases.enums import AdviceLevel, AdviceType, CaseTypeSubTypeEnum, CaseTypeEnum
from api.cases.generated_documents.models import GeneratedCaseDocument
from api.cases.generated_documents.helpers import auto_generate_case_document
from api.cases.libraries.get_flags import get_flags
from api.cases.notify import notify_exporter_appeal_acknowledgement
from api.cases.serializers import ApplicationManageSubStatusSerializer
from api.cases.celery_tasks import get_application_target_sla
from api.core.authentication import ExporterAuthentication, SharedAuthentication, GovAuthentication
from api.core.constants import ExporterPermissions, GovPermissions, AutoGeneratedDocuments
from api.core.decorators import (
application_in_state,
authorised_to_view_application,
allowed_application_types,
)
from api.core.helpers import str_to_bool
from api.core.permissions import (
assert_user_has_permission,
IsExporterInOrganisation,
)
from api.applications.views.helpers.advice import ensure_lu_countersign_complete
from api.flags.enums import FlagStatuses, SystemFlags
from api.goods.serializers import GoodCreateSerializer
from api.goods.models import FirearmGoodDetails
from api.goodstype.models import GoodsType
from api.licences.enums import LicenceStatus
from api.licences.helpers import get_licence_reference_code, update_licence_status
from api.licences.models import Licence
from api.licences.serializers.create_licence import LicenceCreateSerializer
from lite_content.lite_api import strings
from api.organisations.libraries.get_organisation import get_request_user_organisation, get_request_user_organisation_id
from api.organisations.models import Site
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.libraries.case_status_validate import is_case_status_draft
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
from api.staticdata.statuses.serializers import CaseSubStatusSerializer
from api.staticdata.statuses.models import CaseSubStatus
from api.users.libraries.notifications import get_case_notifications
from api.users.models import ExporterUser
from api.workflow.flagging_rules_automation import apply_flagging_rules_to_case
from lite_routing.routing_rules_internal.routing_engine import run_routing_rules
from api.cases.libraries.finalise import remove_flags_on_finalisation, remove_flags_from_audit_trail
class ApplicationList(ListCreateAPIView):
authentication_classes = (ExporterAuthentication,)
serializer_class = GenericApplicationListSerializer
def get_queryset(self):
"""
Filter applications on submitted
"""
try:
submitted = optional_str_to_bool(self.request.GET.get("submitted"))
except ValueError:
return BaseApplication.objects.none()
organisation = get_request_user_organisation(self.request)
if submitted is None:
applications = BaseApplication.objects.filter(organisation=organisation)
elif submitted:
applications = BaseApplication.objects.submitted(organisation)
else:
applications = BaseApplication.objects.drafts(organisation)
users_sites = Site.objects.get_by_user_and_organisation(self.request.user.exporteruser, organisation)
disallowed_applications = SiteOnApplication.objects.exclude(site__id__in=users_sites).values_list(
"application", flat=True
)
applications = applications.exclude(id__in=disallowed_applications)
return applications.prefetch_related("status", "case_type").select_subclasses()
def get_paginated_response(self, data):
data = get_case_notifications(data, self.request)
return super().get_paginated_response(data)
def post(self, request, **kwargs):
"""
Create a new application
"""
data = request.data
if not data.get("application_type"):
raise ValidationError({"application_type": [strings.Applications.Generic.SELECT_AN_APPLICATION_TYPE]})
case_type = data.pop("application_type", None)
serializer = get_application_create_serializer(case_type)
serializer = serializer(
data=data,
case_type_id=CaseTypeEnum.reference_to_id(case_type),
context=get_request_user_organisation(request),
)
if serializer.is_valid(raise_exception=True):
application = serializer.save()
return JsonResponse(data={"id": application.id}, status=status.HTTP_201_CREATED)
class ApplicationsRequireSerialNumbersList(ListAPIView):
authentication_classes = (ExporterAuthentication,)
serializer_class = StandardApplicationRequiresSerialNumbersSerializer
def get_queryset(self):
organisation = get_request_user_organisation(self.request)
applications = StandardApplication.objects.filter(organisation=organisation).prefetch_related(
"goods__firearm_details"
)
applications = applications.filter(
status__in=[
get_case_status_by_status(CaseStatusEnum.SUBMITTED),
get_case_status_by_status(CaseStatusEnum.FINALISED),
]
)
applications = applications.filter(
Q(
goods__firearm_details__serial_numbers_available__in=FirearmGoodDetails.SerialNumberAvailability.get_has_serial_numbers_values()
)
& (
Q(goods__firearm_details__serial_numbers__len__lt=F("goods__firearm_details__number_of_items"))
| Q(goods__firearm_details__serial_numbers__contains=[""])
)
)
return applications
class ApplicationExisting(APIView):
"""
This view returns boolean values depending on the type of organisation:
Standard - Whether the organisation has any drafts/applications
"""
authentication_classes = (ExporterAuthentication,)
def get(self, request):
organisation = get_request_user_organisation(request)
has_applications = BaseApplication.objects.filter(organisation=organisation).exists()
return JsonResponse(
data={
"applications": has_applications,
}
)
class ApplicationDetail(RetrieveUpdateDestroyAPIView):
authentication_classes = (ExporterAuthentication,)
@authorised_to_view_application(ExporterUser)
def get(self, request, pk):
"""
Retrieve an application instance
"""
application = get_application(pk)
serializer = get_application_view_serializer(application)
data = serializer(
application,
context={
"user_type": request.user.type,
"exporter_user": request.user.exporteruser,
"organisation_id": get_request_user_organisation_id(request),
},
).data
return JsonResponse(data=data, status=status.HTTP_200_OK)
@authorised_to_view_application(ExporterUser)
@application_in_state(is_editable=True)
def put(self, request, pk):
"""
Update an application instance
"""
application = get_application(pk)
update_serializer = get_application_update_serializer(application)
case = application.get_case()
data = request.data.copy()
serializer = update_serializer(
application, data=data, context=get_request_user_organisation(request), partial=True
)
# Prevent minor edits of the clearance level
if not application.is_major_editable() and request.data.get("clearance_level"):
return JsonResponse(
data={"errors": {"clearance_level": [strings.Applications.Generic.NOT_POSSIBLE_ON_MINOR_EDIT]}},
status=status.HTTP_400_BAD_REQUEST,
)
if not serializer.is_valid():
return JsonResponse(data={"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
if application.case_type.sub_type == CaseTypeSubTypeEnum.HMRC:
serializer.save()
return JsonResponse(data={}, status=status.HTTP_200_OK)
# Audit block
if request.data.get("name"):
old_name = application.name
serializer.save()
audit_trail_service.create(
actor=request.user.exporteruser,
verb=AuditType.UPDATED_APPLICATION_NAME,
target=case,
payload={"old_name": old_name, "new_name": serializer.data.get("name")},
)
return JsonResponse(data={}, status=status.HTTP_200_OK)
if request.data.get("clearance_level"):
serializer.save()
return JsonResponse(data={}, status=status.HTTP_200_OK)
if application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD:
save_and_audit_have_you_been_informed_ref(request, application, serializer)
serializer.save()
return JsonResponse(data={}, status=status.HTTP_200_OK)
@authorised_to_view_application(ExporterUser)
def delete(self, request, pk):
"""
Deleting an application should only be allowed for draft applications
"""
application = get_application(pk)
if not is_case_status_draft(application.status.status):
return JsonResponse(
data={"errors": strings.Applications.Generic.DELETE_SUBMITTED_APPLICATION_ERROR},
status=status.HTTP_400_BAD_REQUEST,
)
application.delete()
return JsonResponse(
data={"status": strings.Applications.Generic.DELETE_DRAFT_APPLICATION}, status=status.HTTP_200_OK
)
class ApplicationSubmission(APIView):
authentication_classes = (ExporterAuthentication,)
@transaction.atomic
@application_in_state(is_major_editable=True)
@authorised_to_view_application(ExporterUser)
def put(self, request, pk):
"""
Submit a draft application which will set its submitted_at datetime and status before creating a case
Depending on the application subtype, this will also submit the declaration of the licence
"""
application = get_application(pk)
old_status = application.status.status
if application.case_type.sub_type != CaseTypeSubTypeEnum.HMRC:
assert_user_has_permission(
request.user.exporteruser, ExporterPermissions.SUBMIT_LICENCE_APPLICATION, application.organisation
)
errors = validate_application_ready_for_submission(application)
if errors:
return JsonResponse(data={"errors": errors}, status=status.HTTP_400_BAD_REQUEST)
# Queries are completed directly when submit is clicked on the task list
# HMRC are completed when submit is clicked on the summary page (page after task list)
# Applications are completed when submit is clicked on the declaration page (page after summary page)
if application.case_type.sub_type in [CaseTypeSubTypeEnum.EUA, CaseTypeSubTypeEnum.GOODS] or (
CaseTypeSubTypeEnum.HMRC and request.data.get("submit_hmrc")
):
application.submitted_by = request.user.exporteruser
create_submitted_audit(request, application, old_status)
submit_application(application)
if request.data.get("submit_hmrc"):
auto_generate_case_document(
"application_form",
application,
AutoGeneratedDocuments.APPLICATION_FORM,
request.build_absolute_uri(),
)
elif application.case_type.sub_type in [
CaseTypeSubTypeEnum.STANDARD,
CaseTypeSubTypeEnum.OPEN,
CaseTypeSubTypeEnum.F680,
CaseTypeSubTypeEnum.GIFTING,
CaseTypeSubTypeEnum.EXHIBITION,
]:
if request.data.get("submit_declaration"):
errors = _validate_agree_to_declaration(request, errors)
if errors:
return JsonResponse(data={"errors": errors}, status=status.HTTP_400_BAD_REQUEST)
# If a valid declaration is provided, save the application
application.submitted_by = request.user.exporteruser
application.agreed_to_foi = request.data.get("agreed_to_foi")
application.foi_reason = request.data.get("foi_reason", "")
submit_application(application)
if application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD:
set_case_flags_on_submitted_standard_application(application)
add_goods_flags_to_submitted_application(application)
apply_flagging_rules_to_case(application)
create_submitted_audit(request, application, old_status)
auto_generate_case_document(
"application_form",
application,
AutoGeneratedDocuments.APPLICATION_FORM,
request.build_absolute_uri(),
)
run_routing_rules(application)
# Set the sites on this application as used so their name/site records located at are no longer editable
sites_on_application = SiteOnApplication.objects.filter(application=application)
Site.objects.filter(id__in=sites_on_application.values_list("site_id", flat=True)).update(
is_used_on_application=True
)
if application.case_type.sub_type in [
CaseTypeSubTypeEnum.STANDARD,
CaseTypeSubTypeEnum.OPEN,
CaseTypeSubTypeEnum.HMRC,
]:
if UUID(SystemFlags.ENFORCEMENT_CHECK_REQUIRED) not in application.flags.values_list("id", flat=True):
application.flags.add(SystemFlags.ENFORCEMENT_CHECK_REQUIRED)
if application.case_type.sub_type in [CaseTypeSubTypeEnum.STANDARD, CaseTypeSubTypeEnum.OPEN]:
auto_match_sanctions(application)
# Serialize for the response message
serializer = get_application_view_serializer(application)
serializer = serializer(application, context={"user_type": request.user.type})
application_data = serializer.data
data = {"application": {"reference_code": application.reference_code, **application_data}}
if application.reference_code:
data["reference_code"] = application.reference_code
return JsonResponse(data=data, status=status.HTTP_200_OK)
class ApplicationManageStatus(APIView):
authentication_classes = (SharedAuthentication,)
@transaction.atomic
def put(self, request, pk):
application = get_application(pk)
data = deepcopy(request.data)
error_response = check_user_can_set_status(request, application, data)
if error_response:
return error_response
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": case_status.status,
"old": old_status.status,
},
"additional_text": data.get("note"),
},
)
if old_status != application.status:
run_routing_rules(case=application, keep_status=True)
if application.status.status == CaseStatusEnum.APPLICANT_EDITING:
notify_exporter_case_opened_for_editing(application)
data = get_application_view_serializer(application)(application, context={"user_type": request.user.type}).data
# Remove needed flags when case is Withdrawn/Closed
if case_status.status in [CaseStatusEnum.WITHDRAWN, CaseStatusEnum.CLOSED]:
remove_flags_on_finalisation(application.get_case())
remove_flags_from_audit_trail(application.get_case())
return JsonResponse(data={"data": data}, status=status.HTTP_200_OK)
class ApplicationManageSubStatus(UpdateAPIView):
authentication_classes = (GovAuthentication,)
queryset = StandardApplication.objects.all()
serializer_class = ApplicationManageSubStatusSerializer
def put(self, request, pk):
case = get_application(pk).get_case()
sub_status = request.data.get("sub_status")
response_data = super().put(request, pk)
if not sub_status:
sub_status = None
else:
sub_status = CaseSubStatus.objects.get(id=sub_status).name
# Update the model
audit_trail_service.create(
actor=request.user,
verb=AuditType.UPDATED_SUB_STATUS,
target=case,
payload={"sub_status": sub_status, "status": CaseStatusEnum.get_text(case.status.status)},
)
return response_data
class ApplicationSubStatuses(ListAPIView):
authentication_classes = (GovAuthentication,)
serializer_class = CaseSubStatusSerializer
pagination_class = None
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.application = get_object_or_404(StandardApplication, pk=self.kwargs["pk"])
def get_queryset(self):
return self.application.status.sub_statuses.all().order_by("order")
class ApplicationFinaliseView(APIView):
authentication_classes = (GovAuthentication,)
def get(self, request, pk):
"""
Get goods to set licenced quantity for, with advice
"""
approved_goods_on_application = (
GoodOnApplication.objects.filter(
application_id=pk,
good__advice__level=AdviceLevel.FINAL,
good__advice__type__in=[AdviceType.APPROVE, AdviceType.PROVISO, AdviceType.NO_LICENCE_REQUIRED],
good__advice__case_id=pk,
good__advice__good_id__isnull=False,
)
.annotate(
advice_type=F("good__advice__type"),
advice_text=F("good__advice__text"),
advice_proviso=F("good__advice__proviso"),
)
.distinct()
)
good_on_applications_with_advice = [
{
"id": str(goa.id),
"good": GoodCreateSerializer(goa.good).data,
"unit": goa.unit,
"quantity": goa.quantity,
"control_list_entries": [
{"rating": item.rating, "text": item.text} for item in goa.control_list_entries.all()
],
"is_good_controlled": goa.is_good_controlled,
"value": goa.value,
"advice": {
"type": AdviceType.as_representation(goa.advice_type),
"text": goa.advice_text,
"proviso": goa.advice_proviso,
},
}
for goa in approved_goods_on_application
]
return JsonResponse({"goods": good_on_applications_with_advice})
@transaction.atomic # noqa
def put(self, request, pk):
"""
Finalise an application
"""
application = get_application(pk)
# Check permissions
is_mod_clearance = application.case_type.sub_type in CaseTypeSubTypeEnum.mod
if not can_status_be_set_by_gov_user(
request.user.govuser, application.status.status, CaseStatusEnum.FINALISED, is_mod_clearance
):
return JsonResponse(
data={"errors": [strings.Applications.Generic.Finalise.Error.SET_FINALISED]},
status=status.HTTP_400_BAD_REQUEST,
)
licence_data = request.data.copy()
action = licence_data.get("action")
if not action:
return JsonResponse(
data={"errors": [strings.Applications.Finalise.Error.NO_ACTION_GIVEN]},
status=status.HTTP_400_BAD_REQUEST,
)
# Check countersigning requirements and required countersignatures are present
ensure_lu_countersign_complete(application)
# Check if any blocking flags are on the case
blocking_flags = (
get_flags(application.get_case())
.filter(status=FlagStatuses.ACTIVE, blocks_finalising=True)
.order_by("name")
.values_list("name", flat=True)
)
if blocking_flags:
raise PermissionDenied(
[f"{strings.Applications.Finalise.Error.BLOCKING_FLAGS}{','.join(list(blocking_flags))}"]
)
# Refusals & NLRs
if action in [AdviceType.REFUSE, AdviceType.NO_LICENCE_REQUIRED]:
return JsonResponse(data={"application": str(application.id)}, status=status.HTTP_200_OK)
# Approvals & Provisos
else:
try:
active_licence = Licence.objects.get_active_licence(application)
default_licence_duration = active_licence.duration
except Licence.DoesNotExist:
default_licence_duration = get_default_duration(application)
licence_data["duration"] = licence_data.get("duration", default_licence_duration)
# Check change default duration permission
if licence_data["duration"] != default_licence_duration and not request.user.govuser.has_permission(
GovPermissions.MANAGE_LICENCE_DURATION
):
raise PermissionDenied([strings.Applications.Finalise.Error.SET_DURATION_PERMISSION])
# Validate date
try:
start_date = timezone.datetime(
year=int(licence_data["year"]), month=int(licence_data["month"]), day=int(licence_data["day"])
)
except (KeyError, ValueError):
raise ParseError({"start_date": [strings.Applications.Finalise.Error.INVALID_DATE]})
# Delete existing draft if one exists
try:
licence = Licence.objects.get_draft_licence(application)
except Licence.DoesNotExist:
licence = None
licence_data["start_date"] = start_date.strftime("%Y-%m-%d")
if licence:
# Update Draft Licence object
licence_serializer = LicenceCreateSerializer(instance=licence, data=licence_data, partial=True)
else:
# Create Draft Licence object
licence_data["case"] = application.id
licence_data["status"] = LicenceStatus.DRAFT
licence_data["reference_code"] = get_licence_reference_code(application.reference_code)
licence_serializer = LicenceCreateSerializer(data=licence_data)
if not licence_serializer.is_valid():
raise ParseError(licence_serializer.errors)
licence = licence_serializer.save()
# Delete draft licence document that may now be invalid
GeneratedCaseDocument.objects.filter(
case_id=pk, advice_type=AdviceType.APPROVE, visible_to_exporter=False
).delete()
# Only validate & save GoodsOnLicence (quantities & values) for Standard applications
if application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD:
errors = validate_and_create_goods_on_licence(pk, licence.id, request.data)
if errors:
raise ParseError(errors)
return JsonResponse(data=LicenceCreateSerializer(licence).data, status=status.HTTP_200_OK)
class ApplicationDurationView(APIView):
authentication_classes = (GovAuthentication,)
def get(self, request, pk):
"""
Retrieve default duration for an application.
"""
application = get_application(pk)
duration = get_default_duration(application)
return JsonResponse(data={"licence_duration": duration}, status=status.HTTP_200_OK)
class ApplicationCopy(APIView):
authentication_classes = (ExporterAuthentication,)
@transaction.atomic
def post(self, request, pk):
"""
Copy an application
In this function we get the application and remove it's relation to itself on the database, which allows for us
keep most of the data in relation to the application intact.
"""
self.old_application_id = pk
old_application = get_application(pk)
data = request.data
serializer = GenericApplicationCopySerializer(
data=data, context={"application_type": old_application.case_type}
)
if not serializer.is_valid():
return JsonResponse(data={"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
# Deepcopy so new_application is not a pointer to old_application
# (if not deepcopied, any changes done on one applies to the other)
self.new_application = deepcopy(old_application)
if self.new_application.case_type.sub_type == CaseTypeSubTypeEnum.F680:
for field in constants.F680.ADDITIONAL_INFORMATION_FIELDS:
setattr(self.new_application, field, None)
# Clear references to parent objects, and current application instance object
self.strip_id_for_application_copy()
# Replace the reference and have you been informed (if required) with users answer. Also sets some defaults
self.new_application.name = request.data["name"]
if (
self.new_application.case_type.sub_type == CaseTypeSubTypeEnum.STANDARD
and not self.new_application.case_type.id == CaseTypeEnum.SICL.id
):
self.new_application.have_you_been_informed = request.data.get("have_you_been_informed")
self.new_application.reference_number_on_information_form = request.data.get(
"reference_number_on_information_form"
)
self.new_application.status = get_case_status_by_status(CaseStatusEnum.DRAFT)
self.new_application.copy_of_id = self.old_application_id
# Remove SLA data
self.new_application.sla_days = 0
self.new_application.sla_remaining_days = get_application_target_sla(self.new_application.case_type.sub_type)
self.new_application.last_closed_at = None
self.new_application.sla_updated_at = None
# Remove data that should not be copied
self.remove_data_from_application_copy()
# Need to save here to create the pk/id for relationships
self.new_application.save()
# Create new foreign key connection using data from old application (this is for tables pointing to the case)
self.create_foreign_relations_for_new_application()
self.duplicate_goodstypes_for_new_application()
# Get all parties connected to the application and produce a copy (and replace reference for each one)
self.duplicate_parties_on_new_application()
# Remove usage & licenced quantity/ value
self.new_application.goods_type.update(usage=0)
# Save
self.new_application.created_at = now()
self.new_application.save()
return JsonResponse(data={"data": self.new_application.id}, status=status.HTTP_201_CREATED)
def strip_id_for_application_copy(self):
"""
The current object id and pk need removed, and the pointers otherwise save() will determine the object exists
"""
self.new_application.pk = None
self.new_application.id = None
self.new_application.case_ptr = None
self.new_application.base_application_ptr = None
def remove_data_from_application_copy(self):
"""
Removes data of fields that are stored on the case model, and we wish not to copy.
"""
set_none = [
"case_officer",
"reference_code",
"submitted_at",
"licence_duration",
"is_informed_wmd",
"informed_wmd_ref",
"is_suspected_wmd",
"suspected_wmd_ref",
"is_military_end_use_controls",
"military_end_use_controls_ref",
"is_eu_military",
"is_compliant_limitations_eu",
"compliant_limitations_eu_ref",
"is_shipped_waybill_or_lading",
"non_waybill_or_lading_route_details",
"intended_end_use",
"temp_export_details",
"is_temp_direct_control",
"temp_direct_control_details",
"proposed_return_date",
]
for attribute in set_none:
setattr(self.new_application, attribute, None)
def duplicate_parties_on_new_application(self):
"""
Generates a copy of each party, and recreates any old application Party relations using the new copied party.
Deleted parties are not copied over.
"""
party_on_old_application = PartyOnApplication.objects.filter(
application_id=self.old_application_id, deleted_at__isnull=True
)
for old_party_on_app in party_on_old_application:
old_party_on_app.pk = None
old_party_on_app.id = None
# copy party
old_party_id = old_party_on_app.party.id
party = old_party_on_app.party
party.id = None
party.pk = None
if not party.copy_of:
party.copy_of_id = old_party_id
party.created_at = now()
party.save()
old_party_on_app.party = party
old_party_on_app.application = self.new_application
old_party_on_app.created_at = now()
old_party_on_app.save()
def create_foreign_relations_for_new_application(self):
"""
Recreates any connections from foreign tables existing on the current application,
we wish to move to the new application.
"""
# This is the super set of all many to many related objects for ALL application types.
# The loop below caters for the possibility that any of the relationships are not relevant to the current
# application type
relationships = [
GoodOnApplication,
SiteOnApplication,
ExternalLocationOnApplication,
]
for relation in relationships:
old_application_relation_results = relation.objects.filter(application_id=self.old_application_id).all()
for result in old_application_relation_results:
result.pk = None
result.id = None
result.application = self.new_application
# Some models listed above are not inheriting timestampable models,
# as such we need to ensure created_at exists
if getattr(result, "created_at", False):
result.created_at = now()
result.save()
def duplicate_goodstypes_for_new_application(self):
"""
Creates a duplicate GoodsType and attaches it to the new application if applicable.
"""
# GoodsType has more logic than in "create_foreign_relations_for_new_application",
# such as listing the countries on the goodstype, and flags as such it is seperated.
for good in GoodsType.objects.filter(application_id=self.old_application_id).all():
old_good_countries = list(good.countries.all())
old_good_flags = list(good.flags.all())
old_good_control_list_entries = list(good.control_list_entries.all())
good.pk = None
good.id = None
good.application = self.new_application
good.created_at = now()
good.save()
good.countries.set(old_good_countries)
good.flags.set(old_good_flags)
good.control_list_entries.set(old_good_control_list_entries)
class ApplicationRouteOfGoods(UpdateAPIView):
authentication_classes = (ExporterAuthentication,)
@authorised_to_view_application(ExporterUser)
@application_in_state(is_major_editable=True)
@allowed_application_types([CaseTypeSubTypeEnum.OPEN, CaseTypeSubTypeEnum.STANDARD])
def put(self, request, pk):
"""Update an application instance with route of goods data."""
application = get_application(pk)
serializer = get_application_update_serializer(application)
case = application.get_case()
data = request.data.copy()
serializer = serializer(application, data=data, context=get_request_user_organisation(request), partial=True)
if not serializer.is_valid():
return JsonResponse(data={"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
previous_answer = application.is_shipped_waybill_or_lading
new_answer = str_to_bool(data.get("is_shipped_waybill_or_lading"))
if previous_answer != new_answer:
self.add_audit_entry(request, case, "is shipped waybill or lading", previous_answer, new_answer)
if not new_answer:
previous_details = application.non_waybill_or_lading_route_details
new_details = data.get("non_waybill_or_lading_route_details")
if previous_details != new_details:
self.add_audit_entry(
request, case, "non_waybill_or_lading_route_details", previous_details, new_details
)
serializer.save()
return JsonResponse(data={}, status=status.HTTP_200_OK)
@staticmethod
def add_audit_entry(request, case, field, previous_value, new_value):
audit_trail_service.create(
actor=request.user,
verb=AuditType.UPDATED_ROUTE_OF_GOODS,
target=case,
payload={"route_of_goods_field": field, "previous_value": previous_value, "new_value": new_value},
)
class BaseApplicationAppeal:
authentication_classes = (ExporterAuthentication,)
permission_classes = [
IsExporterInOrganisation,
]
serializer_class = AppealSerializer
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
try:
self.application = BaseApplication.objects.get(pk=self.kwargs["pk"])
except BaseApplication.DoesNotExist:
raise Http404()
def get_organisation(self):
return self.application.organisation
def notify_exporter_appeal_received(self):
notify_exporter_appeal_acknowledgement(self.application)
class ApplicationAppeals(BaseApplicationAppeal, CreateAPIView):
def perform_create(self, serializer):
super().perform_create(serializer)
self.application.set_appealed(
serializer.instance,
self.request.user.exporteruser,
)
self.notify_exporter_appeal_received()
class ApplicationAppeal(BaseApplicationAppeal, RetrieveAPIView):
lookup_url_kwarg = "appeal_pk"
queryset = Appeal.objects.all()