Update to form per design - Part 1 of 2
This commit is contained in:
leigh-mil 2019-01-14 10:12:39 -05:00 committed by GitHub
commit 28b10107e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 381 additions and 164 deletions

View File

@ -0,0 +1,32 @@
"""add funding columns to task order
Revision ID: 4536f50b25bc
Revises: 3d346b5c8f19
Create Date: 2019-01-10 14:24:03.101309
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '4536f50b25bc'
down_revision = '3d346b5c8f19'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('task_orders', sa.Column('attachment_id', postgresql.UUID(as_uuid=True), nullable=True))
op.add_column('task_orders', sa.Column('performance_length', sa.Integer(), nullable=True))
op.create_foreign_key('task_orders_attachments_attachment_id', 'task_orders', 'attachments', ['attachment_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('task_orders_attachments_attachment_id', 'task_orders', type_='foreignkey')
op.drop_column('task_orders', 'performance_length')
op.drop_column('task_orders', 'attachment_id')
# ### end Alembic commands ###

View File

@ -0,0 +1,32 @@
"""add oversight columns to task order
Revision ID: 7f2040715b0d
Revises: 4536f50b25bc
Create Date: 2019-01-10 16:34:20.185768
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7f2040715b0d'
down_revision = '4536f50b25bc'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('task_orders', sa.Column('cor_phone_number', sa.String(), nullable=True))
op.add_column('task_orders', sa.Column('ko_phone_number', sa.String(), nullable=True))
op.add_column('task_orders', sa.Column('so_phone_number', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('task_orders', 'so_phone_number')
op.drop_column('task_orders', 'ko_phone_number')
op.drop_column('task_orders', 'cor_phone_number')
# ### end Alembic commands ###

View File

@ -24,8 +24,8 @@ class TaskOrders(object):
"team_experience", "team_experience",
], ],
"funding": [ "funding": [
"start_date", "performance_length",
"end_date", # "pdf",
"clin_01", "clin_01",
"clin_02", "clin_02",
"clin_03", "clin_03",
@ -35,14 +35,17 @@ class TaskOrders(object):
"ko_first_name", "ko_first_name",
"ko_last_name", "ko_last_name",
"ko_email", "ko_email",
"ko_phone_number",
"ko_dod_id", "ko_dod_id",
"cor_first_name", "cor_first_name",
"cor_last_name", "cor_last_name",
"cor_email", "cor_email",
"cor_phone_number",
"cor_dod_id", "cor_dod_id",
"so_first_name", "so_first_name",
"so_last_name", "so_last_name",
"so_email", "so_email",
"so_phone_number",
"so_dod_id", "so_dod_id",
], ],
} }

View File

