diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 428eddd5..1f1277fc 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -9,27 +9,44 @@ from atst.utils.flash import formatted_flash as flash @task_orders_bp.route("/portfolios//task_orders/new") +@task_orders_bp.route("/portfolios//task_orders//edit") @user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form") -def new(portfolio_id): +def edit(portfolio_id, task_order_id=None): + form = None + + if task_order_id: + task_order = TaskOrders.get(task_order_id) + form = TaskOrderForm(number=task_order.number) + else: + form = TaskOrderForm() + cancel_url = ( http_request.referrer if http_request.referrer else url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id) ) - return render_template( - "task_orders/new.html", form=TaskOrderForm(), cancel_url=cancel_url - ) + + return render_template("task_orders/edit.html", form=form, cancel_url=cancel_url) @task_orders_bp.route("/portfolios//task_orders/new", methods=["POST"]) +@task_orders_bp.route( + "/portfolios//task_orders/", methods=["POST"] +) @user_can(Permissions.CREATE_TASK_ORDER, message="create new task order") -def create(portfolio_id): +def update(portfolio_id, task_order_id=None): form_data = http_request.form form = TaskOrderForm(form_data) - # todo: add in better error handling for dupe TO numbers + if form.validate(): - task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data) + task_order = None + if task_order_id: + task_order = TaskOrders.update(task_order_id, **form.data) + else: + task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data) + flash("task_order_draft") + return redirect( url_for( "task_orders.edit", @@ -37,33 +54,6 @@ def create(portfolio_id): task_order_id=task_order.id, ) ) - else: - flash("form_errors") - return render_template("task_orders/new.html", form=form) - - -# Combine with new route? -@task_orders_bp.route("/portfolios//task_orders//edit") -@user_can(Permissions.CREATE_TASK_ORDER, message="update task order") -def edit(portfolio_id, task_order_id): - task_order = TaskOrders.get(task_order_id) - form = TaskOrderForm(number=task_order.number) - return render_template("task_orders/edit.html", form=form) - - -# Combine with create route? -@task_orders_bp.route( - "/portfolios//task_orders/", methods=["POST"] -) -@user_can(Permissions.CREATE_TASK_ORDER, message="update task order") -def update(portfolio_id, task_order_id=None): - form_data = http_request.form - form = TaskOrderForm(form_data) - - if form.validate(): - TaskOrders.update(task_order_id, **form.data) - flash("task_order_draft") - return render_template("task_orders/new.html", form=form) else: flash("form_errors") return render_template("task_orders/edit.html", form=form) diff --git a/templates/portfolios/task_orders/index.html b/templates/portfolios/task_orders/index.html index a10f35ee..59c0a3f8 100644 --- a/templates/portfolios/task_orders/index.html +++ b/templates/portfolios/task_orders/index.html @@ -93,7 +93,7 @@ {% call StickyCTA(text="Funding") %} {% endcall %} @@ -125,7 +125,7 @@ {{ EmptyState( 'This portfolio doesn’t have any active or pending task orders.', action_label='Add a New Task Order', - action_href=url_for('task_orders.new', portfolio_id=portfolio.id), + action_href=url_for('task_orders.edit', portfolio_id=portfolio.id), icon='cloud', ) }} {% endif %} diff --git a/templates/task_orders/_new-delete-later.html b/templates/task_orders/_new-delete-later.html deleted file mode 100644 index d4b7c48a..00000000 --- a/templates/task_orders/_new-delete-later.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -
- - {% include 'task_orders/new/menu.html' %} - - {% include "fragments/flash.html" %} - - {% block form_action %} -
- {% endblock %} - -
- -
-

-
Task Order Builder
- {% block heading %}{% endblock %} -

