Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] F680s #2299

Draft
wants to merge 30 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ea91c28
Ensure caseworkers can view F680 applications
currycoder Apr 4, 2024
7b70be9
f680 exporter application
Tllew Apr 4, 2024
b307d81
temporarily remove all questions to allow creation of basic applicati…
Tllew Apr 4, 2024
69c42ac
clean up some comments
Tllew Apr 4, 2024
15044e6
Fix failing test
saruniitr Apr 4, 2024
31ef0fb
Add additional info questions for F680 application
saruniitr Apr 4, 2024
2c3d43c
Add required clearances types question for F680 application
saruniitr Apr 4, 2024
b89f880
Add product details to F680 application
saruniitr Apr 5, 2024
8da332a
Ensure F680 case detail page shows correct tabs
currycoder Apr 4, 2024
58a8995
Improve F680 case details page
currycoder Apr 5, 2024
02985c8
fix post f680 advise
depsiatwal Apr 5, 2024
5ef8b42
Add case type filter to queue view
currycoder Apr 5, 2024
14b72b6
Fix unit tests
currycoder Apr 5, 2024
6399be6
Fix linting issue and a failing test
saruniitr Apr 5, 2024
c14771d
Ensure OGDs can give advice/move case forward on F680 applications
currycoder Apr 8, 2024
d9496d1
Ensure MOD-ECJU can finalise F680 applications
currycoder Apr 8, 2024
15d4d1f
Update description for F680 application option
saruniitr Apr 8, 2024
c41a4e3
Rebase fixup: Remove problematic reference number generation
currycoder Jan 13, 2025
71b2d93
Tidy up F680 MTCR answers in caseworker screen
currycoder Jan 16, 2025
408e4e8
Merge pull request #2308 from uktrade/LTD-5802-make-mtcr-response-rea…
currycoder Jan 16, 2025
d7d09b2
add in f680 approval advice type
markj0hnst0n Jan 16, 2025
3da3896
add f680 png
markj0hnst0n Jan 17, 2025
915890b
modify screenshot
markj0hnst0n Jan 17, 2025
dbe293c
Merge pull request #2314 from uktrade/LTD-5806
markj0hnst0n Jan 17, 2025
1f8c276
Merge pull request #2311 from uktrade/LTD-5804
markj0hnst0n Jan 17, 2025
550f7c4
show f680 approval info instead of nlr
markj0hnst0n Jan 17, 2025
4b00752
remove F680 from list of optional sections
markj0hnst0n Jan 20, 2025
c5a06a9
comment added
markj0hnst0n Jan 20, 2025
0d8c8c5
Merge pull request #2316 from uktrade/LTD-5816
markj0hnst0n Jan 20, 2025
fa95ea0
Merge pull request #2319 from uktrade/LTD-5822_
markj0hnst0n Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion caseworker/activities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def get_context_data(self, **kwargs):
"filtering_by": list(self.request.GET.keys()),
"queue": self.queue,
"team_filters": self.get_team_filters(),
"tabs": self.get_standard_application_tabs(),
"tabs": self.get_tabs_by_case_type(self.case.sub_type),
"current_tab": "cases:activities:notes-and-timeline",
"activities": get_activity(self.request, self.case_id, activity_filters=self.request.GET),
}
Expand Down
2 changes: 2 additions & 0 deletions caseworker/advice/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"Refuse": "refused",
"Conflicting": "given conflicting advice",
"No Licence Required": "no licence required",
"F680": "F680 Approval",
}


TEAM_DECISION_APPROVED = "has approved"
TEAM_DECISION_APPROVED_F680 = "has approved this F680 application"
TEAM_DECISION_APPROVED_REFUSED = "has approved and refused"
TEAM_DECISION_PROVISO = "has approved with licence conditions"
TEAM_DECISION_REFUSED = "has refused"
Expand Down
10 changes: 8 additions & 2 deletions caseworker/advice/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def filter_current_user_advice(all_advice, user_id):
advice
for advice in all_advice
if advice["level"] == constants.AdviceLevel.USER
and advice["type"]["key"] in ["approve", "proviso", "refuse"]
and advice["type"]["key"] in ["approve", "proviso", "refuse", "f680"]
and (advice["user"]["id"] == user_id)
]

Expand Down Expand Up @@ -343,9 +343,15 @@ def get_advice_subjects(case, countries=None):


