diff --git a/atst/forms/forms.py b/atst/forms/forms.py index bb0d086f..4287c81c 100644 --- a/atst/forms/forms.py +++ b/atst/forms/forms.py @@ -30,9 +30,9 @@ class BaseForm(FlaskForm): _data[field] = None return _data - def validate(self, *args, **kwargs): + def validate(self, *args, flash_invalid=True, **kwargs): valid = super().validate(*args, **kwargs) - if not valid: + if not valid and flash_invalid: flash("form_errors") return valid diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index fca7bcfd..80a31085 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -53,7 +53,11 @@ class CLINForm(FlaskForm): def validate(self, *args, **kwargs): valid = super().validate(*args, **kwargs) - if self.start_date.data > self.end_date.data: + if ( + self.start_date.data + and self.end_date.data + and self.start_date.data > self.end_date.data + ): self.start_date.errors.append( translate("forms.task_order.start_date_error") ) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 43736e1a..9e88f8d4 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -21,8 +21,16 @@ def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, for else: render_args["form"] = form or TaskOrderForm() - render_args["cancel_url"] = url_for( - "task_orders.portfolio_funding", portfolio_id=portfolio_id + render_args["cancel_save_url"] = url_for( + "task_orders.cancel_edit", + task_order_id=task_order_id, + portfolio_id=portfolio_id, + save=True, + ) + render_args["cancel_discard_url"] = url_for( + "task_orders.cancel_edit", + task_order_id=task_order_id, + portfolio_id=portfolio_id, ) return render_template(template, **render_args) @@ -115,6 +123,36 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): ) +@task_orders_bp.route( + "/portfolios//task_orders/form/cancel", methods=["POST"] +) +@task_orders_bp.route("/task_orders//form/cancel", methods=["POST"]) +@user_can(Permissions.CREATE_TASK_ORDER, message="cancel task order form") +def cancel_edit(task_order_id=None, portfolio_id=None): + save = http_request.args.get("save", False) + if save: + form_data = {**http_request.form} + form = None + if task_order_id: + task_order = TaskOrders.get(task_order_id) + form = TaskOrderForm(form_data, obj=task_order) + else: + form = TaskOrderForm(form_data) + + if form.validate(flash_invalid=False): + 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 + ) + + return redirect( + url_for("task_orders.portfolio_funding", portfolio_id=g.portfolio.id) + ) + + @task_orders_bp.route("/task_orders//form/step_2") @user_can(Permissions.CREATE_TASK_ORDER, message="view task order form") def form_step_two_add_number(task_order_id): diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index f9604e17..eb801826 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -249,3 +249,12 @@ } } } + +.task-order__modal-cancel { + text-align: center; +} + +.task-order__modal-cancel_buttons { + display: flex; + justify-content: center; +} diff --git a/templates/task_orders/builder_base.html b/templates/task_orders/builder_base.html index 3b571459..0f4848f2 100644 --- a/templates/task_orders/builder_base.html +++ b/templates/task_orders/builder_base.html @@ -1,6 +1,7 @@ {% extends "portfolios/base.html" %} {% from "components/sticky_cta.html" import StickyCTA %} +{% from "components/modal.html" import Modal %} {% block portfolio_content %} @@ -26,13 +27,23 @@ {% endif %} {{ "common.cancel" | translate }} {% endcall %} + {% call Modal(name='cancel', dismissable=True) %} +
+

Do you want to save this draft?

+
+ + +
+
+ {% endcall %} + {% include "fragments/flash.html" %}
diff --git a/templates/task_orders/step_1.html b/templates/task_orders/step_1.html index 21b38aac..e81ad47c 100644 --- a/templates/task_orders/step_1.html +++ b/templates/task_orders/step_1.html @@ -12,7 +12,6 @@ {% endif %} {% set next_button_text = "Next: Add TO Number" %} -{% set previous_button_link = cancel_url %} {% set step = "1" %} diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index b88bbf7b..0ab8a2bc 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -1,20 +1,12 @@ import pytest -from flask import url_for +from flask import url_for, get_flashed_messages from datetime import timedelta, date -from atst.domain.permission_sets import PermissionSets from atst.domain.task_orders import TaskOrders from atst.models.task_order import Status as TaskOrderStatus -from atst.models import Attachment, TaskOrder -from atst.utils.localization import translate +from atst.models import TaskOrder -from tests.factories import ( - CLINFactory, - PortfolioFactory, - PortfolioRoleFactory, - TaskOrderFactory, - UserFactory, -) +from tests.factories import CLINFactory, PortfolioFactory, TaskOrderFactory, UserFactory @pytest.fixture @@ -326,6 +318,72 @@ def test_task_orders_edit_redirects_to_latest_incomplete_step( assert expected_step in response.location +def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, session): + user_session(task_order.portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data={"number": "7896564324567"}, + ) + assert response.status_code == 302 + + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.number == "7896564324567" + + +def test_cancel_can_create_new_to(client, user_session, portfolio): + user_session(portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", portfolio_id=portfolio.id), + data={"number": "7643906432984"}, + ) + assert response.status_code == 302 + + +def test_cancel_edit_does_not_save_invalid_form_input(client, user_session, session): + task_order = TaskOrderFactory.create() + user_session(task_order.portfolio.owner) + bad_data = {"clins-0-jedi_clin_type": "foo"} + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data=bad_data, + ) + assert response.status_code == 302 + + # CLINs should be unchanged + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.clins == task_order.clins + + +def test_cancel_edit_on_invalid_input_does_not_flash( + app, client, user_session, session +): + task_order = TaskOrderFactory.create() + user_session(task_order.portfolio.owner) + + bad_data = {"clins-0-jedi_clin_type": "foo"} + + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True), + data=bad_data, + ) + + assert len(get_flashed_messages()) == 0 + + +def test_cancel_edit_without_saving(client, user_session, session): + task_order = TaskOrderFactory.create(number=None) + user_session(task_order.portfolio.owner) + response = client.post( + url_for("task_orders.cancel_edit", task_order_id=task_order.id), + data={"number": "7643906432984"}, + ) + assert response.status_code == 302 + + # TO number should be unchanged + updated_task_order = session.query(TaskOrder).get(task_order.id) + assert updated_task_order.number is None + + @pytest.mark.skip(reason="Reevaluate how form handles invalid data") def test_task_orders_update_invalid_data(client, user_session, portfolio): user_session(portfolio.owner)