Skip to content

Commit

Permalink
Merge pull request #2436 from uktrade/LTD-5946-f680-generate-approval…
Browse files Browse the repository at this point in the history
…-document-api

[LTD-5946] F680 generate approval document
  • Loading branch information
currycoder authored Mar 4, 2025
2 parents d3233ff + 1f55176 commit 7ead372
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 4 deletions.
8 changes: 7 additions & 1 deletion api/cases/application_manifest.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
from api.applications.models import StandardApplication
from api.applications.serializers.standard_application import StandardApplicationViewSerializer
from api.cases.enums import CaseTypeSubTypeEnum
from api.cases.enums import CaseTypeSubTypeEnum, ApplicationFeatures
from api.f680.models import F680Application
from api.f680.caseworker.serializers import F680ApplicationSerializer


class BaseManifest:
caseworker_serializers = {}
model_class = None
features = {}

def has_feature(self, feature):
return self.features.get(feature, False)


class StandardApplicationManifest(BaseManifest):
model_class = StandardApplication
caseworker_serializers = {"view": StandardApplicationViewSerializer}
features = {ApplicationFeatures.LICENCE_ISSUE: True}


class F680ApplicationManifest(BaseManifest):
model_class = F680Application
caseworker_serializers = {"view": F680ApplicationSerializer}
features = {ApplicationFeatures.LICENCE_ISSUE: False}


# TODO: Make it so that each application django app defines/registers its own
Expand Down
4 changes: 4 additions & 0 deletions api/cases/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,7 @@ def templates(cls):
@classmethod
def advice_type_to_decision(cls, advice_type):
return cls.decision_map[advice_type]


class ApplicationFeatures:
LICENCE_ISSUE = "licence_issue"
8 changes: 6 additions & 2 deletions api/cases/generated_documents/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.exceptions import ParseError, ValidationError

from api.staticdata.statuses.enums import CaseStatusEnum
from api.cases.enums import CaseDocumentState, AdviceType
from api.cases.enums import CaseDocumentState, AdviceType, ApplicationFeatures
from api.cases.libraries.get_case import get_case
from api.cases.models import CaseDocument
from api.core.exceptions import NotFoundError
Expand Down Expand Up @@ -105,8 +105,12 @@ def get_decision_type(advice_type, template):


def get_draft_licence(case, advice_type):
licence = None
# TODO: This helper may be better located on the Case object
application_manifest = case.get_application_manifest()
if not application_manifest.has_feature(ApplicationFeatures.LICENCE_ISSUE):
return None

licence = None
# this is the case of regenerating document which can happen after
# finalising the Case so there won't be any draft licences in this case
if case.status.status == CaseStatusEnum.FINALISED:
Expand Down
45 changes: 45 additions & 0 deletions api/cases/generated_documents/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest

from rest_framework.exceptions import ParseError

from api.cases.generated_documents.helpers import get_draft_licence
from api.cases.enums import AdviceType
from api.applications.tests.factories import StandardApplicationFactory
from api.licences.tests.factories import StandardLicenceFactory
from api.f680.tests.factories import SubmittedF680ApplicationFactory
from api.staticdata.statuses.models import CaseStatus
from api.staticdata.statuses.enums import CaseStatusEnum


pytestmark = pytest.mark.django_db


def test_get_draft_licence_f680_application_returns_none():
application = SubmittedF680ApplicationFactory()
assert get_draft_licence(application.case_ptr, AdviceType.APPROVE) is None


def test_get_draft_licence_standard_application_case_finalised_returns_none():
finalised_status = CaseStatus.objects.get(status=CaseStatusEnum.FINALISED)
application = StandardApplicationFactory(status=finalised_status)
assert get_draft_licence(application.case_ptr, AdviceType.APPROVE) is None


def test_get_draft_licence_standard_application_draft_exists_success():
final_review_status = CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW)
application = StandardApplicationFactory(status=final_review_status)
draft_licence = StandardLicenceFactory(case=application.case_ptr)
assert get_draft_licence(application.case_ptr, AdviceType.APPROVE) == draft_licence


def test_get_draft_licence_standard_application_refusal_advice_returns_none():
final_review_status = CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW)
application = StandardApplicationFactory(status=final_review_status)
assert get_draft_licence(application.case_ptr, AdviceType.REFUSE) is None


def test_get_draft_licence_standard_application_draft_missing_raises_error():
final_review_status = CaseStatus.objects.get(status=CaseStatusEnum.UNDER_FINAL_REVIEW)
application = StandardApplicationFactory(status=final_review_status)
with pytest.raises(ParseError):
get_draft_licence(application.case_ptr, AdviceType.APPROVE)
32 changes: 31 additions & 1 deletion api/cases/tests/test_application_manifest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pytest
from unittest import mock

