Merge pull request #180 from dod-ccpo/ui/financial-verification

UI/financial verification
This commit is contained in:
dandds 2018-08-20 11:22:30 -04:00 committed by GitHub
commit 9e24dd4913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 444 additions and 228 deletions

48
atst/forms/data.py Normal file
View File

@ -0,0 +1,48 @@
SERVICE_BRANCHES = [
(None, "Select an option"),
("Air Force, Department of the", "Air Force, Department of the"),
("Army and Air Force Exchange Service", "Army and Air Force Exchange Service"),
("Army, Department of the", "Army, Department of the"),
("Defense Advanced Research Projects Agency", "Defense Advanced Research Projects Agency"),
("Defense Commissary Agency", "Defense Commissary Agency"),
("Defense Contract Audit Agency", "Defense Contract Audit Agency"),
("Defense Contract Management Agency", "Defense Contract Management Agency"),
("Defense Finance & Accounting Service", "Defense Finance & Accounting Service"),
("Defense Health Agency", "Defense Health Agency"),
("Defense Information System Agency", "Defense Information System Agency"),
("Defense Intelligence Agency", "Defense Intelligence Agency"),
("Defense Legal Services Agency", "Defense Legal Services Agency"),
("Defense Logistics Agency", "Defense Logistics Agency"),
("Defense Media Activity", "Defense Media Activity"),
("Defense Micro Electronics Activity", "Defense Micro Electronics Activity"),
("Defense POW-MIA Accounting Agency", "Defense POW-MIA Accounting Agency"),
("Defense Security Cooperation Agency", "Defense Security Cooperation Agency"),
("Defense Security Service", "Defense Security Service"),
("Defense Technical Information Center", "Defense Technical Information Center"),
("Defense Technology Security Administration", "Defense Technology Security Administration"),
("Defense Threat Reduction Agency", "Defense Threat Reduction Agency"),
("DoD Education Activity", "DoD Education Activity"),
("DoD Human Recourses Activity", "DoD Human Recourses Activity"),
("DoD Inspector General", "DoD Inspector General"),
("DoD Test Resource Management Center", "DoD Test Resource Management Center"),
("Headquarters Defense Human Resource Activity ", "Headquarters Defense Human Resource Activity "),
("Joint Staff", "Joint Staff"),
("Missile Defense Agency", "Missile Defense Agency"),
("National Defense University", "National Defense University"),
("National Geospatial Intelligence Agency (NGA)", "National Geospatial Intelligence Agency (NGA)"),
("National Oceanic and Atmospheric Administration (NOAA)", "National Oceanic and Atmospheric Administration (NOAA)"),
("National Reconnaissance Office", "National Reconnaissance Office"),
("National Reconnaissance Office (NRO)", "National Reconnaissance Office (NRO)"),
("National Security Agency (NSA)", "National Security Agency (NSA)"),
("National Security Agency-Central Security Service", "National Security Agency-Central Security Service"),
("Navy, Department of the", "Navy, Department of the"),
("Office of Economic Adjustment", "Office of Economic Adjustment"),
("Office of the Secretary of Defense", "Office of the Secretary of Defense"),
("Pentagon Force Protection Agency", "Pentagon Force Protection Agency"),
("Uniform Services University of the Health Sciences", "Uniform Services University of the Health Sciences"),
("US Cyber Command (USCYBERCOM)", "US Cyber Command (USCYBERCOM)"),
("US Special Operations Command (USSOCOM)", "US Special Operations Command (USSOCOM)"),
("US Strategic Command (USSTRATCOM)", "US Strategic Command (USSTRATCOM)"),
("US Transportation Command (USTRANSCOM)", "US Transportation Command (USTRANSCOM)"),
("Washington Headquarters Services", "Washington Headquarters Services"),
]

View File

@ -1,5 +1,5 @@
from wtforms.fields.html5 import DateField
from wtforms.fields import Field
from wtforms.fields import Field, SelectField as SelectField_
from wtforms.widgets import TextArea
from atst.domain.date import parse_date
@ -32,6 +32,22 @@ class NewlineListField(Field):
def process_formdata(self, valuelist):
if valuelist:
self.data = [l.strip() for l in valuelist[0].split("\n")]
self.data = [l.strip() for l in valuelist[0].split("\n") if l]
else:
self.data = []
def process_data(self, value):
if isinstance(value, list):
self.data = "\n".join(value)
else:
self.data = value
class SelectField(SelectField_):
def __init__(self, *args, **kwargs):
render_kw = kwargs.get("render_kw", {})
kwargs["render_kw"] = {
**render_kw,
"required": False
}
super().__init__(*args, **kwargs)

View File

