Merge pull request #995 from dod-ccpo/pop-dates-bug

PoP start date must be before end
This commit is contained in:
leigh-mil 2019-08-02 16:10:17 -04:00 committed by GitHub
commit e333f32aea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 23 deletions

View File

@ -51,6 +51,16 @@ class CLINForm(FlaskForm):
) )
loas = FieldList(StringField()) loas = FieldList(StringField())
def validate(self, *args, **kwargs):
valid = super().validate(*args, **kwargs)
if self.start_date.data > self.end_date.data:
self.start_date.errors.append(
translate("forms.task_order.start_date_error")
)
return False
else:
return valid
class TaskOrderForm(BaseForm): class TaskOrderForm(BaseForm):
number = StringField(label=translate("forms.task_order.number_description")) number = StringField(label=translate("forms.task_order.number_description"))

View File

@ -5,6 +5,9 @@ import textinput from './text_input'
const JEDI_CLIN_TYPE = 'jedi_clin_type' const JEDI_CLIN_TYPE = 'jedi_clin_type'
const OBLIGATED_AMOUNT = 'obligated_amount' const OBLIGATED_AMOUNT = 'obligated_amount'
const START_DATE = 'start_date'
const END_DATE = 'end_date'
const POP = 'period_of_performance'
export default { export default {
name: 'clin-fields', name: 'clin-fields',
@ -26,11 +29,27 @@ export default {
type: Number, type: Number,
default: 0, default: 0,
}, },
initialStartDate: {
type: String,
default: null,
},
initialEndDate: {
type: String,
default: null,
},
}, },
data: function() { data: function() {
const loas = this.initialLoaCount == 0 ? 1 : 0 const loas = this.initialLoaCount == 0 ? 1 : 0
const indexOffset = this.initialLoaCount const indexOffset = this.initialLoaCount
const start = !!this.initialStartDate
? new Date(this.initialStartDate)
: undefined
const end = !!this.initialEndDate
? new Date(this.initialEndDate)
: undefined
const popValidation = !this.initialStartDate ? false : start < end
const showPopValidation = !this.initialStartDate ? false : !popValidation
return { return {
clinIndex: this.initialClinIndex, clinIndex: this.initialClinIndex,
@ -38,6 +57,10 @@ export default {
loas: loas, loas: loas,
clinType: this.initialClinType, clinType: this.initialClinType,
amount: this.initialAmount || 0, amount: this.initialAmount || 0,
startDate: start,
endDate: end,
popValid: popValidation,
showPopError: showPopValidation,
} }
}, },
@ -51,6 +74,11 @@ export default {
clinType: this.clinType, clinType: this.clinType,
amount: this.initialAmount, amount: this.initialAmount,
}) })
emitEvent('field-mount', this, {
optional: false,
name: POP,
valid: this.checkPopValid(),
})
}, },
methods: { methods: {
@ -70,6 +98,23 @@ export default {
}) })
}, },
checkPopValid: function() {
return this.startDate < this.endDate
},
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
}
emitEvent('field-change', this, {
name: POP,
valid: this.checkPopValid(),
})
},
handleFieldChange: function(event) { handleFieldChange: function(event) {
if (this._uid === event.parent_uid) { if (this._uid === event.parent_uid) {
if (event.name.includes(JEDI_CLIN_TYPE)) { if (event.name.includes(JEDI_CLIN_TYPE)) {
@ -78,6 +123,12 @@ export default {
} else if (event.name.includes(OBLIGATED_AMOUNT)) { } else if (event.name.includes(OBLIGATED_AMOUNT)) {
this.amount = parseFloat(event.value) this.amount = parseFloat(event.value)
this.clinChangeEvent() this.clinChangeEvent()
} else if (event.name.includes(START_DATE)) {
if (!!event.value) this.startDate = new Date(event.value)
this.validatePop()
} else if (event.name.includes(END_DATE)) {
if (!!event.value) this.endDate = new Date(event.value)
this.validatePop()
} }
} }
}, },

View File

@ -51,18 +51,24 @@ export default {
month(newMonth, oldMonth) { month(newMonth, oldMonth) {
if (!!newMonth && newMonth.length > 2) { if (!!newMonth && newMonth.length > 2) {
this.month = oldMonth this.month = oldMonth
} else {
this.month = newMonth
} }
}, },
day(newDay, oldDay) { day(newDay, oldDay) {
if (!!newDay && newDay.length > 2) { if (!!newDay && newDay.length > 2) {
this.day = oldDay this.day = oldDay
} else {
this.day = newDay
} }
}, },
year(newYear, oldYear) { year(newYear, oldYear) {
if (!!newYear && newYear.length > 4) { if (!!newYear && newYear.length > 4) {
this.year = oldYear this.year = oldYear
} else {
this.year = newYear
} }
}, },
}, },
@ -96,7 +102,7 @@ export default {
isYearValid: function() { isYearValid: function() {
// Emit a change event // Emit a change event
var valid = parseInt(this.year) >= 1 var valid = parseInt(this.year) >= 1
// this._emitChange('year', this.year, valid) this._emitChange('year', this.year, valid)
return valid return valid
}, },
@ -154,9 +160,8 @@ export default {
methods: { methods: {
onInput: function(e) { onInput: function(e) {
console.log('emitting event')
emitEvent('field-change', this, { emitEvent('field-change', this, {
value: e.target.value, value: this.formattedDate,
name: this.name, name: this.name,
watch: this.watch, watch: this.watch,
valid: this.isDateValid, valid: this.isDateValid,

View File

@ -44,6 +44,7 @@
type="number" type="number"
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }" v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
v-model="month" v-model="month"
v-on:change="onInput"
/> />
</div> </div>
@ -79,12 +80,6 @@
{{ Icon("ok", classes="icon--green") }} {{ Icon("ok", classes="icon--green") }}
</div> </div>
</div> </div>
<p class="usa-input-error-message" v-bind:class="{ 'form-has-errors': !isWithinDateRange }">
{% if maxdate and mindate %}Date must be between {{mindate.strftime("%m/%d/%Y")}} and {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if maxdate and not mindate %}Date must be before or on {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if mindate and not maxdate %}Date must be after or on {{mindate.strftime("%m/%d/%Y")}}{% endif %}
</p>
</fieldset> </fieldset>
</date-selector> </date-selector>

View File

@ -50,6 +50,8 @@
v-bind:initial-loa-count="{{ fields.loas.data | length or 0 }}" v-bind:initial-loa-count="{{ fields.loas.data | length or 0 }}"
v-bind:initial-clin-type="'{{ fields.jedi_clin_type.data }}'" v-bind:initial-clin-type="'{{ fields.jedi_clin_type.data }}'"
v-bind:initial-amount='{{ fields.obligated_amount.data or 0 }}' v-bind:initial-amount='{{ fields.obligated_amount.data or 0 }}'
v-bind:initial-start-date="'{{ fields.start_date.data | string }}'"
v-bind:initial-end-date="'{{ fields.end_date.data | string }}'"
{% else %} {% else %}
v-bind:initial-clin-index='clinIndex' v-bind:initial-clin-index='clinIndex'
v-bind:initial-clin-type="'JEDI_CLIN_1'" v-bind:initial-clin-type="'JEDI_CLIN_1'"
@ -138,14 +140,15 @@
<div class="form-row"> <div class="form-row">
{% if fields %} {% if fields %}
<div class="form-col"> <div class="form-col form-col--half">
{{ DatePicker(fields.start_date, watch=True, optional=False) }} {{ DatePicker(fields.start_date, watch=True, optional=False) }}
</div> </div>
<div class="form-col"> <div class="form-col form-col--half">
{{ DatePicker(fields.end_date, watch=True, optional=False) }} {{ DatePicker(fields.end_date, watch=True, optional=False) }}
</div> </div>
{% else %} {% else %}
<div class="form-col"> <div class="form-col form-col--half">
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :watch='true' :optional='false' inline-template> <date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :watch='true' :optional='false' inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }"> <fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
<legend> <legend>
@ -200,16 +203,11 @@
{{ Icon("ok", classes="icon--green") }} {{ Icon("ok", classes="icon--green") }}
</div> </div>
</div> </div>
<p class="usa-input-error-message" v-bind:class="{ 'form-has-errors': !isWithinDateRange }">
{% if maxdate and mindate %}Date must be between {{mindate.strftime("%m/%d/%Y")}} and {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if maxdate and not mindate %}Date must be before or on {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if mindate and not maxdate %}Date must be after or on {{mindate.strftime("%m/%d/%Y")}}{% endif %}
</p>
</fieldset> </fieldset>
</date-selector> </date-selector>
</div> </div>
<div class="form-col"> <div class="form-col form-col--half">
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :watch='true' :optional='false' inline-template> <date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :watch='true' :optional='false' inline-template>
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }"> <fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
<legend> <legend>
@ -264,16 +262,22 @@
{{ Icon("ok", classes="icon--green") }} {{ Icon("ok", classes="icon--green") }}
</div> </div>
</div> </div>
<p class="usa-input-error-message" v-bind:class="{ 'form-has-errors': !isWithinDateRange }">
{% if maxdate and mindate %}Date must be between {{mindate.strftime("%m/%d/%Y")}} and {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if maxdate and not mindate %}Date must be before or on {{maxdate.strftime("%m/%d/%Y")}}{% endif %}
{% if mindate and not maxdate %}Date must be after or on {{mindate.strftime("%m/%d/%Y")}}{% endif %}
</p>
</fieldset> </fieldset>
</date-selector> </date-selector>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="form-row">
<p class="usa-input-error-message form-has-errors">
<template v-if='showPopError'>
{% if fields and fields.start_date.errors %}
{{ fields.start_date.errors[0] }}
{% else %}
{{ "forms.task_order.start_date_error" | translate }}
{% endif %}
</template>
</p>
</div>
{% if fields %} {% if fields %}
{{ TextInput(fields.obligated_amount, validation='dollars', watch=True) }} {{ TextInput(fields.obligated_amount, validation='dollars', watch=True) }}

View File

@ -1,5 +1,8 @@
import datetime
from atst.forms.task_order import CLINForm from atst.forms.task_order import CLINForm
from atst.models import JEDICLINType from atst.models import JEDICLINType
from atst.utils.localization import translate
import tests.factories as factories import tests.factories as factories
@ -9,3 +12,21 @@ def test_clin_form_jedi_clin_type():
clin = factories.CLINFactory.create(jedi_clin_type=jedi_type) clin = factories.CLINFactory.create(jedi_clin_type=jedi_type)
clin_form = CLINForm(obj=clin) clin_form = CLINForm(obj=clin)
assert clin_form.jedi_clin_type.data == jedi_type.value assert clin_form.jedi_clin_type.data == jedi_type.value
def test_clin_form_start_date_before_end_date():
invalid_start = datetime.date(2020, 12, 12)
invalid_end = datetime.date(2020, 1, 1)
invalid_clin = factories.CLINFactory.create(
start_date=invalid_start, end_date=invalid_end
)
clin_form = CLINForm(obj=invalid_clin)
assert not clin_form.validate()
assert translate("forms.task_order.start_date_error") in clin_form.start_date.errors
valid_start = datetime.date(2020, 1, 1)
valid_end = datetime.date(2020, 12, 12)
valid_clin = factories.CLINFactory.create(
start_date=valid_start, end_date=valid_end
)
valid_clin_form = CLINForm(obj=valid_clin)
assert valid_clin_form.validate()

View File

@ -175,6 +175,7 @@ forms:
number_description: Task order number (13 digits) number_description: Task order number (13 digits)
scope_description: 'What do you plan to do on the cloud? Some examples might include migrating an existing application or creating a prototype. You dont need to include a detailed plan of execution, but should list key requirements. This section will be reviewed by your contracting officer, but wont be sent to the CCPO. <p>Not sure how to describe your scope? <a href="#">Read some examples</a> to get some inspiration.</p>' scope_description: 'What do you plan to do on the cloud? Some examples might include migrating an existing application or creating a prototype. You dont need to include a detailed plan of execution, but should list key requirements. This section will be reviewed by your contracting officer, but wont be sent to the CCPO. <p>Not sure how to describe your scope? <a href="#">Read some examples</a> to get some inspiration.</p>'
scope_label: Cloud project scope scope_label: Cloud project scope
start_date_error: PoP start date must be before end date.
start_date_label: Start of period of performance (PoP) start_date_label: Start of period of performance (PoP)
team_experience: team_experience:
built_1: Built, migrated, or consulted on 1-2 applications built_1: Built, migrated, or consulted on 1-2 applications