diff --git a/common/static/common/js/application.js b/common/static/common/js/application.js index 5fd28c541..499632ab2 100644 --- a/common/static/common/js/application.js +++ b/common/static/common/js/application.js @@ -12,6 +12,7 @@ import { initAll } from 'govuk-frontend'; import initCheckboxes from './checkboxes'; import initConditionalMeasureConditions from './conditionalMeasureConditions'; import initFilterDisabledToggleForComCode from './conditionalDisablingFilters' +import initOpenCloseAccordionSection from './openCloseAccordion'; showHideCheckboxes(); // Initialise accessible-autocomplete components without a `name` attr in order // to avoid the "dummy" autocomplete field being submitted as part of the form @@ -24,4 +25,5 @@ initAll(); initCheckboxes(); initConditionalMeasureConditions(); initAutocompleteProgressiveEnhancement(); -initFilterDisabledToggleForComCode(); \ No newline at end of file +initFilterDisabledToggleForComCode(); +initOpenCloseAccordionSection(); \ No newline at end of file diff --git a/common/static/common/js/openCloseAccordion.js b/common/static/common/js/openCloseAccordion.js new file mode 100644 index 000000000..df2e08989 --- /dev/null +++ b/common/static/common/js/openCloseAccordion.js @@ -0,0 +1,16 @@ +// TODO: adjust the pathname filter to account for all objects search/filter pages +const initOpenCloseAccordionSection = () => { + document.addEventListener('DOMContentLoaded', function() { + + let expandedSection = document.getElementById('accordion-open-close-section') + let pathname = window.location.pathname + if(expandedSection){ + if (!pathname.includes('search')){ + expandedSection.classList.remove('govuk-accordion__section--expanded'); + } + } + } + ) +} + +export default initOpenCloseAccordionSection; \ No newline at end of file diff --git a/common/static/common/scss/_accordion.scss b/common/static/common/scss/_accordion.scss index 7665693e7..29fd9d7b1 100644 --- a/common/static/common/scss/_accordion.scss +++ b/common/static/common/scss/_accordion.scss @@ -5,3 +5,12 @@ .govuk-accordion__section-content > :last-child { @extend .govuk-\!-margin-bottom-6; } + +.black-label--no-button { + .govuk-accordion__section-button { + color: black !important; + } + .govuk-accordion__icon { + display: none !important; + } +} \ No newline at end of file diff --git a/common/tests/factories.py b/common/tests/factories.py index da8f952cd..696275669 100644 --- a/common/tests/factories.py +++ b/common/tests/factories.py @@ -1449,6 +1449,20 @@ class Meta: model = "notifications.EnvelopeReadyForProcessingNotification" +class EnvelopeAcceptedNotificationFactory(factory.django.DjangoModelFactory): + """This is a factory for an envelope that has been accepted notification.""" + + class Meta: + model = "notifications.EnvelopeAcceptedNotification" + + +class EnvelopeRejectedNotificationFactory(factory.django.DjangoModelFactory): + """This is a factory for an envelope that has been rejected notification.""" + + class Meta: + model = "notifications.EnvelopeRejectedNotification" + + class CrownDependenciesEnvelopeSuccessNotificationFactory( factory.django.DjangoModelFactory, ): @@ -1457,3 +1471,13 @@ class CrownDependenciesEnvelopeSuccessNotificationFactory( class Meta: model = "notifications.CrownDependenciesEnvelopeSuccessNotification" + + +class CrownDependenciesEnvelopeFailedNotificationFactory( + factory.django.DjangoModelFactory, +): + """This is a factory for a crown dependencies envelope failed + notification.""" + + class Meta: + model = "notifications.CrownDependenciesEnvelopeFailedNotification" diff --git a/conftest.py b/conftest.py index 170f98c0e..ec6785447 100644 --- a/conftest.py +++ b/conftest.py @@ -1937,6 +1937,44 @@ def factory_method(**kwargs): return factory_method +@pytest.fixture(scope="function") +def failed_envelope_factory( + published_envelope_factory, + mocked_send_emails_apply_async, +): + """ + Factory fixture to create a successfully processed envelope and update the + packaged_workbasket envelope field. + + params: + packaged_workbasket defaults to packaged_workbasket_factory() which creates a + Packaged workbasket with a Workbasket in the state QUEUED + with an approved transaction and tracked models + """ + + def factory_method(**kwargs): + envelope = published_envelope_factory(**kwargs) + + packaged_workbasket = PackagedWorkBasket.objects.get( + envelope=envelope, + ) + + packaged_workbasket.begin_processing() + assert packaged_workbasket.position == 0 + assert ( + packaged_workbasket.pk + == PackagedWorkBasket.objects.currently_processing().pk + ) + factories.LoadingReportFactory.create(packaged_workbasket=packaged_workbasket) + + packaged_workbasket.processing_failed() + packaged_workbasket.save() + assert packaged_workbasket.position == 0 + return envelope + + return factory_method + + @pytest.fixture(scope="function") def crown_dependencies_envelope_factory(successful_envelope_factory): """ diff --git a/measures/conditions.py b/measures/conditions.py index e8a3b0c32..e63d8988a 100644 --- a/measures/conditions.py +++ b/measures/conditions.py @@ -34,10 +34,19 @@ def show_step_duties(wizard): return MeasureEditSteps.DUTIES.value in cleaned_data.get("fields_to_edit") +def show_step_geographical_area_exclusions(wizard): + cleaned_data = wizard.get_cleaned_data_for_step(START) + if cleaned_data: + return MeasureEditSteps.GEOGRAPHICAL_AREA_EXCLUSIONS.value in cleaned_data.get( + "fields_to_edit", + ) + + measure_edit_condition_dict = { MeasureEditSteps.START_DATE: show_step_start_date, MeasureEditSteps.END_DATE: show_step_end_date, MeasureEditSteps.QUOTA_ORDER_NUMBER: show_step_quota_order_number, MeasureEditSteps.REGULATION: show_step_regulation, MeasureEditSteps.DUTIES: show_step_duties, + MeasureEditSteps.GEOGRAPHICAL_AREA_EXCLUSIONS: show_step_geographical_area_exclusions, } diff --git a/measures/constants.py b/measures/constants.py index fa05e96a0..afaf7318b 100644 --- a/measures/constants.py +++ b/measures/constants.py @@ -9,3 +9,7 @@ class MeasureEditSteps(models.TextChoices): QUOTA_ORDER_NUMBER = ("quota_order_number", "Quota order number") REGULATION = ("regulation", "Regulation") DUTIES = ("duties", "Duties") + GEOGRAPHICAL_AREA_EXCLUSIONS = ( + "geographical_area_exclusions", + "Geographical area exclusions", + ) diff --git a/measures/forms.py b/measures/forms.py index 75fc04314..c9ae7e85e 100644 --- a/measures/forms.py +++ b/measures/forms.py @@ -777,13 +777,17 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( Accordion( AccordionSection( - "Select one or more options to search", + "Search and filter", + HTML( + '

