From ceca0db630110ca9c7bb101eb221c18c84cf262b Mon Sep 17 00:00:00 2001 From: Morgan Rees <65167892+morganmaerees@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:10:59 +0100 Subject: [PATCH] DST-514/licensing grounds views (#18) * Data model * Updating models * Migrations and tidying up user flows * Fix third party flows * Adding business employing individual view * Types of service * Services views * remove unneeded file * Squash migrations * Service views update * service views updates * case switch * remove migration * Choices field * remove unnecessary repeated code * recipients initial views * updating field sizes * Required fields on recipient models * Remove sanctions regimes unneeded js * Remove unneeded JS for sanctions regimes * Review comments * Licensing grounds views * squash migrations * Licesngin grounds with legal advisory * purpose of licesning * Edit comments * Add variable for clarity --------- Co-authored-by: Morgan Rees --- django_app/apply_for_a_licence/choices.py | 28 ++++++ django_app/apply_for_a_licence/forms.py | 96 +++++++++++++++++++ django_app/apply_for_a_licence/models.py | 2 + .../javascript/licensing_grounds.js | 17 ++++ django_app/apply_for_a_licence/urls.py | 7 ++ django_app/apply_for_a_licence/views.py | 34 +++++-- 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 django_app/apply_for_a_licence/static/apply_for_a_licence/javascript/licensing_grounds.js diff --git a/django_app/apply_for_a_licence/choices.py b/django_app/apply_for_a_licence/choices.py index ad51abe9..70888ee3 100644 --- a/django_app/apply_for_a_licence/choices.py +++ b/django_app/apply_for_a_licence/choices.py @@ -64,3 +64,31 @@ class ProfessionalOrBusinessServicesChoices(models.TextChoices): it_consultancy_or_design = "it_consultancy_or_design", "IT consultancy or design" legal_advisory = "legal_advisory", "Legal advisory" public_relations = "public_relations", "Public relations" + + +class LicensingGroundsChoices(models.TextChoices): + civil_society = ( + "civil_society", + "Civil society activities that directly promote democracy, human rights or the rule of law in Russia", + ) + energy = "energy", "Services necessary for ensuring critical energy supply to any country" + divest = ( + "divest", + "Services necessary for non - Russian persons to divest from Russia, or to wind down business operations in Russia", + ) + humanitarian = "humanitarian", "The delivery of humanitarian assistance activity" + parent_or_subsidiary_company = ( + "parent_or_subsidiary_company", + "Services to a person connected with Russia by a UK parent company or UK subsidiary of that parent company", + ) + medical_and_pharmaceutical = ( + "medical_and_pharmaceutical", + "Medical and pharmaceutical purposes for the benefit of the civilian population", + ) + safety = ( + "safety", + "Services required to enable activities necessary for the urgent prevention or mitigation of an " + "event likely to have a serious and significant impact on human health or safety, including the " + "safety of existing infrastructure, or the environment", + ) + food = "food", "Services in connection with the production or distribution of food for the benefit of the civilian population" diff --git a/django_app/apply_for_a_licence/forms.py b/django_app/apply_for_a_licence/forms.py index 343de5ba..a5164744 100644 --- a/django_app/apply_for_a_licence/forms.py +++ b/django_app/apply_for_a_licence/forms.py @@ -30,6 +30,7 @@ Address, Applicant, ApplicationOrganisation, + ApplicationServices, ApplicationType, BaseApplication, ExistingLicences, @@ -861,3 +862,98 @@ def __init__(self, *args: object, **kwargs: object) -> None: super().__init__(*args, **kwargs) self.fields["relationship"].required = True self.fields["relationship"].widget.attrs = {"rows": 5} + + +class LicensingGroundsForm(BaseForm): + form_h1_header = "Which of these licensing grounds describes your purpose for providing the sanctioned services?" + licensing_grounds = forms.MultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + choices=choices.LicensingGroundsChoices.choices, + required=True, + label="Select all that apply", + error_messages={ + "required": "Select the licensing grounds", + "invalid": "Select the licensing grounds or select I do not know", + }, + ) + + class Media: + js = ["apply_for_a_licence/javascript/licensing_grounds.js"] + + def __init__(self, *args: object, **kwargs: object) -> None: + self.legal_advisory = kwargs.pop("legal_advisory", False) + super().__init__(*args, **kwargs) + checkbox_choices = self.fields["licensing_grounds"].choices + # Create the 'or' divider between the last choice and I do not know + last_checkbox_value = checkbox_choices[-1][0] + last_checkbox_label = checkbox_choices[-1][1] + checkbox_choices[-1] = Choice( + value=last_checkbox_value, + label=last_checkbox_label, + divider="or", + ) + checkbox_choices.append(Choice("Unknown grounds", "I do not know")) + checkbox_choices.append(Choice("None of these", "None of these")) + self.fields["licensing_grounds"].choices = checkbox_choices + self.helper.layout = Layout( + Fieldset( + get_field_with_label_id("licensing_grounds", field_method=Field.checkboxes, label_id="checkbox"), + aria_describedby="checkbox", + ) + ) + + if self.legal_advisory: + self.form_h1_header = ( + "Which of these licensing grounds describes your purpose for providing the " + "(non-legal advisory) sanctioned services?" + ) + + else: + if professional_or_business_service := self.request.session.get("ProfessionalOrBusinessServicesView", False): + if professional_or_business_service.get("professional_or_business_service") == "legal_advisory": + self.form_h1_header = ( + "Which of the licensing grounds describes the purpose of the relevant activity for which " + "the legal advice is being given?" + ) + self.fields["licensing_grounds"].label = ( + "If your client is a non-UK national or business and is also outside the UK " + "then UK sanctions do not apply to them. Instead you should select the licensing " + "grounds that would apply if UK sanctions did apply to them." + ) + self.fields["licensing_grounds"].help_text = "Select all that apply" + + def clean(self): + cleaned_data = super().clean() + if licensing_grounds := cleaned_data.get("licensing_grounds"): + if ("Unknown grounds" in licensing_grounds or "None of these" in licensing_grounds) and len(licensing_grounds) >= 2: + # the user has selected "I do not know" or 'None of these' and other grounds, this is an issue. + # note that the user can select both "I do not know" and "None of these" + self.add_error( + "licensing_grounds", + forms.ValidationError(code="invalid", message=self.fields["licensing_grounds"].error_messages["invalid"]), + ) + return cleaned_data + + +class PurposeOfProvisionForm(BaseModelForm): + class Meta: + model = ApplicationServices + fields = ["purpose_of_provision"] + labels = { + "purpose_of_provision": "What is your purpose for providing these services?", + } + help_texts = { + "purpose_of_provision": "Tell us how your reasons for providing these services are consistent with " + "the purposes of the sanctions and why providing them is both necessary and justified. If you've " + "selected any licensing considerations (licensing grounds), as laid out in the statutory guidance, " + "explain how your purpose aligns with those grounds" + } + error_messages = { + "purpose_of_provision": { + "required": "Select the purpose for providing these services", + } + } + + def __init__(self, *args: object, **kwargs: object) -> None: + super().__init__(*args, **kwargs) + self.fields["purpose_of_provision"].required = True diff --git a/django_app/apply_for_a_licence/models.py b/django_app/apply_for_a_licence/models.py index 6d2986dd..7175067a 100644 --- a/django_app/apply_for_a_licence/models.py +++ b/django_app/apply_for_a_licence/models.py @@ -6,6 +6,7 @@ from django_countries.fields import CountryField from . import choices +from .choices import LicensingGroundsChoices class Address(BaseModelID): @@ -238,6 +239,7 @@ class Meta: class Ground(BaseModelID): # ! This table is required as it is for data pipelines - speak with data architect before modifying + licensing_grounds = models.CharField(choices=LicensingGroundsChoices.choices) label = models.CharField() start_date = models.DateField() end_date = models.DateField() diff --git a/django_app/apply_for_a_licence/static/apply_for_a_licence/javascript/licensing_grounds.js b/django_app/apply_for_a_licence/static/apply_for_a_licence/javascript/licensing_grounds.js new file mode 100644 index 00000000..3a563d2f --- /dev/null +++ b/django_app/apply_for_a_licence/static/apply_for_a_licence/javascript/licensing_grounds.js @@ -0,0 +1,17 @@ +document.addEventListener("DOMContentLoaded", function (event) { + $(document).on('change', 'input[value="Unknown grounds"], input[value="None of these"]', function(){ + if ($(this).is(':checked')) { + // clear the other inputs + $('input[name$="licensing_grounds"]').not($(this)).prop('checked', false); + } + }) + + $('.govuk-checkboxes__divider').prevAll().find('input[name$="licensing_grounds"]').on('change', function () { + if ($(this).is(':checked')) { + // clear the unknown grounds input or the none of these input + $('input[name$="licensing_grounds"][value="Unknown grounds"]').prop('checked', false); + $('input[name$="licensing_grounds"][value="None of these"]').prop('checked', false); + } + }); + +}); diff --git a/django_app/apply_for_a_licence/urls.py b/django_app/apply_for_a_licence/urls.py index 9aa74fd0..1ea97c3d 100644 --- a/django_app/apply_for_a_licence/urls.py +++ b/django_app/apply_for_a_licence/urls.py @@ -55,5 +55,12 @@ views.RelationshipProviderRecipientView.as_view(), name="relationship_provider_recipient", ), + path("licensing_grounds", views.LicensingGroundsView.as_view(), name="licensing_grounds"), + path( + "licensing_grounds_legal_advisory", + views.LicensingGroundsLegalAdvisoryView.as_view(), + name="licensing_grounds_legal_advisory", + ), + path("purpose_of_provision", views.PurposeOfProvisionView.as_view(), name="purpose_of_provision"), path("complete", views.CompleteView.as_view(), name="complete"), ] diff --git a/django_app/apply_for_a_licence/views.py b/django_app/apply_for_a_licence/views.py index bc5960f6..e66e8029 100644 --- a/django_app/apply_for_a_licence/views.py +++ b/django_app/apply_for_a_licence/views.py @@ -101,11 +101,6 @@ def get_success_url(self): class PreviousLicenceView(BaseFormView): form_class = forms.ExistingLicencesForm - def get_form_kwargs(self) -> dict[str, Any]: - kwargs = super().get_form_kwargs() - kwargs.update({"request": self.request}) - return kwargs - def get_success_url(self): success_url = reverse("type_of_service") if start_view := self.request.session.get("StartView", False): @@ -472,8 +467,7 @@ def get_success_url(self): if add_recipient: return reverse("where_is_the_recipient_located") else: - # todo: change success url to licensing grounds flow - return reverse("complete") + return reverse("licensing_grounds") class DeleteRecipientView(BaseFormView): @@ -490,3 +484,29 @@ def post(self, *args: object, **kwargs: object) -> HttpResponse: class RelationshipProviderRecipientView(BaseFormView): form_class = forms.RelationshipProviderRecipientForm success_url = reverse_lazy("recipient_added") + + +class LicensingGroundsView(BaseFormView): + form_class = forms.LicensingGroundsForm + success_url = reverse_lazy("purpose_of_provision") + + def get_success_url(self) -> str: + if professional_or_business_service := self.request.session.get("ProfessionalOrBusinessServicesView", False): + if professional_or_business_service.get("professional_or_business_service") == "legal_advisory": + return reverse("licensing_grounds_legal_advisory") + return reverse("purpose_of_provision") + + +class LicensingGroundsLegalAdvisoryView(BaseFormView): + form_class = forms.LicensingGroundsForm + success_url = reverse_lazy("upload_documents") + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["legal_advisory"] = True + return kwargs + + +class PurposeOfProvisionView(BaseFormView): + form_class = forms.PurposeOfProvisionForm + success_url = reverse_lazy("upload_documents")