def post_approval_advice(request, case, data, level="user-advice"):
advice_type = "approve"
if data["proviso"]:
advice_type = "proviso"
elif case.sub_type == "f680_clearance":
advice_type = "f680"

json = [
{
"type": "proviso" if data.get("proviso", False) else "approve",
"type": advice_type,
"text": data["approval_reasons"],
"proviso": data.get("proviso", ""),
"note": data.get("instructions_to_exporter", ""),
Expand Down
2 changes: 2 additions & 0 deletions caseworker/advice/templatetags/advice_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,7 @@ def _add_team_decisions(grouped_advice):
team_advice["decision"] = constants.TEAM_DECISION_PROVISO
elif decisions == {"Refuse"}:
team_advice["decision"] = constants.TEAM_DECISION_REFUSED
elif decisions == {"F680"}:
team_advice["decision"] = constants.TEAM_DECISION_APPROVED_F680
else:
team_advice["decision"] = constants.TEAM_DECISION_APPROVED_REFUSED
6 changes: 5 additions & 1 deletion caseworker/advice/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def get_context(self, **kwargs):
"security_approvals_classified_display": self.security_approvals_classified_display,
"assessed_trigger_list_goods": self.assessed_trigger_list_goods,
"unassessed_trigger_list_goods": self.unassessed_trigger_list_goods,
"tabs": self.get_standard_application_tabs(),
"tabs": self.get_tabs_by_case_type(self.case.sub_type),
"current_tab": "cases:advice_view",
**services.get_advice_tab_context(
self.case,
Expand Down Expand Up @@ -687,6 +687,10 @@ def get_context(self, **kwargs):
lu_countersign_required = False
finalise_case = False

# Hack to ensure that F680 cases can be finalised by MOD-ECJU
if user_team_alias == services.MOD_ECJU_TEAM and self.case.case_type["reference"]["key"] == "f680":
finalise_case = True

if user_team_alias == services.LICENSING_UNIT_TEAM:
rejected_lu_countersignature = self.rejected_countersign_advice()
lu_countersign_required = self.get_lu_countersign_required(rejected_lu_countersignature)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions caseworker/cases/forms/finalise_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ def approve_licence_form(queue_pk, case_id, is_open_licence, editable_duration,
)


def deny_licence_form(queue_pk, case_id, is_open_licence, nlr):
if nlr:
def deny_licence_form(queue_pk, case_id, is_open_licence, nlr, is_case_f680):
# This F680 approval is a hack to show the corrent information to the user when using the bespoke F680 approval advice type
# as F680 is not a licence unsure as to to whether deny licence is the correct path for this eventually
if is_case_f680:
description = "You'll be approving this F680 application"
elif nlr:
description = "You'll be informing the exporter that no licence is required"
else:
description = "You'll be denying the case"
Expand Down
2 changes: 2 additions & 0 deletions caseworker/cases/views/advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def get(self, request, *args, **kwargs):
approve = False
all_nlr = False
is_case_open = case_type == CaseType.OPEN.value
is_case_f680 = case_type == "f680_clearance"

if is_case_open:
approve = get_open_licence_decision(request, str(kwargs["pk"])) == "approve"
Expand Down Expand Up @@ -203,6 +204,7 @@ def get(self, request, *args, **kwargs):
case_id,
is_case_open,
all_nlr,
is_case_f680,
),
)

Expand Down
30 changes: 27 additions & 3 deletions caseworker/cases/views/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,27 @@ def get_standard_application_tabs(self):

return tabs

def get_f680_application_tabs(self):
tabs = [
Tabs.QUICK_SUMMARY,
Tabs.DETAILS,
Tabs.ECJU_QUERIES,
Tabs.DOCUMENTS,
]
tabs.append(self.get_notes_and_timelines_tab())
tabs.append(self.get_advice_tab())

return tabs

def get_tabs_by_case_type(self, case_type):
# TODO: this is pretty naff - the problem is that the place we choose tabs based on case type
# is over in cases.helpers.case:CaseView. We need to make this more DRY.
tabs_by_case_type = {
"f680_clearance": self.get_f680_application_tabs(),
"standard": self.get_standard_application_tabs(),
}
return tabs_by_case_type[case_type]

def get_advice_tab(self):
data, _ = get_gov_user(self.request, str(self.request.session["lite_api_user_id"]))
return Tab(
Expand Down Expand Up @@ -283,15 +304,18 @@ def get_gifting_clearance_application(self):
self.additional_context = self.get_advice_additional_context()

def get_f680_clearance_application(self):
self.tabs = self.get_tabs()
self.tabs.insert(1, Tabs.LICENCES)
self.tabs.append(Tabs.ADVICE)
self.tabs = self.get_f680_application_tabs()
self.slices = [
Slices.GOODS,
Slices.DESTINATIONS,
conditional(self.case.data["denial_matches"], Slices.DENIAL_MATCHES),
conditional(self.case.data["sanction_matches"], Slices.SANCTION_MATCHES),
conditional(self.case.data["end_user"], Slices.END_USER_DOCUMENTS),
Slices.LOCATIONS,
Slices.F680_DETAILS,
Slices.END_USE_DETAILS,
Slices.SUPPORTING_DOCUMENTS,
Slices.FREEDOM_OF_INFORMATION,
]
self.additional_context = self.get_advice_additional_context()

Expand Down
10 changes: 10 additions & 0 deletions caseworker/queues/views/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ class CasesFiltersForm(forms.Form):
widget=forms.TextInput(attrs={"id": "case_reference"}),
required=False,
)
case_type = forms.ChoiceField(
label="Case type",
choices=(
("", ""),
("siel", "SIEL"),
("f680", "F680"),
),
required=False,
)
export_type = forms.ChoiceField(
label="Permanent or temporary",
choices=(
Expand Down Expand Up @@ -216,6 +225,7 @@ def __init__(self, queue, filters_data, all_flags, all_cles, all_regimes, countr

case_filters = [
"case_reference",
"case_type",
"status",
"sub_status",
Field("case_officer", css_class="single-select-filter"),
Expand Down
139 changes: 60 additions & 79 deletions caseworker/templates/case/slices/f680-details.html
Original file line number Diff line number Diff line change
@@ -1,79 +1,60 @@
{% load humanize %}

<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th class="govuk-table__header" scope="col">#</th>
<th class="govuk-table__header" scope="col">Question</th>
<th class="govuk-table__header" scope="col">Answer</th>
</tr>
</thead>
<tbody class="govuk-table__body">
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">1.</td>
<th class="govuk-table__header" scope="row">Has electronic warfare requirement</th>
<td class="govuk-table__cell">{{ case.data.electronic_warfare_requirement|friendly_boolean }}</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">2.</td>
<th class="govuk-table__header" scope="row">Is expedited</th>
<td class="govuk-table__cell">
<p class="govuk-body govuk-!-margin-0">{{ case.data.expedited|friendly_boolean }}</p>
{% if case.data.expedited_date %}
<p class="govuk-hint govuk-!-margin-top-1 govuk-!-margin-bottom-0">{{ case.data.expedited_date }}</p>
{% endif %}
</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">3.</td>
<th class="govuk-table__header" scope="row">Has foreign technology</th>
<td class="govuk-table__cell">
<p class="govuk-body govuk-!-margin-0">{{ case.data.foreign_technology|friendly_boolean }}</p>
{% if case.data.foreign_technology_description %}
<p class="govuk-hint govuk-!-margin-top-1 govuk-!-margin-bottom-0">
{{ case.data.foreign_technology_description }}
</p>
{% endif %}
</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">4.</td>
<th class="govuk-table__header" scope="row">Is locally manufactured</th>
<td class="govuk-table__cell">
<p class="govuk-body govuk-!-margin-0">{{ case.data.locally_manufactured|friendly_boolean }}</p>
{% if case.data.locally_manufactured_description %}
<p class="govuk-hint govuk-!-margin-top-1 govuk-!-margin-bottom-0" data-max-length="200">
{{ case.data.locally_manufactured_description }}
</p>
{% endif %}
</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">5.</td>
<th class="govuk-table__header" scope="row">MTCR type</th>
<td class="govuk-table__cell">{{ case.data.mtcr_type.value }}</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">6.</td>
<th class="govuk-table__header" scope="row">Has UK service equipment</th>
<td class="govuk-table__cell">{{ case.data.uk_service_equipment|friendly_boolean }}</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">7.</td>
<th class="govuk-table__header" scope="row">UK service equipment</th>
<td class="govuk-table__cell">
<p class="govuk-body govuk-!-margin-0">{{ case.data.uk_service_equipment_type.value }}</p>
{% if case.data.uk_service_equipment_description %}
<p class="govuk-hint govuk-!-margin-top-1 govuk-!-margin-bottom-0" data-max-length="200">
{{ case.data.uk_service_equipment_description }}
</p>
{% endif %}
</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell govuk-table__cell--line-number">8.</td>
<th class="govuk-table__header" scope="row">Prospect value</th>
<td class="govuk-table__cell">£{{ case.data.prospect_value|intcomma }}</td>
</tr>
</tbody>
</table>
<dl class="govuk-summary-list">
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Select the types of clearance you need</dt>
<dd class="govuk-summary-list__value">
{% for item in case.data.clearances %}
{% if not forloop.last %}
{{ item.value }},
{% else %}
{{ item.value }}
{% endif %}
{% endfor %}
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Do you need the F680 clearance in less than 30 days due to exceptional circumstances?</dt>
<dd class="govuk-summary-list__value">{{ case.data.exceptional_circumstances|friendly_boolean }}</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Is there any foreign technology or information involved in the release?</dt>
<dd class="govuk-summary-list__value">{{ case.data.foreign_technology_information|friendly_boolean }}</dd>
</div>
{% if case.data.foreign_technology_information %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Foreign technology details</dt>
<dd class="govuk-summary-list__value">{{ case.data.foreign_technology_information_details }}</dd>
</div>
{% endif %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Is local assembly or manufacture required?</dt>
<dd class="govuk-summary-list__value">{{ case.data.is_local_assembly_manufacture|friendly_boolean }}</dd>
</div>
{% if case.data.is_local_assembly_manufacture %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Local assembly manufacture details</dt>
<dd class="govuk-summary-list__value">{{ case.data.is_local_assembly_manufacture_details }}</dd>
</div>
{% endif %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Do you believe the products are rated under the Missile Technology Control Regime (MTCR)</dt>
<dd class="govuk-summary-list__value">{{ case.data.product_mtcr_rating_type|sentence_case }}</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Is there is a requirement to release UK MOD owned EW data or information in support of this export</dt>
<dd class="govuk-summary-list__value">{{ case.data.ew_data|friendly_boolean }}</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Is the equipment or a version of it due to enter service with the UK armed forces?</dt>
<dd class="govuk-summary-list__value">{{ case.data.armed_forces_usage|friendly_boolean }}</dd>
</div>
{% if case.data.armed_forces_usage %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Armed forces usage details</dt>
<dd class="govuk-summary-list__value">{{ case.data.armed_forces_usage_details }}</dd>
</div>
{% endif %}
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">Select how the product is funded</dt>
<dd class="govuk-summary-list__value">{{ case.data.product_funding }}</dd>
</div>
</dl>
31 changes: 31 additions & 0 deletions core/summaries/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,37 @@ def document_formatter(document, url, link_text=None):
}


F680_GOOD_DETAILS_FORMATTERS = {
"is-good-controlled": key_value_formatter,
"control-list-entries": comma_separated_list(itemgetter("rating")),
"is-pv-graded": mapping_formatter(
{
"yes": "Yes",
"no": "No",
}
),
}

F680_GOOD_DETAILS_ON_APPLICATION_FORMATTERS = {
"number-of-items": integer,
"total-value": money_formatter,
}

F680_GOOD_DETAILS_LABELS = {
"name": "Give the product a descriptive name",
"is-good-controlled": "Do you know the product's control list entry?",
"control-list-entries": "Enter the control list entry",
"is-pv-graded": "Does the product have a government security grading or classification?",
"product-description": "Describe the product and what it is designed to do",
}


F680_GOOD_DETAILS_ON_APPLICATION_LABELS = {
"number-of-items": "Number of items",
"total-value": "Total value",
}


def add_edit_links(
summary,
edit_links,
Expand Down
21 changes: 21 additions & 0 deletions core/summaries/reducers.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,24 @@ def technology_on_application_reducer(good_on_application):
)
summary += is_onward_exported_reducer(good_on_application)
return summary


def f680_good_details_reducer(good):
summary = (
(
"name",
good["name"],
),
)
summary += is_good_controlled_reducer(good)
summary += is_pv_graded_reducer(good)

return summary


def f680_good_details_on_application_reducer(good_on_application):
summary = (
("number-of-items", good_on_application["quantity"]),
("total-value", Decimal(good_on_application["value"])),
)
return summary
Loading
Loading