from api.cases.application_manifest import ManifestRegistry
from api.cases.enums import ApplicationFeatures
from api.cases.application_manifest import (
ManifestRegistry,
BaseManifest,
F680ApplicationManifest,
StandardApplicationManifest,
)


pytestmark = pytest.mark.django_db
Expand All @@ -25,3 +31,27 @@ def test_get_manifest_manifest_missing(self):
registry = ManifestRegistry()
with pytest.raises(KeyError):
registry.get_manifest("some_application_type")


class TestBaseManifest:

def test_has_feature(self):
manifest = BaseManifest()
manifest.features = {"my-feature": True, "my-other-feature": False}
assert manifest.has_feature("my-feature")
assert manifest.has_feature("my-other-feature") is False
assert manifest.has_feature("my-unknown-feature") is False


class TestStandardApplicationManifest:

def test_has_feature(self):
manifest = StandardApplicationManifest()
assert manifest.has_feature(ApplicationFeatures.LICENCE_ISSUE) is True


class TestF680ApplicationManifest:

def test_has_feature(self):
manifest = F680ApplicationManifest()
assert manifest.has_feature(ApplicationFeatures.LICENCE_ISSUE) is False
15 changes: 15 additions & 0 deletions api/letter_templates/context_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,18 @@ def to_representation(self, obj):
return ret


class F680Serializer(serializers.ModelSerializer):
class Meta:
model = Case
fields = ["application"]

application = serializers.SerializerMethodField()

def get_application(self, obj):
# Expose the application JSON to the template
return obj.get_application().application


class ComplianceSiteLicenceSerializer(serializers.ModelSerializer):
class Meta:
model = Case
Expand Down Expand Up @@ -742,13 +754,16 @@ def get_document_context(case, addressee=None):
}


# TODO: This mapping/serializers business feels nuts - we should just generate a context
# dict in a function instead
SERIALIZER_MAPPING = {
CaseTypeSubTypeEnum.STANDARD: FlattenedStandardApplicationSerializer,
CaseTypeSubTypeEnum.EUA: EndUserAdvisoryQuerySerializer,
CaseTypeSubTypeEnum.GOODS: GoodsQuerySerializer,
CaseTypeSubTypeEnum.COMP_SITE: FlattenedComplianceSiteWithVisitReportsSerializer,
CaseTypeSubTypeEnum.EUA: EndUserAdvisoryQueryCaseSerializer,
CaseTypeSubTypeEnum.COMP_VISIT: ComplianceVisitSerializer,
CaseTypeSubTypeEnum.F680: F680Serializer,
}


Expand Down
19 changes: 19 additions & 0 deletions api/letter_templates/templates/letter_templates/f680_approval.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@
{% block body %}

<div class="govuk-body">We Have Approved Your F680 Application</div>
<p>{{user_content}}</p>
<h2 class="govuk-heading-l">Strategic Export Licensing Criteria</h3>
{% for section_key, section in details.application.sections.items %}
<h3>{{section.label}}</h3>
{% if section.type == "single" %}
{% for field in section.fields %}
{% if field.answer %}
<p>{{field.question}}: {{field.answer}}</p>
{% endif %}
{% endfor %}
{% else %}
{% for item in section.items %}
{% for field in item.fields %}
{% if field.answer %}
<p>{{field.question}}: {{field.answer}}</p>
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}

{% endblock %}
8 changes: 8 additions & 0 deletions api/letter_templates/tests/test_context_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from api.applications.enums import ApplicationExportType, ApplicationExportLicenceOfficialType
from api.applications.models import ExternalLocationOnApplication, GoodOnApplication
from api.applications.tests.factories import GoodOnApplicationFactory
from api.f680.tests.factories import SubmittedF680ApplicationFactory
from api.cases.enums import AdviceType
from api.licences.tests.factories import StandardLicenceFactory
from api.letter_templates.context_generator import EcjuQuerySerializer
Expand Down Expand Up @@ -691,6 +692,13 @@ def test_generate_context_with_standard_application_details(self):
self._assert_base_application_details(context["details"], case)
self._assert_standard_application_details(context["details"], case)

def test_generate_context_with_f680_details(self):
application = SubmittedF680ApplicationFactory(application={"some": "json"})
case = application.case_ptr

context = get_document_context(case)
assert context["details"] == {"application": {"some": "json"}}

def test_generate_context_with_end_user_advisory_query_details(self):
case = self.create_end_user_advisory(note="abc", reasoning="def", organisation=self.organisation)

Expand Down

0 comments on commit 7ead372

Please sign in to comment.