-
- -
- - {{ form.csrf_token }} - {% block form %} - form goes here - {% endblock %} - -
- -
- - {% block next %} - -
- -
- - {% endblock %} - -
- -
- -{% endblock %} diff --git a/templates/task_orders/edit.html b/templates/task_orders/edit.html index a856925b..f67c42ea 100644 --- a/templates/task_orders/edit.html +++ b/templates/task_orders/edit.html @@ -11,11 +11,17 @@ {% include "portfolios/header.html" %} {% endblock %} -
+ {{ form.csrf_token }}
Add Funding + + + {{ "common.cancel" | translate }} + {{ SaveButton(text=('common.save' | translate), element='input', form='new-task-order') }}
diff --git a/templates/task_orders/new.html b/templates/task_orders/new.html deleted file mode 100644 index 43b96c23..00000000 --- a/templates/task_orders/new.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "base.html" %} - -{% from 'components/save_button.html' import SaveButton %} -{% from 'components/text_input.html' import TextInput %} - -{% block content %} -
- {% include "fragments/flash.html" %} -
- {% block portfolio_header %} - {% include "portfolios/header.html" %} - {% endblock %} - - - {{ form.csrf_token }} -
- - Add Funding - - - {{ "common.cancel" | translate }} - - {{ SaveButton(text=('common.save' | translate), element='input', form='new-task-order') }} -
-
- {{ "task_orders.new.form_help_text" | translate }} -
- {{ TextInput(form.number, validation='taskOrderNumber') }} -
- -
-
-
-{% endblock %} diff --git a/templates/task_orders/new/_user_fields.html b/templates/task_orders/new/_user_fields.html deleted file mode 100644 index 51028005..00000000 --- a/templates/task_orders/new/_user_fields.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
- {{ TextInput(first_name) }} -
- -
- {{ TextInput(last_name) }} -
-
- -
-
- {{ TextInput(email, placeholder='name@mail.mil') }} -
- -
- {{ TextInput(dod_id, placeholder='1234567890') }} -
-
diff --git a/templates/task_orders/new/app_info.html b/templates/task_orders/new/app_info.html deleted file mode 100644 index 39f5fbb5..00000000 --- a/templates/task_orders/new/app_info.html +++ /dev/null @@ -1,56 +0,0 @@ -{% from "components/text_input.html" import TextInput %} -{% 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 }} -{% endblock %} - -{% block form %} - - - -

{{ "forms.task_order.basic_intro" | translate }}

-

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

- -{% 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) }} - -
- {% if portfolio %} - {{ ReviewField(heading="forms.task_order.defense_component_label" | translate, field=portfolio.defense_component) }} - {% else %} - {{ OptionsInput(form.defense_component) }} - {% endif %} -
- -
- -

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

-

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

-{{ OptionsInput(form.app_migration) }} -

{{ "forms.task_order.app_migration.not_sure_help" | translate }}

-{{ OptionsInput(form.native_apps) }} -

{{ "forms.task_order.native_apps.not_sure_help" | translate }}

-{{ MultiCheckboxInput(form.complexity, form.complexity_other) }} - -
- -

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

-

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

-{{ MultiCheckboxInput(form.dev_team, form.dev_team_other) }} -{{ OptionsInput(form.team_experience) }} - -
- -

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

-

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

- -{% endblock %} diff --git a/templates/task_orders/new/funding.html b/templates/task_orders/new/funding.html deleted file mode 100644 index 6c6ab3fb..00000000 --- a/templates/task_orders/new/funding.html +++ /dev/null @@ -1,66 +0,0 @@ -{% from "components/text_input.html" import TextInput %} -{% from "components/options_input.html" import OptionsInput %} -{% from "components/date_input.html" import DateInput %} -{% from "components/upload_input.html" import UploadInput %} - -{% from "components/icon.html" import Icon %} - -{% block heading %} - {{ "task_orders.new.funding.section_title" | translate }} -{% endblock %} - -{% block form %} - - -
- -

{{ "task_orders.new.funding.subtitle" | translate }}

- -

{{ "task_orders.new.funding.performance_period_title" | translate }}

-

{{ "task_orders.new.funding.performance_period_description" | translate }}

-

{{ "task_orders.new.funding.performance_period_paragraph" | translate }}

- {{ OptionsInput(form.performance_length) }} - -
- -

{{ "task_orders.new.funding.estimate_usage_title" | translate }}

-

{{ "task_orders.new.funding.estimate_usage_description" | translate }}

-

