-
Notifications
You must be signed in to change notification settings - Fork 1
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
saruniitr
wants to merge
11
commits into
dev
Choose a base branch
from
LTD-5948-F680-select-recommendation-type
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
02ad2f3
Add view that provides an option to make a recommendation
saruniitr be8de16
Add mixin for Case context in F680 recommendation views
saruniitr 765f401
Add view to select the type of recommendation for F80 applications
saruniitr a9363e8
Add forms to give approval recommendation for F680
saruniitr 3484877
Add view to give approval recommendation for F680
saruniitr 7d3576d
Add view to display recommendation given by user
saruniitr 45dfcc5
Move common fixtures to a conftest file
saruniitr 7a5e4e6
Update view names to use recommendation instead of advice
saruniitr 66d064b
Add unit tests for F680 approval recommendation
saruniitr 29a18fd
Add test to address coverage
saruniitr f89856a
Merge branch 'dev' into LTD-5948-F680-select-recommendation-type
saruniitr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
|
||
|
||
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), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
{ | ||
"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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.