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

Ltd 5653 desnz multiple licence conditions #2243

Merged
merged 8 commits into from
Dec 3, 2024
15 changes: 15 additions & 0 deletions caseworker/advice/conditionals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from caseworker.advice import services


def form_add_licence_conditions(step_name):
Tllew marked this conversation as resolved.
Show resolved Hide resolved
"""Returns the boolean value for add_licence_conditions from the approval form"""

def _get_form_field_boolean(wizard):
cleaned_data = wizard.get_cleaned_data_for_step(step_name)
return cleaned_data.get("add_licence_conditions", False)

return _get_form_field_boolean


def is_desnz_team(wizard):
return wizard.caseworker["team"]["alias"] in services.DESNZ_TEAMS
6 changes: 6 additions & 0 deletions caseworker/advice/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ class AdviceType:
PROVISO = "proviso"
REFUSE = "refuse"
NO_LICENCE_REQUIRED = "no_licence_required"


class AdviceSteps:
RECOMMEND_APPROVAL = "recommend_approval"
LICENCE_CONDITIONS = "licence_conditions"
LICENCE_FOOTNOTES = "licence_footnotes"
167 changes: 166 additions & 1 deletion caseworker/advice/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@
from django.forms.formsets import formset_factory
from django.utils.html import format_html

from core.common.forms import BaseForm
from crispy_forms_gds.helper import FormHelper
from crispy_forms_gds.layout import Field, Layout, Submit
from crispy_forms_gds.choices import Choice

from core.forms.layouts import ConditionalRadios, ConditionalRadiosQuestion, ExpandingFieldset, RadioTextArea
from core.forms.layouts import (
ConditionalCheckboxes,
ConditionalCheckboxesQuestion,
ConditionalRadios,
ConditionalRadiosQuestion,
ExpandingFieldset,
RadioTextArea,
)
from core.forms.utils import coerce_str_to_bool
from caseworker.tau.summaries import get_good_on_application_tau_summary
from caseworker.tau.widgets import GoodsMultipleSelect
Expand Down Expand Up @@ -484,3 +492,160 @@ def __init__(
self.application_pk = application_pk
self.is_user_rfd = is_user_rfd
self.organisation_documents = organisation_documents


class RecommendAnApprovalForm(PicklistAdviceForm, BaseForm):
class Layout:
TITLE = "Recommend an approval"

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=(),
)
add_licence_conditions = forms.BooleanField(
label="Add licence conditions, instructions to exporter or footnotes (optional)",
required=False,
)

def __init__(self, *args, **kwargs):
del kwargs["proviso"]
del kwargs["footnote_details"]
approval_reason = kwargs.pop("approval_reason")
# this follows the same pattern as denial_reasons.
approval_choices, approval_text = self._picklist_to_choices(approval_reason)
self.approval_text = approval_text
super().__init__(*args, **kwargs)

self.fields["approval_radios"].choices = approval_choices

def get_layout_fields(self):
return (
RadioTextArea("approval_radios", "approval_reasons", self.approval_text),
"add_licence_conditions",
)


class PicklistApprovalAdviceEditForm(BaseForm):
class Layout:
TITLE = "Add licence conditions, instructions to exporter or footnotes (optional)"

proviso = forms.CharField(
widget=forms.Textarea(attrs={"rows": 30, "class": "govuk-!-margin-top-4"}),
label="",
required=False,
)

def __init__(self, *args, **kwargs):
del kwargs["approval_reason"]
del kwargs["proviso"]
del kwargs["footnote_details"]
super().__init__(*args, **kwargs)

def get_layout_fields(self):
return ("proviso",)


class LicenceConditionsForm(PicklistAdviceForm, BaseForm):
class Layout:
TITLE = "Add licence conditions, instructions to exporter or footnotes (optional)"

proviso = forms.CharField(
widget=forms.Textarea(attrs={"rows": 7, "class": "govuk-!-margin-top-4"}),
label="",
required=False,
)

approval_radios = forms.ChoiceField(
label="What is your reason for approving?",
required=False,
widget=forms.RadioSelect,
choices=(),
)
proviso_checkboxes = forms.MultipleChoiceField(
label="Add a licence condition (optional)",
required=False,
widget=forms.CheckboxSelectMultiple,
choices=(),
)