- {{ Icon("link")}} Go to Cloud Service Provider’s estimate calculator -

-

{{ "task_orders.new.funding.estimate_usage_paragraph" | translate }}

- {{ UploadInput(form.csp_estimate, show_label=True) }} - -
- -

{{ "task_orders.new.funding.cloud_calculations_title" | translate }}

-

{{ "task_orders.new.funding.cloud_calculations_paragraph" | translate }}

- -

{{ "task_orders.new.funding.cloud_offerings_title" | translate }}

-

{{ "task_orders.new.funding.cloud_offerings_paragraph" | translate }}

- {{ TextInput(form.clin_01, validation='dollars', placeholder="$0.00") }} - {{ TextInput(form.clin_02, validation='dollars', placeholder="$0.00", disabled=(not config.CLASSIFIED)) }} - -

{{ "task_orders.new.funding.support_assistance_title" | translate }}

-

{{ "task_orders.new.funding.support_assistance_paragraph" | translate }}

- {{ TextInput(form.clin_03, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.', placeholder="$0.00") }} - {{ TextInput(form.clin_04, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.', placeholder="$0.00", disabled=(not config.CLASSIFIED)) }} -
-
- -{% endblock %} - -{% block next %} -
-
-

{{ "task_orders.new.funding.total" | translate }}

-
-
- {{ super() }} -
-
-{% endblock %} diff --git a/templates/task_orders/new/get_started.html b/templates/task_orders/new/get_started.html deleted file mode 100644 index 73b00074..00000000 --- a/templates/task_orders/new/get_started.html +++ /dev/null @@ -1,78 +0,0 @@ -{% from "components/icon.html" import Icon %} - - -{% extends "base.html" %} - -{% macro Help(icon_name="", name="", description="", link_text="") %} -
- {{ Icon(icon_name, classes="task-order-help__icon") }} -

{{ name }}

-

{{ description }}

- {{ link_text }} -
-{% endmacro %} - -{% block content %} - -
-
-

{{ "task_orders.new.get_started.title" | translate }}

-
-

- {{ "task_orders.new.get_started.intro" | translate }} -

-

- {{ "task_orders.new.get_started.intro2" | translate }} -

-
- -
    -
  • Statement of work
  • -
  • Market research
  • -
  • Security documentation
  • -
  • Various approvals
  • -
-
-
-

- {{ "task_orders.new.get_started.intro3" | translate }} -

-

- {{ "task_orders.new.get_started.intro4" | translate }} -

-
- -
- -
-

{{ "task_orders.new.get_started.team_header" | translate }}

-
- {{ Help( - name="Development Lead", - icon_name="computer", - description="Your development lead will decide and estimate what types of cloud offerings your group needs.", - link_text="Share cloud estimate link") }} - {{ Help( - name="Security Lead", - icon_name="shield", - description="Your security lead will review and approve your security classification needs. They will also review and complete a standardized DD-254.", - link_text="You'll need their DoD ID number") }} - {{ Help( - name="Contracting Officer", - icon_name="dollar-sign", - description="Your contracting officer will review your funding needs and ultimately approve your task order.", - link_text="You'll need their DoD ID number") }} -
-
- - -
-{% endblock %} diff --git a/templates/task_orders/new/menu.html b/templates/task_orders/new/menu.html deleted file mode 100644 index 296c44fd..00000000 --- a/templates/task_orders/new/menu.html +++ /dev/null @@ -1,22 +0,0 @@ -
- -
diff --git a/templates/task_orders/new/oversight.html b/templates/task_orders/new/oversight.html deleted file mode 100644 index 4c7dc5ac..00000000 --- a/templates/task_orders/new/oversight.html +++ /dev/null @@ -1,57 +0,0 @@ -{% from "components/user_info.html" import UserInfo %} -{% from "components/checkbox_input.html" import CheckboxInput %} -{% from "components/text_input.html" import TextInput %} - -{% block heading %} - {{ "task_orders.new.oversight.section_title" | translate }} -{% endblock %} - -{% block form %} - - -

{{ "task_orders.new.oversight.ko_info_title" | translate }}

-

