diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index b70e4f99..cc24c14e 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -27,26 +27,11 @@ from .data import ( from atst.utils.localization import translate -class AppInfoForm(BaseForm): - portfolio_name = StringField( - translate("forms.task_order.portfolio_name_label"), - description=translate("forms.task_order.portfolio_name_description"), - validators=[ - Required(), - Length( - min=4, - max=100, - message=translate("forms.portfolio.name_length_validation_message"), - ), - ], - ) +class AppInfoWithExistingPortfolioForm(BaseForm): scope = TextAreaField( translate("forms.task_order.scope_label"), description=translate("forms.task_order.scope_description"), ) - defense_component = SelectField( - translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES - ) app_migration = RadioField( translate("forms.task_order.app_migration.label"), description=translate("forms.task_order.app_migration.description"), @@ -88,6 +73,24 @@ class AppInfoForm(BaseForm): ) +class AppInfoForm(AppInfoWithExistingPortfolioForm): + portfolio_name = StringField( + translate("forms.task_order.portfolio_name_label"), + description=translate("forms.task_order.portfolio_name_description"), + validators=[ + Required(), + Length( + min=4, + max=100, + message=translate("forms.portfolio.name_length_validation_message"), + ), + ], + ) + defense_component = SelectField( + translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES + ) + + class FundingForm(BaseForm): performance_length = SelectField( translate("forms.task_order.performance_length.label"), diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index 01e9e299..2d9a3aa0 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -36,6 +36,10 @@ class Portfolio(Base, mixins.TimestampsMixin, mixins.AuditableMixin): def user_count(self): return len(self.members) + @property + def num_task_orders(self): + return len(self.task_orders) + @property def members(self): return ( diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 77cb0c63..799fd024 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -46,12 +46,14 @@ TASK_ORDER_SECTIONS = [ class ShowTaskOrderWorkflow: - def __init__(self, user, screen=1, task_order_id=None): + def __init__(self, user, screen=1, task_order_id=None, portfolio_id=None): self.user = user self.screen = screen self.task_order_id = task_order_id - self._section = TASK_ORDER_SECTIONS[screen - 1] self._task_order = None + self.portfolio_id = portfolio_id + self._portfolio = None + self._section = TASK_ORDER_SECTIONS[screen - 1] self._form = None @property @@ -61,6 +63,16 @@ class ShowTaskOrderWorkflow: return self._task_order + @property + def portfolio(self): + if not self._portfolio: + if self.task_order: + self._portfolio = self.task_order.portfolio + elif self.portfolio_id: + self._portfolio = Portfolios.get(self.user, self.portfolio_id) + + return self._portfolio + @property def form(self): form_type = ( @@ -90,6 +102,11 @@ class ShowTaskOrderWorkflow: else: self._form = self._section[form_type]() + if self.pf_attributes_read_only and self.screen == 1: + self._form = task_order_form.AppInfoWithExistingPortfolioForm( + obj=self.task_order + ) + return self._form @property @@ -110,9 +127,17 @@ class ShowTaskOrderWorkflow: @property def is_complete(self): - if self.task_order: - if TaskOrders.all_sections_complete(self.task_order): - return True + if self.task_order and TaskOrders.all_sections_complete(self.task_order): + return True + else: + return False + + @property + def pf_attributes_read_only(self): + if self.task_order and self.portfolio.num_task_orders > 1: + return True + elif self.portfolio_id: + return True else: return False @@ -128,15 +153,27 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): self.portfolio_id = portfolio_id self._task_order = None self._section = TASK_ORDER_SECTIONS[screen - 1] - form_type = ( - "unclassified_form" - if "unclassified_form" in self._section and not app.config.get("CLASSIFIED") - else "form" - ) - self._form = self._section[form_type](self.form_data, obj=self.task_order) + self._form = None @property def form(self): + if not self._form: + form_type = ( + "unclassified_form" + if "unclassified_form" in self._section + and not app.config.get("CLASSIFIED") + else "form" + ) + + if self.pf_attributes_read_only and self.screen == 1: + self._form = task_order_form.AppInfoWithExistingPortfolioForm( + self.form_data + ) + else: + self._form = self._section[form_type]( + self.form_data, obj=self.task_order + ) + return self._form @property @@ -206,21 +243,24 @@ def get_started(): @task_orders_bp.route("/task_orders/new//") @task_orders_bp.route("/portfolios//task_orders/new/") def new(screen, task_order_id=None, portfolio_id=None): - if task_order_id and screen is 4: - task_order = TaskOrders.get(g.current_user, task_order_id) - if not TaskOrders.all_sections_complete(task_order): - flash("task_order_draft") - - workflow = ShowTaskOrderWorkflow(g.current_user, screen, task_order_id) + workflow = ShowTaskOrderWorkflow( + g.current_user, screen, task_order_id, portfolio_id + ) template_args = { "current": screen, "task_order_id": task_order_id, - "portfolio_id": portfolio_id, "screens": workflow.display_screens, "form": workflow.form, "complete": workflow.is_complete, } + if task_order_id and screen is 4: + if not TaskOrders.all_sections_complete(workflow.task_order): + flash("task_order_draft") + + if workflow.pf_attributes_read_only: + template_args["portfolio"] = workflow.portfolio + url_args = {"screen": screen} if task_order_id: url_args["task_order_id"] = task_order_id diff --git a/templates/task_orders/new/app_info.html b/templates/task_orders/new/app_info.html index ee8fe008..f70be8ca 100644 --- a/templates/task_orders/new/app_info.html +++ b/templates/task_orders/new/app_info.html @@ -4,6 +4,7 @@ {% from "components/options_input.html" import OptionsInput %} {% from "components/date_input.html" import DateInput %} {% from "components/multi_checkbox_input.html" import MultiCheckboxInput %} +{% from "components/review_field.html" import ReviewField %} {% block heading %} {{ "task_orders.new.app_info.section_title"| translate }} @@ -14,11 +15,21 @@

{{ "task_orders.new.app_info.basic_info_title"| translate }}

-{{ TextInput(form.portfolio_name, placeholder="The name of your office or organization", validation="portfolioName") }} + +{% if portfolio %} + {{ ReviewField(heading="forms.portfolio.name_label" | translate, field=portfolio.name) }} +{% else %} + {{ TextInput(form.portfolio_name, placeholder="The name of your office or organization", validation="portfolioName") }} +{% endif %} {{ TextInput(form.scope, paragraph=True) }}

{{ "task_orders.new.app_info.sample_scope" | translate | safe }}

+
- {{ OptionsInput(form.defense_component) }} + {% if portfolio %} + {{ ReviewField(heading="forms.task_order.defense_component_label" | translate, field=portfolio.defense_component) }} + {% else %} + {{ OptionsInput(form.defense_component) }} + {% endif %}

diff --git a/tests/routes/task_orders/test_new_task_order.py b/tests/routes/task_orders/test_new_task_order.py index a6908a21..fe4ec09e 100644 --- a/tests/routes/task_orders/test_new_task_order.py +++ b/tests/routes/task_orders/test_new_task_order.py @@ -9,6 +9,26 @@ from atst.utils.localization import translate from tests.factories import UserFactory, TaskOrderFactory, PortfolioFactory +class TestShowTaskOrderWorkflow: + def test_portfolio_when_task_order_exists(self): + portfolio = PortfolioFactory.create() + task_order = TaskOrderFactory(portfolio=portfolio) + assert portfolio.num_task_orders > 0 + + workflow = ShowTaskOrderWorkflow( + user=task_order.creator, task_order_id=task_order.id + ) + assert portfolio == workflow.portfolio + + def test_portfolio_with_portfolio_id(self): + user = UserFactory.create() + portfolio = PortfolioFactory.create(owner=user) + workflow = ShowTaskOrderWorkflow( + user=portfolio.owner, portfolio_id=portfolio.id + ) + assert portfolio == workflow.portfolio + + def test_new_task_order(client, user_session): creator = UserFactory.create() user_session() @@ -42,6 +62,37 @@ def serialize_dates(data): return data +def test_new_to_can_edit_pf_attributes_screen_1(): + portfolio = PortfolioFactory.create() + workflow = ShowTaskOrderWorkflow(user=portfolio.owner) + assert not workflow.pf_attributes_read_only + + +def test_new_pf_can_edit_pf_attributes_on_back_navigation(): + portfolio = PortfolioFactory.create() + pf_task_order = TaskOrderFactory(portfolio=portfolio) + pf_workflow = ShowTaskOrderWorkflow( + user=pf_task_order.creator, task_order_id=pf_task_order.id + ) + assert not pf_workflow.pf_attributes_read_only + + +def test_to_on_pf_cannot_edit_pf_attributes(): + portfolio = PortfolioFactory.create() + pf_task_order = TaskOrderFactory(portfolio=portfolio) + + workflow = ShowTaskOrderWorkflow(user=portfolio.owner, portfolio_id=portfolio.id) + assert portfolio.num_task_orders == 1 + assert workflow.pf_attributes_read_only + + second_task_order = TaskOrderFactory(portfolio=portfolio) + second_workflow = ShowTaskOrderWorkflow( + user=portfolio.owner, task_order_id=second_task_order.id + ) + assert portfolio.num_task_orders > 1 + assert second_workflow.pf_attributes_read_only + + # TODO: this test will need to be more complicated when we add validation to # the forms def test_create_new_task_order(client, user_session, pdf_upload): @@ -66,7 +117,6 @@ def test_create_new_task_order(client, user_session, pdf_upload): created_task_order = TaskOrders.get(creator, created_task_order_id) assert created_task_order.portfolio is not None assert created_task_order.portfolio.name == portfolio_name - assert created_task_order.portfolio is not None assert created_task_order.portfolio.defense_component == defense_component funding_data = slice_data_for_section(task_order_data, "funding") @@ -91,10 +141,8 @@ def test_create_new_task_order_for_portfolio(client, user_session): task_order_data = TaskOrderFactory.dictionary() app_info_data = slice_data_for_section(task_order_data, "app_info") - portfolio_name = "This is ignored for now" - app_info_data["portfolio_name"] = portfolio_name - defense_component = "Defense Health Agency" # this is also ignored - app_info_data["defense_component"] = defense_component + app_info_data["portfolio_name"] = portfolio.name + app_info_data["defense_component"] = portfolio.defense_component response = client.post( url_for("task_orders.update", screen=1, portfolio_id=portfolio.id), @@ -105,6 +153,8 @@ def test_create_new_task_order_for_portfolio(client, user_session): created_task_order_id = response.headers["Location"].split("/")[-1] created_task_order = TaskOrders.get(creator, created_task_order_id) + assert created_task_order.portfolio_name == portfolio.name + assert created_task_order.defense_component == portfolio.defense_component assert created_task_order.portfolio == portfolio