Select one or more options to search

', + ), Div( Div( Div( "goods_nomenclature", Field.text("sid", field_width=Fluid.TWO_THIRDS), "regulation", + "footnote", css_class="govuk-grid-column-one-third", ), Div( @@ -813,6 +817,9 @@ def __init__(self, *args, **kwargs): ), Div( "modc", + HTML( + "

To use the 'Include inherited measures' filter, enter a valid commodity code in the 'Select commodity code' filter above

", + ), css_class="govuk-grid-column-full form-group-margin-bottom-2", ), css_class="govuk-grid-row govuk-!-margin-top-6", @@ -840,7 +847,7 @@ def __init__(self, *args, **kwargs): "end_date", css_class="govuk-grid-column-one-half form-group-margin-bottom-2", ), - css_class="govuk-grid-row govuk-!-padding-top-6 filter-layout__filters", + css_class="govuk-grid-row govuk-!-padding-top-6", ), Div( Div( @@ -857,7 +864,8 @@ def __init__(self, *args, **kwargs): css_class="govuk-grid-row govuk-!-padding-top-3", ), ), - css_class="govuk-grid-row govuk-!-padding-3", + css_class="govuk-grid-row govuk-!-padding-3 black-label--no-button govuk-accordion__section--expanded", + id="accordion-open-close-section", ), ), ) @@ -1636,3 +1644,42 @@ def clean(self): validate_duties(duties, measure.valid_between.lower) return cleaned_data + + +class MeasureGeographicalAreaExclusionsForm(forms.Form): + excluded_area = forms.ModelChoiceField( + label="", + queryset=GeographicalArea.objects.all(), + help_text="Select a geographical area to be excluded", + required=False, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields["excluded_area"].queryset = ( + GeographicalArea.objects.current() + .with_latest_description() + .as_at_today_and_beyond() + .order_by("description") + ) + + self.fields[ + "excluded_area" + ].label_from_instance = lambda obj: f"{obj.area_id} - {obj.description}" + + self.helper = FormHelper(self) + self.helper.form_tag = False + self.helper.legend_size = Size.SMALL + self.helper.layout = Layout( + Fieldset( + "excluded_area", + ), + ) + + +class MeasureGeographicalAreaExclusionsFormSet(FormSet): + """Allows editing the geographical area exclusions of multiple measures in + `MeasureEditWizard`.""" + + form = MeasureGeographicalAreaExclusionsForm diff --git a/measures/jinja2/measures/edit-multiple-formset.jinja b/measures/jinja2/measures/edit-multiple-formset.jinja new file mode 100644 index 000000000..797cdbfb3 --- /dev/null +++ b/measures/jinja2/measures/edit-multiple-formset.jinja @@ -0,0 +1,58 @@ +{% extends "measures/edit-wizard-step.jinja" %} + +{% from "components/fieldset/macro.njk" import govukFieldset %} +{% from "components/error-summary/macro.njk" import govukErrorSummary %} + +{% block form %} + {% set formset = form %} + {{ crispy(formset.management_form, no_form_tags) }} + + {% if formset.non_form_errors() %} + {% set error_list = [] %} + {% for error in formset.non_form_errors() %} + {{ error_list.append({ + "html": '

' ~ error ~ '

', + }) or "" }} + {% endfor %} + + {{ govukErrorSummary({ + "titleText": "There is a problem", + "errorList": error_list + }) }} + {% endif %} + + {% for form in formset %} + {% if form.non_field_errors() %} + {% set error_list = [] %} + {% for error in form.non_field_errors() %} + {{ error_list.append({ + "html": '

' ~ error ~ '

', + }) or "" }} + {% endfor %} + + {{ govukErrorSummary({ + "titleText": "There is a problem", + "errorList": error_list + }) }} + {% endif %} + {{ crispy(form) }} + {% endfor %} + + {% if formset.data[formset.prefix ~ "-ADD"] %} + {{ crispy(formset.empty_form)|replace("__prefix__", formset.forms|length)|safe }} + {% endif %} + +
+ {{ govukButton({"text": "Save"}) }} + + {% if formset.total_form_count() + 1 < formset.max_num %} + {{ govukButton({ + "text": "Add another", + "attributes": {"id": "add-new"}, + "classes": "govuk-button--secondary", + "value": "1", + "name": formset.prefix ~ "-ADD", + }) }} + {% endif %} +
+{% endblock %} diff --git a/measures/jinja2/measures/list.jinja b/measures/jinja2/measures/list.jinja index 3c79c71f5..663bf38a3 100644 --- a/measures/jinja2/measures/list.jinja +++ b/measures/jinja2/measures/list.jinja @@ -15,10 +15,6 @@