def clean(self):
cleaned_data = super().clean()
# only return proviso (text) for selected radios, nothing else matters, join by 2 newlines
currycoder marked this conversation as resolved.
Show resolved Hide resolved
return {"proviso": "\r\n\r\n".join([cleaned_data[selected] for selected in cleaned_data["proviso_checkboxes"]])}

def __init__(self, *args, **kwargs):
del kwargs["approval_reason"]
del kwargs["footnote_details"]

proviso = kwargs.pop("proviso")

proviso_choices, proviso_text = self._picklist_to_choices(proviso)
self.proviso_text = proviso_text

self.conditional_checkbox_choices = (
ConditionalCheckboxesQuestion(choices.label, choices.value) for choices in proviso_choices
)

super().__init__(*args, **kwargs)

self.fields["proviso_checkboxes"].choices = proviso_choices
for choices in proviso_choices:
self.fields[choices.value] = forms.CharField(
widget=forms.Textarea(attrs={"rows": 3, "class": "govuk-!-margin-top-4"}),
label="Description",
required=False,
initial=proviso_text[choices.value],
)

def get_layout_fields(self):

return (ConditionalCheckboxes("proviso_checkboxes", *self.conditional_checkbox_choices),)


class FootnotesApprovalAdviceForm(PicklistAdviceForm, BaseForm):
class Layout:
TITLE = "Instructions for the exporter (optional)"

instructions_to_exporter = forms.CharField(
widget=forms.Textarea(attrs={"rows": "3"}),
label="Add any instructions for the exporter (optional)",
help_text="These may be added to the licence cover letter, subject to review by the Licensing Unit.",
required=False,
)

footnote_details_radios = forms.ChoiceField(
label="Add a reporting footnote (optional)",
required=False,
widget=forms.RadioSelect,
choices=(),
)
footnote_details = forms.CharField(
widget=forms.Textarea(attrs={"rows": 3, "class": "govuk-!-margin-top-4"}),
label="",
required=False,
)

def __init__(self, *args, **kwargs):
del kwargs["approval_reason"]
del kwargs["proviso"]

footnote_details = kwargs.pop("footnote_details")
footnote_details_choices, footnote_text = self._picklist_to_choices(footnote_details)
self.footnote_text = footnote_text

super().__init__(*args, **kwargs)

self.fields["footnote_details_radios"].choices = footnote_details_choices

def get_layout_fields(self):
return (
"instructions_to_exporter",
RadioTextArea("footnote_details_radios", "footnote_details", self.footnote_text),
)
14 changes: 14 additions & 0 deletions caseworker/advice/payloads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from caseworker.advice.constants import AdviceSteps
from core.wizard.payloads import MergingPayloadBuilder


def get_cleaned_data(form):
return form.cleaned_data


class GiveApprovalAdvicePayloadBuilder(MergingPayloadBuilder):
payload_dict = {
AdviceSteps.RECOMMEND_APPROVAL: get_cleaned_data,
AdviceSteps.LICENCE_CONDITIONS: get_cleaned_data,
AdviceSteps.LICENCE_FOOTNOTES: get_cleaned_data,
}
23 changes: 23 additions & 0 deletions caseworker/advice/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ def can_desnz_make_recommendation(user, case, queue_alias):
return True


def can_desnz_make_edit(team):
return team in services.DESNZ_TEAMS


def case_has_approval_advice(advice):
if advice:
return advice[0]["type"]["key"] in ["proviso", "approve"]
return False


@rules.predicate
def can_user_make_desnz_edit(request, case):
try:
user = request.lite_user
except AttributeError:
return False

team = user["team"]["alias"]
advice = services.filter_current_user_advice(case.advice, user["id"])
return can_desnz_make_edit(team) and case_has_approval_advice(advice)


@rules.predicate
def can_user_make_recommendation(request, case):
try:
Expand Down Expand Up @@ -62,3 +84,4 @@ def can_user_make_recommendation(request, case):

