diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index bd2a8f77..09a8fe8e 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -9,11 +9,13 @@ from wtforms.fields import ( from wtforms.fields.html5 import DateField from wtforms.validators import Required, Optional, Length from flask_wtf import FlaskForm +from datetime import datetime from .data import JEDI_CLIN_TYPES from .fields import SelectField from .forms import BaseForm from atst.utils.localization import translate +from flask import current_app as app def coerce_enum(enum_inst): @@ -52,17 +54,42 @@ class CLINForm(FlaskForm): def validate(self, *args, **kwargs): valid = super().validate(*args, **kwargs) + contract_start = datetime.strptime( + app.config.get("CONTRACT_START_DATE"), "%Y-%m-%d" + ).date() + contract_end = datetime.strptime( + app.config.get("CONTRACT_END_DATE"), "%Y-%m-%d" + ).date() + 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") + translate("forms.task_order.pop_errors.date_order") ) - return False - else: - return valid + valid = False + + if self.start_date.data and self.start_date.data <= contract_start: + self.start_date.errors.append( + translate( + "forms.task_order.pop_errors.start", + {"date": contract_start.strftime("%b %d, %Y")}, + ) + ) + valid = False + + if self.end_date.data and self.end_date.data >= contract_end: + self.end_date.errors.append( + translate( + "forms.task_order.pop_errors.end", + {"date": contract_end.strftime("%b %d, %Y")}, + ) + ) + valid = False + + return valid class AttachmentForm(BaseForm): diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index c284d3f1..5cc51d9c 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -22,6 +22,9 @@ def render_task_orders_edit( ): render_args = extra_args or {} + render_args["contract_start"] = app.config.get("CONTRACT_START_DATE") + render_args["contract_end"] = app.config.get("CONTRACT_END_DATE") + if task_order_id: task_order = TaskOrders.get(task_order_id) portfolio_id = task_order.portfolio_id diff --git a/config/base.ini b/config/base.ini index 1d4461b8..c034cc7a 100644 --- a/config/base.ini +++ b/config/base.ini @@ -2,6 +2,8 @@ CAC_URL = http://localhost:8000/login-redirect CA_CHAIN = ssl/server-certs/ca-chain.pem CLASSIFIED = false +CONTRACT_START_DATE = 2019-09-14 +CONTRACT_END_DATE = 2022-09-14 COOKIE_SECRET = some-secret-please-replace DISABLE_CRL_CHECK = false CRL_FAIL_OPEN = false diff --git a/js/components/clin_fields.js b/js/components/clin_fields.js index acf45d09..eb958780 100644 --- a/js/components/clin_fields.js +++ b/js/components/clin_fields.js @@ -1,3 +1,6 @@ +import * as R from 'ramda' +import { format } from 'date-fns' + import DateSelector from './date_selector' import { emitEvent } from '../lib/emitters' import Modal from '../mixins/modal' @@ -34,6 +37,14 @@ export default { type: String, default: null, }, + contractStart: { + type: String, + required: true, + }, + contractEnd: { + type: String, + required: true, + }, }, data: function() { @@ -44,19 +55,44 @@ export default { ? new Date(this.initialEndDate) : undefined const popValidation = !this.initialStartDate ? false : start < end - const showPopValidation = !this.initialStartDate ? false : !popValidation const clinNumber = !!this.initialClinNumber ? this.initialClinNumber : undefined + const contractStartDate = new Date(this.contractStart) + const contractEndDate = new Date(this.contractEnd) return { clinIndex: this.initialClinIndex, startDate: start, endDate: end, popValid: popValidation, - showPopError: showPopValidation, + startDateValid: false, + endDateValid: false, + contractStartDate: contractStartDate, + contractEndDate: contractEndDate, clinNumber: clinNumber, showClin: true, + popErrors: [], + validations: [ + { + func: this.popDateOrder, + message: 'PoP start date must be before end date.', + }, + { + func: this.popStartsAfterContract, + message: `PoP start date must be on or after ${format( + contractStartDate, + 'MMM D, YYYY' + )}.`, + }, + { + func: this.popEndsBeforeContract, + message: `PoP end date must be before or on ${format( + contractEndDate, + 'MMM D, YYYY' + )}.`, + }, + ], } }, @@ -74,29 +110,58 @@ export default { methods: { checkPopValid: function() { - return this.startDate < this.endDate + return ( + this.popDateOrder() && + this.popStartsAfterContract() && + this.popEndsBeforeContract() + ) }, validatePop: function() { - if (!!this.startDate && !!this.endDate) { - // only want to update popValid and showPopError if both dates are filled in - this.popValid = this.checkPopValid() - this.showPopError = !this.popValid - } - + this.popValid = this.checkPopValid() emitEvent('field-change', this, { name: 'clins-' + this.clinIndex + '-' + POP, - valid: this.checkPopValid(), + valid: this.popValid, }) + + this.popErrors = R.pipe( + R.map(validation => + !validation.func() ? validation.message : undefined + ), + R.filter(Boolean) + )(this.validations) + }, + + popStartsAfterContract: function() { + if (this.startDateValid) { + return this.startDate >= this.contractStartDate + } + return true + }, + + popEndsBeforeContract: function() { + if (this.endDateValid) { + return this.endDate <= this.contractEndDate + } + return true + }, + + popDateOrder: function() { + if (!!this.startDate && !!this.endDate) { + return this.startDate < this.endDate + } + return true }, handleFieldChange: function(event) { if (this._uid === event.parent_uid) { if (event.name.includes(START_DATE)) { if (!!event.value) this.startDate = new Date(event.value) + if (!!event.valid) this.startDateValid = event.valid this.validatePop() } else if (event.name.includes(END_DATE)) { if (!!event.value) this.endDate = new Date(event.value) + if (!!event.valid) this.endDateValid = event.valid this.validatePop() } else if (event.name.includes(NUMBER)) { this.clinNumber = event.value diff --git a/templates/task_orders/step_3.html b/templates/task_orders/step_3.html index 1d86a672..bff66003 100644 --- a/templates/task_orders/step_3.html +++ b/templates/task_orders/step_3.html @@ -23,6 +23,8 @@ v-bind:initial-clin-index='clinIndex' v-bind:initial-clin-type="'JEDI_CLIN_1'" {% endif %} + v-bind:contract-start="'{{ contract_start | string }}'" + v-bind:contract-end="'{{ contract_end | string }}'" inline-template>
{{ 'task_orders.form.pop_example' | translate }} @@ -314,15 +317,9 @@
Not sure how to describe your scope? Read some examples to get some inspiration.
' scope_label: Cloud project scope - start_date_error: PoP start date must be before end date. team_experience: built_1: Built, migrated, or consulted on 1-2 applications built_3: Built, migrated, or consulted on 3-5 applications @@ -382,7 +385,7 @@ task_orders: obligated_funds_label: Obligated Funds pop: Period of Performance pop_end: End Date - pop_end_alert: "A CLIN's period of performance must end before September 14, 2022." + pop_end_alert: "A CLIN's period of performance must end before {end_date}." pop_example: "For example: 07 04 1776" pop_start: Start Date review_button: Review task order