diff --git a/caseworker/f680/recommendation/forms/forms.py b/caseworker/f680/recommendation/forms/forms.py
new file mode 100644
index 000000000..067ebdba4
--- /dev/null
+++ b/caseworker/f680/recommendation/forms/forms.py
@@ -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),
+ )
diff --git a/caseworker/f680/recommendation/mixins.py b/caseworker/f680/recommendation/mixins.py
new file mode 100644
index 000000000..4c3bb3ee2
--- /dev/null
+++ b/caseworker/f680/recommendation/mixins.py
@@ -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,
+ }
diff --git a/caseworker/f680/recommendation/services.py b/caseworker/f680/recommendation/services.py
new file mode 100644
index 000000000..b2dfbdd84
--- /dev/null
+++ b/caseworker/f680/recommendation/services.py
@@ -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 = [
+ {
+ "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
diff --git a/caseworker/f680/recommendation/views.py b/caseworker/f680/recommendation/views.py
new file mode 100644
index 000000000..1ee9ea340
--- /dev/null
+++ b/caseworker/f680/recommendation/views.py
@@ -0,0 +1,133 @@
+from django.shortcuts import redirect
+from django.urls import reverse
+from django.views.generic import FormView, TemplateView
+from http import HTTPStatus
+
+from core.auth.views import LoginRequiredMixin
+
+from caseworker.advice.constants import AdviceSteps
+from caseworker.advice.conditionals import form_add_licence_conditions
+from caseworker.advice.payloads import GiveApprovalAdvicePayloadBuilder
+from caseworker.advice.picklist_helpers import approval_picklist, footnote_picklist, proviso_picklist
+from caseworker.cases.services import get_case
+from caseworker.cases.helpers.case import CaseworkerMixin
+from caseworker.f680.recommendation.forms.forms import (
+ FootnotesApprovalAdviceForm,
+ PicklistLicenceConditionsForm,
+ RecommendAnApprovalForm,
+ SelectRecommendationTypeForm,
+ SimpleLicenceConditionsForm,
+)
+from caseworker.f680.recommendation.mixins import CaseContextMixin
+from caseworker.f680.recommendation.services import get_current_user_recommendation, post_approval_recommendation
+from caseworker.queues.services import get_queue
+from core.decorators import expect_status
+from core.wizard.conditionals import C
+from core.wizard.views import BaseSessionWizardView
+
+
+class CaseRecommendationView(LoginRequiredMixin, CaseworkerMixin, TemplateView):
+ template_name = "f680/case/recommendation/recommendation.html"
+
+ def setup(self, request, *args, **kwargs):
+ super().setup(request, *args, **kwargs)
+
+ self.case_id = str(kwargs["pk"])
+ self.case = get_case(request, self.case_id)
+ self.queue_id = kwargs["queue_pk"]
+ self.queue = get_queue(request, self.queue_id)
+
+ def get_context_data(self, **kwargs):
+ context_data = super().get_context_data(**kwargs)
+ context_data["case"] = self.case
+ return context_data
+
+
+class MyRecommendationView(LoginRequiredMixin, CaseContextMixin, TemplateView):
+ template_name = "f680/case/recommendation/view_my_recommendation.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ title = f"View recommendation for this case - {self.case.reference_code} - {self.case.organisation['name']}"
+ recommendation = get_current_user_recommendation(
+ self.case.advice, self.caseworker_id, self.caseworker["team"]["alias"]
+ )
+ recommendation = recommendation.get(self.caseworker_id)
+
+ return {
+ **context,
+ "title": title,
+ "recommendation": recommendation[0] if recommendation else None,
+ }
+
+
+class SelectRecommendationTypeView(LoginRequiredMixin, CaseContextMixin, FormView):
+ template_name = "f680/case/recommendation/select_recommendation_type.html"
+ form_class = SelectRecommendationTypeForm
+
+ def get_success_url(self):
+ return reverse("cases:f680:approve_all", kwargs=self.kwargs)
+
+ def form_valid(self, form):
+ self.recommendation = form.cleaned_data["recommendation"]
+ return super().form_valid(form)
+
+
+class BaseApprovalRecommendationView(LoginRequiredMixin, CaseContextMixin, BaseSessionWizardView):
+ template_name = "f680/case/recommendation/form_wizard.html"
+
+ condition_dict = {
+ AdviceSteps.LICENCE_CONDITIONS: C(form_add_licence_conditions(AdviceSteps.RECOMMEND_APPROVAL)),
+ AdviceSteps.LICENCE_FOOTNOTES: C(form_add_licence_conditions(AdviceSteps.RECOMMEND_APPROVAL)),
+ }
+
+ form_list = [
+ (AdviceSteps.RECOMMEND_APPROVAL, RecommendAnApprovalForm),
+ (AdviceSteps.LICENCE_CONDITIONS, PicklistLicenceConditionsForm),
+ (AdviceSteps.LICENCE_FOOTNOTES, FootnotesApprovalAdviceForm),
+ ]
+
+ step_kwargs = {
+ AdviceSteps.RECOMMEND_APPROVAL: approval_picklist,
+ AdviceSteps.LICENCE_CONDITIONS: proviso_picklist,
+ AdviceSteps.LICENCE_FOOTNOTES: footnote_picklist,
+ }
+
+ def get_success_url(self):
+ return reverse("cases:f680:view_my_recommendation", kwargs=self.kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["back_link_url"] = reverse("cases:f680:select_recommendation_type", kwargs=self.kwargs)
+ return context
+
+ @expect_status(
+ HTTPStatus.CREATED,
+ "Error adding approval recommendation",
+ "Unexpected error adding approval recommendation",
+ )
+ def post_approval_recommendation(self, data):
+ return post_approval_recommendation(self.request, self.case, data)
+
+ def get_payload(self, form_dict):
+ return GiveApprovalAdvicePayloadBuilder().build(form_dict)
+
+ def done(self, form_list, form_dict, **kwargs):
+ data = self.get_payload(form_dict)
+ self.post_approval_recommendation(data)
+ return redirect(self.get_success_url())
+
+
+class GiveApprovalRecommendationView(BaseApprovalRecommendationView):
+
+ def get_form(self, step=None, data=None, files=None):
+
+ if step == AdviceSteps.LICENCE_CONDITIONS:
+ picklist_form_kwargs = self.step_kwargs[AdviceSteps.LICENCE_CONDITIONS](self)
+ picklist_options_exist = len(picklist_form_kwargs["proviso"]["results"]) > 0
+ if picklist_options_exist:
+ return PicklistLicenceConditionsForm(data=data, prefix=step, **picklist_form_kwargs)
+ else:
+ return SimpleLicenceConditionsForm(data=data, prefix=step)
+
+ return super().get_form(step, data, files)
diff --git a/caseworker/f680/templates/f680/case/recommendation/form_wizard.html b/caseworker/f680/templates/f680/case/recommendation/form_wizard.html
new file mode 100644
index 000000000..83f3744a3
--- /dev/null
+++ b/caseworker/f680/templates/f680/case/recommendation/form_wizard.html
@@ -0,0 +1 @@
+{% extends 'core/form-wizard.html' %}
diff --git a/caseworker/f680/templates/f680/case/recommendation/recommendation.html b/caseworker/f680/templates/f680/case/recommendation/recommendation.html
new file mode 100644
index 000000000..5aff0a715
--- /dev/null
+++ b/caseworker/f680/templates/f680/case/recommendation/recommendation.html
@@ -0,0 +1,9 @@
+{% extends 'layouts/case.html' %}
+
+
+{% block details %}
+
+
+Make recommendation
+
+{% endblock %}
diff --git a/caseworker/f680/templates/f680/case/recommendation/recommendation_details.html b/caseworker/f680/templates/f680/case/recommendation/recommendation_details.html
new file mode 100644
index 000000000..2dc1edeac
--- /dev/null
+++ b/caseworker/f680/templates/f680/case/recommendation/recommendation_details.html
@@ -0,0 +1,60 @@
+{% load advice_tags %}
+{% load static custom_tags %}
+{% with user=recommendation.user decision=recommendation.type.key %}
+
{{ recommendation.text|linebreaks }}
+ + {% if recommendation.proviso %} +{{ recommendation.note|linebreaks }}
+ {% endif %} + + {% if recommendation.footnote %} +{{ recommendation.footnote|linebreaks }}
+ {% endif %} +{{ refusal_note.0.text|linebreaks }}
+ {% else %} +{{ recommendation.text|linebreaks }}
+ {% endif %} + +