Skip to content

Commit

Permalink
Merge pull request #2264 from uktrade/LTD-5708-pre-fill-lu-licence-co…
Browse files Browse the repository at this point in the history
…nditions

[LTD-5708] Pre-fill LU licence conditions
  • Loading branch information
currycoder authored Dec 12, 2024
2 parents d3fb86f + 4554194 commit d69a84f
Show file tree
Hide file tree
Showing 19 changed files with 646 additions and 423 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ django-log-formatter-asim = "~=0.0.5"
dbt-copilot-python = "~=0.2.1"
cryptography = "~=43.0.1"
django-cacheops = "~=7.0.2"
ordered-set = "==4.1.0"

[requires]
python_version = "3.9"
646 changes: 338 additions & 308 deletions Pipfile.lock

Large diffs are not rendered by default.

43 changes: 37 additions & 6 deletions caseworker/advice/forms/consolidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from django.utils.html import format_html

from caseworker.advice.forms.approval import SelectAdviceForm
from caseworker.advice.forms.forms import GiveApprovalAdviceForm
from caseworker.advice.forms.forms import PicklistAdviceForm
from crispy_forms_gds.helper import FormHelper
from crispy_forms_gds.layout import Layout, Submit

from core.forms.layouts import (
RadioTextArea,
CannedSnippetsTextArea,
)


Expand All @@ -30,16 +31,46 @@ def __init__(self, team_name, *args, **kwargs):
self.fields["recommendation"].label = f"{recommendation_label}?"


class ConsolidateApprovalForm(GiveApprovalAdviceForm):
"""Approval form minus some fields."""
class ConsolidateApprovalForm(PicklistAdviceForm):

def __init__(self, team_alias, *args, **kwargs):
super().__init__(*args, **kwargs)
approval_reasons = forms.CharField(
widget=forms.Textarea(attrs={"rows": 7, "class": "govuk-!-margin-top-4"}),
label="",
error_messages={"required": "Enter a reason for approving"},
)
approval_radios = forms.ChoiceField(
label="What is your reason for approving?",
required=False,
widget=forms.RadioSelect,
choices=(),
)
proviso = forms.CharField(
widget=forms.Textarea(attrs={"rows": 7, "class": "govuk-!-margin-top-4"}),
label="",
required=False,
)
proviso_snippets = forms.ChoiceField(
label="Add a licence condition (optional)",
required=False,
widget=forms.CheckboxSelectMultiple,
choices=(),
)

def __init__(self, approval_reason, proviso, **kwargs):
super().__init__(**kwargs)

approval_choices, approval_text = self._picklist_to_choices(approval_reason, include_other=False)
self.approval_text = approval_text
self.fields["approval_radios"].choices = approval_choices

proviso_choices, proviso_text = self._picklist_to_choices(proviso, include_other=False)
self.proviso_text = proviso_text
self.fields["proviso_snippets"].choices = proviso_choices

self.helper = FormHelper()
self.helper.layout = Layout(
RadioTextArea("approval_radios", "approval_reasons", self.approval_text),
RadioTextArea("proviso_radios", "proviso", self.proviso_text),
CannedSnippetsTextArea("proviso_snippets", "proviso", self.proviso_text, add_label="Add licence condition"),
Submit("submit", "Submit recommendation"),
)