{{ "task_orders.new.oversight.ko_info_paragraph" | translate }}

- -
- {{ UserInfo(form.ko_first_name, form.ko_last_name, form.ko_email, form.ko_phone_number) }} - {{ CheckboxInput(form.ko_invite) }} - - - {{ TextInput(form.ko_dod_id, placeholder="1234567890", tooltip="task_orders.new.oversight.dod_id_tooltip" | translate, tooltip_title='Why', validation='dodId', classes="task-order__invite-officer")}} - - - -
- -

{{ "task_orders.new.oversight.cor_info_title" | translate }}

-

{{ "task_orders.new.oversight.cor_info_paragraph" | translate }}

- {{ CheckboxInput(form.am_cor, classes="normal") }} - - - -
- {{ UserInfo(form.cor_first_name, form.cor_last_name, form.cor_email, form.cor_phone_number) }} - {{ CheckboxInput(form.cor_invite) }} - -
-
-
- -
- -

{{ "task_orders.new.oversight.so_info_title" | translate }}

-

{{ "task_orders.new.oversight.so_info_paragraph" | translate }}

- {{ UserInfo(form.so_first_name, form.so_last_name, form.so_email, form.so_phone_number) }} - {{ CheckboxInput(form.so_invite) }} - - - {{ TextInput(form.so_dod_id, placeholder="1234567890", tooltip="task_orders.new.oversight.dod_id_tooltip" | translate, tooltip_title='Why', validation='dodId', classes="task-order__invite-officer")}} - - -
-
- - -{% endblock %} diff --git a/templates/task_orders/new/review.html b/templates/task_orders/new/review.html deleted file mode 100644 index 0819bf39..00000000 --- a/templates/task_orders/new/review.html +++ /dev/null @@ -1,104 +0,0 @@ -{% from "components/edit_link.html" import EditLink %} -{% from "components/required_label.html" import RequiredLabel %} -{% from "components/icon.html" import Icon %} -{% from "components/review_field.html" import ReviewField %} - -{% block heading %} - {{ "task_orders.new.review.section_title"| translate }} -{% endblock %} - -{% block form %} - -

{{ "task_orders.new.review.app_info"| translate }} {{ EditLink(url_for("task_orders.new", screen=1, task_order_id=task_order.id)) }} -

-{% include "fragments/task_order_review/app_info.html" %} -
- -

{{ "task_orders.new.review.reporting"| translate }} {{ EditLink(url_for("task_orders.new", screen=1, task_order_id=task_order.id, _anchor="reporting")) }}

- -
- {{ - ReviewField( - ("forms.task_order.app_migration.label" | translate), - ( - ("forms.task_order.app_migration.{}".format(task_order.app_migration) | translate) if task_order.app_migration - ), - filter='safe' - ) - }} - - {{ - ReviewField( - ("forms.task_order.native_apps.label" | translate), - ( - ("forms.task_order.native_apps.{}".format(task_order.native_apps) | translate) if task_order.native_apps - ) - ) - }} -
- -

{{ "task_orders.new.review.complexity"| translate }}

-{% if task_order.complexity %} -
    - {% for item in task_order.complexity %} -
  • - {{ Icon('ok', classes='icon--gray icon--medium') }}{{ "forms.task_order.complexity.{}".format(item) | translate }}{% if item == 'other' %}: {{ task_order.complexity_other }}{% endif %} -
  • - {% endfor %} -
-{% else %} -

{{ RequiredLabel() }}

-{% endif %} - -
-
-

{{ "task_orders.new.review.team"| translate }}

- {% if task_order.dev_team %} -
    - {% for item in task_order.dev_team %} -
  • - {% if item == 'other' %} - {{ Icon('ok', classes='icon--gray icon--medium') }}Other: {{ task_order.dev_team_other }} - {% else %} - {{ Icon('ok', classes='icon--gray icon--medium') }}{{ "forms.task_order.dev_team.{}".format(item) | translate }} - {% endif %} -
  • - {% endfor %} -
- {% else %} -

{{ RequiredLabel() }}