rules.add_rule("can_user_make_recommendation", is_user_allocated & can_user_make_recommendation)
rules.add_rule("can_user_allocate_and_approve", can_user_make_recommendation)
rules.add_rule("can_user_make_desnz_edit", can_user_make_desnz_edit)
10 changes: 5 additions & 5 deletions caseworker/advice/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,12 @@ def get_advice_subjects(case, countries=None):
def post_approval_advice(request, case, data, level="user-advice"):
json = [
{
"type": "proviso" if data["proviso"] else "approve",
"type": "proviso" if data.get("proviso", False) else "approve",
"text": data["approval_reasons"],
"proviso": data["proviso"],
"note": data["instructions_to_exporter"],
"footnote_required": True if data["footnote_details"] else False,
"footnote": data["footnote_details"],
"proviso": data.get("proviso", ""),
"note": data.get("instructions_to_exporter", ""),
"footnote_required": True if data.get("footnote_details") else False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any ideas what this is for? I realise this is from old code but it seems a bit nuts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just for formatting, it needs gets because the keys now won't exist if the steps have been skipped where previously they were present but empty strings.

in the next iteration I'd like to update this section, the refuse and the getting of advice so that we don't advice = my_advice[0] every time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the field in general - footnote and footnote_required seems odd

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@currycoder @Tllew this is because these fields were already existing in the data model and Advice updated to work with them.
Footnote is an optional text box, initially UCD wanted to include a checkbox and when checked it reveals the textbox but that is too many clicks so it was decided to show the textbox by default and if there is any data then we set footnote_required accordingly.

"footnote": data.get("footnote_details", ""),
subject_name: subject_id,
"denial_reasons": [],
}
Expand Down
23 changes: 14 additions & 9 deletions caseworker/advice/templates/advice/view_my_advice.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends 'layouts/case.html' %}
{% load crispy_forms_tags advice_tags %}
{% load static advice_tags %}
Comment on lines 2 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

advice_tags is being loaded twice and I can't see where static is being used.

{% load rules %}
{% block header_tabs %}
<div id="tab-bar" class="app-case-tab-bar">
Expand Down Expand Up @@ -33,16 +34,20 @@ <h2 class="govuk-error-summary__title" id="error-summary-title">
<div class="govuk-grid-column-three-quarters">
<h1 class="govuk-heading-xl">View recommendation</h1>
{% if my_advice %}
{% if buttons.edit_recommendation %}
<a role="button" draggable="false" class="govuk-button govuk-button--secondary" href="{% url 'cases:edit_advice' queue_pk case.id %}">
Edit recommendation
</a>
{% endif %}
{% if buttons.clear_recommendation %}
<a role="button" draggable="false" class="govuk-button govuk-button--secondary" href="{% url 'cases:delete_advice' queue_pk case.id %}">
Clear recommendation
</a>
{% test_rule 'can_user_make_desnz_edit' request case as can_user_make_desnz_edit %}

{% if buttons.edit_recommendation %}
{% if can_user_make_desnz_edit %}
<a role="button" draggable="false" class="govuk-button govuk-button--secondary" href="{% url 'cases:edit_advice' queue_pk case.id %}">Edit recommendation</a>
{% else %}
<a role="button" draggable="false" class="govuk-button govuk-button--secondary" href="{% url 'cases:edit_advice_legacy' queue_pk case.id %}">Edit recommendation</a>
{% endif %}
{% endif %}
{% if buttons.clear_recommendation %}
<a role="button" draggable="false" class="govuk-button govuk-button--secondary" href="{% url 'cases:delete_advice' queue_pk case.id %}">
Clear recommendation
</a>
{% endif %}
{% for advice in my_advice %}
{% include "advice/advice_details.html" %}
{% endfor %}
Expand Down
2 changes: 2 additions & 0 deletions caseworker/advice/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
path("", views.AdviceView.as_view(), name="advice_view"),
path("case-details/", views.CaseDetailView.as_view(), name="case_details"),
path("select-advice/", views.SelectAdviceView.as_view(), name="select_advice"),
path("approve-all-legacy/", views.GiveApprovalAdviceViewLegacy.as_view(), name="approve_all_legacy"),
path("approve-all/", views.GiveApprovalAdviceView.as_view(), name="approve_all"),
path("refuse-all/", views.RefusalAdviceView.as_view(), name="refuse_all"),
path("view-my-advice/", views.AdviceDetailView.as_view(), name="view_my_advice"),
path("edit-advice-legacy/", views.EditAdviceViewLegacy.as_view(), name="edit_advice_legacy"),
path("edit-advice/", views.EditAdviceView.as_view(), name="edit_advice"),
path("delete-advice/", views.DeleteAdviceView.as_view(), name="delete_advice"),
path("countersign/", views.CountersignAdviceView.as_view(), name="countersign_advice_view"),
Expand Down
Loading
Loading