From 5408e57a33e6c0f435538a4491612badeed7ef66 Mon Sep 17 00:00:00 2001 From: Elizabeth Pedley <37877423+EPedley@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:42:19 +0000 Subject: [PATCH] tss-2243 preliminary assessments (#854) Preliminary Assessment Frontend --------- Co-authored-by: abarolo Co-authored-by: asmund bekker <145478290+abarolo@users.noreply.github.com> --- barriers/constants.py | 6 ++ barriers/forms/assessments/economic.py | 1 - .../assessments/preliminary_assessment.py | 43 ++++++++++++++ barriers/models/__init__.py | 2 + barriers/models/assessments.py | 13 +++++ barriers/models/history/__init__.py | 6 +- .../models/history/assessments/economic.py | 21 +++++++ barriers/urls.py | 6 ++ barriers/views/assessments/overview.py | 1 + .../assessments/preliminary_assessment.py | 34 +++++++++++ barriers/views/mixins.py | 18 ++++++ .../src/css/pages/barrier/_assessment.scss | 3 + core/tests.py | 11 ++++ templates/barriers/assessments/overview.html | 28 ++++++++- .../assessments/preliminary_assessment.html | 24 ++++++++ .../test_economic_assessment_documents.py | 5 +- .../test_preliminary_assessment.py | 58 +++++++++++++++++++ utils/api/client.py | 2 + utils/api/resources.py | 18 ++++++ 19 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 barriers/forms/assessments/preliminary_assessment.py create mode 100644 barriers/views/assessments/preliminary_assessment.py create mode 100644 templates/barriers/assessments/preliminary_assessment.html create mode 100644 tests/assessments/test_preliminary_assessment.py diff --git a/barriers/constants.py b/barriers/constants.py index 01681969e..ec17211af 100644 --- a/barriers/constants.py +++ b/barriers/constants.py @@ -285,6 +285,12 @@ class Statuses: ("investments", "Investments"), ) +PRELIMINARY_ASSESSMENT_CHOICES = Choices( + ("1", "Over 10 million pounds"), + ("2", "Less than 10 million pounds"), + ("3", "Unable to assess"), +) + # Related barriers rag tags RELATED_BARRIER_TAGS = { "duplicate": { diff --git a/barriers/forms/assessments/economic.py b/barriers/forms/assessments/economic.py index 4fdebc536..b39f1726f 100644 --- a/barriers/forms/assessments/economic.py +++ b/barriers/forms/assessments/economic.py @@ -32,7 +32,6 @@ def save(self): class EconomicAssessmentRatingForm(DocumentMixin, forms.Form): rating = forms.ChoiceField( - label="What is the initial economic assessment of this barrier?", choices=[], error_messages={ "required": "Select the initial economic assessment of this barrier" diff --git a/barriers/forms/assessments/preliminary_assessment.py b/barriers/forms/assessments/preliminary_assessment.py new file mode 100644 index 000000000..ab9a3273a --- /dev/null +++ b/barriers/forms/assessments/preliminary_assessment.py @@ -0,0 +1,43 @@ +from django import forms + +from barriers.constants import PRELIMINARY_ASSESSMENT_CHOICES +from barriers.forms.mixins import APIFormMixin +from utils.api.client import MarketAccessAPIClient + + +class UpdatePreliminaryAssessmentForm(APIFormMixin, forms.Form): + preliminary_value = forms.ChoiceField( + label="Barrier value", + choices=PRELIMINARY_ASSESSMENT_CHOICES, + widget=forms.RadioSelect, + error_messages={ + "required": "Select a value", + }, + ) + + preliminary_value_details = forms.CharField( + widget=forms.Textarea, + label="Provide an explanation on how the value has been assessed", + error_messages={"required": "Enter details of the preliminary value"}, + ) + + def __init__(self, preliminary_assessment=None, *args, **kwargs): + self.token = kwargs.pop("token") + self.barrier = kwargs.pop("barrier") + self.preliminary_assessment = preliminary_assessment + super().__init__(*args, **kwargs) + + def save(self): + client = MarketAccessAPIClient(self.token) + if self.preliminary_assessment: + client.preliminary_assessment.patch_preliminary_assessment( + barrier_id=self.barrier.id, + value=self.cleaned_data["preliminary_value"], + details=self.cleaned_data["preliminary_value_details"], + ) + else: + client.preliminary_assessment.create_preliminary_assessment( + barrier_id=self.barrier.id, + value=self.cleaned_data["preliminary_value"], + details=self.cleaned_data["preliminary_value_details"], + ) diff --git a/barriers/models/__init__.py b/barriers/models/__init__.py index 9cd9c7fae..e66a14bb2 100644 --- a/barriers/models/__init__.py +++ b/barriers/models/__init__.py @@ -2,6 +2,7 @@ from .assessments import ( EconomicAssessment, EconomicImpactAssessment, + PreliminaryAssessment, ResolvabilityAssessment, StrategicAssessment, ) @@ -33,4 +34,5 @@ ActionPlan, Stakeholder, BarrierDownload, + PreliminaryAssessment, ] diff --git a/barriers/models/assessments.py b/barriers/models/assessments.py index 4d4df9bed..f026ac1ed 100644 --- a/barriers/models/assessments.py +++ b/barriers/models/assessments.py @@ -1,3 +1,4 @@ +from barriers.constants import PRELIMINARY_ASSESSMENT_CHOICES from utils.models import APIModel from .documents import Document @@ -45,3 +46,15 @@ class ResolvabilityAssessment(APIModel): class StrategicAssessment(APIModel): date_fields = ("archived_on", "created_on", "reviewed_on") + + +class PreliminaryAssessment(APIModel): + date_fields = "created_on" + + @property + def get_value_display(self): + value = str(self.data.get("value", "")) + if value: + return PRELIMINARY_ASSESSMENT_CHOICES[value] + else: + return "" diff --git a/barriers/models/history/__init__.py b/barriers/models/history/__init__.py index 0f83f5a57..ed03652f9 100644 --- a/barriers/models/history/__init__.py +++ b/barriers/models/history/__init__.py @@ -4,7 +4,10 @@ ActionPlanTaskHistoryItem, ) -from .assessments.economic import EconomicAssessmentHistoryItem +from .assessments.economic import ( + EconomicAssessmentHistoryItem, + PreliminaryAssessmentHistoryItem, +) from .assessments.economic_impact import EconomicImpactAssessmentHistoryItem from .assessments.resolvability import ResolvabilityAssessmentHistoryItem from .assessments.strategic import StrategicAssessmentHistoryItem @@ -45,5 +48,6 @@ class HistoryItem(PolymorphicBase): ProgressUpdateHistoryItem, BarrierTopPrioritySummaryItem, ProgrammeFundsHistoryItem, + PreliminaryAssessmentHistoryItem, ) class_lookup = {} diff --git a/barriers/models/history/assessments/economic.py b/barriers/models/history/assessments/economic.py index 1d9ffb917..95b513c3c 100644 --- a/barriers/models/history/assessments/economic.py +++ b/barriers/models/history/assessments/economic.py @@ -90,3 +90,24 @@ class EconomicAssessmentHistoryItem(PolymorphicBase): ) default_subclass = GenericHistoryItem class_lookup = {} + + +class PreliminaryAssessmentValueHistoryItem(BaseHistoryItem): + field = "value" + field_name = "Preliminary assessment value" + + +class PreliminaryAssessmentDetailsHistoryItem(BaseHistoryItem): + field = "details" + field_name = "Preliminary assessment details" + + +class PreliminaryAssessmentHistoryItem(PolymorphicBase): + model = "preliminary_assessment" + key = "field" + subclasses = ( + PreliminaryAssessmentValueHistoryItem, + PreliminaryAssessmentDetailsHistoryItem, + ) + default_subclass = GenericHistoryItem + class_lookup = {} diff --git a/barriers/urls.py b/barriers/urls.py index 9d64352ab..c987aa11f 100644 --- a/barriers/urls.py +++ b/barriers/urls.py @@ -39,6 +39,7 @@ EconomicImpactAssessmentDetail, ) from barriers.views.assessments.overview import AssessmentOverview +from barriers.views.assessments.preliminary_assessment import PreliminaryAssessmentValue from barriers.views.assessments.resolvability import ( AddResolvabilityAssessment, ArchiveResolvabilityAssessment, @@ -331,6 +332,11 @@ BarrierEditCommercialValue.as_view(), name="edit_commercial_value", ), + path( + "barriers//edit/preliminary-assessment/", + PreliminaryAssessmentValue.as_view(), + name="edit_preliminary_assessment", + ), path( "barriers//edit/commodities/", BarrierEditCommodities.as_view(), diff --git a/barriers/views/assessments/overview.py b/barriers/views/assessments/overview.py index e1bd181b2..7eb888cda 100644 --- a/barriers/views/assessments/overview.py +++ b/barriers/views/assessments/overview.py @@ -16,4 +16,5 @@ def get_context_data(self, **kwargs): assement_class += " visually-hidden" context["strategic_ass"] = assement_class + context["preliminary_assessment"] = self.preliminary_assessment return context diff --git a/barriers/views/assessments/preliminary_assessment.py b/barriers/views/assessments/preliminary_assessment.py new file mode 100644 index 000000000..28994851f --- /dev/null +++ b/barriers/views/assessments/preliminary_assessment.py @@ -0,0 +1,34 @@ +from django.urls import reverse +from django.views.generic import FormView + +from barriers.forms.assessments.preliminary_assessment import ( + UpdatePreliminaryAssessmentForm, +) +from barriers.views.mixins import APIBarrierFormViewMixin + + +class PreliminaryAssessmentValue(APIBarrierFormViewMixin, FormView): + template_name = "barriers/assessments/preliminary_assessment.html" + form_class = UpdatePreliminaryAssessmentForm + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["token"] = self.request.session.get("sso_token") + kwargs["barrier"] = self.barrier + kwargs["preliminary_assessment"] = self.preliminary_assessment + return kwargs + + def get_initial(self): + if self.preliminary_assessment: + return { + "preliminary_value": self.preliminary_assessment.value, + "preliminary_value_details": self.preliminary_assessment.details, + } + else: + return + + def get_success_url(self): + return reverse( + "barriers:assessment_detail", + kwargs={"barrier_id": self.kwargs.get("barrier_id")}, + ) diff --git a/barriers/views/mixins.py b/barriers/views/mixins.py index 9b62d414c..1988c4bae 100644 --- a/barriers/views/mixins.py +++ b/barriers/views/mixins.py @@ -20,6 +20,7 @@ class BarrierMixin: _notes = None _note = None _action_plan = None + _preliminary_assessment = None @property def barrier(self): @@ -51,6 +52,12 @@ def action_plan(self): self._action_plan = self.get_action_plan() return self._action_plan + @property + def preliminary_assessment(self): + if not self._preliminary_assessment: + self._preliminary_assessment = self.get_preliminary_assessment() + return self._preliminary_assessment + def get_barrier(self): client = MarketAccessAPIClient(self.request.session.get("sso_token")) barrier_id = self.kwargs.get("barrier_id") @@ -97,6 +104,17 @@ def get_action_plan(self): raise Http404() raise + def get_preliminary_assessment(self): + client = MarketAccessAPIClient(self.request.session.get("sso_token")) + barrier_id = self.barrier.id + try: + return client.preliminary_assessment.get_preliminary_assessment( + barrier_id=barrier_id + ) + except APIHttpException as e: + if e.status_code == HTTPStatus.NOT_FOUND: + return None + class PublicBarrierMixin: _public_barrier = None diff --git a/core/frontend/src/css/pages/barrier/_assessment.scss b/core/frontend/src/css/pages/barrier/_assessment.scss index f532655d6..032a3af27 100644 --- a/core/frontend/src/css/pages/barrier/_assessment.scss +++ b/core/frontend/src/css/pages/barrier/_assessment.scss @@ -6,6 +6,9 @@ &:first-child { margin-top: govuk-em( 32, 16 ); } + &.archived { + opacity: 0.5; + } } .assessment-item__heading { diff --git a/core/tests.py b/core/tests.py index e7b1b95a3..bfd494453 100644 --- a/core/tests.py +++ b/core/tests.py @@ -14,6 +14,7 @@ ActionPlanTaskResource, BarriersResource, NotesResource, + PreliminaryAssessmentResource, PublicBarriersResource, ReportsResource, UserProfileResource, @@ -485,3 +486,13 @@ def profile(self): "overseas_regions": [], } ) + + @property + def preliminary_assessment(self): + return PreliminaryAssessmentResource.model( + { + "id": 1, + "value": "1", + "details": "test details", + } + ) diff --git a/templates/barriers/assessments/overview.html b/templates/barriers/assessments/overview.html index 10ea9db30..3d0cb9bfe 100644 --- a/templates/barriers/assessments/overview.html +++ b/templates/barriers/assessments/overview.html @@ -16,9 +16,27 @@ {% include 'barriers/partials/barrier_tabs.html' with active='assessment' %}
+
-

Initial economic assessment

- {% include "barriers/assessments/economic/partials/summary.html" %} +

Preliminary economic assessment

+ {% if preliminary_assessment %} +

Preliminary value: {{ preliminary_assessment.get_value_display }}

+

Details:

+

{{ preliminary_assessment.details|linebreaksbr }}

+ {% if current_user|has_permission:"change_preliminaryassessment" %} + Edit + {% endif %} + {% else %} +
+ {% if current_user|has_permission:"add_preliminaryassessment" %} +

No preliminary assessment

+ Add preliminary assessment + {% else %} +

This barrier has not yet received a preliminary assessment. Analysts carry out preliminary assessments of all new market access barriers uploaded to DMAS.

+

Preliminary assessments are an estimate of whether a market access barrier is likely to be worth less or more than £10M in additional exports, or earnings on UK outward Foreign Direct Investment (FDI), over a 5-year period.

+ {% endif %} +
+ {% endif %}
@@ -50,5 +68,11 @@

Resolvability assessment

Strategic assessment

{% include "barriers/assessments/strategic/partials/summary.html" %}
+ +
+

Initial economic assessment - archived

+ {% include "barriers/assessments/economic/partials/summary.html" %} +
+
{% endblock %} diff --git a/templates/barriers/assessments/preliminary_assessment.html b/templates/barriers/assessments/preliminary_assessment.html new file mode 100644 index 000000000..3837810f8 --- /dev/null +++ b/templates/barriers/assessments/preliminary_assessment.html @@ -0,0 +1,24 @@ +{% extends "barriers/edit/base.html" %} + +{% block page_title %}{{ block.super }} - Commercial value estimate{% endblock %} + +{% block back_link %} + Back +{% endblock %} + +{% block page_content %} + + {% include 'partials/heading.html' with text='Preliminary economic assessment' %} + +
+ + {% csrf_token %} + + {% include "partials/forms/radio_input.html" with field=form.preliminary_value %} + {% include "partials/forms/textarea.html" with field=form.preliminary_value_details %} + + + Cancel +
+ +{% endblock %} diff --git a/tests/assessments/test_economic_assessment_documents.py b/tests/assessments/test_economic_assessment_documents.py index f41688a3a..48899f08d 100644 --- a/tests/assessments/test_economic_assessment_documents.py +++ b/tests/assessments/test_economic_assessment_documents.py @@ -166,7 +166,10 @@ def test_delete_new_economic_assessment_document_ajax(self): ] assert session_document_ids == document_ids[1:] - def test_strategic_assessments(self): + @patch( + "utils.api.resources.PreliminaryAssessmentResource.get_preliminary_assessment" + ) + def test_strategic_assessments(self, mock_get_preliminary_assessment): url = reverse( "barriers:assessment_detail", kwargs={ diff --git a/tests/assessments/test_preliminary_assessment.py b/tests/assessments/test_preliminary_assessment.py new file mode 100644 index 000000000..6f0130415 --- /dev/null +++ b/tests/assessments/test_preliminary_assessment.py @@ -0,0 +1,58 @@ +from http import HTTPStatus + +from django.urls import reverse +from mock import Mock, patch + +from core.tests import MarketAccessTestCase + + +class EditPreliminaryAssessmentTestCase(MarketAccessTestCase): + + @patch( + "utils.api.resources.PreliminaryAssessmentResource.get_preliminary_assessment" + ) + def test_preliminary_assessment_has_initial_data( + self, mock_get_preliminary_assessment: Mock + ): + mock_get_preliminary_assessment.return_value = self.preliminary_assessment + response = self.client.get( + reverse( + "barriers:edit_preliminary_assessment", + kwargs={"barrier_id": self.barrier["id"]}, + ), + ) + assert response.status_code == HTTPStatus.OK + + assert "form" in response.context + form = response.context["form"] + assert form.initial["preliminary_value"] == self.preliminary_assessment.value + assert ( + form.initial["preliminary_value_details"] + == self.preliminary_assessment.details + ) + + @patch( + "utils.api.resources.PreliminaryAssessmentResource.get_preliminary_assessment" + ) + def test_edit_preliminary_assessment_calls_api( + self, + mock_get_preliminary_assessment: Mock, + ): + barrier_id = self.barrier["id"] + + url = reverse( + "barriers:edit_preliminary_assessment", + kwargs={ + "barrier_id": barrier_id, + }, + ) + response = self.client.post( + url, + data={ + "value": "2", + "details": "updated description", + }, + ) + + mock_get_preliminary_assessment.assert_called_once_with(barrier_id=barrier_id) + assert response.status_code == 200 diff --git a/utils/api/client.py b/utils/api/client.py index 2e2173645..e6ae7d170 100644 --- a/utils/api/client.py +++ b/utils/api/client.py @@ -23,6 +23,7 @@ MentionResource, NotesResource, NotificationExclusionResource, + PreliminaryAssessmentResource, PublicBarrierNotesResource, PublicBarriersResource, ReportsResource, @@ -63,6 +64,7 @@ def __init__(self, token=None, **kwargs): self.barrier_download = BarrierDownloadsResource(self) self.dashboard_tasks = DashboardTasksResource(self) self.profile = UserProfileResource(self) + self.preliminary_assessment = PreliminaryAssessmentResource(self) def request(self, method, path, **kwargs): url = f"{settings.MARKET_ACCESS_API_URI}{path}" diff --git a/utils/api/resources.py b/utils/api/resources.py index 21c3b5f6d..51119fbdf 100644 --- a/utils/api/resources.py +++ b/utils/api/resources.py @@ -19,6 +19,7 @@ EconomicImpactAssessment, HistoryItem, Note, + PreliminaryAssessment, PublicBarrier, PublicBarrierNote, ResolvabilityAssessment, @@ -549,3 +550,20 @@ def create(self, *args, **kwargs): class UserProfileResource(APIResource): resource_name = "users/profile" model = UserProfile + + +class PreliminaryAssessmentResource(APIResource): + resource_name = "preliminary-assessment" + model = PreliminaryAssessment + + def get_preliminary_assessment(self, barrier_id): + url = f"barriers/{barrier_id}/preliminary-assessment" + return self.model(self.client.get(url)) + + def create_preliminary_assessment(self, barrier_id, *args, **kwargs): + url = f"barriers/{barrier_id}/preliminary-assessment" + return self.model(self.client.post(url, json=kwargs)) + + def patch_preliminary_assessment(self, barrier_id, *args, **kwargs): + url = f"barriers/{barrier_id}/preliminary-assessment" + return self.model(self.client.patch(url, json=kwargs))