@ -176,16 +176,20 @@ FUNDING_TYPES = [
TASK_ORDER_SOURCES = [("MANUAL", "Manual"), ("EDA", "EDA")] TASK_ORDER_SOURCES = [("MANUAL", "Manual"), ("EDA", "EDA")]
APP_MIGRATION = [ APP_MIGRATION = [
("on_premise", "Yes, migrating from an on-premise data center"), ("on_premise", "Yes, migrating from an <strong>on-premise data center</strong>"),
("cloud", "Yes, migrating from another cloud provider "), ("cloud", "Yes, migrating from <strong>another cloud provider</strong>"),
("none", "Not planning to migrate any applications "), (
"both",
"Yes, migrating from an <strong>on-premise data center</strong> and <strong>another cloud provider</strong>",
),
("none", "Not planning to migrate any applications"),
("not_sure", "Not Sure"), ("not_sure", "Not Sure"),
] ]
PROJECT_COMPLEXITY = [ PROJECT_COMPLEXITY = [
("storage", "Storage "), ("storage", "Storage "),
("data_analytics", "Data Analytics "), ("data_analytics", "Data Analytics "),
("conus", "CONUS Only Access "), ("conus", "CONUS Access "),
("oconus", "OCONUS Access "), ("oconus", "OCONUS Access "),
("tactical_edge", "Tactical Edge Access "), ("tactical_edge", "Tactical Edge Access "),
("not_sure", "Not Sure "), ("not_sure", "Not Sure "),
@ -193,11 +197,10 @@ PROJECT_COMPLEXITY = [
] ]
DEV_TEAM = [ DEV_TEAM = [
("government", "Government"), ("government_civilians", "Government Civilians"),
("civilians", "Civilians"),
("military", "Military "), ("military", "Military "),
("contractor", "Contractor "), ("contractor", "Contractor "),
("other", "Other"), ("other", "Other (E.g. University or other partner)"),
] ]
TEAM_EXPERIENCE = [ TEAM_EXPERIENCE = [
@ -210,3 +213,30 @@ TEAM_EXPERIENCE = [
"Built or migrated many applications, or consulted on several such projects", "Built or migrated many applications, or consulted on several such projects",
), ),
] ]
PERIOD_OF_PERFORMANCE_LENGTH = [
("1", "1 Month"),
("2", "2 Months"),
("3", "3 Months"),
("4", "4 Months"),
("5", "5 Months"),
("6", "6 Months"),
("7", "7 Months"),
("8", "8 Months"),
("9", "9 Months"),
("10", "10 Months"),
("11", "11 Months"),
("12", "1 Year"),
("13", "1 Year, 1 Month"),
("14", "1 Year, 2 Months"),
("15", "1 Year, 3 Months"),
("16", "1 Year, 4 Months"),
("17", "1 Year, 5 Months"),
("18", "1 Year, 6 Months"),
("19", "1 Year, 7 Months"),
("20", "1 Year, 8 Months"),
("21", "1 Year, 9 Months"),
("22", "1 Year, 10 Months"),
("23", "1 Year, 11 Months"),
("24", "2 Years"),
]

View File

@ -6,12 +6,13 @@ from wtforms.fields import (
SelectMultipleField, SelectMultipleField,
StringField, StringField,
TextAreaField, TextAreaField,
FileField,
) )
from wtforms.fields.html5 import DateField from wtforms.fields.html5 import DateField, TelField
from wtforms.widgets import ListWidget, CheckboxInput from wtforms.widgets import ListWidget, CheckboxInput
from wtforms.validators import Required, Length from wtforms.validators import Required, Length
from atst.forms.validators import IsNumber from atst.forms.validators import IsNumber, PhoneNumber
from .forms import CacheableForm from .forms import CacheableForm
from .data import ( from .data import (
@ -20,70 +21,84 @@ from .data import (
PROJECT_COMPLEXITY, PROJECT_COMPLEXITY,
DEV_TEAM, DEV_TEAM,
TEAM_EXPERIENCE, TEAM_EXPERIENCE,
PERIOD_OF_PERFORMANCE_LENGTH,
) )
from atst.utils.localization import translate
class AppInfoForm(CacheableForm): class AppInfoForm(CacheableForm):
portfolio_name = StringField( portfolio_name = StringField(
"Organization Portfolio Name", translate("forms.task_order.portfolio_name_label"),
description="The name of your office or organization. You can add multiple applications to your portfolio. Your task orders are used to pay for these applications and their environments", description=translate("forms.task_order.portfolio_name_description"),
) )
scope = TextAreaField( scope = TextAreaField(
"Cloud Project Scope", translate("forms.task_order.scope_label"),
description="Your team's plan for using the cloud, such as migrating an existing application or creating a prototype.", description=translate("forms.task_order.scope_description"),
) )
defense_component = SelectField( defense_component = SelectField(
"Department of Defense Component", choices=SERVICE_BRANCHES translate("forms.task_order.defense_component_label"), choices=SERVICE_BRANCHES
) )
app_migration = RadioField( app_migration = RadioField(
"App Migration", translate("forms.task_order.app_migration_label"),
description="Do you plan to migrate existing application(s) to the cloud?", description=translate("forms.task_order.app_migration_description"),
choices=APP_MIGRATION, choices=APP_MIGRATION,
default="", default="",
) )
native_apps = RadioField( native_apps = RadioField(
"Native Apps", translate("forms.task_order.native_apps_label"),
description="Do you plan to develop application(s) natively in the cloud? ", description=translate("forms.task_order.native_apps_description"),
choices=[("yes", "Yes"), ("no", "No"), ("not_sure", "Not Sure")], choices=[("yes", "Yes"), ("no", "No"), ("not_sure", "Not Sure")],
) )
complexity = SelectMultipleField( complexity = SelectMultipleField(
"Project Complexity", translate("forms.task_order.complexity_label"),
description="Which of these describes how complex your team's use of the cloud will be? (Select all that apply.)", description=translate("forms.task_order.complexity_description"),
choices=PROJECT_COMPLEXITY, choices=PROJECT_COMPLEXITY,
default="", default="",
widget=ListWidget(prefix_label=False), widget=ListWidget(prefix_label=False),
option_widget=CheckboxInput(), option_widget=CheckboxInput(),
) )
complexity_other = StringField("Project Complexity Other") complexity_other = StringField(translate("forms.task_order.complexity_other_label"))
dev_team = SelectMultipleField( dev_team = SelectMultipleField(
"Development Team", translate("forms.task_order.dev_team_label"),
description="Which people or teams will be completing the development work for your cloud applications?", description=translate("forms.task_order.dev_team_description"),
choices=DEV_TEAM, choices=DEV_TEAM,
default="", default="",
widget=ListWidget(prefix_label=False), widget=ListWidget(prefix_label=False),
option_widget=CheckboxInput(), option_widget=CheckboxInput(),
) )
dev_team_other = StringField("Development Team Other") dev_team_other = StringField(translate("forms.task_order.dev_team_other_label"))
team_experience = RadioField( team_experience = RadioField(
"Team Experience", translate("forms.task_order.team_experience_label"),
description="How much experience does your team have with development in the cloud?", description=translate("forms.task_order.team_experience_description"),
choices=TEAM_EXPERIENCE, choices=TEAM_EXPERIENCE,
default="", default="",
) )
class FundingForm(CacheableForm): class FundingForm(CacheableForm):
start_date = DateField("Start Date", format="%m/%d/%Y") performance_length = SelectField(
end_date = DateField("End Date", format="%m/%d/%Y") translate("forms.task_order.performance_length_label"),
clin_01 = IntegerField("CLIN 01 : Unclassified") choices=PERIOD_OF_PERFORMANCE_LENGTH,
clin_02 = IntegerField("CLIN 02: Classified") )
clin_03 = IntegerField("CLIN 03: Unclassified") start_date = DateField(
clin_04 = IntegerField("CLIN 04: Classified") 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"
)
pdf = FileField(
translate("forms.task_order.pdf_label"),
description=translate("forms.task_order.pdf_description"),
)
clin_01 = IntegerField(translate("forms.task_order.clin_01_label"))
clin_02 = IntegerField(translate("forms.task_order.clin_02_label"))
clin_03 = IntegerField(translate("forms.task_order.clin_03_label"))
clin_04 = IntegerField(translate("forms.task_order.clin_04_label"))
class UnclassifiedFundingForm(FundingForm): class UnclassifiedFundingForm(FundingForm):
clin_02 = IntegerField("CLIN 02: Classified (available soon)") clin_02 = IntegerField(translate("forms.task_order.unclassified_clin_02_label"))
clin_04 = IntegerField("CLIN 04: Classified (available soon)") clin_04 = IntegerField(translate("forms.task_order.unclassified_clin_04_label"))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -92,52 +107,56 @@ class UnclassifiedFundingForm(FundingForm):
class OversightForm(CacheableForm): class OversightForm(CacheableForm):
ko_first_name = StringField("First Name") ko_first_name = StringField(
ko_last_name = StringField("Last Name") translate("forms.task_order.oversight_first_name_label")
ko_email = StringField("Email") )
ko_last_name = StringField(translate("forms.task_order.oversight_last_name_label"))
ko_email = StringField(translate("forms.task_order.oversight_email_label"))
ko_phone_number = TelField(
translate("forms.task_order.oversight_phone_label"), validators=[PhoneNumber()]
)
ko_dod_id = StringField( ko_dod_id = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()] translate("forms.task_order.oversight_dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
) )
cor_first_name = StringField("First Name") cor_first_name = StringField(
cor_last_name = StringField("Last Name") translate("forms.task_order.oversight_first_name_label")
cor_email = StringField("Email") )
cor_last_name = StringField(translate("forms.task_order.oversight_last_name_label"))
cor_email = StringField(translate("forms.task_order.oversight_email_label"))
cor_phone_number = TelField(
translate("forms.task_order.oversight_phone_label"), validators=[PhoneNumber()]
)
cor_dod_id = StringField( cor_dod_id = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()] translate("forms.task_order.oversight_dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
) )
so_first_name = StringField("First Name") so_first_name = StringField(
so_last_name = StringField("Last Name") translate("forms.task_order.oversight_first_name_label")
so_email = StringField("Email") )
so_last_name = StringField(translate("forms.task_order.oversight_last_name_label"))
so_email = StringField(translate("forms.task_order.oversight_email_label"))
so_phone_number = TelField(
translate("forms.task_order.oversight_phone_label"), validators=[PhoneNumber()]
)
so_dod_id = StringField( so_dod_id = StringField(
"DOD ID", validators=[Required(), Length(min=10), IsNumber()] translate("forms.task_order.oversight_dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
) )
ko_invite = BooleanField( ko_invite = BooleanField(
"Invite KO to Task Order Builder", translate("forms.task_order.ko_invite_label"),
description=""" description=translate("forms.task_order.skip_invite_description"),
Your KO will need to approve funding for this Task Order by logging
into the JEDI Cloud Portal, submitting the Task Order documents
within their official system of record, and electronically signing.
<i>You may choose to skip this for now and invite them later.</i>
""",
) )
cor_invite = BooleanField( cor_invite = BooleanField(
"Invite COR to Task Order Builder", translate("forms.task_order.cor_invite_label"),
description=""" description=translate("forms.task_order.skip_invite_description"),
Your COR may assist with submitting the Task Order documents within
their official system of record. <i>You may choose to skip this for
now and invite them later.</i>
""",
) )
so_invite = BooleanField( so_invite = BooleanField(
"Invite Security Officer to Task Order Builder", translate("forms.task_order.so_invite_label"),
description=""" description=translate("forms.task_order.skip_invite_description"),
Your Security Officer will need to answer some security
configuration questions in order to generate a DD-254 document,
then electronically sign. <i>You may choose to skip this for now
and invite them later.</i>
""",
) )

View File

@ -1,6 +1,14 @@
from enum import Enum from enum import Enum
from sqlalchemy import Column, Enum as SQLAEnum, Numeric, String, ForeignKey, Date from sqlalchemy import (
Column,
Enum as SQLAEnum,
Numeric,
String,
ForeignKey,
Date,
Integer,
)
from sqlalchemy.types import ARRAY from sqlalchemy.types import ARRAY
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
@ -46,6 +54,9 @@ class TaskOrder(Base, mixins.TimestampsMixin):
team_experience = Column(String) # Team Experience team_experience = Column(String) # Team Experience
start_date = Column(Date) # Period of Performance start_date = Column(Date) # Period of Performance
end_date = Column(Date) end_date = Column(Date)
performance_length = Column(Integer)
attachment_id = Column(ForeignKey("attachments.id"))
pdf = relationship("Attachment")
clin_01 = Column(Numeric(scale=2)) clin_01 = Column(Numeric(scale=2))
clin_02 = Column(Numeric(scale=2)) clin_02 = Column(Numeric(scale=2))
clin_03 = Column(Numeric(scale=2)) clin_03 = Column(Numeric(scale=2))
@ -53,14 +64,17 @@ class TaskOrder(Base, mixins.TimestampsMixin):
ko_first_name = Column(String) # First Name ko_first_name = Column(String) # First Name
ko_last_name = Column(String) # Last Name ko_last_name = Column(String) # Last Name
ko_email = Column(String) # Email ko_email = Column(String) # Email
ko_phone_number = Column(String) # Phone Number
ko_dod_id = Column(String) # DOD ID ko_dod_id = Column(String) # DOD ID
cor_first_name = Column(String) # First Name cor_first_name = Column(String) # First Name
cor_last_name = Column(String) # Last Name cor_last_name = Column(String) # Last Name
cor_email = Column(String) # Email cor_email = Column(String) # Email
cor_phone_number = Column(String) # Phone Number
cor_dod_id = Column(String) # DOD ID cor_dod_id = Column(String) # DOD ID
so_first_name = Column(String) # First Name so_first_name = Column(String) # First Name
so_last_name = Column(String) # Last Name so_last_name = Column(String) # Last Name
so_email = Column(String) # Email so_email = Column(String) # Email
so_phone_number = Column(String) # Phone Number
so_dod_id = Column(String) # DOD ID so_dod_id = Column(String) # DOD ID
number = Column(String, unique=True) # Task Order Number number = Column(String, unique=True) # Task Order Number
loa = Column(ARRAY(String)) # Line of Accounting (LOA) loa = Column(ARRAY(String)) # Line of Accounting (LOA)

View File

@ -20,7 +20,7 @@ from atst.services.invitation import Invitation as InvitationService
TASK_ORDER_SECTIONS = [ TASK_ORDER_SECTIONS = [
{ {
"section": "app_info", "section": "app_info",
"title": "What You're Building", "title": "What You're Making",
"template": "task_orders/new/app_info.html", "template": "task_orders/new/app_info.html",
"form": task_order_form.AppInfoForm, "form": task_order_form.AppInfoForm,
}, },
@ -166,7 +166,13 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow):
prefix = officer_type["prefix"] prefix = officer_type["prefix"]
officer_data = { officer_data = {
field: getattr(self.task_order, prefix + "_" + field) field: getattr(self.task_order, prefix + "_" + field)
for field in ["first_name", "last_name", "email", "dod_id"] for field in [
"first_name",
"last_name",
"email",
"phone_number",
"dod_id",
]
} }
officer = TaskOrders.add_officer( officer = TaskOrders.add_officer(
self.user, self.task_order, officer_type["role"], officer_data self.user, self.task_order, officer_type["role"], officer_data

View File

@ -3,6 +3,7 @@ import { conformToMask } from 'vue-text-mask'
import FormMixin from '../../mixins/form' import FormMixin from '../../mixins/form'
import textinput from '../text_input' import textinput from '../text_input'
import optionsinput from '../options_input'
export default { export default {
name: 'funding', name: 'funding',
@ -11,6 +12,7 @@ export default {
components: { components: {
textinput, textinput,
optionsinput
}, },
props: { props: {

View File

@ -6,6 +6,7 @@
label=field.label | striptags, label=field.label | striptags,
description=field.description, description=field.description,
tooltip='', tooltip='',
tooltip_title='Help',
placeholder='', placeholder='',
validation='anything', validation='anything',
paragraph=False, paragraph=False,
@ -30,7 +31,7 @@
<label for={{field.name}}> <label for={{field.name}}>
<div class="usa-input__title"> <div class="usa-input__title">
{{ label }} {{ label }}
{% if tooltip %}{{ Tooltip(tooltip) }}{% endif %} {% if tooltip %}{{ Tooltip(tooltip, tooltip_title) }}{% endif %}
</div> </div>
{% if field.description %} {% if field.description %}
@ -69,6 +70,7 @@
type='text' type='text'
{% if disabled %} {% if disabled %}
disabled="disabled" disabled="disabled"
readonly="readonly"
{% endif %} {% endif %}
ref='input' ref='input'
placeholder='{{ placeholder }}'> placeholder='{{ placeholder }}'>

View File

@ -1,6 +1,6 @@
{% from "components/text_input.html" import TextInput %} {% from "components/text_input.html" import TextInput %}
{% macro UserInfo(first_name, last_name, email, dod_id) -%} {% macro UserInfo(first_name, last_name, email, phone) -%}
<div class='form-row'> <div class='form-row'>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(first_name) }} {{ TextInput(first_name) }}
@ -17,7 +17,7 @@
</div> </div>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(dod_id, placeholder='1234567890', validation='dodId') }} {{ TextInput(phone, placeholder='(123) 456-7890', validation='usPhone') }}
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}

View File

@ -38,6 +38,7 @@
<div class='action-group'> <div class='action-group'>
<input type='submit' class='usa-button usa-button-primary' value='Save & Continue' /> <input type='submit' class='usa-button usa-button-primary' value='Save & Continue' />
<input class='usa-button usa-button-secondary' value='Save Draft' />
</div> </div>
{% endblock %} {% endblock %}

View File

@ -6,43 +6,34 @@
{% from "components/multi_checkbox_input.html" import MultiCheckboxInput %} {% from "components/multi_checkbox_input.html" import MultiCheckboxInput %}
{% block heading %} {% block heading %}
What You're Building {{ "task_orders.new.app_info.section_title"| translate }}
{% endblock %} {% endblock %}
{% block form %} {% block form %}
<h3>Basic Information</h3> <!-- App Info Section -->
<h3>{{ "task_orders.new.app_info.basic_info_title"| translate }}</h3>
{{ TextInput(form.portfolio_name, placeholder="The name of your office or organization") }} {{ TextInput(form.portfolio_name, placeholder="The name of your office or organization") }}
{{ TextInput(form.scope, paragraph=True) }} {{ TextInput(form.scope, paragraph=True) }}
<p> <p><i>{{ "task_orders.new.app_info.sample_scope" | translate | safe }}</i></p>
<i>
Not sure how to describe your scope? <a href="#">Read some Sample Scopes</a> to
get an idea of what is appropriate.
</i>
</p>
{{ OptionsInput(form.defense_component) }} {{ OptionsInput(form.defense_component) }}
<hr> <hr>
<h3>About Your Project</h3> <h3>{{ "task_orders.new.app_info.project_title" | translate }}</h3>
{{ OptionsInput(form.app_migration) }} {{ OptionsInput(form.app_migration) }}
{{ OptionsInput(form.native_apps) }} {{ OptionsInput(form.native_apps) }}
{{ MultiCheckboxInput(form.complexity, form.complexity_other) }} {{ MultiCheckboxInput(form.complexity, form.complexity_other) }}
<hr> <hr>
<h3>About Your Team</h3> <h3>{{ "task_orders.new.app_info.team_title" | translate }}</h3>
{{ MultiCheckboxInput(form.dev_team, form.dev_team_other) }} {{ MultiCheckboxInput(form.dev_team, form.dev_team_other) }}
{{ OptionsInput(form.team_experience) }} {{ OptionsInput(form.team_experience) }}
<hr> <hr>
<h3>Market Research</h3> <h3>{{ "task_orders.new.app_info.market_research_title" | translate }}</h3>
<p> <p>{{ "task_orders.new.app_info.market_research_paragraph" | translate | safe }}</p>
The JEDI Cloud Computing Program Office (CCPO) has completed the market
research requirement for all related task orders. The Department of Defense CIO
has approved this research. <a href="#">View JEDI Cloud Market Research</a>
</p>
{% endblock %} {% endblock %}

View File

@ -4,76 +4,51 @@
{% from "components/options_input.html" import OptionsInput %} {% from "components/options_input.html" import OptionsInput %}
{% from "components/date_input.html" import DateInput %} {% from "components/date_input.html" import DateInput %}
{% from "components/icon.html" import Icon %}
{% block heading %} {% block heading %}
Funding {{ "task_orders.new.funding.section_title" | translate }}
{% endblock %} {% endblock %}
{% block form %} {% block form %}
<funding inline-template v-bind:initial-data='{{ form.data|tojson }}'> <funding inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<div> <div>
<!-- Get Funding Section --> <!-- Get Funding Section -->
<h3>Period of Performance</h3> <h3>{{ "task_orders.new.funding.performance_period_title" | translate }}</h3>
<p>{{ "task_orders.new.funding.performance_period_description" | translate }}</p>
<p>Choose the dates your task order will cover.</p> <p>{{ "task_orders.new.funding.performance_period_paragraph" | translate }}</p>
{{ OptionsInput(form.performance_length) }}
<p>
Because your funds will be lost if you dont use them, we strongly recommend
submitting small, short-duration task orders, usually a three month period.
Well notify you when your period of performance is nearing the end so you can
request your next set of funds with a new task order.
</p>
{{ DateInput(form.start_date, placeholder='MM / DD / YYYY', validation='date') }}
{{ DateInput(form.end_date, placeholder='MM / DD / YYYY', validation='date') }}
<hr> <hr>
<h3>Cloud Usage Estimate</h3>
<p> <h3>{{ "task_orders.new.funding.estimate_usage_title" | translate }}</h3>
Calculate how much your cloud usage will cost. A technical representative <p>{{ "task_orders.new.funding.estimate_usage_description" | translate }}</p>
should help you complete this calculation. <p><a class="icon-link" href="{{ url_for('atst.jedi_csp_calculator') }}">
<a href="{{ url_for('atst.jedi_csp_calculator') }}"> {{ Icon("link")}} Cloud Service Provider's estimate calculator
Cloud Service Provider's estimate calculator </a></p>
</a> <p>{{ "task_orders.new.funding.estimate_usage_paragraph" | translate }}</p>
</p> <div class="usa-input">
<h4>Upload a copy of your CSP Cost Estimate Research</h4> {{ form.pdf.label }}
{{ form.pdf.description }}
<p> <input type="file" disabled="disabled" />
Upload your anticipated cloud usage from the CSP tool linked above. PDFs and </div>
screengrabs of the tool are sufficient.
</p>
<p>
This is only an estimation tool to help you make and informed evaluation of
what you expect to use. While you're tied to the dollar amount you specify in
your task order, you're not obligated by the resources you indicate in the
calculator.
</p>
<input type="file">
<hr> <hr>
<h3>Cloud Usage Calculations</h3>
<p>
Enter the results of your cloud usage calculations. These will correspond with
your task order's period of performance.
</p>
<h4>Cloud Offerings</h4>
<p>
Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) offerings
</p>
{{ TextInput(form.clin_01, validation='dollars') }} <h3>{{ "task_orders.new.funding.cloud_calculations_title" | translate }}</h3>
<p>{{ "task_orders.new.funding.cloud_calculations_paragraph" | translate }}</p>
<h4>{{ "task_orders.new.funding.cloud_offerings_title" | translate }}</h4>
<p>{{ "task_orders.new.funding.cloud_offerings_paragraph" | translate }}</p>
{{ TextInput(form.clin_01, validation='dollars', placeholder="$0.00") }}
{{ TextInput(form.clin_02, validation='dollars', disabled=(not config.CLASSIFIED)) }} {{ TextInput(form.clin_02, validation='dollars', disabled=(not config.CLASSIFIED)) }}
<h4>Cloud Support and Assistance</h4> <h4>{{ "task_orders.new.funding.support_assistance_title" | translate }}</h4>
<p> <p>{{ "task_orders.new.funding.support_assistance_paragraph" | translate }}</p>
Technical guidance from the cloud service provider, including architecture, {{ TextInput(form.clin_03, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.', placeholder="$0.00") }}
configuration of IaaS and PaaS, integration, troubleshooting assistance, and
other services.
</p>
{{ TextInput(form.clin_03, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.') }}
{{ TextInput(form.clin_04, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.', disabled=(not config.CLASSIFIED)) }} {{ TextInput(form.clin_04, validation='dollars', tooltip='The cloud support and assistance packages cannot be used as a primary development resource.', disabled=(not config.CLASSIFIED)) }}
</div> </div>
</funding> </funding>
{% endblock %} {% endblock %}
@ -81,13 +56,10 @@
{% block next %} {% block next %}
<div class="row"> <div class="row">
<div class="col col--grow"> <div class="col col--grow">
<p>Total Task Order Value:<br><span id="to-target"></span></p> <p>{{ "task_orders.new.funding.total" | translate }}<br><span id="to-target"></span></p>
</div> </div>
<div class="col col--grow"> <div class="col col--grow">
{{ super() }} {{ super() }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,24 +2,43 @@
{% from "components/user_info.html" import UserInfo %} {% from "components/user_info.html" import UserInfo %}
{% from "components/checkbox_input.html" import CheckboxInput %} {% from "components/checkbox_input.html" import CheckboxInput %}
{% from "components/text_input.html" import TextInput %}
{% block heading %} {% block heading %}
Oversight {{ "task_orders.new.oversight.section_title" | translate }}
{% endblock %} {% endblock %}
{% block form %} {% block form %}
<!-- Oversight Section --> <!-- Oversight Section -->
<h3>Contracting Officer (KO) Information</h3> <h3>{{ "task_orders.new.oversight.ko_info_title" | translate }}</h3>
{{ UserInfo(form.ko_first_name, form.ko_last_name, form.ko_email, form.ko_dod_id) }} <p>{{ "task_orders.new.oversight.ko_info_paragraph" | translate }}</p>
{{ UserInfo(form.ko_first_name, form.ko_last_name, form.ko_email, form.ko_phone_number) }}
{{ CheckboxInput(form.ko_invite) }} {{ CheckboxInput(form.ko_invite) }}
{{ TextInput(form.ko_dod_id, placeholder="1234567890", tooltip="Why", tooltip_title='Why', validation='dodId')}}
<h3>Contractive Officer Representative (COR) Information</h3> <hr />
{{ UserInfo(form.cor_first_name, form.cor_last_name, form.cor_email, form.cor_dod_id) }}
<h3>{{ "task_orders.new.oversight.cor_info_title" | translate }}</h3>
<p>{{ "task_orders.new.oversight.cor_info_paragraph" | translate }}</p>
<div class='usa-input'>
<fieldset class="usa-input__choices">
<legend>
<input type="checkbox" name="am_cor" id="am_cor" value="y" />
<label for="am_cor">{{ "task_orders.new.oversight.am_cor_label" | translate }}</label>
</legend>
</fieldset>
</div>
{{ UserInfo(form.cor_first_name, form.cor_last_name, form.cor_email, form.cor_phone_number) }}
{{ CheckboxInput(form.cor_invite) }} {{ CheckboxInput(form.cor_invite) }}
{{ TextInput(form.cor_dod_id, placeholder="1234567890", tooltip="Why", tooltip_title='Why', validation='dodId')}}
<h3>Security Officer Information</h3> <hr />
{{ UserInfo(form.so_first_name, form.so_last_name, form.so_email, form.so_dod_id) }}
<h3>{{ "task_orders.new.oversight.so_info_title" | translate }}</h3>
<p>{{ "task_orders.new.oversight.so_info_paragraph" | translate }}</p>
{{ UserInfo(form.so_first_name, form.so_last_name, form.so_email, form.so_phone_number) }}
{{ CheckboxInput(form.so_invite) }} {{ CheckboxInput(form.so_invite) }}
{{ TextInput(form.so_dod_id, placeholder="1234567890", tooltip="Why", tooltip_title='Why', validation='dodId')}}
{% endblock %} {% endblock %}

View File

@ -27,7 +27,7 @@
<div class="row"> <div class="row">
<div class="col col--grow"> <div class="col col--grow">
<h3>Period of Performance length {{ TOEditLink(screen=2) }}</h3> <h3>Period of Performance length {{ TOEditLink(screen=2) }}</h3>
{{ task_order.period or RequiredLabel() }} {{ task_order.performance_length or RequiredLabel() }}
</div> </div>
<div class="col col--grow"> <div class="col col--grow">

View File

@ -37,6 +37,10 @@ def random_dod_id():
return "".join(random.choices(string.digits, k=10)) return "".join(random.choices(string.digits, k=10))
def random_phone_number():
return "".join(random.choices(string.digits, k=10))
def random_future_date(year_min=1, year_max=5): def random_future_date(year_min=1, year_max=5):
if year_min == year_max: if year_min == year_max:
inc = year_min inc = year_min
@ -71,9 +75,7 @@ class UserFactory(Base):
last_name = factory.Faker("last_name") last_name = factory.Faker("last_name")
atat_role = factory.LazyFunction(lambda: Roles.get("default")) atat_role = factory.LazyFunction(lambda: Roles.get("default"))
dod_id = factory.LazyFunction(random_dod_id) dod_id = factory.LazyFunction(random_dod_id)
phone_number = factory.LazyFunction( phone_number = factory.LazyFunction(random_phone_number)
lambda: "".join(random.choices(string.digits, k=10))
)
service_branch = factory.LazyFunction(random_service_branch) service_branch = factory.LazyFunction(random_service_branch)
citizenship = "United States" citizenship = "United States"
designation = "military" designation = "military"
@ -383,16 +385,20 @@ class TaskOrderFactory(Base):
end_date = factory.LazyFunction( end_date = factory.LazyFunction(
lambda *args: random_future_date(year_min=2, year_max=5) lambda *args: random_future_date(year_min=2, year_max=5)
) )
performance_length = random.randint(1, 24)
ko_first_name = factory.Faker("first_name") ko_first_name = factory.Faker("first_name")
ko_last_name = factory.Faker("last_name") ko_last_name = factory.Faker("last_name")
ko_email = factory.Faker("email") ko_email = factory.Faker("email")
ko_phone_number = factory.LazyFunction(random_phone_number)
ko_dod_id = factory.LazyFunction(random_dod_id) ko_dod_id = factory.LazyFunction(random_dod_id)
cor_first_name = factory.Faker("first_name") cor_first_name = factory.Faker("first_name")
cor_last_name = factory.Faker("last_name") cor_last_name = factory.Faker("last_name")
cor_email = factory.Faker("email") cor_email = factory.Faker("email")
cor_phone_number = factory.LazyFunction(random_phone_number)
cor_dod_id = factory.LazyFunction(random_dod_id) cor_dod_id = factory.LazyFunction(random_dod_id)
so_first_name = factory.Faker("first_name") so_first_name = factory.Faker("first_name")
so_last_name = factory.Faker("last_name") so_last_name = factory.Faker("last_name")
so_email = factory.Faker("email") so_email = factory.Faker("email")
so_phone_number = factory.LazyFunction(random_phone_number)
so_dod_id = factory.LazyFunction(random_dod_id) so_dod_id = factory.LazyFunction(random_dod_id)

View File

@ -224,7 +224,10 @@ def test_task_order_officer_accepts_invite(monkeypatch, client, user_session):
"ko_first_name": user_info["first_name"], "ko_first_name": user_info["first_name"],
"ko_last_name": user_info["last_name"], "ko_last_name": user_info["last_name"],
"ko_email": user_info["email"], "ko_email": user_info["email"],
"ko_phone_number": user_info["phone_number"],
"ko_dod_id": user_info["dod_id"], "ko_dod_id": user_info["dod_id"],
"cor_phone_number": user_info["phone_number"],
"so_phone_number": user_info["phone_number"],
"so_dod_id": task_order.so_dod_id, "so_dod_id": task_order.so_dod_id,
"cor_dod_id": task_order.cor_dod_id, "cor_dod_id": task_order.cor_dod_id,
"ko_invite": True, "ko_invite": True,

View File

@ -1,3 +1,13 @@
# How to use text containing html tags in .html files:
# In the template add the `safe` filter when referencing the string
# from the template file. ie:
# login:
# title: A title with <a href="#">a link</a>!
# `{{ "login.title" | translate | safe }}`
audit_log: audit_log:
events: events:
default: default:
@ -153,6 +163,45 @@ forms:
environment_names_required_validation_message: Provide at least one environment name. environment_names_required_validation_message: Provide at least one environment name.
environment_names_unique_validation_message: Environment names must be unique. environment_names_unique_validation_message: Environment names must be unique.
name_label: Project Name name_label: Project Name
task_order:
portfolio_name_label: Organization Portfolio Name
portfolio_name_description: The name of your office or organization. You can add multiple applications to your portfolio. Your task orders are used to pay for these applications and their environments.
scope_label: Cloud Project Scope
scope_description: Your team's plan for using the cloud, such as migrating an existing application or creating a prototype.
defense_component_label: Department of Defense Component
app_migration_label: App Migration
app_migration_description: Do you plan to migrate existing application(s) to the cloud?
native_apps_label: Native Apps
native_apps_description: Do you plan to develop application(s) natively in the cloud?
complexity_label: Project Complexity
complexity_description: Which of these describes how complex your team's use of the cloud will be? Select all that apply.
complexity_other_label: Project Complexity Other
dev_team_label: Development Team
dev_team_description: Which people or teams will be completing the development work for your cloud applications? Select all that apply.
dev_team_other_label: Development Team Other
team_experience_label: Team Experience
team_experience_description: How much experience does your team have with development in the cloud?
performance_length_label: Period of Performance length
start_date_label: Start Date
end_date_label: End Date
pdf_label: Upload a copy of your CSP Cost Estimate Research
pdf_description: Upload a PDF or screenshot of your usage estimate from the calculator.
clin_01_label: 'CLIN 01 : Unclassified'
clin_02_label: 'CLIN 02: Classified'
clin_03_label: 'CLIN 03: Unclassified'
clin_04_label: 'CLIN 04: Classified'
unclassified_clin_02_label: 'CLIN 02: Classified (available soon)'
unclassified_clin_04_label: 'CLIN 04: Classified (available soon)'
oversight_first_name_label: First Name
oversight_last_name_label: Last Name
oversight_email_label: Email
oversight_phone_label: Phone Number
oversight_dod_id_label: DoD ID
ko_invite_label: Invite KO to Task Order Builder
cor_invite_label: Invite COR to Task Order Builder
so_invite_label: Invite Security Officer to Task Order Builder
skip_invite_description: |
<i>You may choose to skip this for now and invite them later.</i>
validators: validators:
is_number_message: Please enter a valid number. is_number_message: Please enter a valid number.
list_item_required_message: Please provide at least one. list_item_required_message: Please provide at least one.
@ -295,6 +344,42 @@ requests:
num_software_systems_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.' num_software_systems_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.'
questions_title_text: Questions related to JEDI Cloud migration questions_title_text: Questions related to JEDI Cloud migration
rationalization_software_systems_tooltip: Rationalization is the DoD process to determine whether the application should move to the cloud. rationalization_software_systems_tooltip: Rationalization is the DoD process to determine whether the application should move to the cloud.
task_orders:
new:
app_info:
section_title: "What You're Making"
basic_info_title: Basic information
sample_scope: |
Not sure how to describe your scope? <a href="#">Read some Sample Scopes</a> to get an idea of what is appropriate.
project_title: About your project
team_title: About your team
market_research_title: Market Research
market_research_paragraph: 'The JEDI Cloud Computing Program Office (CCPO) has completed the market research requirements for all related task orders. The Department of Defense CIO has approved this research.<br /><a href="#">View JEDI Market Research Memo</a>'
funding:
section_title: Funding
performance_period_title: Period of Performance
performance_period_description: Choose the length of time your task order will cover.
performance_period_paragraph: Because your funds will be lost if you dont use them, we strongly recommend submitting small, short-duration task orders, usually a three month period. Well notify you when your period of performance is nearing the end so you can request your next set of funds with a new task order.
estimate_usage_title: Estimate Your Cloud Usage
estimate_usage_description: Calculate how much your cloud usage will cost. A technical representative should help you complete this calculation. These calculations will become your CLINs.
estimate_usage_paragraph: This is only an estimation tool to help you make an informed evaluation of what you expect to use. While you're tied to the dollar amount you specify in your task order, you're not obligated by the resources you indicate in the calculator.
cloud_calculations_title: Cloud Usage Calculations
cloud_calculations_paragraph: Enter the results of your cloud usage calculations.
cloud_offerings_title: Cloud Offerings
cloud_offerings_paragraph: Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) offerings
support_assistance_title: Cloud Support and Assistance
support_assistance_paragraph: Technical guidance from the cloud service provider, including architecture, configuration of IaaS and PaaS, integration, troubleshooting assistance, and other services.
total: 'Total Task Order Value:'
oversight:
section_title: Oversight
ko_info_title: Contracting Officer (KO) Information
ko_info_paragraph: Your KO will need to approve funding for this Task Order by logging into the JEDI Cloud Portal, submitting the Task Order documents within their official system of record, and electronically signing. You might want to work with your program Financial Manager to get your TO documents moving in the right dirction.
skip_ko_label: "Skip for now (We'll remind you to enter one later)"
cor_info_title: Contractive Officer Representative (COR) Information
cor_info_paragraph: Your COR may assist in submitting the Task Order documents within thier official system of record. They may also be invited to log in an manage the Task Order entry within the JEDI Cloud portal.
am_cor_label: I am the Contracting Officer Representative (COR) for this Task Order
so_info_title: Security Officer Information
so_info_paragraph: our Security Officer will need to answer some security configuration questions in order to generate a DD-254 document, then electronically sign.
testing: testing:
example_string: Hello World example_string: Hello World
example_with_variables: 'Hello, {name}!' example_with_variables: 'Hello, {name}!'