Expand Down
5 changes: 3 additions & 2 deletions caseworker/advice/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def get_approval_advice_form_factory(advice, approval_reason, proviso, footnote_


class PicklistAdviceForm(forms.Form):
def _picklist_to_choices(self, picklist_data):
def _picklist_to_choices(self, picklist_data, include_other=True):
reasons_choices = []
reasons_text = {"other": ""}

Expand All @@ -40,7 +40,8 @@ def _picklist_to_choices(self, picklist_data):
choice = Choice(key, result.get("name"), divider="or")
reasons_choices.append(choice)
reasons_text[key] = result.get("text")
reasons_choices.append(Choice("other", "Other"))
if include_other:
reasons_choices.append(Choice("other", "Other"))
return reasons_choices, reasons_text


Expand Down
2 changes: 1 addition & 1 deletion caseworker/advice/templates/advice/review_consolidate.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ <h2 class="govuk-heading-xl">{{ subtitle|default:"Review and combine" }}</h2>
{% include "advice/advice_details.html" with team=True %}
<br>
{% endfor %}
<div class="govuk-!-width-two-thirds advice-refusal-labels">
<div class="advice-refusal-labels">
{% crispy form %}
</div>
</div>
Expand Down
35 changes: 22 additions & 13 deletions caseworker/advice/views/consolidate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.views.generic import FormView
from django.shortcuts import redirect
from django.urls import reverse
from ordered_set import OrderedSet

from requests.exceptions import HTTPError

Expand All @@ -23,13 +24,18 @@ class BaseConsolidationView(LoginRequiredMixin, CaseContextMixin, FormView):
def get_title(self):
return f"Review and combine case recommendation - {self.case.reference_code} - {self.case.organisation['name']}"

def get_context(self, **kwargs):
context = super().get_context()
team_alias = (
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.team_alias = (
self.caseworker["team"]["alias"] if self.caseworker["team"]["alias"] else self.caseworker["team"]["id"]
)
advice_to_consolidate = services.get_advice_to_consolidate(self.case.advice, team_alias)
context["advice_to_consolidate"] = list(advice_to_consolidate.values())
self.advice_to_consolidate = list(
services.get_advice_to_consolidate(self.case.advice, self.team_alias).values()
)

def get_context(self, **kwargs):
context = super().get_context()
context["advice_to_consolidate"] = self.advice_to_consolidate
context["denial_reasons_display"] = self.denial_reasons_display
context["security_approvals_classified_display"] = self.security_approvals_classified_display
context["title"] = self.get_title()
Expand All @@ -46,7 +52,6 @@ class ConsolidateSelectDecisionView(BaseConsolidationView):
form_class = ConsolidateSelectAdviceForm

def dispatch(self, request, *args, **kwargs):
self.team_alias = self.caseworker["team"].get("alias", None)
approve_advice_types = ("approve", "proviso", "no_licence_required")
is_all_advice_approval = all(a["type"]["key"] in approve_advice_types for a in self.case.advice)
if is_all_advice_approval:
Expand Down Expand Up @@ -87,9 +92,16 @@ class ConsolidateApproveView(BaseConsolidationView):
template_name = "advice/review_consolidate.html"
form_class = ConsolidateApprovalForm

def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.team_alias = self.caseworker["team"].get("alias", None)
def collate_all_provisos(self):
"""
Collate all provisos across all team advice in to a single string.
"""
unique_provisos = OrderedSet()
for team_advice in self.advice_to_consolidate:
for advice in team_advice:
if advice["proviso"]:
unique_provisos.add(advice["proviso"])
return "\n\n--------\n".join(unique_provisos)

def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
Expand All @@ -99,10 +111,7 @@ def get_form_kwargs(self):
form_kwargs["proviso"] = get_picklists_list(
self.request, type="proviso", disable_pagination=True, show_deactivated=False
)
form_kwargs["footnote_details"] = get_picklists_list(
self.request, type="footnotes", disable_pagination=True, show_deactivated=False
)
form_kwargs["team_alias"] = self.team_alias
form_kwargs["initial"]["proviso"] = self.collate_all_provisos()
return form_kwargs

def form_valid(self, form):
Expand Down
95 changes: 77 additions & 18 deletions caseworker/advice/views/tests/test_consolidate_advice.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from bs4 import BeautifulSoup
from django.urls import reverse

from core import client
Expand Down Expand Up @@ -61,7 +62,7 @@ def consolidate_view_url(data_queue, data_standard_case):


@pytest.fixture
def advice_data(current_user, admin_team):
def advice_data(current_user):
return {
"consignee": "cd2263b4-a427-4f14-8552-505e1d192bb8", # /PS-IGNORE
"country": None,
Expand All @@ -79,7 +80,7 @@ def advice_data(current_user, admin_team):
"type": {"key": "approve", "value": "Approve"},
"ultimate_end_user": None,
"user": current_user,
"team": admin_team,
"team": {"id": "some-team", "alias": "FCO"},
}


Expand Down Expand Up @@ -344,6 +345,68 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_GET(
)


def test_ConsolidateApproveView_GET_collated_provisos(
authorized_client,
consolidate_approve_url,
mixed_advice,
advice_data,
data_standard_case,
lu_gov_user,
):
"""
Ensure that proviso is pre-filled from collecting provisos across all valid advice.
"""
mixed_advice[0]["proviso"] = "condition 1"
mixed_advice[1]["proviso"] = "condition 2"
extra_advice = {
**advice_data,
"team": {"id": "mod-ecju-team", "alias": "MOD_ECJU"},
"good": "6daad1c3-cf97-4aad-b711-d5c9a9f4586e",
"type": {"key": "approve", "value": "Approve"},
"proviso": "",
}
mixed_advice.append(extra_advice)
data_standard_case["case"]["advice"] = mixed_advice

response = authorized_client.get(consolidate_approve_url, follow=False)
assert response.status_code == 200
assert response.context["form"].initial == {"proviso": "condition 1\n\n--------\ncondition 2"}


def test_ConsolidateApproveView_GET_canned_snippets(
authorized_client,
consolidate_approve_url,
mixed_advice,
advice_data,
data_standard_case,
lu_gov_user,
):
"""
Ensure that the canned snippets proviso component renders as expected.
"""
mixed_advice[0]["proviso"] = "condition 1"
mixed_advice[1]["proviso"] = "condition 2"
extra_advice = {
**advice_data,
"team": {"id": "mod-ecju-team", "alias": "MOD_ECJU"},
"good": "6daad1c3-cf97-4aad-b711-d5c9a9f4586e",
"type": {"key": "approve", "value": "Approve"},
"proviso": "",
}
mixed_advice.append(extra_advice)
data_standard_case["case"]["advice"] = mixed_advice

response = authorized_client.get(consolidate_approve_url, follow=False)
assert response.status_code == 200
soup = BeautifulSoup(response.content, "html.parser")
assert "firearm serial numbers" in soup.find("div", {"id": "div_id_proviso_snippets"}).text
assert soup.find("button", attrs={"data-snippet-key": "firearm_serial_numbers"}).text == "Add licence condition"
assert (
soup.find("script", {"id": "proviso"}).text
== '{"other": "", "firearm_serial_numbers": "Firearm serial numbers text"}'
)


def test_ConsolidateApproveView_POST_bad_input(
authorized_client,
consolidate_approve_url,
Expand Down Expand Up @@ -446,16 +509,14 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
{
"approval_reasons": "yep, go for it",
"proviso": "just consider this",
"instructions_to_exporter": "and this",
"footnote": "some footnote",
},
[
{
"denial_reasons": [],
"end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5",
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -465,7 +526,7 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -474,7 +535,7 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -484,7 +545,7 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"third_party": "95c2d6b7-5cfd-47e8-b3c8-dc76e1ac9747",
Expand All @@ -495,7 +556,7 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
"footnote": "",
"footnote_required": False,
"good": "0bedd1c3-cf97-4aad-b711-d5c9a9f4586e",
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -514,7 +575,7 @@ def mock_post_approval_team_advice(requests_mock, data_standard_case):
),
),
)
def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
def test_ConsolidateApproveView_lu_gov_user_POST_success(
approval_data,
expected_post_data,
authorized_client,
Expand All @@ -532,7 +593,7 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
assert response.status_code == 302
assert response.url == consolidate_view_url
assert len(mock_post_approval_final_advice.request_history) == 1
assert mock_post_approval_team_advice.request_history[0].json() == expected_post_data
assert mock_post_approval_final_advice.request_history[0].json() == expected_post_data


@pytest.mark.parametrize(
Expand Down Expand Up @@ -607,16 +668,14 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
{
"approval_reasons": "yep, go for it",
"proviso": "just consider this",
"instructions_to_exporter": "and this",
"footnote": "some footnote",
},
[
{
"denial_reasons": [],
"end_user": "95d3ea36-6ab9-41ea-a744-7284d17b9cc5",
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -626,7 +685,7 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -635,7 +694,7 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand All @@ -645,7 +704,7 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
"denial_reasons": [],
"footnote": "",
"footnote_required": False,
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"third_party": "95c2d6b7-5cfd-47e8-b3c8-dc76e1ac9747",
Expand All @@ -656,7 +715,7 @@ def test_ConsolidateApproveView_mod_ecju_gov_user_POST_success(
"footnote": "",
"footnote_required": False,
"good": "0bedd1c3-cf97-4aad-b711-d5c9a9f4586e",
"note": "and this",
"note": "",
"proviso": "just consider this",
"text": "yep, go for it",
"type": "proviso",
Expand Down
Loading

0 comments on commit d69a84f

Please sign in to comment.