Merge pull request #863 from dod-ccpo/add-clin-to-form
Add CLIN fields to Task Order Form
This commit is contained in:
commit
ad385b9de9
@ -1,6 +1,7 @@
|
|||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
|
from atst.models.clin import CLIN
|
||||||
from atst.models.task_order import TaskOrder
|
from atst.models.task_order import TaskOrder
|
||||||
from . import BaseDomainClass
|
from . import BaseDomainClass
|
||||||
|
|
||||||
@ -18,27 +19,49 @@ class TaskOrders(BaseDomainClass):
|
|||||||
UNCLASSIFIED_FUNDING = []
|
UNCLASSIFIED_FUNDING = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, creator, portfolio_id, **kwargs):
|
def create(cls, creator, portfolio_id, number, clins, pdf):
|
||||||
task_order = TaskOrder(portfolio_id=portfolio_id, creator=creator)
|
task_order = TaskOrder(
|
||||||
for key, value in kwargs.items():
|
portfolio_id=portfolio_id, creator=creator, number=number, pdf=pdf
|
||||||
setattr(task_order, key, value)
|
)
|
||||||
|
|
||||||
db.session.add(task_order)
|
db.session.add(task_order)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
TaskOrders.create_clins(task_order.id, clins)
|
||||||
|
|
||||||
return task_order
|
return task_order
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, task_order_id, **kwargs):
|
def update(cls, task_order_id, number, clins, pdf):
|
||||||
task_order = TaskOrders.get(task_order_id)
|
task_order = TaskOrders.get(task_order_id)
|
||||||
for key, value in kwargs.items():
|
task_order.pdf = pdf
|
||||||
setattr(task_order, key, value)
|
|
||||||
|
|
||||||
|
for clin in task_order.clins:
|
||||||
|
db.session.delete(clin)
|
||||||
|
|
||||||
|
if number != task_order.number:
|
||||||
|
task_order.number = number
|
||||||
db.session.add(task_order)
|
db.session.add(task_order)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
TaskOrders.create_clins(task_order_id, clins)
|
||||||
|
|
||||||
return task_order
|
return task_order
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_clins(cls, task_order_id, clin_list):
|
||||||
|
for clin_data in clin_list:
|
||||||
|
clin = CLIN(
|
||||||
|
task_order_id=task_order_id,
|
||||||
|
number=clin_data["number"],
|
||||||
|
loas=clin_data["loas"],
|
||||||
|
start_date=clin_data["start_date"],
|
||||||
|
end_date=clin_data["end_date"],
|
||||||
|
obligated_amount=clin_data["obligated_amount"],
|
||||||
|
jedi_clin_type=clin_data["jedi_clin_type"],
|
||||||
|
)
|
||||||
|
db.session.add(clin)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def section_completion_status(cls, task_order, section):
|
def section_completion_status(cls, task_order, section):
|
||||||
if section in TaskOrders.mission_owner_sections():
|
if section in TaskOrders.mission_owner_sections():
|
||||||
|
@ -221,3 +221,10 @@ ENV_ROLE_NO_ACCESS = "No Access"
|
|||||||
ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [
|
ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [
|
||||||
(ENV_ROLE_NO_ACCESS, "No access")
|
(ENV_ROLE_NO_ACCESS, "No access")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
JEDI_CLIN_TYPES = [
|
||||||
|
("JEDI_CLIN_1", translate("forms.task_order.clin_01_label")),
|
||||||
|
("JEDI_CLIN_2", translate("forms.task_order.clin_02_label")),
|
||||||
|
("JEDI_CLIN_3", translate("forms.task_order.clin_03_label")),
|
||||||
|
("JEDI_CLIN_4", translate("forms.task_order.clin_04_label")),
|
||||||
|
]
|
||||||
|
@ -1,13 +1,45 @@
|
|||||||
from wtforms.fields import BooleanField, DecimalField, FileField, StringField
|
from wtforms.fields import (
|
||||||
|
BooleanField,
|
||||||
|
DecimalField,
|
||||||
|
FieldList,
|
||||||
|
FileField,
|
||||||
|
FormField,
|
||||||
|
StringField,
|
||||||
|
)
|
||||||
from wtforms.fields.html5 import DateField
|
from wtforms.fields.html5 import DateField
|
||||||
from wtforms.validators import Required, Optional
|
from wtforms.validators import Required
|
||||||
from flask_wtf.file import FileAllowed
|
from flask_wtf.file import FileAllowed
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
|
from .data import JEDI_CLIN_TYPES
|
||||||
|
from .fields import SelectField
|
||||||
from .forms import BaseForm
|
from .forms import BaseForm
|
||||||
from atst.forms.validators import FileLength
|
from atst.forms.validators import FileLength
|
||||||
from atst.utils.localization import translate
|
from atst.utils.localization import translate
|
||||||
|
|
||||||
|
|
||||||
|
class CLINForm(FlaskForm):
|
||||||
|
jedi_clin_type = SelectField("Jedi CLIN type", choices=JEDI_CLIN_TYPES)
|
||||||
|
number = StringField(validators=[Required()])
|
||||||
|
start_date = DateField(
|
||||||
|
translate("forms.task_order.start_date_label"),
|
||||||
|
format="%m/%d/%Y",
|
||||||
|
validators=[Required()],
|
||||||
|
)
|
||||||
|
end_date = DateField(
|
||||||
|
translate("forms.task_order.end_date_label"),
|
||||||
|
format="%m/%d/%Y",
|
||||||
|
validators=[Required()],
|
||||||
|
)
|
||||||
|
obligated_amount = DecimalField()
|
||||||
|
loas = FieldList(StringField())
|
||||||
|
|
||||||
|
|
||||||
|
class UnclassifiedCLINForm(CLINForm):
|
||||||
|
# TODO: overwrite jedi_clin_type to only include the unclassified options
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TaskOrderForm(BaseForm):
|
class TaskOrderForm(BaseForm):
|
||||||
number = StringField(
|
number = StringField(
|
||||||
translate("forms.task_order.number_label"),
|
translate("forms.task_order.number_label"),
|
||||||
@ -22,38 +54,7 @@ class TaskOrderForm(BaseForm):
|
|||||||
],
|
],
|
||||||
render_kw={"accept": ".pdf,application/pdf"},
|
render_kw={"accept": ".pdf,application/pdf"},
|
||||||
)
|
)
|
||||||
|
clins = FieldList(FormField(CLINForm))
|
||||||
|
|
||||||
class FundingForm(BaseForm):
|
|
||||||
start_date = DateField(
|
|
||||||
translate("forms.task_order.start_date_label"), format="%m/%d/%Y"
|
|
||||||
)
|
|
||||||
end_date = DateField(
|
|
||||||
translate("forms.task_order.end_date_label"), format="%m/%d/%Y"
|
|
||||||
)
|
|
||||||
clin_01 = DecimalField(
|
|
||||||
translate("forms.task_order.clin_01_label"), validators=[Optional()]
|
|
||||||
)
|
|
||||||
clin_02 = DecimalField(
|
|
||||||
translate("forms.task_order.clin_02_label"), validators=[Optional()]
|
|
||||||
)
|
|
||||||
clin_03 = DecimalField(
|
|
||||||
translate("forms.task_order.clin_03_label"), validators=[Optional()]
|
|
||||||
)
|
|
||||||
clin_04 = DecimalField(
|
|
||||||
translate("forms.task_order.clin_04_label"), validators=[Optional()]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnclassifiedFundingForm(FundingForm):
|
|
||||||
clin_02 = StringField(
|
|
||||||
translate("forms.task_order.unclassified_clin_02_label"),
|
|
||||||
filters=[BaseForm.remove_empty_string],
|
|
||||||
)
|
|
||||||
clin_04 = StringField(
|
|
||||||
translate("forms.task_order.unclassified_clin_04_label"),
|
|
||||||
filters=[BaseForm.remove_empty_string],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureForm(BaseForm):
|
class SignatureForm(BaseForm):
|
||||||
|
@ -7,10 +7,10 @@ from atst.models import Base, mixins, types
|
|||||||
|
|
||||||
|
|
||||||
class JEDICLINType(Enum):
|
class JEDICLINType(Enum):
|
||||||
JEDI_CLIN_1 = "jedi clin 0001"
|
JEDI_CLIN_1 = "JEDI_CLIN_1"
|
||||||
JEDI_CLIN_2 = "jedi clin 0002"
|
JEDI_CLIN_2 = "JEDI_CLIN_2"
|
||||||
JEDI_CLIN_3 = "jedi clin 0003"
|
JEDI_CLIN_3 = "JEDI_CLIN_3"
|
||||||
JEDI_CLIN_4 = "jedi clin 0004"
|
JEDI_CLIN_4 = "JEDI_CLIN_4"
|
||||||
|
|
||||||
|
|
||||||
class CLIN(Base, mixins.TimestampsMixin):
|
class CLIN(Base, mixins.TimestampsMixin):
|
||||||
@ -27,3 +27,10 @@ class CLIN(Base, mixins.TimestampsMixin):
|
|||||||
end_date = Column(Date, nullable=False)
|
end_date = Column(Date, nullable=False)
|
||||||
obligated_amount = Column(Numeric(scale=2), nullable=False)
|
obligated_amount = Column(Numeric(scale=2), nullable=False)
|
||||||
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False)
|
jedi_clin_type = Column(SQLAEnum(JEDICLINType, native_enum=False), nullable=False)
|
||||||
|
|
||||||
|
def to_dictionary(self):
|
||||||
|
return {
|
||||||
|
c.name: getattr(self, c.name)
|
||||||
|
for c in self.__table__.columns
|
||||||
|
if c.name not in ["id"]
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
signer_dod_id = Column(String)
|
signer_dod_id = Column(String)
|
||||||
signed_at = Column(DateTime)
|
signed_at = Column(DateTime)
|
||||||
|
|
||||||
clins = relationship("CLIN")
|
clins = relationship("CLIN", back_populates="task_order")
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
def pdf(self):
|
def pdf(self):
|
||||||
@ -129,6 +129,8 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
def to_dictionary(self):
|
def to_dictionary(self):
|
||||||
return {
|
return {
|
||||||
"portfolio_name": self.portfolio_name,
|
"portfolio_name": self.portfolio_name,
|
||||||
|
"pdf": self.pdf,
|
||||||
|
"clins": [clin.to_dictionary() for clin in self.clins],
|
||||||
**{
|
**{
|
||||||
c.name: getattr(self, c.name)
|
c.name: getattr(self, c.name)
|
||||||
for c in self.__table__.columns
|
for c in self.__table__.columns
|
||||||
|
@ -13,9 +13,7 @@ def render_task_orders_edit(portfolio_id, task_order_id=None, form=None):
|
|||||||
|
|
||||||
if task_order_id:
|
if task_order_id:
|
||||||
task_order = TaskOrders.get(task_order_id)
|
task_order = TaskOrders.get(task_order_id)
|
||||||
render_args["form"] = form or TaskOrderForm(
|
render_args["form"] = form or TaskOrderForm(**task_order.to_dictionary())
|
||||||
number=task_order.number, pdf=task_order.pdf
|
|
||||||
)
|
|
||||||
render_args["task_order_id"] = task_order_id
|
render_args["task_order_id"] = task_order_id
|
||||||
else:
|
else:
|
||||||
render_args["form"] = form or TaskOrderForm()
|
render_args["form"] = form or TaskOrderForm()
|
||||||
|
19
js/components/clin_fields.js
Normal file
19
js/components/clin_fields.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import DateSelector from './date_selector'
|
||||||
|
import textinput from './text_input'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'clin-fields',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DateSelector,
|
||||||
|
textinput,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
initialClinIndex: Number,
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return { clinIndex: this.initialClinIndex }
|
||||||
|
},
|
||||||
|
}
|
@ -19,6 +19,7 @@ export default {
|
|||||||
initialyear: { type: String },
|
initialyear: { type: String },
|
||||||
mindate: { type: String },
|
mindate: { type: String },
|
||||||
maxdate: { type: String },
|
maxdate: { type: String },
|
||||||
|
nameTag: { type: String },
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
@ -26,6 +27,7 @@ export default {
|
|||||||
day: this.initialday,
|
day: this.initialday,
|
||||||
month: this.initialmonth,
|
month: this.initialmonth,
|
||||||
year: this.initialyear,
|
year: this.initialyear,
|
||||||
|
name: this.nameTag,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
41
js/components/forms/to_form.js
Normal file
41
js/components/forms/to_form.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import ClinFields from '../clin_fields'
|
||||||
|
import DateSelector from '../date_selector'
|
||||||
|
import FormMixin from '../../mixins/form'
|
||||||
|
import optionsinput from '../options_input'
|
||||||
|
import textinput from '../text_input'
|
||||||
|
import uploadinput from '../upload_input'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'to-form',
|
||||||
|
|
||||||
|
mixins: [FormMixin],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ClinFields,
|
||||||
|
DateSelector,
|
||||||
|
optionsinput,
|
||||||
|
textinput,
|
||||||
|
uploadinput,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
initialClinCount: Number,
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
const clins = this.initialClinCount == 0 ? 1 : 0
|
||||||
|
const clinIndex = this.initialClinCount == 0 ? 0 : this.initialClinCount - 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
clins,
|
||||||
|
clinIndex,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addClin: function(event) {
|
||||||
|
this.clins = this.clins + 1
|
||||||
|
this.clinIndex = this.clinIndex + 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -41,6 +41,8 @@ import DeleteConfirmation from './components/delete_confirmation'
|
|||||||
import NewEnvironment from './components/forms/new_environment'
|
import NewEnvironment from './components/forms/new_environment'
|
||||||
import EnvironmentRole from './components/environment_role'
|
import EnvironmentRole from './components/environment_role'
|
||||||
import SemiCollapsibleText from './components/semi_collapsible_text'
|
import SemiCollapsibleText from './components/semi_collapsible_text'
|
||||||
|
import ToForm from './components/forms/to_form'
|
||||||
|
import ClinFields from './components/clin_fields'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
@ -83,6 +85,8 @@ const app = new Vue({
|
|||||||
NewEnvironment,
|
NewEnvironment,
|
||||||
EnvironmentRole,
|
EnvironmentRole,
|
||||||
SemiCollapsibleText,
|
SemiCollapsibleText,
|
||||||
|
ToForm,
|
||||||
|
ClinFields,
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
{% extends "portfolios/base.html" %}
|
{% extends "portfolios/base.html" %}
|
||||||
|
|
||||||
|
{% from 'components/date_picker.html' import DatePicker %}
|
||||||
|
{% from 'components/icon.html' import Icon %}
|
||||||
{% from 'components/save_button.html' import SaveButton %}
|
{% from 'components/save_button.html' import SaveButton %}
|
||||||
|
{% from 'components/options_input.html' import OptionsInput %}
|
||||||
{% from 'components/text_input.html' import TextInput %}
|
{% from 'components/text_input.html' import TextInput %}
|
||||||
{% from 'components/upload_input.html' import UploadInput %}
|
{% from 'components/upload_input.html' import UploadInput %}
|
||||||
|
|
||||||
|
{% macro CLINFields(fields) %}
|
||||||
|
<div>
|
||||||
|
<hr>
|
||||||
|
{{ OptionsInput(fields.jedi_clin_type) }}
|
||||||
|
{{ TextInput(fields.number) }}
|
||||||
|
{{ DatePicker(fields.start_date) }}
|
||||||
|
{{ DatePicker(fields.end_date) }}
|
||||||
|
{{ TextInput(fields.obligated_amount, validation='dollars') }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block portfolio_content %}
|
{% block portfolio_content %}
|
||||||
<div class="col task-order-form">
|
<div class="col task-order-form">
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
<base-form inline-template>
|
<to-form inline-template v-bind:initial-clin-count="{{ form.clins.data | length }}">
|
||||||
{% if task_order_id %}
|
{% if task_order_id %}
|
||||||
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order_id) %}
|
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order_id) %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -23,14 +37,220 @@
|
|||||||
<span class="icon icon--x"></span>
|
<span class="icon icon--x"></span>
|
||||||
{{ "common.cancel" | translate }}
|
{{ "common.cancel" | translate }}
|
||||||
</a>
|
</a>
|
||||||
{{ SaveButton(text=('common.save' | translate), element='input', form='new-task-order') }}
|
<input type="submit" tabindex="0" value="Save" form="new-task-order" class="usa-button usa-button-primary">
|
||||||
<p>
|
<p>
|
||||||
{{ "task_orders.new.form_help_text" | translate }}
|
{{ "task_orders.new.form_help_text" | translate }}
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
{{ TextInput(form.number, validation='taskOrderNumber') }}
|
{{ TextInput(form.number, validation='taskOrderNumber') }}
|
||||||
|
{% for clin in form.clins %}
|
||||||
|
{{ CLINFields(clin) }}
|
||||||
|
{% endfor %}
|
||||||
|
<div v-for="clin in clins">
|
||||||
|
<hr>
|
||||||
|
<clin-fields v-bind:initial-clin-index='clinIndex' inline-template>
|
||||||
|
<div>
|
||||||
|
<div class="usa-input">
|
||||||
|
<fieldset data-ally-disabled="true" class="usa-input__choices">
|
||||||
|
<legend>
|
||||||
|
<div class="usa-input__title">
|
||||||
|
</div>
|
||||||
|
</legend>
|
||||||
|
<select :id="'clins-' + clinIndex + '-jedi_clin_type'" :name="'clins-' + clinIndex + '-jedi_clin_type'">
|
||||||
|
<option value="JEDI_CLIN_1">{{ "forms.task_order.clin_01_label" | translate }}</option>
|
||||||
|
<option value="JEDI_CLIN_2">{{ "forms.task_order.clin_02_label" | translate }}</option>
|
||||||
|
<option value="JEDI_CLIN_3">{{ "forms.task_order.clin_03_label" | translate }}</option>
|
||||||
|
<option value="JEDI_CLIN_4">{{ "forms.task_order.clin_04_label" | translate }}</option>
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textinput :name="'clins-' + clinIndex + '-number'" inline-template>
|
||||||
|
<div>
|
||||||
|
<label :for="name">
|
||||||
|
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||||
|
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||||
|
<div class="usa-input__title"> Number </div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<masked-input
|
||||||
|
v-on:input='onInput'
|
||||||
|
v-on:blur='onBlur'
|
||||||
|
v-on:change='onChange'
|
||||||
|
v-bind:value='value'
|
||||||
|
v-bind:mask='mask'
|
||||||
|
v-bind:pipe='pipe'
|
||||||
|
v-bind:keep-char-positions='keepCharPositions'
|
||||||
|
v-bind:aria-invalid='showError'
|
||||||
|
type='text'
|
||||||
|
:id='name'
|
||||||
|
ref='input'>
|
||||||
|
</masked-input>
|
||||||
|
|
||||||
|
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||||
|
|
||||||
|
<template v-if='showError'>
|
||||||
|
<span class='usa-input__message' v-html='validationError'></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class='usa-input__message'></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</textinput>
|
||||||
|
|
||||||
|
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" inline-template>
|
||||||
|
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||||
|
<legend>
|
||||||
|
<div class="usa-input__title">
|
||||||
|
Start of period of performance (PoP)
|
||||||
|
</div>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="date-picker-component">
|
||||||
|
<input :name="name" v-bind:value="formattedDate" type="hidden" />
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-month">
|
||||||
|
<label>Month</label>
|
||||||
|
<input
|
||||||
|
name="date-month"
|
||||||
|
max="12"
|
||||||
|
maxlength="2"
|
||||||
|
min="1"
|
||||||
|
type="number"
|
||||||
|
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||||
|
v-model="month"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-day">
|
||||||
|
<label>Day</label>
|
||||||
|
<input
|
||||||
|
name="date-day"
|
||||||
|
maxlength="2"
|
||||||
|
min="1"
|
||||||
|
type="number"
|
||||||
|
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||||
|
v-bind:max="daysMaxCalculation"
|
||||||
|
v-model="day"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-year">
|
||||||
|
<label>Year</label>
|
||||||
|
<input
|
||||||
|
name="date-year"
|
||||||
|
maxlength="4"
|
||||||
|
type="number"
|
||||||
|
v-model="year"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||||
|
{{ Icon("ok", classes="icon--green") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</date-selector>
|
||||||
|
|
||||||
|
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" inline-template>
|
||||||
|
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||||
|
<legend>
|
||||||
|
<div class="usa-input__title">
|
||||||
|
End of period of performance (PoP)
|
||||||
|
</div>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="date-picker-component">
|
||||||
|
<input :name="name" v-bind:value="formattedDate" type="hidden" />
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-month">
|
||||||
|
<label>Month</label>
|
||||||
|
<input
|
||||||
|
name="date-month"
|
||||||
|
max="12"
|
||||||
|
maxlength="2"
|
||||||
|
min="1"
|
||||||
|
type="number"
|
||||||
|
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||||
|
v-model="month"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-day">
|
||||||
|
<label>Day</label>
|
||||||
|
<input
|
||||||
|
name="date-day"
|
||||||
|
maxlength="2"
|
||||||
|
min="1"
|
||||||
|
type="number"
|
||||||
|
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||||
|
v-bind:max="daysMaxCalculation"
|
||||||
|
v-model="day"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group usa-form-group-year">
|
||||||
|
<label>Year</label>
|
||||||
|
<input
|
||||||
|
name="date-year"
|
||||||
|
maxlength="4"
|
||||||
|
type="number"
|
||||||
|
v-model="year"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||||
|
{{ Icon("ok", classes="icon--green") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</date-selector>
|
||||||
|
|
||||||
|
<textinput :name="'clins-' + clinIndex + '-obligated_amount'" validation="dollars" inline-template>
|
||||||
|
<div>
|
||||||
|
<label :for="name">
|
||||||
|
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||||
|
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||||
|
<div class="usa-input__title">Obligated Amount</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<masked-input
|
||||||
|
v-on:input='onInput'
|
||||||
|
v-on:blur='onBlur'
|
||||||
|
v-on:change='onChange'
|
||||||
|
v-bind:value='value'
|
||||||
|
v-bind:mask='mask'
|
||||||
|
v-bind:pipe='pipe'
|
||||||
|
v-bind:keep-char-positions='keepCharPositions'
|
||||||
|
v-bind:aria-invalid='showError'
|
||||||
|
v-bind:show-mask='true'
|
||||||
|
type='text'
|
||||||
|
:id='name'
|
||||||
|
ref='input'>
|
||||||
|
</masked-input>
|
||||||
|
|
||||||
|
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||||
|
|
||||||
|
<template v-if='showError'>
|
||||||
|
<span class='usa-input__message' v-html='validationError'></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class='usa-input__message'></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</textinput>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</clin-fields>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button v-on:click="addClin" type="button">
|
||||||
|
Add CLIN
|
||||||
|
</button>
|
||||||
{{ UploadInput(form.pdf) }}
|
{{ UploadInput(form.pdf) }}
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</to-form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from atst.domain.task_orders import TaskOrders, TaskOrderError
|
from atst.domain.task_orders import TaskOrders, TaskOrderError
|
||||||
from atst.domain.exceptions import UnauthorizedError
|
from atst.domain.exceptions import UnauthorizedError
|
||||||
@ -51,3 +53,93 @@ def test_all_sections_complete():
|
|||||||
assert not TaskOrders.all_sections_complete(task_order)
|
assert not TaskOrders.all_sections_complete(task_order)
|
||||||
task_order.scope = "str12345"
|
task_order.scope = "str12345"
|
||||||
assert TaskOrders.all_sections_complete(task_order)
|
assert TaskOrders.all_sections_complete(task_order)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_adds_clins(pdf_upload):
|
||||||
|
portfolio = PortfolioFactory.create()
|
||||||
|
clins = [
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "12312",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["123123123123", "345345234"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "12312",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["78979087"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
task_order = TaskOrders.create(
|
||||||
|
creator=portfolio.owner,
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
number="0123456789",
|
||||||
|
clins=clins,
|
||||||
|
pdf=pdf_upload,
|
||||||
|
)
|
||||||
|
assert len(task_order.clins) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_adds_clins(pdf_upload):
|
||||||
|
task_order = TaskOrderFactory.create(number="1231231234")
|
||||||
|
to_number = task_order.number
|
||||||
|
clins = [
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "12312",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["123123123123", "345345234"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "12312",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["78979087"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
task_order = TaskOrders.create(
|
||||||
|
creator=task_order.creator,
|
||||||
|
portfolio_id=task_order.portfolio_id,
|
||||||
|
number="0000000000",
|
||||||
|
clins=clins,
|
||||||
|
pdf=pdf_upload,
|
||||||
|
)
|
||||||
|
assert task_order.number != to_number
|
||||||
|
assert len(task_order.clins) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_does_not_duplicate_clins(pdf_upload):
|
||||||
|
task_order = TaskOrderFactory.create(number="3453453456", clins=["123", "456"])
|
||||||
|
clins = [
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "123",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["123123123123", "345345234"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"number": "111",
|
||||||
|
"start_date": date(2020, 1, 1),
|
||||||
|
"end_date": date(2021, 1, 1),
|
||||||
|
"obligated_amount": Decimal("5000"),
|
||||||
|
"loas": ["78979087"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
task_order = TaskOrders.update(
|
||||||
|
task_order_id=task_order.id, number="0000000000", clins=clins, pdf=pdf_upload
|
||||||
|
)
|
||||||
|
assert len(task_order.clins) == 2
|
||||||
|
for clin in task_order.clins:
|
||||||
|
assert clin.number != "456"
|
||||||
|
@ -41,6 +41,10 @@ def random_future_date(year_min=1, year_max=5):
|
|||||||
return _random_date(year_min, year_max, operator.add)
|
return _random_date(year_min, year_max, operator.add)
|
||||||
|
|
||||||
|
|
||||||
|
def random_loa_numbers():
|
||||||
|
return ["".join(random.choices(string.digits, k=43))]
|
||||||
|
|
||||||
|
|
||||||
def _random_date(year_min, year_max, operation):
|
def _random_date(year_min, year_max, operation):
|
||||||
if year_min == year_max:
|
if year_min == year_max:
|
||||||
inc = year_min
|
inc = year_min
|
||||||
@ -267,6 +271,17 @@ class TaskOrderFactory(Base):
|
|||||||
|
|
||||||
portfolio = factory.SubFactory(PortfolioFactory)
|
portfolio = factory.SubFactory(PortfolioFactory)
|
||||||
number = factory.LazyFunction(random_task_order_number)
|
number = factory.LazyFunction(random_task_order_number)
|
||||||
|
creator = factory.SubFactory(UserFactory)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create(cls, model_class, *args, **kwargs):
|
||||||
|
with_clins = kwargs.pop("clins", [])
|
||||||
|
task_order = super()._create(model_class, *args, **kwargs)
|
||||||
|
|
||||||
|
for clin in with_clins:
|
||||||
|
CLINFactory.create(task_order=task_order, number=clin)
|
||||||
|
|
||||||
|
return task_order
|
||||||
|
|
||||||
|
|
||||||
class CLINFactory(Base):
|
class CLINFactory(Base):
|
||||||
|
@ -33,24 +33,75 @@ def user():
|
|||||||
return UserFactory.create()
|
return UserFactory.create()
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_new(client, user_session, portfolio):
|
def test_task_orders_edit(client, user_session, portfolio):
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
response = client.get(url_for("task_orders.edit", portfolio_id=portfolio.id))
|
response = client.get(url_for("task_orders.edit", portfolio_id=portfolio.id))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_create(client, user_session, portfolio, pdf_upload, session):
|
def test_task_orders_update(client, user_session, portfolio):
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
data = {"number": "0123456789", "pdf": pdf_upload}
|
form_data = {
|
||||||
|
"number": "0123456789",
|
||||||
|
"pdf": pdf_upload,
|
||||||
|
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"clins-0-clin_number": "12312",
|
||||||
|
"clins-0-start_date": "01/01/2020",
|
||||||
|
"clins-0-end_date": "01/01/2021",
|
||||||
|
"clins-0-obligated_amount": "5000",
|
||||||
|
"clins-0-loas-0": "123123123123",
|
||||||
|
"clins-0-loas-1": "345345234",
|
||||||
|
"clins-1-jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"clins-1-number": "12312",
|
||||||
|
"clins-1-start_date": "01/01/2020",
|
||||||
|
"clins-1-end_date": "01/01/2021",
|
||||||
|
"clins-1-obligated_amount": "5000",
|
||||||
|
"clins-1-loas-0": "78979087",
|
||||||
|
}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", portfolio_id=portfolio.id), data=data
|
url_for("task_orders.update", portfolio_id=portfolio.id), data=form_data
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
task_order = session.query(TaskOrder).filter_by(number=data["number"]).one()
|
task_order = session.query(TaskOrder).filter_by(number=data["number"]).one()
|
||||||
assert task_order.pdf.filename == pdf_upload.filename
|
assert task_order.pdf.filename == pdf_upload.filename
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_create_invalid_data(client, user_session, portfolio):
|
def test_task_orders_edit_existing_to(client, user_session, task_order):
|
||||||
|
user_session(task_order.creator)
|
||||||
|
response = client.get(
|
||||||
|
url_for(
|
||||||
|
"task_orders.edit",
|
||||||
|
portfolio_id=task_order.portfolio_id,
|
||||||
|
task_order_id=task_order.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_orders_update_existing_to(client, user_session, task_order):
|
||||||
|
user_session(task_order.creator)
|
||||||
|
form_data = {
|
||||||
|
"number": "0123456789",
|
||||||
|
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||||
|
"clins-0-number": "12312",
|
||||||
|
"clins-0-start_date": "01/01/2020",
|
||||||
|
"clins-0-end_date": "01/01/2021",
|
||||||
|
"clins-0-obligated_amount": "5000",
|
||||||
|
"clins-0-loas-0": "123123123123",
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"task_orders.update",
|
||||||
|
portfolio_id=task_order.portfolio_id,
|
||||||
|
task_order_id=task_order.id,
|
||||||
|
),
|
||||||
|
data=form_data,
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert task_order.number == "0123456789"
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_orders_update_invalid_data(client, user_session, portfolio):
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
num_task_orders = len(portfolio.task_orders)
|
num_task_orders = len(portfolio.task_orders)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user