- {% endif %} -
- - {{ - ReviewField( - ("forms.task_order.team_experience.label" |translate), - ( - ("forms.task_order.team_experience.{}".format(task_order.team_experience) | translate) if task_order.team_experience - ) - ) - }} -
- -
- -

{{ "task_orders.new.review.funding"| translate }} {{ EditLink(url_for("task_orders.new", screen=2, task_order_id=task_order.id)) }}

-{% include "fragments/task_order_review/funding.html" %} -
- -

{{ "task_orders.new.review.oversight"| translate }} {{ EditLink(url_for("task_orders.new", screen=3, task_order_id=task_order.id)) }}

-{% include "fragments/task_order_review/oversight.html" %} -{% endblock %} - -{% block next %} -
- -
-{% endblock %} - -{% block form_action %} - {% if task_order_id %} -
- {% endif %} -{% endblock %} diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 6ac1df4a..17904355 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -35,14 +35,14 @@ def user(): def test_task_orders_new(client, user_session, portfolio): user_session(portfolio.owner) - response = client.get(url_for("task_orders.new", portfolio_id=portfolio.id)) + response = client.get(url_for("task_orders.edit", portfolio_id=portfolio.id)) assert response.status_code == 200 def test_task_orders_create(client, user_session, portfolio): user_session(portfolio.owner) response = client.post( - url_for("task_orders.create", portfolio_id=portfolio.id), + url_for("task_orders.update", portfolio_id=portfolio.id), data={"number": "0123456789"}, ) assert response.status_code == 302 @@ -52,7 +52,7 @@ def test_task_orders_create_invalid_data(client, user_session, portfolio): user_session(portfolio.owner) num_task_orders = len(portfolio.task_orders) response = client.post( - url_for("task_orders.create", portfolio_id=portfolio.id), data={"number": ""} + url_for("task_orders.update", portfolio_id=portfolio.id), data={"number": ""} ) assert response.status_code == 200 assert num_task_orders == len(portfolio.task_orders) @@ -78,7 +78,9 @@ def test_task_order_form_shows_errors(client, user_session, task_order): funding_data.update({"clin_01": "one milllllion dollars"}) response = client.post( - url_for("task_orders.update", screen=2, task_order_id=task_order.id), + url_for( + "task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id + ), data=funding_data, follow_redirects=False, ) diff --git a/tests/test_access.py b/tests/test_access.py index 7effd375..a9095a41 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -477,7 +477,7 @@ def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monke get_url_assert_status(rando, url, 404) -# task_orders.new +# task_orders.edit @pytest.mark.skip(reason="Update after new TO form implemented") def test_task_orders_new_access(get_url_assert_status): ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) @@ -485,7 +485,7 @@ def test_task_orders_new_access(get_url_assert_status): rando = user_with() portfolio = PortfolioFactory.create(owner=owner) - url = url_for("task_orders.new", portfolio_id=portfolio.id) + url = url_for("task_orders.edit", portfolio_id=portfolio.id) get_url_assert_status(owner, url, 200) get_url_assert_status(ccpo, url, 200) get_url_assert_status(rando, url, 404) @@ -535,21 +535,23 @@ def test_task_orders_update_access(post_url_assert_status): ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) owner = user_with() rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) - url = url_for("task_orders.update", screen=1) + url = url_for("task_orders.update", portfolio_id=portfolio.id) post_url_assert_status(owner, url, 200) post_url_assert_status(ccpo, url, 200) post_url_assert_status(rando, url, 200) - portfolio = PortfolioFactory.create(owner=owner) task_order = TaskOrderFactory.create(portfolio=portfolio) - url = url_for("task_orders.update", screen=2, task_order_id=task_order.id) + url = url_for( + "task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id + ) post_url_assert_status(owner, url, 302) post_url_assert_status(ccpo, url, 302) post_url_assert_status(rando, url, 404) - url = url_for("task_orders.update", screen=1, portfolio_id=portfolio.id) + url = url_for("task_orders.update", portfolio_id=portfolio.id) post_url_assert_status(owner, url, 302) post_url_assert_status(ccpo, url, 302) post_url_assert_status(rando, url, 404)