Merge pull request #995 from dod-ccpo/pop-dates-bug
PoP start date must be before end
This commit is contained in:
commit
e333f32aea
@ -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"))
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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) }}
|
||||||
|
@ -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()
|
||||||
|
@ -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 don’t need to include a detailed plan of execution, but should list key requirements. This section will be reviewed by your contracting officer, but won’t 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 don’t need to include a detailed plan of execution, but should list key requirements. This section will be reviewed by your contracting officer, but won’t 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user