From f6a04aea6264be050db28f4e44a1f6f58cb06e5e Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 28 Jan 2019 10:06:59 -0500 Subject: [PATCH 1/6] Change TO form to accept decimals for CLIN input fields --- atst/forms/task_order.py | 44 ++++++++++++++----- .../routes/task_orders/test_new_task_order.py | 2 +- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 5196e9d0..0e18e7a0 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -1,6 +1,6 @@ from wtforms.fields import ( BooleanField, - IntegerField, + DecimalField, RadioField, SelectField, SelectMultipleField, @@ -10,7 +10,7 @@ from wtforms.fields import ( ) from wtforms.fields.html5 import DateField, TelField from wtforms.widgets import ListWidget, CheckboxInput -from wtforms.validators import Length +from wtforms.validators import Length, InputRequired from flask_wtf.file import FileAllowed from atst.forms.validators import IsNumber, PhoneNumber, RequiredIf @@ -97,20 +97,44 @@ class FundingForm(CacheableForm): ], render_kw={"accept": ".pdf,.png,application/pdf,image/png"}, ) - clin_01 = IntegerField(translate("forms.task_order.clin_01_label")) - clin_02 = IntegerField(translate("forms.task_order.clin_02_label")) - clin_03 = IntegerField(translate("forms.task_order.clin_03_label")) - clin_04 = IntegerField(translate("forms.task_order.clin_04_label")) + clin_01 = DecimalField( + translate("forms.task_order.clin_01_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) + clin_02 = DecimalField( + translate("forms.task_order.clin_02_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) + clin_03 = DecimalField( + translate("forms.task_order.clin_03_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) + clin_04 = DecimalField( + translate("forms.task_order.clin_04_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) class UnclassifiedFundingForm(FundingForm): - clin_02 = IntegerField(translate("forms.task_order.unclassified_clin_02_label")) - clin_04 = IntegerField(translate("forms.task_order.unclassified_clin_04_label")) + clin_02 = DecimalField( + translate("forms.task_order.unclassified_clin_02_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) + clin_04 = DecimalField( + translate("forms.task_order.unclassified_clin_04_label"), + places=2, + validators=[InputRequired(message="Please enter a dollar amount")], + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.clin_02.data = "0" - self.clin_04.data = "0" + self.clin_02.data = "0.00" + self.clin_04.data = "0.00" class OversightForm(CacheableForm): diff --git a/tests/routes/task_orders/test_new_task_order.py b/tests/routes/task_orders/test_new_task_order.py index 16a14c63..5a107bb1 100644 --- a/tests/routes/task_orders/test_new_task_order.py +++ b/tests/routes/task_orders/test_new_task_order.py @@ -119,7 +119,7 @@ def test_task_order_form_shows_errors(client, user_session): body = response.data.decode() assert "There were some errors" in body - assert "Not a valid integer" in body + assert "Not a valid decimal" in body @pytest.fixture From 5057d2d603e1356851b79409409a8e53db7e3576 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Mon, 28 Jan 2019 10:10:03 -0500 Subject: [PATCH 2/6] Update TO Total value to display cents and add in placeholders for classified CLINs --- js/components/forms/funding.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/js/components/forms/funding.js b/js/components/forms/funding.js index dc1f6f46..20b25a2c 100644 --- a/js/components/forms/funding.js +++ b/js/components/forms/funding.js @@ -48,22 +48,21 @@ export default { totalBudget: function() { return [this.clin_01, this.clin_02, this.clin_03, this.clin_04].reduce( function(acc, curr) { - curr = !curr ? 0 : parseInt(curr) + curr = !curr ? 0 : parseFloat(curr) return acc + curr }, 0 ) }, totalBudgetStr: function() { - return this.formatDollars(this.totalBudget) + return this.totalBudget.toLocaleString('us-US', { + style: 'currency', + currency: 'USD', + }) }, }, methods: { - formatDollars: function(intValue) { - const mask = createNumberMask({ prefix: '$', allowDecimal: true }) - return conformToMask(intValue.toString(), mask).conformedValue - }, showUploadInput: function() { this.showUpload = true }, From 090fd167bb32bc891d76c564e1e7298ebf16fe7f Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 29 Jan 2019 14:14:18 -0500 Subject: [PATCH 3/6] Fix UpdateTaskOrderWorkflow to validate using the correct form when unclassified --- atst/domain/task_orders.py | 14 ++++++++++++-- atst/forms/task_order.py | 15 ++++----------- atst/routes/task_orders/new.py | 8 ++++++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 65e9b8dd..4d8bbd78 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -1,5 +1,7 @@ from sqlalchemy.orm.exc import NoResultFound +from flask import current_app as app + from atst.database import db from atst.models.task_order import TaskOrder from atst.models.permissions import Permissions @@ -50,6 +52,8 @@ class TaskOrders(object): ], } + UNCLASSIFIED_FUNDING = ["performance_length", "csp_estimate", "clin_01", "clin_03"] + @classmethod def get(cls, user, task_order_id): try: @@ -90,7 +94,10 @@ class TaskOrders(object): @classmethod def is_section_complete(cls, task_order, section): - if section in TaskOrders.SECTIONS: + sections = TaskOrders.SECTIONS + if not app.config.get("CLASSIFIED"): + sections["funding"] = TaskOrders.UNCLASSIFIED_FUNDING + if section in sections: for attr in TaskOrders.SECTIONS[section]: if getattr(task_order, attr) is None: return False @@ -102,7 +109,10 @@ class TaskOrders(object): @classmethod def all_sections_complete(cls, task_order): - for section in TaskOrders.SECTIONS.keys(): + sections = TaskOrders.SECTIONS + if not app.config.get("CLASSIFIED"): + sections["funding"] = TaskOrders.UNCLASSIFIED_FUNDING + for section in sections: if not TaskOrders.is_section_complete(task_order, section): return False diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 0e18e7a0..5398690b 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -120,22 +120,15 @@ class FundingForm(CacheableForm): class UnclassifiedFundingForm(FundingForm): - clin_02 = DecimalField( + clin_02 = StringField( translate("forms.task_order.unclassified_clin_02_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + filters=[lambda x: x or None], ) - clin_04 = DecimalField( + clin_04 = StringField( translate("forms.task_order.unclassified_clin_04_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + filters=[lambda x: x or None], ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.clin_02.data = "0.00" - self.clin_04.data = "0.00" - class OversightForm(CacheableForm): ko_first_name = StringField( diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 77fa64e3..3a860311 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -128,7 +128,12 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): self.portfolio_id = portfolio_id self._task_order = None self._section = TASK_ORDER_SECTIONS[screen - 1] - self._form = self._section["form"](self.form_data) + 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) @property def form(self): @@ -277,7 +282,6 @@ def update(screen, task_order_id=None, portfolio_id=None): workflow = UpdateTaskOrderWorkflow( g.current_user, form_data, screen, task_order_id, portfolio_id ) - if workflow.validate(): workflow.update() return redirect( From 15f54f10ca8e5732094d3a91785d91b6151cfa3f Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 29 Jan 2019 14:14:38 -0500 Subject: [PATCH 4/6] Use translations for clin error message --- atst/forms/task_order.py | 20 ++++++++++++-------- translations.yaml | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 5398690b..2075e834 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -99,23 +99,27 @@ class FundingForm(CacheableForm): ) clin_01 = DecimalField( translate("forms.task_order.clin_01_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + validators=[ + InputRequired(message=(translate("forms.task_order.clin_validation_error"))) + ], ) clin_02 = DecimalField( translate("forms.task_order.clin_02_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + validators=[ + InputRequired(message=(translate("forms.task_order.clin_validation_error"))) + ], ) clin_03 = DecimalField( translate("forms.task_order.clin_03_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + validators=[ + InputRequired(message=(translate("forms.task_order.clin_validation_error"))) + ], ) clin_04 = DecimalField( translate("forms.task_order.clin_04_label"), - places=2, - validators=[InputRequired(message="Please enter a dollar amount")], + validators=[ + InputRequired(message=(translate("forms.task_order.clin_validation_error"))) + ], ) diff --git a/translations.yaml b/translations.yaml index 471911ed..d49085d7 100644 --- a/translations.yaml +++ b/translations.yaml @@ -222,6 +222,7 @@ forms: clin_02_label: 'CLIN 02: Classified' clin_03_label: 'CLIN 03: Unclassified' clin_04_label: 'CLIN 04: Classified' + clin_validation_error: Please enter a dollar amount unclassified_clin_02_label: 'CLIN 02: Classified (available soon)' unclassified_clin_04_label: 'CLIN 04: Classified (available soon)' oversight_first_name_label: First Name From 3d8013560699bd4941b6f94759556a62a1fc94c0 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 29 Jan 2019 14:48:54 -0500 Subject: [PATCH 5/6] Remove unnecessary classified/unclassified check --- atst/domain/task_orders.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 4d8bbd78..28b238ca 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -109,10 +109,7 @@ class TaskOrders(object): @classmethod def all_sections_complete(cls, task_order): - sections = TaskOrders.SECTIONS - if not app.config.get("CLASSIFIED"): - sections["funding"] = TaskOrders.UNCLASSIFIED_FUNDING - for section in sections: + for section in TaskOrders.SECTIONS.keys(): if not TaskOrders.is_section_complete(task_order, section): return False From 6d61839c654404251b3c6feed53d95795b55ebbb Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Tue, 29 Jan 2019 15:48:02 -0500 Subject: [PATCH 6/6] Move classified/unclassified check into its own method --- atst/domain/task_orders.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 28b238ca..235351a0 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -94,10 +94,7 @@ class TaskOrders(object): @classmethod def is_section_complete(cls, task_order, section): - sections = TaskOrders.SECTIONS - if not app.config.get("CLASSIFIED"): - sections["funding"] = TaskOrders.UNCLASSIFIED_FUNDING - if section in sections: + if section in TaskOrders.sections(): for attr in TaskOrders.SECTIONS[section]: if getattr(task_order, attr) is None: return False @@ -115,6 +112,13 @@ class TaskOrders(object): return True + @classmethod + def sections(cls): + section_list = TaskOrders.SECTIONS + if not app.config.get("CLASSIFIED"): + section_list["funding"] = TaskOrders.UNCLASSIFIED_FUNDING + return section_list + OFFICERS = [ "contracting_officer", "contracting_officer_representative",