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-5948: Add option to make approval recommendation for F680 application #2379

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Open
181 changes: 181 additions & 0 deletions caseworker/f680/recommendation/forms/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from django import forms

from crispy_forms_gds.choices import Choice
from crispy_forms_gds.helper import FormHelper
from crispy_forms_gds.layout import Submit

from core.common.forms import BaseForm
from core.forms.layouts import (
ConditionalCheckboxes,
ConditionalCheckboxesQuestion,
RadioTextArea,
)


Comment on lines +13 to +14
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These forms are created separately for F680 with the idea they can be different following user feedback once they see initial version.

class SelectRecommendationTypeForm(forms.Form):
CHOICES = [
("approve_all", "Approve all"),
]

recommendation = forms.ChoiceField(
choices=CHOICES,
widget=forms.RadioSelect,
label="",
error_messages={"required": "Select if you approve all or refuse all"},
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Submit("submit", "Continue"))


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

for result in picklist_data["results"]:
key = "_".join(result.get("name").lower().split())
choice = Choice(key, result.get("name"))
if result == picklist_data["results"][-1]:
choice = Choice(key, result.get("name"), divider="or")
reasons_choices.append(choice)
reasons_text[key] = result.get("text")
picklist_choices = len(reasons_choices) > 0
if include_other and picklist_choices:
reasons_text["other"] = ""
reasons_choices.append(Choice("other", "Other"))
return reasons_choices, reasons_text


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", "name": "approval_reasons"}),
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):
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 SimpleLicenceConditionsForm(BaseForm):
class Layout:
TITLE = "Add licence conditions (optional)"

proviso = forms.CharField(
widget=forms.Textarea(attrs={"rows": 7}),
label="Licence condition",
required=False,
)

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


class PicklistLicenceConditionsForm(PicklistAdviceForm, BaseForm):
class Layout:
TITLE = "Add licence conditions (optional)"

proviso_checkboxes = forms.MultipleChoiceField(
label="",
required=False,
widget=forms.CheckboxSelectMultiple,
choices=(),
)

def clean(self):
cleaned_data = super().clean()
# only return proviso (text) for selected checkboxes, nothing else matters, join by 2 newlines
return {
"proviso": "\n\n--------\n".join(
[cleaned_data[selected] for selected in cleaned_data["proviso_checkboxes"]]
)
}

def __init__(self, *args, **kwargs):
proviso = kwargs.pop("proviso")

proviso_choices, proviso_text = self._picklist_to_choices(proviso)

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}),
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 = "Add instructions to the exporter, or a reporting footnote (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}),
label="",
required=False,
)

def __init__(self, *args, **kwargs):
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),
)
42 changes: 42 additions & 0 deletions caseworker/f680/recommendation/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.utils.functional import cached_property

from caseworker.cases.services import get_case
from caseworker.users.services import get_gov_user


class CaseContextMixin:
"""Most advice views need a reference to the associated
Case object. This mixin, injects a reference to the Case
in the context.
"""

@property
def case_id(self):
return str(self.kwargs["pk"])

@cached_property
def case(self):
return get_case(self.request, self.case_id)

@property
def caseworker_id(self):
return str(self.request.session["lite_api_user_id"])

@property
def caseworker(self):
data, _ = get_gov_user(self.request, self.caseworker_id)
return data["user"]

def get_context(self, **kwargs):
return {}

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

return {
**context,
**self.get_context(case=self.case),
"case": self.case,
"queue_pk": self.kwargs["queue_pk"],
"caseworker": self.caseworker,
}
54 changes: 54 additions & 0 deletions caseworker/f680/recommendation/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from collections import defaultdict

from caseworker.advice import constants
from core import client


def filter_current_user_recommendation(all_recommendation, user_id):
return [
recommendation
for recommendation in all_recommendation
if recommendation["level"] == constants.AdviceLevel.USER
and recommendation["type"]["key"] in ["approve", "proviso", "refuse"]
and (recommendation["user"]["id"] == user_id)
]


def filter_recommendation_by_level(all_recommendation, recommendation_levels):
return [recommendation for recommendation in all_recommendation if recommendation["level"] in recommendation_levels]


def filter_recommendation_by_team(all_recommendation, team_alias):
return [recommendation for recommendation in all_recommendation if recommendation["team"]["alias"] == team_alias]


def group_recommendation_by_user(recommendation):
result = defaultdict(list)
for item in recommendation:
result[item["user"]["id"]].append(item)
return result


def get_current_user_recommendation(recommendation, caseworker, team_alias):
user_level_recommendation = filter_recommendation_by_level(recommendation, ["user"])
user_recommendation = filter_current_user_recommendation(user_level_recommendation, caseworker)
user_recommendation = filter_recommendation_by_team(user_recommendation, team_alias)
grouped_user_recommendation = group_recommendation_by_user(user_recommendation)
return grouped_user_recommendation


def post_approval_recommendation(request, case, data, level="user-advice"):
json = [
Comment on lines +40 to +41
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here, some of these are same as advice function but unlike SIELs we are only going to create one Advice instance for F680 so some of these helper functions will be different.

{
"type": "proviso" if data.get("proviso", False) else "approve",
"text": data["approval_reasons"],
"proviso": data.get("proviso", ""),
"note": data.get("instructions_to_exporter", ""),
"footnote_required": True if data.get("footnote_details") else False,
"footnote": data.get("footnote_details", ""),
"denial_reasons": [],
}
]
response = client.post(request, f"/cases/{case['id']}/{level}/", json)
response.raise_for_status()
return response.json(), response.status_code
Loading