@ -1,12 +1,12 @@
import re
from wtforms.fields.html5 import EmailField
from wtforms.fields import StringField, SelectField
from wtforms.validators import Required, Email
from wtforms.fields import StringField
from wtforms.validators import Required, Email, Regexp
from atst.domain.exceptions import NotFoundError
from atst.domain.pe_numbers import PENumbers
from .fields import NewlineListField
from .fields import NewlineListField, SelectField
from .forms import ValidatedForm
@ -21,6 +21,9 @@ PE_REGEX = re.compile(
re.X,
)
TREASURY_CODE_REGEX = re.compile(r"^0*([1-9]{4}|[1-9]{6})$")
BA_CODE_REGEX = re.compile(r"^0*[1-9]{2}\w?$")
def suggest_pe_id(pe_id):
suggestion = pe_id
@ -55,6 +58,18 @@ def validate_pe_id(field, existing_request):
class FinancialForm(ValidatedForm):
def validate(self, *args, **kwargs):
if self.funding_type.data == "OTHER":
self.funding_type_other.validators.append(Required())
return super().validate(*args, **kwargs)
def reset(self):
"""
Reset UII info so that it can be de-parsed rendered properly.
This is a stupid workaround, and there's probably a better way.
"""
self.uii_ids.process_data(self.uii_ids.data)
def perform_extra_validation(self, existing_request):
valid = True
if not existing_request or existing_request.get("pe_id") != self.pe_id.data:
@ -62,25 +77,28 @@ class FinancialForm(ValidatedForm):
return valid
task_order_id = StringField(
"Task Order Number associated with this request.", validators=[Required()]
"Task Order Number associated with this request",
description="Include the original Task Order number (including the 000X at the end). Do not include any modification numbers. Note that there may be a lag between approving a task order and when it becomes available in our system.",
validators=[Required()]
)
uii_ids = NewlineListField(
"Unique Item Identifier (UII)s related to your application(s) if you already have them."
"Unique Item Identifier (UII)s related to your application(s) if you already have them",
validators=[Required()]
)
pe_id = StringField("Program Element (PE) Number related to your request", validators=[Required()])
treasury_code = StringField("Program Treasury Code")
treasury_code = StringField("Program Treasury Code", validators=[Required(), Regexp(TREASURY_CODE_REGEX)])
ba_code = StringField("Program BA Code")
ba_code = StringField("Program Budget Activity (BA) Code", validators=[Required(), Regexp(BA_CODE_REGEX)])
fname_co = StringField("Contracting Officer First Name", validators=[Required()])
lname_co = StringField("Contracting Officer Last Name", validators=[Required()])
email_co = EmailField("Contracting Officer Email", validators=[Required(), Email()])
office_co = StringField("Contracting Office Office", validators=[Required()])
office_co = StringField("Contracting Officer Office", validators=[Required()])
fname_cor = StringField(
"Contracting Officer Representative (COR) First Name", validators=[Required()]
@ -100,7 +118,7 @@ class FinancialForm(ValidatedForm):
)
funding_type = SelectField(
validators=[Required()],
description="What is the source of funding?",
choices=[
("", "- Select -"),
("RDTE", "Research, Development, Testing & Evaluation (RDT&E)"),
@ -108,38 +126,44 @@ class FinancialForm(ValidatedForm):
("PROC", "Procurement (PROC)"),
("OTHER", "Other"),
],
validators=[Required()],
render_kw={"required": False}
)
funding_type_other = StringField(
"If other, please specify", validators=[Required()]
)
funding_type_other = StringField("If other, please specify")
clin_0001 = StringField(
"<dl><dt>CLIN 0001</dt> - <dd>Unclassified IaaS and PaaS Amount</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)
clin_0003 = StringField(
"<dl><dt>CLIN 0003</dt> - <dd>Unclassified Cloud Support Package</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)
clin_1001 = StringField(
"<dl><dt>CLIN 1001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 1</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)
clin_1003 = StringField(
"<dl><dt>CLIN 1003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 1</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)
clin_2001 = StringField(
"<dl><dt>CLIN 2001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 2</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)
clin_2003 = StringField(
"<dl><dt>CLIN 2003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 2</dd></dl>",
validators=[Required()],
description="Review your task order document, the amounts for each CLIN must match exactly here"
)

View File

@ -1,10 +1,12 @@
from wtforms.fields.html5 import EmailField, TelField
from wtforms.fields import RadioField, StringField, SelectField
from wtforms.fields import RadioField, StringField
from wtforms.validators import Required, Email
import pendulum
from .fields import DateField
from .fields import DateField, SelectField
from .forms import ValidatedForm
from .validators import DateRange, PhoneNumber, Alphabet
from .data import SERVICE_BRANCHES
class OrgForm(ValidatedForm):
@ -21,54 +23,7 @@ class OrgForm(ValidatedForm):
service_branch = SelectField(
"Service Branch or Agency",
description="Which services and organizations do you belong to within the DoD?",
choices=[
("null", "Select an option"),
("Air Force, Department of the", "Air Force, Department of the"),
("Army and Air Force Exchange Service", "Army and Air Force Exchange Service"),
("Army, Department of the", "Army, Department of the"),
("Defense Advanced Research Projects Agency", "Defense Advanced Research Projects Agency"),
("Defense Commissary Agency", "Defense Commissary Agency"),
("Defense Contract Audit Agency", "Defense Contract Audit Agency"),
("Defense Contract Management Agency", "Defense Contract Management Agency"),
("Defense Finance & Accounting Service", "Defense Finance & Accounting Service"),
("Defense Health Agency", "Defense Health Agency"),
("Defense Information System Agency", "Defense Information System Agency"),
("Defense Intelligence Agency", "Defense Intelligence Agency"),
("Defense Legal Services Agency", "Defense Legal Services Agency"),
("Defense Logistics Agency", "Defense Logistics Agency"),
("Defense Media Activity", "Defense Media Activity"),
("Defense Micro Electronics Activity", "Defense Micro Electronics Activity"),
("Defense POW-MIA Accounting Agency", "Defense POW-MIA Accounting Agency"),
("Defense Security Cooperation Agency", "Defense Security Cooperation Agency"),
("Defense Security Service", "Defense Security Service"),
("Defense Technical Information Center", "Defense Technical Information Center"),
("Defense Technology Security Administration", "Defense Technology Security Administration"),
("Defense Threat Reduction Agency", "Defense Threat Reduction Agency"),
("DoD Education Activity", "DoD Education Activity"),
("DoD Human Recourses Activity", "DoD Human Recourses Activity"),
("DoD Inspector General", "DoD Inspector General"),
("DoD Test Resource Management Center", "DoD Test Resource Management Center"),
("Headquarters Defense Human Resource Activity ", "Headquarters Defense Human Resource Activity "),
("Joint Staff", "Joint Staff"),
("Missile Defense Agency", "Missile Defense Agency"),
("National Defense University", "National Defense University"),
("National Geospatial Intelligence Agency (NGA)", "National Geospatial Intelligence Agency (NGA)"),
("National Oceanic and Atmospheric Administration (NOAA)", "National Oceanic and Atmospheric Administration (NOAA)"),
("National Reconnaissance Office", "National Reconnaissance Office"),
("National Reconnaissance Office (NRO)", "National Reconnaissance Office (NRO)"),
("National Security Agency (NSA)", "National Security Agency (NSA)"),
("National Security Agency-Central Security Service", "National Security Agency-Central Security Service"),
("Navy, Department of the", "Navy, Department of the"),
("Office of Economic Adjustment", "Office of Economic Adjustment"),
("Office of the Secretary of Defense", "Office of the Secretary of Defense"),
("Pentagon Force Protection Agency", "Pentagon Force Protection Agency"),
("Uniform Services University of the Health Sciences", "Uniform Services University of the Health Sciences"),
("US Cyber Command (USCYBERCOM)", "US Cyber Command (USCYBERCOM)"),
("US Special Operations Command (USSOCOM)", "US Special Operations Command (USSOCOM)"),
("US Strategic Command (USSTRATCOM)", "US Strategic Command (USSTRATCOM)"),
("US Transportation Command (USTRANSCOM)", "US Transportation Command (USTRANSCOM)"),
("Washington Headquarters Services", "Washington Headquarters Services"),
],
choices=SERVICE_BRANCHES,
)
citizenship = RadioField(
@ -93,7 +48,7 @@ class OrgForm(ValidatedForm):
)
date_latest_training = DateField(
"Latest Information Assurance (IA) Training completion date",
"Latest Information Assurance (IA) Training Completion Date",
description="To complete the training, you can find it in <a class=\"icon-link\" href=\"https://iatraining.disa.mil/eta/disa_cac2018/launchPage.htm\" target=\"_blank\">Information Assurance Cyber Awareness Challange</a> website.",
validators=[
Required(),

View File

@ -20,7 +20,7 @@ class POCForm(ValidatedForm):
am_poc = BooleanField(
"I am the Workspace Owner.",
"I am the Workspace Owner",
default=False,
false_values=(False, "false", "False", "no", "")
)

View File

@ -1,9 +1,10 @@
from wtforms.fields.html5 import IntegerField
from wtforms.fields import RadioField, TextAreaField, SelectField
from wtforms.fields import RadioField, TextAreaField
from wtforms.validators import Optional, Required
from .fields import DateField
from .fields import DateField, SelectField
from .forms import ValidatedForm
from .data import SERVICE_BRANCHES
from atst.domain.requests import Requests
@ -37,65 +38,20 @@ class RequestForm(ValidatedForm):
dod_component = SelectField(
"DoD Component",
description="Identify the DoD component that is requesting access to the JEDI Cloud",
choices=[
("null", "Select an option"),
("Air Force, Department of the", "Air Force, Department of the"),
("Army and Air Force Exchange Service", "Army and Air Force Exchange Service"),
("Army, Department of the", "Army, Department of the"),
("Defense Advanced Research Projects Agency", "Defense Advanced Research Projects Agency"),
("Defense Commissary Agency", "Defense Commissary Agency"),
("Defense Contract Audit Agency", "Defense Contract Audit Agency"),
("Defense Contract Management Agency", "Defense Contract Management Agency"),
("Defense Finance & Accounting Service", "Defense Finance & Accounting Service"),
("Defense Health Agency", "Defense Health Agency"),
("Defense Information System Agency", "Defense Information System Agency"),
("Defense Intelligence Agency", "Defense Intelligence Agency"),
("Defense Legal Services Agency", "Defense Legal Services Agency"),
("Defense Logistics Agency", "Defense Logistics Agency"),
("Defense Media Activity", "Defense Media Activity"),
("Defense Micro Electronics Activity", "Defense Micro Electronics Activity"),
("Defense POW-MIA Accounting Agency", "Defense POW-MIA Accounting Agency"),
("Defense Security Cooperation Agency", "Defense Security Cooperation Agency"),
("Defense Security Service", "Defense Security Service"),
("Defense Technical Information Center", "Defense Technical Information Center"),
("Defense Technology Security Administration", "Defense Technology Security Administration"),
("Defense Threat Reduction Agency", "Defense Threat Reduction Agency"),
("DoD Education Activity", "DoD Education Activity"),
("DoD Human Recourses Activity", "DoD Human Recourses Activity"),
("DoD Inspector General", "DoD Inspector General"),
("DoD Test Resource Management Center", "DoD Test Resource Management Center"),
("Headquarters Defense Human Resource Activity ", "Headquarters Defense Human Resource Activity "),
("Joint Staff", "Joint Staff"),
("Missile Defense Agency", "Missile Defense Agency"),
("National Defense University", "National Defense University"),
("National Geospatial Intelligence Agency (NGA)", "National Geospatial Intelligence Agency (NGA)"),
("National Oceanic and Atmospheric Administration (NOAA)", "National Oceanic and Atmospheric Administration (NOAA)"),
("National Reconnaissance Office", "National Reconnaissance Office"),
("National Reconnaissance Office (NRO)", "National Reconnaissance Office (NRO)"),
("National Security Agency (NSA)", "National Security Agency (NSA)"),
("National Security Agency-Central Security Service", "National Security Agency-Central Security Service"),
("Navy, Department of the", "Navy, Department of the"),
("Office of Economic Adjustment", "Office of Economic Adjustment"),
("Office of the Secretary of Defense", "Office of the Secretary of Defense"),
("Pentagon Force Protection Agency", "Pentagon Force Protection Agency"),
("Uniform Services University of the Health Sciences", "Uniform Services University of the Health Sciences"),
("US Cyber Command (USCYBERCOM)", "US Cyber Command (USCYBERCOM)"),
("US Special Operations Command (USSOCOM)", "US Special Operations Command (USSOCOM)"),
("US Strategic Command (USSTRATCOM)", "US Strategic Command (USSTRATCOM)"),
("US Transportation Command (USTRANSCOM)", "US Transportation Command (USTRANSCOM)"),
("Washington Headquarters Services", "Washington Headquarters Services"),
],
choices=SERVICE_BRANCHES,
validators=[Required()]
)
jedi_usage = TextAreaField(
"JEDI Usage",
description="Your answer will help us provide tangible examples to DoD leadership how and why commercial cloud resources are accelerating the Department's missions",
validators=[Required()]
)
# Details of Use: Cloud Readiness
num_software_systems = IntegerField(
"Number of Software System",
"Number of Software Systems",
description="Estimate the number of software systems that will be supported by this JEDI Cloud access request",
)
@ -108,7 +64,7 @@ class RequestForm(ValidatedForm):
rationalization_software_systems = RadioField(
description="Have you completed a “rationalization” of your software systems to move to the cloud?",
choices=[("yes", "Yes"), ("no", "No"), ("in_progress", "In Progress")],
choices=[("yes", "Yes"), ("no", "No"), ("In Progress", "In Progress")],
default="",
)
@ -121,45 +77,45 @@ class RequestForm(ValidatedForm):
organization_providing_assistance = RadioField( # this needs to be updated to use checkboxes instead of radio
description="If you are receiving migration assistance, what is the type of organization providing assistance?",
choices=[
("in_house_staff", "In-house staff"),
("contractor", "Contractor"),
("other_dod_organization", "Other DoD organization"),
("none", "None"),
("In-house staff", "In-house staff"),
("Contractor", "Contractor"),
("Other DoD Organization", "Other DoD Organization"),
("None", "None"),
],
default="",
)
engineering_assessment = RadioField(
description="Have you completed an engineering assessment of your systems for cloud readiness?",
choices=[("yes", "Yes"), ("no", "No"), ("in_progress", "In Progress")],
choices=[("yes", "Yes"), ("no", "No"), ("In Progress", "In Progress")],
default="",
)
data_transfers = SelectField(
description="How much data is being transferred to the cloud?",
choices=[
("null", "Select an option"),
("less_than_100gb", "Less than 100GB"),
("100gb-500gb", "100GB-500GB"),
("500gb-1tb", "500GB-1TB"),
("1tb-50tb", "1TB-50TB"),
("50tb-100tb", "50TB-100TB"),
("100tb-500tb", "100TB-500TB"),
("500tb-1pb", "500TB-1PB"),
("1pb-5pb", "1PB-5PB"),
("5pb-10pb", "5PB-10PB"),
("above_10pb", "Above 10PB"),
("", "Select an option"),
("Less than 100GB", "Less than 100GB"),
("100GB-500GB", "100GB-500GB"),
("500GB-1TB", "500GB-1TB"),
("1TB-50TB", "1TB-50TB"),
("50TB-100TB", "50TB-100TB"),
("100TB-500TB", "100TB-500TB"),
("500TB-1PB", "500TB-1PB"),
("1PB-5PB", "1PB-5PB"),
("5PB-10PB", "5PB-10PB"),
("Above 10PB", "Above 10PB"),
],
)
expected_completion_date = SelectField(
description="When do you expect to complete your migration to the JEDI Cloud?",
choices=[
("null", "Select an option"),
("less_than_1_month", "Less than 1 month"),
("1_to_3_months", "1-3 months"),
("3_to_6_months", "3-6 months"),
("above_12_months", "Above 12 months"),
("", "Select an option"),
("Less than 1 month", "Less than 1 month"),
("1-3 months", "1-3 months"),
("3-6 months", "3-6 months"),
("Above 12 months", "Above 12 months"),
],
)
@ -171,7 +127,7 @@ class RequestForm(ValidatedForm):
# Details of Use: Financial Usage
estimated_monthly_spend = IntegerField(
"Estimated monthly spend",
"Estimated Monthly Spend",
description='Use the <a href="#" target="_blank" class="icon-link">JEDI CSP Calculator</a> to estimate your <b>monthly</b> cloud resource usage and enter the dollar amount below. Note these estimates are for initial approval only. After the request is approved, you will be asked to provide a valid Task Order number with specific CLIN amounts for cloud services.',
)
@ -195,5 +151,7 @@ class RequestForm(ValidatedForm):
)
start_date = DateField(
description="When do you expect to start using the JEDI Cloud (not for billing purposes)?"
description="When do you expect to start using the JEDI Cloud (not for billing purposes)?",
validators=[
Required()]
)

View File

@ -24,7 +24,7 @@ def update_financial_verification(request_id):
rerender_args = dict(request_id=request_id, f=form)
if form.validate():
request_data = {"financial_verification": post_data}
request_data = {"financial_verification": form.data}
valid = form.perform_extra_validation(
existing_request.body.get("financial_verification")
)
@ -32,10 +32,12 @@ def update_financial_verification(request_id):
if valid:
return redirect(url_for("requests.financial_verification_submitted"))
else:
form.reset()
return render_template(
"requests/financial_verification.html", **rerender_args
)
else:
form.reset()
return render_template("requests/financial_verification.html", **rerender_args)

View File

@ -0,0 +1,41 @@
import optionsinput from '../options_input'
import textinput from '../text_input'
export default {
name: 'financial',
components: {
optionsinput,
textinput,
},
props: {
initialData: {
type: Object,
default: () => ({})
}
},
data: function () {
const {
funding_type = ""
} = this.initialData
return {
funding_type
}
},
mounted: function () {
this.$root.$on('field-change', this.handleFieldChange)
},
methods: {
handleFieldChange: function (event) {
const { value, name } = event
if (typeof this[name] !== undefined) {
this[name] = value
}
},
}
}

View File

@ -7,6 +7,7 @@ import textinput from './components/text_input'
import checkboxinput from './components/checkbox_input'
import DetailsOfUse from './components/forms/details_of_use'
import poc from './components/forms/poc'
import financial from './components/forms/financial'
Vue.use(VTooltip)
@ -19,6 +20,7 @@ const app = new Vue({
checkboxinput,
DetailsOfUse,
poc,
financial,
},
methods: {
closeModal: function(name) {

View File

@ -47,6 +47,7 @@
.form__sub-fields {
@include alert;
@include alert-level('default');
display: block;
.usa-input {
&:first-child {
@ -73,5 +74,15 @@
}
}
}
&--warning {
@include alert-level('warning');
display: block;
}
&--error {
@include alert-level('error');
display: block;
}
}

View File

@ -110,6 +110,7 @@
margin: 0;
box-sizing: border-box;
max-width: 32em;
resize: none;
&:hover,
&:focus {
@ -133,6 +134,10 @@
}
select {
max-width: 30em;
}
ul {
list-style: none;
margin: 0;
@ -181,16 +186,19 @@
&--anything,
&--email {
input {
max-width: 26em;
max-width: 30em;
}
.icon-validation {
left: 26em;
left: 30em;
}
}
&--paragraph {
textarea {
max-width: 30em;
}
.icon-validation {
left: 32em;
left: 30em;
}
}

View File

@ -6,6 +6,7 @@
{% block content %}
<financial inline-template v-bind:initial-data='{{ f.data|tojson }}'>
<div class="col">
<div class="panel">
@ -34,36 +35,99 @@
<p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p>
{{ TextInput(f.task_order_id,placeholder="e.g.: 1234567899C0001",tooltip="Note that there may be a lag between the time you have created and approved the task order to the time it is searchable within the electronic. <br>A Contracting Officer will likely be the best source for this number.") }}
{{ TextInput(f.uii_ids,paragraph=True,placeholder="e.g.: DI 0CVA5786950 \nUN1945326361234786950",tooltip="A Unique Item Identifer is a unique code that helps the Department of Defense track and report on where and how digital assets are stored. <br>Not all applications have an existing UII number assigned.") }}
{{ TextInput(f.pe_id,placeholder="e.g.: 0203752A",tooltip="Program Element numbers helps the Department of Defense identify which offices\\' budgets are contributing towards this resource use.") }}
{{ TextInput(f.treasury_code,placeholder="e.g.: 1200") }}
{{ TextInput(f.ba_code,placeholder="e.g.: 02") }}
{{ TextInput(
f.task_order_id,
placeholder="e.g.: 1234567899C0001",
tooltip="A Contracting Officer will likely be the best source for this number.",
validation="anything"
) }}
{{ TextInput(f.uii_ids,
paragraph=True,
placeholder="e.g.: DI 0CVA5786950 \nUN1945326361234786950",
tooltip="A Unique Item Identifer is a unique code that helps the Department of Defense track and report on where and how digital assets are stored. <br>Not all applications have an existing UII number assigned."
) }}
{{ TextInput(f.pe_id,
placeholder="e.g.: 0302400A",
tooltip="Program Element numbers helps the Department of Defense identify which offices\\' budgets are contributing towards this resource use."
) }}
{{ TextInput(f.treasury_code,placeholder="e.g.: 00123456") }}
{{ TextInput(f.ba_code,placeholder="e.g.: 02A") }}
<h3>Contracting Officer (KO) Information</h3>
{{ TextInput(f.fname_co,placeholder="Contracting Officer First Name") }}
{{ TextInput(f.lname_co,placeholder="Contracting Officer Last Name") }}
{{ TextInput(f.email_co,validation='email',placeholder="jane@mail.mil") }}
{{ TextInput(f.office_co,placeholder="e.g.: WHS") }}
{{ TextInput(
f.email_co,validation='email',
placeholder="jane@mail.mil"
) }}
{{ TextInput(
f.office_co,
placeholder="e.g.: WHS"
) }}
<h3>Contracting Officer Representative (COR) Information</h3>
{{ TextInput(f.fname_cor,placeholder="Contracting Officer Representative First Name") }}
{{ TextInput(f.lname_cor,placeholder="Contracting Officer Representative Last Name") }}
{{ TextInput(f.email_cor,validation='email',placeholder="jane@mail.mil") }}
{{ TextInput(f.office_cor,placeholder="e.g.: WHS") }}
<br><hr>
<em>&darr; FIELDS NEEDED FOR MANUAL ENTRY OF TASK ORDER INFORMATION (only necessary if EDA info not available)</em>
<hr>
{{ Alert('Task Order not found in EDA',
message="Since the Task Order (TO) number was not found in our system of record, EDA, please populate the additional fields in the form below.",
level='warning'
) }}
<fieldset class="form__sub-fields form__sub-fields--warning">
{{ OptionsInput(f.funding_type) }}
<template v-if="funding_type == 'OTHER'" v-cloak>
{{ TextInput(f.funding_type_other) }}
</template>
{{ TextInput(
f.clin_0001,placeholder="50,000",
validation='integer'
) }}
{{ TextInput(
f.clin_0003,placeholder="13,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1003,placeholder="7,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2003,placeholder="7,000",
validation='integer'
) }}
</fieldset>
{{ OptionsInput(f.funding_type) }}
{{ TextInput(f.funding_type_other) }}
{{ TextInput(f.clin_0001,placeholder="50,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{{ TextInput(f.clin_0003,placeholder="13,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{{ TextInput(f.clin_1001,placeholder="30,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{{ TextInput(f.clin_1003,placeholder="7,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{{ TextInput(f.clin_2001,placeholder="30,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{{ TextInput(f.clin_2003,placeholder="7,000", validation='integer', tooltip="Review your task order document, the amounts for each CLIN must match exactly here.") }}
{% endautoescape %}
{% endblock form %}
@ -71,9 +135,9 @@
<input type='submit' class='usa-button usa-button-primary' value='Save & Continue' />
{% endblock %}
</form>
</div>
</div>
</div>
</financial>
{% endblock %}

View File

@ -25,7 +25,7 @@
<h2>General</h2>
{{ OptionsInput(f.dod_component) }}
{{ TextInput(f.jedi_usage, paragraph=True, placeholder="Briefly describe how you are expecting to use the JEDI Cloud. \n e.g. We are migrating XYZ application to the cloud so that...") }}
{{ TextInput(f.jedi_usage, paragraph=True, placeholder="Briefly describe how you are expecting to use the JEDI Cloud. e.g. We are migrating XYZ application to the cloud so that...") }}
<h2>Cloud Readiness</h2>
{{ TextInput(f.num_software_systems,validation="integer",tooltip="A software system can be any code that you plan to host on cloud infrastructure. For example, it could be a custom-developed web application, or a large ERP system.",placeholder="0") }}
@ -75,8 +75,8 @@
</template>
</transition>
{{ TextInput(f.dollar_value, validation='dollars', placeholder="$0") }}
{{ TextInput(f.start_date, validation='date', placeholder='MM / DD / YYYY') }}
{{ TextInput(f.dollar_value, validation='dollars', placeholder='$0') }}
{{ TextInput(f.start_date, placeholder='MM / DD / YYYY', validation='date') }}
</div>
</details-of-use>

View File

@ -3,6 +3,7 @@ import string
import factory
from uuid import uuid4
from atst.forms.data import SERVICE_BRANCHES
from atst.models.request import Request
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
from atst.models.pe_number import PENumber
@ -13,7 +14,6 @@ from atst.models.request_status_event import RequestStatusEvent
from atst.domain.roles import Roles
class RoleFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = Role
@ -34,7 +34,6 @@ class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = RequestStatusEvent
@ -61,15 +60,16 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
"dodid_poc": user.dod_id,
"email_poc": user.email,
"fname_poc": user.first_name,
"lname_poc": user.last_name
"lname_poc": user.last_name,
},
"details_of_use": {
"jedi_usage": "adf",
"start_date": "2018-08-08",
"cloud_native": "yes",
"dollar_value": dollar_value,
"dod_component": "Army and Air Force Exchange Service",
"data_transfers": "less_than_100gb",
"dod_component": SERVICE_BRANCHES[2][1],
"data_transfers": "Less than 100GB",
"expected_completion_date": "Less than 1 month",
"jedi_migration": "yes",
"num_software_systems": 1,
"number_user_sessions": 2,
@ -78,9 +78,8 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
"technical_support_team": "yes",
"estimated_monthly_spend": 100,
"average_daily_traffic_gb": 4,
"expected_completion_date": "less_than_1_month",
"rationalization_software_systems": "yes",
"organization_providing_assistance": "in_house_staff"
"organization_providing_assistance": "In-house staff",
},
"information_about_you": {
"citizenship": "United States",
@ -89,9 +88,9 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory):
"email_request": user.email,
"fname_request": user.first_name,
"lname_request": user.last_name,
"service_branch": "Air Force, Department of the",
"date_latest_training": "2018-08-06"
}
"service_branch": SERVICE_BRANCHES[1][1],
"date_latest_training": "2018-08-06",
},
}

View File

@ -1,25 +1,58 @@
import pytest
from wtforms import Form
import pendulum
from werkzeug.datastructures import ImmutableMultiDict
from atst.forms.fields import DateField
from atst.forms.fields import DateField, NewlineListField
class MyForm(Form):
class DateForm(Form):
date = DateField()
class NewlineListForm(Form):
newline_list = NewlineListField()
def test_date_ie_format():
form = MyForm(data={"date": "12/24/2018"})
form = DateForm(data={"date": "12/24/2018"})
assert form.date._value() == pendulum.date(2018, 12, 24)
def test_date_sane_format():
form = MyForm(data={"date": "2018-12-24"})
form = DateForm(data={"date": "2018-12-24"})
assert form.date._value() == pendulum.date(2018, 12, 24)
def test_date_insane_format():
form = MyForm(data={"date": "hello"})
form = DateForm(data={"date": "hello"})
with pytest.raises(ValueError):
form.date._value()
@pytest.mark.parametrize("input_,expected", [
("", []),
("hello", ["hello"]),
("hello\n", ["hello"]),
("hello\nworld", ["hello", "world"]),
("hello\nworld\n", ["hello", "world"])
])
def test_newline_list_process(input_, expected):
form_data = ImmutableMultiDict({"newline_list": input_})
form = NewlineListForm(form_data)
assert form.validate()
assert form.data == {"newline_list": expected}
@pytest.mark.parametrize("input_,expected", [
([], ""),
(["hello"], "hello"),
(["hello", "world"], "hello\nworld")
])
def test_newline_list_value(input_, expected):
form_data = {"newline_list": input_}
form = NewlineListForm(data=form_data)
assert form.validate()
assert form.newline_list._value() == expected

View File

@ -1,14 +1,69 @@
import pytest
from atst.forms.financial import suggest_pe_id
from atst.forms.financial import suggest_pe_id, FinancialForm
@pytest.mark.parametrize("input,expected", [
@pytest.mark.parametrize("input_,expected", [
('0603502N', None),
('0603502NZ', None),
('603502N', '0603502N'),
('063502N', '0603502N'),
('63502N', '0603502N'),
])
def test_suggest_pe_id(input, expected):
assert suggest_pe_id(input) == expected
def test_suggest_pe_id(input_, expected):
assert suggest_pe_id(input_) == expected
def test_funding_type_other_not_required_if_funding_type_is_not_other():
form_data = {
"funding_type": "PROC"
}
form = FinancialForm(data=form_data)
form.validate()
assert "funding_type_other" not in form.errors
def test_funding_type_other_required_if_funding_type_is_other():
form_data = {
"funding_type": "OTHER"
}
form = FinancialForm(data=form_data)
form.validate()
assert "funding_type_other" in form.errors
@pytest.mark.parametrize("input_,expected", [
("1234", True),
("123456", True),
("0001234", True),
("000123456", True),
("12345", False),
("00012345", False),
("0001234567", False),
("000000", False),
])
def test_treasury_code_validation(input_, expected):
form_data = {"treasury_code": input_}
form = FinancialForm(data=form_data)
form.validate()
is_valid = "treasury_code" not in form.errors
assert is_valid == expected
@pytest.mark.parametrize("input_,expected", [
("12", True),
("00012", True),
("12A", True),
("000123", True),
("00012A", True),
("0001", False),
("00012AB", False),
])
def test_ba_code_validation(input_, expected):
form_data = {"ba_code": input_}
form = FinancialForm(data=form_data)
form.validate()
is_valid = "ba_code" not in form.errors
assert is_valid == expected

View File

@ -6,80 +6,80 @@ from atst.forms.request import RequestForm
class TestRequestForm:
form_data = {
'dod_component': 'Army and Air Force Exchange Service',
'jedi_usage': 'cloud-ify all the things',
'num_software_systems': '12',
'estimated_monthly_spend': '1000000',
'dollar_value': '42',
'number_user_sessions': '6',
'average_daily_traffic': '0',
'start_date': '12/12/2012',
"dod_component": "Army and Air Force Exchange Service",
"jedi_usage": "cloud-ify all the things",
"num_software_systems": "12",
"estimated_monthly_spend": "1000000",
"dollar_value": "42",
"number_user_sessions": "6",
"average_daily_traffic": "0",
"start_date": "12/12/2012",
}
migration_data = {
'jedi_migration': 'yes',
'rationalization_software_systems': 'yes',
'technical_support_team': 'yes',
'organization_providing_assistance': 'in_house_staff',
'engineering_assessment': 'yes',
'data_transfers': 'less_than_100gb',
'expected_completion_date': 'less_than_1_month'
"jedi_migration": "yes",
"rationalization_software_systems": "yes",
"technical_support_team": "yes",
"organization_providing_assistance": "In-house staff",
"engineering_assessment": "yes",
"data_transfers": "Less than 100GB",
"expected_completion_date": "Less than 1 month",
}
def test_require_cloud_native_when_not_migrating(self):
extra_data = { 'jedi_migration': 'no' }
request_form = RequestForm(data={ **self.form_data, **extra_data })
extra_data = {"jedi_migration": "no"}
request_form = RequestForm(data={**self.form_data, **extra_data})
assert not request_form.validate()
assert request_form.errors == { 'cloud_native': ['Not a valid choice'] }
assert request_form.errors == {"cloud_native": ["Not a valid choice"]}
def test_require_migration_questions_when_migrating(self):
extra_data = { 'jedi_migration': 'yes' }
request_form = RequestForm(data={ **self.form_data, **extra_data })
extra_data = {"jedi_migration": "yes"}
request_form = RequestForm(data={**self.form_data, **extra_data})
assert not request_form.validate()
assert request_form.errors == {
'rationalization_software_systems': ['Not a valid choice'],
'technical_support_team': ['Not a valid choice'],
'organization_providing_assistance': ['Not a valid choice'],
'engineering_assessment': ['Not a valid choice'],
'data_transfers': ['Not a valid choice'],
'expected_completion_date': ['Not a valid choice']
"rationalization_software_systems": ["Not a valid choice"],
"technical_support_team": ["Not a valid choice"],
"organization_providing_assistance": ["Not a valid choice"],
"engineering_assessment": ["Not a valid choice"],
"data_transfers": ["Not a valid choice"],
"expected_completion_date": ["Not a valid choice"],
}
def test_require_organization_when_technical_support_team(self):
data = { **self.form_data, **self.migration_data }
del data['organization_providing_assistance']
data = {**self.form_data, **self.migration_data}
del data["organization_providing_assistance"]
request_form = RequestForm(data=data)
assert not request_form.validate()
assert request_form.errors == {
'organization_providing_assistance': ['Not a valid choice'],
"organization_providing_assistance": ["Not a valid choice"]
}
def test_valid_form_data(self):
data = { **self.form_data, **self.migration_data }
data['technical_support_team'] = 'no'
del data['organization_providing_assistance']
data = {**self.form_data, **self.migration_data}
data["technical_support_team"] = "no"
del data["organization_providing_assistance"]
request_form = RequestForm(data=data)
assert request_form.validate()
def test_sessions_required_for_large_projects(self):
data = { **self.form_data, **self.migration_data }
data['estimated_monthly_spend'] = '9999999'
del data['number_user_sessions']
del data['average_daily_traffic']
data = {**self.form_data, **self.migration_data}
data["estimated_monthly_spend"] = "9999999"
del data["number_user_sessions"]
del data["average_daily_traffic"]
request_form = RequestForm(data=data)
assert not request_form.validate()
assert request_form.errors == {
'number_user_sessions': ['This field is required.'],
'average_daily_traffic': ['This field is required.'],
"number_user_sessions": ["This field is required."],
"average_daily_traffic": ["This field is required."],
}
def test_sessions_not_required_invalid_monthly_spend(self):
data = { **self.form_data, **self.migration_data }
data['estimated_monthly_spend'] = 'foo'
del data['number_user_sessions']
del data['average_daily_traffic']
data = {**self.form_data, **self.migration_data}
data["estimated_monthly_spend"] = "foo"
del data["number_user_sessions"]
del data["average_daily_traffic"]
request_form = RequestForm(data=data)
assert request_form.validate()