diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 2a1b8ad2..41ebeb81 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -142,6 +142,14 @@ class TaskOrderForm(BaseForm): ) clins = FieldList(FormField(CLINForm)) + @property + def data(self): + _data = super().data + if _data["number"] == "": + _data["number"] = None + + return _data + class SignatureForm(BaseForm): signature = BooleanField( diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index dca751e3..f6e53b75 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -10,7 +10,7 @@ from flask import ( from .blueprint import task_orders_bp from atst.domain.authz.decorator import user_can_access_decorator as user_can -from atst.domain.exceptions import NoAccessError +from atst.domain.exceptions import NoAccessError, AlreadyExistsError from atst.domain.task_orders import TaskOrders from atst.forms.task_order import TaskOrderForm, SignatureForm from atst.models.permissions import Permissions @@ -50,7 +50,26 @@ def render_task_orders_edit( return render_template(template, **render_args) -def update_task_order( +def update_task_order(form, portfolio_id=None, task_order_id=None, flash_invalid=True): + if form.validate(flash_invalid=flash_invalid): + task_order = None + try: + if task_order_id: + task_order = TaskOrders.update(task_order_id, **form.data) + portfolio_id = task_order.portfolio_id + else: + task_order = TaskOrders.create(portfolio_id, **form.data) + + return task_order + + except AlreadyExistsError: + flash("task_order_number_error", to_number=form.data["number"]) + return False + else: + return False + + +def update_and_render_next( form_data, next_page, current_template, portfolio_id=None, task_order_id=None ): form = None @@ -60,14 +79,8 @@ def update_task_order( else: form = TaskOrderForm(form_data) - if form.validate(): - task_order = None - if task_order_id: - task_order = TaskOrders.update(task_order_id, **form.data) - portfolio_id = task_order.portfolio_id - else: - task_order = TaskOrders.create(portfolio_id, **form.data) - + task_order = update_task_order(form, portfolio_id, task_order_id) + if task_order: return redirect(url_for(next_page, task_order_id=task_order.id)) else: return ( @@ -149,7 +162,7 @@ def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None): next_page = "task_orders.form_step_two_add_number" current_template = "task_orders/step_1.html" - return update_task_order( + return update_and_render_next( form_data, next_page, current_template, @@ -176,12 +189,8 @@ def cancel_edit(task_order_id=None, portfolio_id=None): 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(portfolio_id, **form.data) + update_task_order(form, portfolio_id, task_order_id, flash_invalid=False) + elif not save and task_order_id: TaskOrders.delete(task_order_id) @@ -205,7 +214,7 @@ def submit_form_step_two_add_number(task_order_id): next_page = "task_orders.form_step_three_add_clins" current_template = "task_orders/step_2.html" - return update_task_order( + return update_and_render_next( form_data, next_page, current_template, task_order_id=task_order_id ) @@ -225,7 +234,7 @@ def submit_form_step_three_add_clins(task_order_id): next_page = "task_orders.form_step_four_review" current_template = "task_orders/step_3.html" - return update_task_order( + return update_and_render_next( form_data, next_page, current_template, task_order_id=task_order_id ) diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 87b543b8..fabba4d2 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -165,6 +165,11 @@ MESSAGES = { "message_template": translate("task_orders.form.draft_alert_message"), "category": "warning", }, + "task_order_number_error": { + "title_template": "", + "message_template": """{{ 'flash.task_order_number_error.message' | translate({ 'to_number': to_number }) }}""", + "category": "error", + }, "task_order_submitted": { "title_template": "Your Task Order has been uploaded successfully.", "message_template": """ diff --git a/tests/forms/test_task_order.py b/tests/forms/test_task_order.py index de95e555..97759c81 100644 --- a/tests/forms/test_task_order.py +++ b/tests/forms/test_task_order.py @@ -2,7 +2,7 @@ import datetime from dateutil.relativedelta import relativedelta from flask import current_app as app -from atst.forms.task_order import CLINForm +from atst.forms.task_order import CLINForm, TaskOrderForm from atst.models import JEDICLINType from atst.utils.localization import translate @@ -106,3 +106,9 @@ def test_clin_form_dollar_amounts_out_of_range(): assert ( translate("forms.task_order.clin_funding_errors.funding_range_error") ) in invalid_clin_form.obligated_amount.errors + + +def test_no_number(): + http_request_form_data = {} + form = TaskOrderForm(http_request_form_data) + assert form.data["number"] is None diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 1e8e16eb..a7f5a991 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -170,6 +170,26 @@ def test_task_orders_submit_form_step_two_add_number(client, user_session, task_ assert task_order.number == "1234567890" +def test_task_orders_submit_form_step_two_enforces_unique_number( + client, user_session, task_order, session +): + number = "1234567890123" + dupe_task_order = TaskOrderFactory.create(number=number) + portfolio = task_order.portfolio + user_session(task_order.portfolio.owner) + form_data = {"number": number} + session.begin_nested() + response = client.post( + url_for( + "task_orders.submit_form_step_two_add_number", task_order_id=task_order.id + ), + data=form_data, + ) + session.rollback() + + assert response.status_code == 400 + + def test_task_orders_submit_form_step_two_add_number_existing_to( client, user_session, task_order ): diff --git a/translations.yaml b/translations.yaml index 1fa09b83..1d876e97 100644 --- a/translations.yaml +++ b/translations.yaml @@ -123,6 +123,8 @@ flash: new_ppoc_message: 'You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC.' new_ppoc_title: Primary point of contact updated success: Success! + task_order_number_error: + message: 'The TO number has already been entered for a JEDI task order #{to_number}. Please double-check the TO number you are entering. If you believe this is in error, please contact support@cloud.mil.' new_application_member: title: "{user_name}'s invitation has been sent" message: "{user_name}'s access to this Application is pending until they sign in for the first time."