diff --git a/alembic/versions/4536f50b25bc_add_columns_to_task_order.py b/alembic/versions/4536f50b25bc_add_columns_to_task_order.py new file mode 100644 index 00000000..b6917329 --- /dev/null +++ b/alembic/versions/4536f50b25bc_add_columns_to_task_order.py @@ -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 ### diff --git a/alembic/versions/7f2040715b0d_add_oversight_columns_to_task_order.py b/alembic/versions/7f2040715b0d_add_oversight_columns_to_task_order.py new file mode 100644 index 00000000..ccd0b06e --- /dev/null +++ b/alembic/versions/7f2040715b0d_add_oversight_columns_to_task_order.py @@ -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 ### diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 865b5b05..e0e76573 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -24,8 +24,8 @@ class TaskOrders(object): "team_experience", ], "funding": [ - "start_date", - "end_date", + "performance_length", + # "pdf", "clin_01", "clin_02", "clin_03", @@ -35,14 +35,17 @@ class TaskOrders(object): "ko_first_name", "ko_last_name", "ko_email", + "ko_phone_number", "ko_dod_id", "cor_first_name", "cor_last_name", "cor_email", + "cor_phone_number", "cor_dod_id", "so_first_name", "so_last_name", "so_email", + "so_phone_number", "so_dod_id", ], } diff --git a/atst/forms/data.py b/atst/forms/data.py index 957dd911..202664fd 100644 --- a/atst/forms/data.py +++ b/atst/forms/data.py @@ -176,16 +176,20 @@ FUNDING_TYPES = [ TASK_ORDER_SOURCES = [("MANUAL", "Manual"), ("EDA", "EDA")] APP_MIGRATION = [ - ("on_premise", "Yes, migrating from an on-premise data center"), - ("cloud", "Yes, migrating from another cloud provider "), - ("none", "Not planning to migrate any applications "), + ("on_premise", "Yes, migrating from an on-premise data center"), + ("cloud", "Yes, migrating from another cloud provider"), + ( + "both", + "Yes, migrating from an on-premise data center and another cloud provider", + ), + ("none", "Not planning to migrate any applications"), ("not_sure", "Not Sure"), ] PROJECT_COMPLEXITY = [ ("storage", "Storage "), ("data_analytics", "Data Analytics "), - ("conus", "CONUS Only Access "), + ("conus", "CONUS Access "), ("oconus", "OCONUS Access "), ("tactical_edge", "Tactical Edge Access "), ("not_sure", "Not Sure "), @@ -193,11 +197,10 @@ PROJECT_COMPLEXITY = [ ] DEV_TEAM = [ - ("government", "Government"), - ("civilians", "Civilians"), + ("government_civilians", "Government Civilians"), ("military", "Military "), ("contractor", "Contractor "), - ("other", "Other"), + ("other", "Other (E.g. University or other partner)"), ] TEAM_EXPERIENCE = [ @@ -210,3 +213,30 @@ TEAM_EXPERIENCE = [ "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"), +] diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 5c062ae6..dee92715 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -6,12 +6,13 @@ from wtforms.fields import ( SelectMultipleField, StringField, TextAreaField, + FileField, ) -from wtforms.fields.html5 import DateField +from wtforms.fields.html5 import DateField, TelField from wtforms.widgets import ListWidget, CheckboxInput from wtforms.validators import Required, Length -from atst.forms.validators import IsNumber +from atst.forms.validators import IsNumber, PhoneNumber from .forms import CacheableForm from .data import ( @@ -20,70 +21,84 @@ from .data import ( PROJECT_COMPLEXITY, DEV_TEAM, TEAM_EXPERIENCE, + PERIOD_OF_PERFORMANCE_LENGTH, ) +from atst.utils.localization import translate class AppInfoForm(CacheableForm): portfolio_name = StringField( - "Organization 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", + translate("forms.task_order.portfolio_name_label"), + description=translate("forms.task_order.portfolio_name_description"), ) scope = TextAreaField( - "Cloud Project Scope", - description="Your team's plan for using the cloud, such as migrating an existing application or creating a prototype.", + translate("forms.task_order.scope_label"), + description=translate("forms.task_order.scope_description"), ) 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", - description="Do you plan to migrate existing application(s) to the cloud?", + translate("forms.task_order.app_migration_label"), + description=translate("forms.task_order.app_migration_description"), choices=APP_MIGRATION, default="", ) native_apps = RadioField( - "Native Apps", - description="Do you plan to develop application(s) natively in the cloud? ", + translate("forms.task_order.native_apps_label"), + description=translate("forms.task_order.native_apps_description"), choices=[("yes", "Yes"), ("no", "No"), ("not_sure", "Not Sure")], ) complexity = SelectMultipleField( - "Project Complexity", - description="Which of these describes how complex your team's use of the cloud will be? (Select all that apply.)", + translate("forms.task_order.complexity_label"), + description=translate("forms.task_order.complexity_description"), choices=PROJECT_COMPLEXITY, default="", widget=ListWidget(prefix_label=False), option_widget=CheckboxInput(), ) - complexity_other = StringField("Project Complexity Other") + complexity_other = StringField(translate("forms.task_order.complexity_other_label")) dev_team = SelectMultipleField( - "Development Team", - description="Which people or teams will be completing the development work for your cloud applications?", + translate("forms.task_order.dev_team_label"), + description=translate("forms.task_order.dev_team_description"), choices=DEV_TEAM, default="", widget=ListWidget(prefix_label=False), 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", - description="How much experience does your team have with development in the cloud?", + translate("forms.task_order.team_experience_label"), + description=translate("forms.task_order.team_experience_description"), choices=TEAM_EXPERIENCE, default="", ) class FundingForm(CacheableForm): - start_date = DateField("Start Date", format="%m/%d/%Y") - end_date = DateField("End Date", format="%m/%d/%Y") - clin_01 = IntegerField("CLIN 01 : Unclassified") - clin_02 = IntegerField("CLIN 02: Classified") - clin_03 = IntegerField("CLIN 03: Unclassified") - clin_04 = IntegerField("CLIN 04: Classified") + performance_length = SelectField( + translate("forms.task_order.performance_length_label"), + choices=PERIOD_OF_PERFORMANCE_LENGTH, + ) + 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" + ) + 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): - clin_02 = IntegerField("CLIN 02: Classified (available soon)") - clin_04 = IntegerField("CLIN 04: Classified (available soon)") + clin_02 = IntegerField(translate("forms.task_order.unclassified_clin_02_label")) + clin_04 = IntegerField(translate("forms.task_order.unclassified_clin_04_label")) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -92,52 +107,56 @@ class UnclassifiedFundingForm(FundingForm): class OversightForm(CacheableForm): - ko_first_name = StringField("First Name") - ko_last_name = StringField("Last Name") - ko_email = StringField("Email") + ko_first_name = StringField( + translate("forms.task_order.oversight_first_name_label") + ) + 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( - "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_last_name = StringField("Last Name") - cor_email = StringField("Email") + cor_first_name = StringField( + translate("forms.task_order.oversight_first_name_label") + ) + 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( - "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_last_name = StringField("Last Name") - so_email = StringField("Email") + so_first_name = StringField( + translate("forms.task_order.oversight_first_name_label") + ) + 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( - "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( - "Invite KO to Task Order Builder", - 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. - You may choose to skip this for now and invite them later. - """, + translate("forms.task_order.ko_invite_label"), + description=translate("forms.task_order.skip_invite_description"), ) cor_invite = BooleanField( - "Invite COR to Task Order Builder", - description=""" - Your COR may assist with submitting the Task Order documents within - their official system of record. You may choose to skip this for - now and invite them later. - """, + translate("forms.task_order.cor_invite_label"), + description=translate("forms.task_order.skip_invite_description"), ) so_invite = BooleanField( - "Invite Security Officer to Task Order Builder", - description=""" - Your Security Officer will need to answer some security - configuration questions in order to generate a DD-254 document, - then electronically sign. You may choose to skip this for now - and invite them later. - """, + translate("forms.task_order.so_invite_label"), + description=translate("forms.task_order.skip_invite_description"), ) diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 2a1c5daa..b4c3e50d 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -1,6 +1,14 @@ 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.orm import relationship @@ -46,6 +54,9 @@ class TaskOrder(Base, mixins.TimestampsMixin): team_experience = Column(String) # Team Experience start_date = Column(Date) # Period of Performance end_date = Column(Date) + performance_length = Column(Integer) + attachment_id = Column(ForeignKey("attachments.id")) + pdf = relationship("Attachment") clin_01 = Column(Numeric(scale=2)) clin_02 = 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_last_name = Column(String) # Last Name ko_email = Column(String) # Email + ko_phone_number = Column(String) # Phone Number ko_dod_id = Column(String) # DOD ID cor_first_name = Column(String) # First Name cor_last_name = Column(String) # Last Name cor_email = Column(String) # Email + cor_phone_number = Column(String) # Phone Number cor_dod_id = Column(String) # DOD ID so_first_name = Column(String) # First Name so_last_name = Column(String) # Last Name so_email = Column(String) # Email + so_phone_number = Column(String) # Phone Number so_dod_id = Column(String) # DOD ID number = Column(String, unique=True) # Task Order Number loa = Column(ARRAY(String)) # Line of Accounting (LOA) diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index 25a63998..532ed13c 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -20,7 +20,7 @@ from atst.services.invitation import Invitation as InvitationService TASK_ORDER_SECTIONS = [ { "section": "app_info", - "title": "What You're Building", + "title": "What You're Making", "template": "task_orders/new/app_info.html", "form": task_order_form.AppInfoForm, }, @@ -166,7 +166,13 @@ class UpdateTaskOrderWorkflow(ShowTaskOrderWorkflow): prefix = officer_type["prefix"] officer_data = { 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( self.user, self.task_order, officer_type["role"], officer_data diff --git a/js/components/forms/funding.js b/js/components/forms/funding.js index 73d7c077..d75ee74b 100644 --- a/js/components/forms/funding.js +++ b/js/components/forms/funding.js @@ -3,6 +3,7 @@ import { conformToMask } from 'vue-text-mask' import FormMixin from '../../mixins/form' import textinput from '../text_input' +import optionsinput from '../options_input' export default { name: 'funding', @@ -11,6 +12,7 @@ export default { components: { textinput, + optionsinput }, props: { diff --git a/templates/components/text_input.html b/templates/components/text_input.html index 21a0839d..5a0214d8 100644 --- a/templates/components/text_input.html +++ b/templates/components/text_input.html @@ -6,6 +6,7 @@ label=field.label | striptags, description=field.description, tooltip='', + tooltip_title='Help', placeholder='', validation='anything', paragraph=False, @@ -30,7 +31,7 @@