Merge pull request #973 from dod-ccpo/separate-to-form-into-steps
Split TO Form into 5 Steps
This commit is contained in:
commit
c8e700c8fe
@ -32,16 +32,17 @@ class TaskOrders(BaseDomainClass):
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
task_order.pdf = pdf
|
||||
|
||||
for clin in task_order.clins:
|
||||
db.session.delete(clin)
|
||||
if len(clins) > 0:
|
||||
for clin in task_order.clins:
|
||||
db.session.delete(clin)
|
||||
|
||||
TaskOrders.create_clins(task_order_id, clins)
|
||||
|
||||
if number != task_order.number:
|
||||
task_order.number = number
|
||||
db.session.add(task_order)
|
||||
|
||||
db.session.commit()
|
||||
TaskOrders.create_clins(task_order_id, clins)
|
||||
|
||||
return task_order
|
||||
|
||||
@classmethod
|
||||
|
@ -53,9 +53,7 @@ class CLINForm(FlaskForm):
|
||||
|
||||
|
||||
class TaskOrderForm(BaseForm):
|
||||
number = StringField(
|
||||
label=translate("forms.task_order.number_description"), validators=[Required()]
|
||||
)
|
||||
number = StringField(label=translate("forms.task_order.number_description"))
|
||||
pdf = FileField(
|
||||
None,
|
||||
description=translate("task_orders.form.supporting_docs_size_limit"),
|
||||
|
@ -7,7 +7,6 @@ from atst.domain.task_orders import TaskOrders
|
||||
from atst.forms.task_order import SignatureForm
|
||||
from atst.models import Permissions
|
||||
from atst.models.task_order import Status as TaskOrderStatus
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/review")
|
||||
@ -15,7 +14,9 @@ from atst.utils.flash import formatted_flash as flash
|
||||
def review_task_order(task_order_id):
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
if task_order.is_draft:
|
||||
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||
return redirect(
|
||||
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
|
||||
)
|
||||
else:
|
||||
signature_form = SignatureForm()
|
||||
return render_template(
|
||||
@ -25,20 +26,6 @@ def review_task_order(task_order_id):
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/submit", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, "submit task order")
|
||||
def submit_task_order(task_order_id):
|
||||
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
TaskOrders.sign(task_order=task_order, signer_dod_id=g.current_user.dod_id)
|
||||
|
||||
flash("task_order_submitted", task_order=task_order)
|
||||
|
||||
return redirect(
|
||||
url_for("task_orders.portfolio_funding", portfolio_id=task_order.portfolio.id)
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders")
|
||||
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING, message="view portfolio funding")
|
||||
def portfolio_funding(portfolio_id):
|
||||
|
@ -3,12 +3,12 @@ from flask import g, redirect, render_template, request as http_request, url_for
|
||||
from . import task_orders_bp
|
||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.forms.task_order import TaskOrderForm
|
||||
from atst.forms.task_order import TaskOrderForm, SignatureForm
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
|
||||
|
||||
def render_task_orders_edit(portfolio_id=None, task_order_id=None, form=None):
|
||||
def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, form=None):
|
||||
render_args = {}
|
||||
|
||||
if task_order_id:
|
||||
@ -24,24 +24,12 @@ def render_task_orders_edit(portfolio_id=None, task_order_id=None, form=None):
|
||||
"task_orders.portfolio_funding", portfolio_id=portfolio_id
|
||||
)
|
||||
|
||||
return render_template("task_orders/edit.html", **render_args)
|
||||
return render_template(template, **render_args)
|
||||
|
||||
|
||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new")
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/edit")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form")
|
||||
def edit(portfolio_id=None, task_order_id=None):
|
||||
return render_task_orders_edit(portfolio_id, task_order_id)
|
||||
|
||||
|
||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new", methods=["POST"])
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="create new task order")
|
||||
def update(portfolio_id=None, task_order_id=None):
|
||||
# TODO: I think saving and incomplete TO and saving a finished one should
|
||||
# be different routes. It would make the route functions more readable.
|
||||
form_data = {**http_request.form, **http_request.files}
|
||||
|
||||
def update_task_order(
|
||||
form_data, next_page, current_template, portfolio_id=None, task_order_id=None
|
||||
):
|
||||
form = None
|
||||
if task_order_id:
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
@ -57,18 +45,110 @@ def update(portfolio_id=None, task_order_id=None):
|
||||
else:
|
||||
task_order = TaskOrders.create(g.current_user, portfolio_id, **form.data)
|
||||
|
||||
# TO is finished and user can review and submit
|
||||
if task_order.is_completed and http_request.args.get("review"):
|
||||
return redirect(
|
||||
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
||||
)
|
||||
# User is trying to review and submit but the TO is not finished
|
||||
elif http_request.args.get("review"):
|
||||
return render_task_orders_edit(portfolio_id, task_order_id, form), 400
|
||||
# User is saving valid but incomplete TO state
|
||||
else:
|
||||
flash("task_order_draft")
|
||||
return redirect(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||
|
||||
return redirect(url_for(next_page, task_order_id=task_order.id))
|
||||
else:
|
||||
return render_task_orders_edit(portfolio_id, task_order_id, form), 400
|
||||
return (
|
||||
render_task_orders_edit(
|
||||
current_template, portfolio_id, task_order_id, form
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/form/step_1")
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_1")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form")
|
||||
def form_step_one_add_pdf(portfolio_id=None, task_order_id=None):
|
||||
return render_task_orders_edit(
|
||||
"task_orders/step_1.html",
|
||||
portfolio_id=portfolio_id,
|
||||
task_order_id=task_order_id,
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route(
|
||||
"/portfolios/<portfolio_id>/task_orders/form/step-1", methods=["POST"]
|
||||
)
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_1", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="update task order form")
|
||||
def submit_form_step_one_add_pdf(portfolio_id=None, task_order_id=None):
|
||||
form_data = {**http_request.form, **http_request.files}
|
||||
next_page = "task_orders.form_step_two_add_number"
|
||||
current_template = "task_orders/step_1.html"
|
||||
|
||||
return update_task_order(
|
||||
form_data,
|
||||
next_page,
|
||||
current_template,
|
||||
portfolio_id=portfolio_id,
|
||||
task_order_id=task_order_id,
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_2")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form")
|
||||
def form_step_two_add_number(task_order_id):
|
||||
return render_task_orders_edit(
|
||||
"task_orders/step_2.html", task_order_id=task_order_id
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_2", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="update task order form")
|
||||
def submit_form_step_two_add_number(task_order_id):
|
||||
form_data = {**http_request.form}
|
||||
next_page = "task_orders.form_step_three_add_clins"
|
||||
current_template = "task_orders/step_2.html"
|
||||
|
||||
return update_task_order(
|
||||
form_data, next_page, current_template, task_order_id=task_order_id
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_3")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form")
|
||||
def form_step_three_add_clins(task_order_id):
|
||||
return render_task_orders_edit(
|
||||
"task_orders/step_3.html", task_order_id=task_order_id
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_3", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="update task order form")
|
||||
def submit_form_step_three_add_clins(task_order_id):
|
||||
form_data = {**http_request.form}
|
||||
next_page = "task_orders.form_step_four_review"
|
||||
current_template = "task_orders/step_3.html"
|
||||
|
||||
return update_task_order(
|
||||
form_data, next_page, current_template, task_order_id=task_order_id
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_4")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form")
|
||||
def form_step_four_review(task_order_id):
|
||||
return render_task_orders_edit(
|
||||
"task_orders/step_4.html", task_order_id=task_order_id
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/form/step_5")
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view task order form")
|
||||
def form_step_five_confirm_signature(task_order_id):
|
||||
return render_task_orders_edit(
|
||||
"task_orders/step_5.html", task_order_id=task_order_id, form=SignatureForm()
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/<task_order_id>/submit", methods=["POST"])
|
||||
@user_can(Permissions.CREATE_TASK_ORDER, "submit task order")
|
||||
def submit_task_order(task_order_id):
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
TaskOrders.sign(task_order=task_order, signer_dod_id=g.current_user.dod_id)
|
||||
|
||||
flash("task_order_submitted", task_order=task_order)
|
||||
|
||||
return redirect(
|
||||
url_for("task_orders.portfolio_funding", portfolio_id=task_order.portfolio_id)
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ally from 'ally.js'
|
||||
import stickybits from 'stickybits'
|
||||
|
||||
import DateSelector from '../date_selector'
|
||||
import FormMixin from '../../mixins/form'
|
||||
@ -8,9 +9,11 @@ import checkboxinput from '../checkbox_input'
|
||||
import levelofwarrant from '../levelofwarrant'
|
||||
import multicheckboxinput from '../multi_checkbox_input'
|
||||
import optionsinput from '../options_input'
|
||||
import SemiCollapsibleText from '../semi_collapsible_text'
|
||||
import textinput from '../text_input'
|
||||
import uploadinput from '../upload_input'
|
||||
import ToForm from './to_form.js'
|
||||
import toggler from '../toggler'
|
||||
import uploadinput from '../upload_input'
|
||||
|
||||
export default {
|
||||
name: 'base-form',
|
||||
@ -22,9 +25,18 @@ export default {
|
||||
levelofwarrant,
|
||||
multicheckboxinput,
|
||||
optionsinput,
|
||||
SemiCollapsibleText,
|
||||
textinput,
|
||||
ToForm,
|
||||
toggler,
|
||||
uploadinput,
|
||||
},
|
||||
mixins: [FormMixin],
|
||||
directives: {
|
||||
sticky: {
|
||||
inserted: el => {
|
||||
stickybits(el)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import stickybits from 'stickybits'
|
||||
|
||||
import ClinFields from '../clin_fields'
|
||||
import DateSelector from '../date_selector'
|
||||
import FormMixin from '../../mixins/form'
|
||||
@ -71,12 +69,4 @@ export default {
|
||||
this.obligated = newObligated
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
sticky: {
|
||||
inserted: el => {
|
||||
stickybits(el)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
import checkboxinput from './checkbox_input'
|
||||
|
||||
export default {
|
||||
name: 'submit-confirmation',
|
||||
|
||||
components: {
|
||||
checkboxinput,
|
||||
},
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
valid: false,
|
||||
checked: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleValid: function() {
|
||||
this.valid = !this.valid
|
||||
},
|
||||
|
||||
handleClose: function() {
|
||||
this.$root.closeModal(this.name)
|
||||
this.checked = false
|
||||
this.valid = false
|
||||
},
|
||||
},
|
||||
}
|
@ -35,7 +35,6 @@ import { isNotInVerticalViewport } from './lib/viewport'
|
||||
import DateSelector from './components/date_selector'
|
||||
import SidenavToggler from './components/sidenav_toggler'
|
||||
import BaseForm from './components/forms/base_form'
|
||||
import SubmitConfirmation from './components/submit_confirmation'
|
||||
import DeleteConfirmation from './components/delete_confirmation'
|
||||
import NewEnvironment from './components/forms/new_environment'
|
||||
import EnvironmentRole from './components/environment_role'
|
||||
@ -79,7 +78,6 @@ const app = new Vue({
|
||||
SidenavToggler,
|
||||
BaseForm,
|
||||
DeleteConfirmation,
|
||||
SubmitConfirmation,
|
||||
nestedcheckboxinput,
|
||||
NewEnvironment,
|
||||
EnvironmentRole,
|
||||
|
@ -44,9 +44,9 @@
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input {
|
||||
.usa-button {
|
||||
margin: $gap $gap * 1.5 $gap 0;
|
||||
width: 19rem;
|
||||
width: 20rem;
|
||||
height: 3.2rem;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||
|
||||
{% macro SubmitConfirmation(modal_id, submit_text, submit_action, form, task_order) %}
|
||||
<submit-confirmation inline-template name="{{ modal_id }}" key="{{ modal_id }}">
|
||||
<div>
|
||||
<div class="usa-input">
|
||||
<label for="{{ modal_id }}-deleted-text">
|
||||
<div class="modal__form--header">
|
||||
<h1>Signature confirmation: <em>Task Order #{{task_order.number}}</em></h1>
|
||||
</div>
|
||||
{{ Alert('',
|
||||
message="All task orders require a Contracting Officer signature."
|
||||
) }}
|
||||
</label>
|
||||
<div v-on:change="toggleValid" class="checkbox">
|
||||
<div class='usa-input'>
|
||||
<fieldset data-ally-disabled="true" class="usa-input__choices">
|
||||
<legend>
|
||||
{{ form.signature(**{"v-model": "checked"}) }}
|
||||
{{ form.signature.label | safe }}
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-group">
|
||||
<form method="POST" action="{{ submit_action }}">
|
||||
{{ form.csrf_token }}
|
||||
<button class="usa-button usa-button-primary" v-bind:disabled="!valid">
|
||||
{{ submit_text }}
|
||||
</button>
|
||||
</form>
|
||||
<button v-on:click="handleClose" class="usa-button usa-button-secondary">{{ "common.cancel" | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</submit-confirmation>
|
||||
{% endmacro %}
|
85
templates/fragments/task_order_review.html
Normal file
85
templates/fragments/task_order_review.html
Normal file
@ -0,0 +1,85 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/semi_collapsible_text.html" import SemiCollapsibleText %}
|
||||
{% from "components/totals_box.html" import TotalsBox %}
|
||||
|
||||
|
||||
<div class="task-order">
|
||||
{{ SemiCollapsibleText() }}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h1">
|
||||
{{ "task_orders.review.review_your_task_order" | translate }}
|
||||
</div>
|
||||
<p>
|
||||
{{ "task_orders.review.check_paragraph" | translate }}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col task-order__details">
|
||||
<div class="h4">
|
||||
{{ "task_orders.review.task_order_number" | translate }}
|
||||
</div>
|
||||
<div>{{task_order.number}}</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h3">
|
||||
{{ "task_orders.review.funding_summary" | translate }}
|
||||
</div>
|
||||
|
||||
{% for clin in task_order.clins %}
|
||||
<div>
|
||||
{{ "{}".format(clin.jedi_clin_type) | translate}}
|
||||
</div>
|
||||
<table class="fixed-table-wrapper">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ "task_orders.review.clins.amount" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.obligated" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.pop_start" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.pop_end" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.loa" | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ clin.obligated_amount | dollars }}</td>
|
||||
<td>
|
||||
{% if clin.is_obligated() %}
|
||||
{{ "common.yes" | translate }}
|
||||
{% else %}
|
||||
{{ "common.no" | translate }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ clin.start_date | formattedDate }}</td>
|
||||
<td>{{ clin.end_date | formattedDate }}</td>
|
||||
<td>
|
||||
{% for loa in clin.loas %}
|
||||
<span title='{{ loa }}'>
|
||||
{{ loa }}
|
||||
</span>
|
||||
<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h3">
|
||||
{{ "task_orders.review.supporting_document.title" | translate }}
|
||||
</div>
|
||||
<div class="h4">
|
||||
<a class="icon-link icon-link--download" href="{{ url_for('task_orders.download_task_order_pdf', task_order_id=task_order.id) }}">
|
||||
{{ Icon('check-circle-solid') }}
|
||||
{{ task_order.pdf.filename }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ TotalsBox(task_order=task_order) }}
|
||||
|
||||
</div>
|
||||
</div>
|
@ -27,7 +27,7 @@
|
||||
icon='funding',
|
||||
text='navigation.portfolio_navigation.breadcrumbs.funding' | translate,
|
||||
url=url_for("task_orders.portfolio_funding", portfolio_id=portfolio.id),
|
||||
active=request.url_rule.endpoint in ["task_orders.portfolio_funding", "task_orders.review_task_order", "task_orders.edit", "task_orders.update"],
|
||||
active=request.url_rule.endpoint in ["task_orders.portfolio_funding", "task_orders.review_task_order", "task_orders.form_step_one_add_pdf", "task_orders.submit_form_step_one_add_pdf", "task_orders.form_step_two_add_number", "task_orders.submit_form_step_two_add_number", "task_orders.form_step_three_add_clins", "task_orders.submit_form_step_three_add_clins", "task_orders.form_step_four_review", "task_orders.form_step_five_confirm_signature"],
|
||||
) }}
|
||||
{{ Link(
|
||||
icon='applications',
|
||||
|
@ -13,7 +13,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro TaskOrderEditButton(task_order, text="Edit", secondary=False) %}
|
||||
<a href="{{ url_for('task_orders.edit', task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
|
||||
<a href="{{ url_for('task_orders.form_step_one_add_pdf', task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
|
||||
{{ text }}
|
||||
</a>
|
||||
{% endmacro %}
|
||||
@ -97,7 +97,7 @@
|
||||
|
||||
{% call StickyCTA(text="Funding") %}
|
||||
{% if user_can(permissions.CREATE_TASK_ORDER) %}
|
||||
<a href="{{ url_for("task_orders.edit", portfolio_id=portfolio.id) }}" class="usa-button usa-button-primary" type="submit">Start a new task order</a>
|
||||
<a href="{{ url_for("task_orders.form_step_one_add_pdf", portfolio_id=portfolio.id) }}" class="usa-button usa-button-primary" type="submit">Start a new task order</a>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
@ -111,7 +111,7 @@
|
||||
{{ EmptyState(
|
||||
'This portfolio doesn’t have any active or pending task orders.',
|
||||
action_label='Add a New Task Order',
|
||||
action_href=url_for('task_orders.edit', portfolio_id=portfolio.id),
|
||||
action_href=url_for('task_orders.form_step_one_add_pdf', portfolio_id=portfolio.id),
|
||||
icon='cloud',
|
||||
add_perms=user_can(permissions.CREATE_TASK_ORDER)
|
||||
) }}
|
||||
|
@ -1,111 +1,12 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/semi_collapsible_text.html" import SemiCollapsibleText %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
{% from "components/submit_confirmation.html" import SubmitConfirmation %}
|
||||
{% from "components/totals_box.html" import TotalsBox %}
|
||||
|
||||
{% extends 'portfolios/base.html' %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% set submit_modal_id = "submit-to-1" %}
|
||||
{% call Modal(name=submit_modal_id) %}
|
||||
{{
|
||||
SubmitConfirmation(
|
||||
modal_id=submit_modal_id,
|
||||
submit_text="Confirm & Submit",
|
||||
submit_action=url_for('task_orders.submit_task_order', task_order_id=task_order.id),
|
||||
form=signature_form,
|
||||
task_order=task_order,
|
||||
)
|
||||
}}
|
||||
|
||||
{% call StickyCTA(text="Task order details") %}
|
||||
<a href="{{ url_for('task_orders.form_step_one_add_pdf', task_order_id=task_order.id) }}" class="usa-button usa-button-secondary" type="submit">Edit</a>
|
||||
{% endcall %}
|
||||
|
||||
{% call StickyCTA(text="Review Funding") %}
|
||||
<a href="{{ url_for("task_orders.edit", task_order_id=task_order.id) }}" class="usa-button usa-button-secondary" type="submit">Edit</a>
|
||||
<a v-on:click="openModal('submit-to-1')" class="usa-button usa-button-primary" type="submit">Submit task order</a>
|
||||
{% endcall %}
|
||||
|
||||
<div class="task-order">
|
||||
|
||||
{{ SemiCollapsibleText() }}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h1">
|
||||
{{ "task_orders.review.review_your_task_order" | translate }}
|
||||
</div>
|
||||
<p>
|
||||
{{ "task_orders.review.check_paragraph" | translate }}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col task-order__details">
|
||||
<div class="h4">
|
||||
{{ "task_orders.review.task_order_number" | translate }}
|
||||
</div>
|
||||
<div>{{task_order.number}}</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h3">
|
||||
{{ "task_orders.review.funding_summary" | translate }}
|
||||
</div>
|
||||
|
||||
{% for clin in task_order.clins %}
|
||||
<div>
|
||||
{{ "{}".format(clin.jedi_clin_type) | translate}}
|
||||
</div>
|
||||
<table class="fixed-table-wrapper">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ "task_orders.review.clins.amount" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.obligated" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.pop_start" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.pop_end" | translate }}</th>
|
||||
<th>{{ "task_orders.review.clins.loa" | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ clin.obligated_amount | dollars }}</td>
|
||||
<td>
|
||||
{% if clin.is_obligated() %}
|
||||
{{ "common.yes" | translate }}
|
||||
{% else %}
|
||||
{{ "common.no" | translate }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ clin.start_date | formattedDate }}</td>
|
||||
<td>{{ clin.end_date | formattedDate }}</td>
|
||||
<td>
|
||||
{% for loa in clin.loas %}
|
||||
<span title='{{ loa }}'>
|
||||
{{ loa }}
|
||||
</span>
|
||||
<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h3">
|
||||
{{ "task_orders.review.supporting_document.title" | translate }}
|
||||
</div>
|
||||
<div class="h4">
|
||||
<a class="icon-link icon-link--download" href="{{ url_for('task_orders.download_task_order_pdf', task_order_id=task_order.id) }}">
|
||||
{{ Icon('check-circle-solid') }}
|
||||
{{ task_order.pdf.filename }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ TotalsBox(task_order=task_order) }}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% include "fragments/task_order_review.html" %}
|
||||
{% endblock %}
|
||||
|
42
templates/task_orders/builder_base.html
Normal file
42
templates/task_orders/builder_base.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% extends "portfolios/base.html" %}
|
||||
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
<base-form inline-template>
|
||||
<form id="to_form" action='{{ action }}' method="POST" autocomplete="off" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
|
||||
{% call StickyCTA(text=('task_orders.form.sticky_header_text' | translate({"step": step}) )) %}
|
||||
<span class="action-group">
|
||||
{% block next_button %}
|
||||
<input
|
||||
type="submit"
|
||||
tabindex="0"
|
||||
:disabled="!changed"
|
||||
value="{{ next_button_text }}"
|
||||
form="to_form"
|
||||
class="usa-button usa-button-primary">
|
||||
{% endblock %}
|
||||
|
||||
{% if step != "1" %}
|
||||
<a class="usa-button usa-button-secondary" href="{{ previous_button_link }}">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a
|
||||
href="{{ cancel_url }}"
|
||||
class="action-group__action icon-link">
|
||||
{{ "common.cancel" | translate }}
|
||||
</a>
|
||||
</span>
|
||||
{% endcall %}
|
||||
|
||||
{% include "fragments/flash.html" %}
|
||||
|
||||
{% block to_builder_form_field %}{% endblock %}
|
||||
|
||||
</form>
|
||||
</base-form>
|
||||
{% endblock %}
|
@ -1,443 +0,0 @@
|
||||
{% extends "portfolios/base.html" %}
|
||||
|
||||
{% from 'components/date_picker.html' import DatePicker %}
|
||||
{% from 'components/icon.html' import Icon %}
|
||||
{% from 'components/options_input.html' import OptionsInput %}
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% from "components/semi_collapsible_text.html" import SemiCollapsibleText %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
{% from 'components/text_input.html' import TextInput %}
|
||||
{% from "components/totals_box.html" import TotalsBox %}
|
||||
{% from 'components/upload_input.html' import UploadInput %}
|
||||
|
||||
{% macro LOAInput() %}
|
||||
<div v-for="loa in loas">
|
||||
<textinput :name="'clins-' + clinIndex + '-loas-' + loaIndex(loa)" :watch='true' inline-template>
|
||||
<div class="usa-input usa-input--validation--anything">
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="icon-link"
|
||||
v-on:click="addLoa"
|
||||
type="button">
|
||||
{{ Icon('plus') }}
|
||||
<span>{{ 'task_orders.form.add_loa' | translate }}</span>
|
||||
</button>
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CLINFields(fields, index) %}
|
||||
{% if index != 0 %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
<clin-fields
|
||||
v-bind:initial-clin-index='{{ index }}'
|
||||
v-bind:initial-loa-count="{{ fields.loas.data | length or 0 }}"
|
||||
v-bind:initial-clin-type="'{{ fields.jedi_clin_type.data }}'"
|
||||
v-bind:initial-amount='{{ fields.obligated_amount.data or 0 }}'
|
||||
inline-template>
|
||||
<div>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
{{ OptionsInput(fields.jedi_clin_type, watch=True) }}
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
{{ TextInput(fields.number, watch=True) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usa-input">
|
||||
<fieldset class="usa-input__choices task-order__loa-fieldset">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.loa_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
{% for loa in fields.loas %}
|
||||
{{ TextInput(loa, showLabel=False, watch=True) }}
|
||||
{% endfor %}
|
||||
|
||||
{{ LOAInput() }}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{{ DatePicker(fields.start_date, watch=True, optional=False) }}
|
||||
{{ DatePicker(fields.end_date, watch=True, optional=False) }}
|
||||
{{ TextInput(fields.obligated_amount, validation='dollars', watch=True) }}
|
||||
</div>
|
||||
</clin-fields>
|
||||
{% endmacro %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% if task_order_id %}
|
||||
{% set action = url_for("task_orders.update", task_order_id=task_order_id) %}
|
||||
{% set review_action = url_for("task_orders.update", task_order_id=task_order_id, review=True) %}
|
||||
{% else %}
|
||||
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id) %}
|
||||
{% set review_action = url_for("task_orders.update", portfolio_id=portfolio.id, review=True) %}
|
||||
{% endif %}
|
||||
<form id="new-task-order" action='{{ action }}' method="POST" autocomplete="off" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
|
||||
{% set obligated = task_order.total_obligated_funds if task_order else 0 %}
|
||||
{% set total = task_order.total_contract_amount if task_order else 0 %}
|
||||
|
||||
<to-form
|
||||
inline-template
|
||||
v-bind:initial-obligated='{{ obligated }}'
|
||||
v-bind:initial-total='{{ total }}'
|
||||
v-bind:initial-clin-count="{{ form.clins.data | length }}">
|
||||
<div>
|
||||
{% call StickyCTA(text=('task_orders.form.sticky_header_text' | translate )) %}
|
||||
<span class="action-group">
|
||||
<input
|
||||
type="submit"
|
||||
formaction="{{ review_action }}"
|
||||
tabindex="0"
|
||||
{% if task_order and task_order.is_draft %}
|
||||
disabled="disabled"
|
||||
{% endif %}
|
||||
:disabled="invalid"
|
||||
value="{{ 'task_orders.form.review_button' | translate }}"
|
||||
form="new-task-order"
|
||||
class="usa-button usa-button-primary">
|
||||
<input
|
||||
type="submit"
|
||||
class="usa-button usa-button-secondary"
|
||||
tabindex="0"
|
||||
:disabled="!changed"
|
||||
value="{{ 'common.save' | translate }}"
|
||||
form="new-task-order"/>
|
||||
<a
|
||||
href="{{ cancel_url }}"
|
||||
class="action-group__action icon-link">
|
||||
{{ "common.cancel" | translate }}
|
||||
</a>
|
||||
</span>
|
||||
{% endcall %}
|
||||
|
||||
<div class="task-order task_order__form">
|
||||
<p class="section-description">
|
||||
{{ "task_orders.new.form_help_text" | translate }}
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
{% include "fragments/flash.html" %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="h1">{{ 'task_orders.form.add_to_header' | translate }}</div>
|
||||
{{ TextInput(form.number, validation='taskOrderNumber', optional=False) }}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h3">{{ 'task_orders.form.cloud_funding_header' | translate }}</div>
|
||||
<div>
|
||||
{{ 'task_orders.form.cloud_funding_text' | translate }}
|
||||
</div>
|
||||
|
||||
{% for clin in form.clins %}
|
||||
{{ CLINFields(clin, index=loop.index - 1) }}
|
||||
{% endfor %}
|
||||
|
||||
<div v-for="clin in clins">
|
||||
<hr v-if="clinIndex !== 0">
|
||||
<clin-fields
|
||||
v-bind:initial-clin-index='clinIndex'
|
||||
v-bind:initial-clin-type="'JEDI_CLIN_1'"
|
||||
inline-template>
|
||||
<div>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
<optionsinput :name="'clins-' + clinIndex + '-jedi_clin_type'" :watch='true' :optional='false' inline-template>
|
||||
<div class="usa-input">
|
||||
<fieldset data-ally-disabled="true" class="usa-input__choices" v-on:change="onInput">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.clin_type_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
<select :id='name' :name='name'>
|
||||
<option value="JEDI_CLIN_1">{{ "forms.task_order.clin_01_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_2">{{ "forms.task_order.clin_02_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_3">{{ "forms.task_order.clin_03_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_4">{{ "forms.task_order.clin_04_label" | translate }}</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</div>
|
||||
</optionsinput>
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
<textinput :name="'clins-' + clinIndex + '-number'" :watch='true' inline-template>
|
||||
<div class="usa-input">
|
||||
<label :for="name">
|
||||
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||
<div class="usa-input__title">{{ 'task_orders.form.clin_number_label' | translate }}</div>
|
||||
</label>
|
||||
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usa-input">
|
||||
<fieldset class="usa-input__choices task-order__loa-fieldset">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.loa_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
{{ LOAInput() }}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :watch='true' :optional='false' inline-template>
|
||||
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.pop_start' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
|
||||
<div class="date-picker-component">
|
||||
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
|
||||
|
||||
<div class="usa-form-group usa-form-group-month">
|
||||
<label>{{ 'components.date_selector.month' | translate }}</label>
|
||||
<input
|
||||
name="date-month"
|
||||
max="12"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||
v-model="month"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-day">
|
||||
<label>{{ 'components.date_selector.day' | translate }}</label>
|
||||
<input
|
||||
name="date-day"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||
v-bind:max="daysMaxCalculation"
|
||||
v-model="day"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-year">
|
||||
<label>{{ 'components.date_selector.year' | translate }}</label>
|
||||
<input
|
||||
name="date-year"
|
||||
maxlength="4"
|
||||
type="number"
|
||||
v-model="year"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||
{{ Icon("ok", classes="icon--green") }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</date-selector>
|
||||
|
||||
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :watch='true' :optional='false' inline-template>
|
||||
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.pop_end' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
|
||||
<div class="date-picker-component">
|
||||
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
|
||||
|
||||
<div class="usa-form-group usa-form-group-month">
|
||||
<label>{{ 'components.date_selector.month' | translate }}</label>
|
||||
<input
|
||||
name="date-month"
|
||||
max="12"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||
v-model="month"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-day">
|
||||
<label>{{ 'components.date_selector.day' | translate }}</label>
|
||||
<input
|
||||
name="date-day"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||
v-bind:max="daysMaxCalculation"
|
||||
v-model="day"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-year">
|
||||
<label>{{ 'components.date_selector.year' | translate }}</label>
|
||||
<input
|
||||
name="date-year"
|
||||
maxlength="4"
|
||||
type="number"
|
||||
v-model="year"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||
{{ Icon("ok", classes="icon--green") }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</date-selector>
|
||||
|
||||
<textinput
|
||||
v-cloak
|
||||
inline-template
|
||||
:name="'clins-' + clinIndex + '-obligated_amount'"
|
||||
validation="dollars"
|
||||
:watch='true'>
|
||||
<div class="usa-input usa-input--validation--dollars noMaxWidth">
|
||||
<label :for="name">
|
||||
<div class="usa-input__title">{{ 'task_orders.form.obligated_funds_label' | translate }}</div>
|
||||
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||
</label>
|
||||
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
v-bind:show-mask='false'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
</div>
|
||||
</clin-fields>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="icon-link icon-link__add-another-clin"
|
||||
v-on:click="addClin"
|
||||
type="button">
|
||||
{{ Icon('plus') }}
|
||||
<span>{{ 'task_orders.form.add_clin' | translate }}</span>
|
||||
</button>
|
||||
|
||||
<hr>
|
||||
<div class="h3">{{ 'task_orders.form.supporting_docs_header' | translate }}</div>
|
||||
<div class="task-order__pdf-help-text">
|
||||
{{ 'task_orders.form.supporting_docs_text' | translate }} {{ Icon('question')}}
|
||||
</div>
|
||||
{{ UploadInput(form.pdf, watch=True) }}
|
||||
</div>
|
||||
|
||||
<totals-box
|
||||
inline-template
|
||||
v-bind:obligated='obligated'
|
||||
v-bind:contract-amount='total'
|
||||
>
|
||||
<div class="col totals-box">
|
||||
<div class="h4">{{ 'components.totals_box.obligated_funds' | translate }}</div>
|
||||
<div class="h3" v-html="formattedObligated"></div>
|
||||
<div>{{ 'components.totals_box.obligated_text' | translate }}</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="h4">{{ 'components.totals_box.total_amount' | translate }}</div>
|
||||
<div class="h3" v-html="formattedContractAmount"></div>
|
||||
<div>{{ 'components.totals_box.total_text' | translate }}</div>
|
||||
</div>
|
||||
</totals-box>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</to-form>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
28
templates/task_orders/step_1.html
Normal file
28
templates/task_orders/step_1.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "task_orders/builder_base.html" %}
|
||||
|
||||
{% from 'components/icon.html' import Icon %}
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
{% from 'components/upload_input.html' import UploadInput %}
|
||||
|
||||
{% if task_order_id %}
|
||||
{% set action = url_for("task_orders.submit_form_step_one_add_pdf", task_order_id=task_order_id) %}
|
||||
{% else %}
|
||||
{% set action = url_for("task_orders.submit_form_step_one_add_pdf", portfolio_id=portfolio.id) %}
|
||||
{% endif %}
|
||||
|
||||
{% set next_button_text = "Next: Add TO Number" %}
|
||||
{% set previous_button_link = cancel_url %}
|
||||
{% set step = "1" %}
|
||||
|
||||
|
||||
{% block to_builder_form_field %}
|
||||
<div class="h3">
|
||||
{{ 'task_orders.form.supporting_docs_header' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="task-order__pdf-help-text">
|
||||
{{ 'task_orders.form.supporting_docs_text' | translate }} {{ Icon('question')}}
|
||||
</div>
|
||||
|
||||
{{ UploadInput(form.pdf, watch=True) }}
|
||||
{% endblock %}
|
16
templates/task_orders/step_2.html
Normal file
16
templates/task_orders/step_2.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "task_orders/builder_base.html" %}
|
||||
|
||||
{% from 'components/text_input.html' import TextInput %}
|
||||
|
||||
{% set action = url_for("task_orders.submit_form_step_two_add_number", task_order_id=task_order_id) %}
|
||||
{% set next_button_text = "Next: Add Base CLIN" %}
|
||||
{% set previous_button_link = url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order_id) %}
|
||||
{% set step = "2" %}
|
||||
|
||||
{% block to_builder_form_field %}
|
||||
<div class="h1">
|
||||
{{ 'task_orders.form.add_to_header' | translate }}
|
||||
</div>
|
||||
|
||||
{{ TextInput(form.number, validation='taskOrderNumber', optional=False) }}
|
||||
{% endblock %}
|
355
templates/task_orders/step_3.html
Normal file
355
templates/task_orders/step_3.html
Normal file
@ -0,0 +1,355 @@
|
||||
{% extends "task_orders/builder_base.html" %}
|
||||
|
||||
{% from 'components/date_picker.html' import DatePicker %}
|
||||
{% from 'components/icon.html' import Icon %}
|
||||
{% from 'components/options_input.html' import OptionsInput %}
|
||||
{% from 'components/text_input.html' import TextInput %}
|
||||
|
||||
{% set action = url_for("task_orders.submit_form_step_three_add_clins", task_order_id=task_order_id) %}
|
||||
{% set next_button_text = "Next: Review Funding" %}
|
||||
{% set previous_button_link = url_for("task_orders.form_step_two_add_number", task_order_id=task_order_id) %}
|
||||
{% set step = "3" %}
|
||||
|
||||
{% macro LOAInput() %}
|
||||
<div v-for="loa in loas">
|
||||
<textinput :name="'clins-' + clinIndex + '-loas-' + loaIndex(loa)" :watch='true' inline-template>
|
||||
<div class="usa-input usa-input--validation--anything">
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="icon-link"
|
||||
v-on:click="addLoa"
|
||||
type="button">
|
||||
{{ Icon('plus') }}
|
||||
<span>{{ 'task_orders.form.add_loa' | translate }}</span>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CLINFields(fields, index) %}
|
||||
{% if index != 0 %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
<clin-fields
|
||||
v-bind:initial-clin-index='{{ index }}'
|
||||
v-bind:initial-loa-count="{{ fields.loas.data | length or 0 }}"
|
||||
v-bind:initial-clin-type="'{{ fields.jedi_clin_type.data }}'"
|
||||
v-bind:initial-amount='{{ fields.obligated_amount.data or 0 }}'
|
||||
inline-template>
|
||||
<div>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
{{ OptionsInput(fields.jedi_clin_type, watch=True) }}
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
{{ TextInput(fields.number, watch=True) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usa-input">
|
||||
<fieldset class="usa-input__choices task-order__loa-fieldset">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.loa_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
{% for loa in fields.loas %}
|
||||
{{ TextInput(loa, showLabel=False, watch=True) }}
|
||||
{% endfor %}
|
||||
|
||||
{{ LOAInput() }}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
{{ DatePicker(fields.start_date, watch=True, optional=False) }}
|
||||
{{ DatePicker(fields.end_date, watch=True, optional=False) }}
|
||||
{{ TextInput(fields.obligated_amount, validation='dollars', watch=True) }}
|
||||
</div>
|
||||
</clin-fields>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% block to_builder_form_field %}
|
||||
|
||||
<to-form
|
||||
inline-template
|
||||
v-bind:initial-clin-count="{{ form.clins.data | length }}">
|
||||
<div>
|
||||
<div class="h3">
|
||||
{{ 'task_orders.form.cloud_funding_header' | translate }}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'task_orders.form.cloud_funding_text' | translate }}
|
||||
</div>
|
||||
|
||||
{% for clin in form.clins %}
|
||||
{{ CLINFields(clin, index=loop.index - 1) }}
|
||||
{% endfor %}
|
||||
|
||||
<div v-for="clin in clins">
|
||||
<hr v-if="clinIndex !== 0">
|
||||
<clin-fields
|
||||
v-bind:initial-clin-index='clinIndex'
|
||||
v-bind:initial-clin-type="'JEDI_CLIN_1'"
|
||||
inline-template>
|
||||
<div>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
<optionsinput :name="'clins-' + clinIndex + '-jedi_clin_type'" :watch='true' :optional='false' inline-template>
|
||||
<div class="usa-input">
|
||||
<fieldset data-ally-disabled="true" class="usa-input__choices" v-on:change="onInput">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.clin_type_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
<select :id='name' :name='name'>
|
||||
<option value="JEDI_CLIN_1">{{ "forms.task_order.clin_01_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_2">{{ "forms.task_order.clin_02_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_3">{{ "forms.task_order.clin_03_label" | translate }}</option>
|
||||
<option value="JEDI_CLIN_4">{{ "forms.task_order.clin_04_label" | translate }}</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</div>
|
||||
</optionsinput>
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
<textinput :name="'clins-' + clinIndex + '-number'" :watch='true' inline-template>
|
||||
<div class="usa-input">
|
||||
<label :for="name">
|
||||
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||
<div class="usa-input__title">{{ 'task_orders.form.clin_number_label' | translate }}</div>
|
||||
</label>
|
||||
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usa-input">
|
||||
<fieldset class="usa-input__choices task-order__loa-fieldset">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.loa_label' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
{{ LOAInput() }}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :watch='true' :optional='false' inline-template>
|
||||
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.pop_start' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
|
||||
<div class="date-picker-component">
|
||||
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
|
||||
|
||||
<div class="usa-form-group usa-form-group-month">
|
||||
<label>{{ 'components.date_selector.month' | translate }}</label>
|
||||
<input
|
||||
name="date-month"
|
||||
max="12"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||
v-model="month"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-day">
|
||||
<label>{{ 'components.date_selector.day' | translate }}</label>
|
||||
<input
|
||||
name="date-day"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||
v-bind:max="daysMaxCalculation"
|
||||
v-model="day"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-year">
|
||||
<label>{{ 'components.date_selector.year' | translate }}</label>
|
||||
<input
|
||||
name="date-year"
|
||||
maxlength="4"
|
||||
type="number"
|
||||
v-model="year"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||
{{ Icon("ok", classes="icon--green") }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</date-selector>
|
||||
|
||||
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :watch='true' :optional='false' inline-template>
|
||||
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
|
||||
<legend>
|
||||
<div class="usa-input__title">
|
||||
{{ 'task_orders.form.pop_end' | translate }}
|
||||
</div>
|
||||
</legend>
|
||||
|
||||
<div class="date-picker-component">
|
||||
<input :name="name" v-bind:value="formattedDate" v-on:change="onInput" type="hidden" />
|
||||
|
||||
<div class="usa-form-group usa-form-group-month">
|
||||
<label>{{ 'components.date_selector.month' | translate }}</label>
|
||||
<input
|
||||
name="date-month"
|
||||
max="12"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (month && !isMonthValid) }"
|
||||
v-model="month"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-day">
|
||||
<label>{{ 'components.date_selector.day' | translate }}</label>
|
||||
<input
|
||||
name="date-day"
|
||||
maxlength="2"
|
||||
min="1"
|
||||
type="number"
|
||||
v-bind:class="{ 'usa-input-error': (day && !isDayValid) }"
|
||||
v-bind:max="daysMaxCalculation"
|
||||
v-model="day"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group usa-form-group-year">
|
||||
<label>{{ 'components.date_selector.year' | translate }}</label>
|
||||
<input
|
||||
name="date-year"
|
||||
maxlength="4"
|
||||
type="number"
|
||||
v-model="year"
|
||||
v-on:change="onInput"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group-date-ok" v-if="isDateValid">
|
||||
{{ Icon("ok", classes="icon--green") }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</date-selector>
|
||||
|
||||
<textinput
|
||||
v-cloak
|
||||
inline-template
|
||||
:name="'clins-' + clinIndex + '-obligated_amount'"
|
||||
validation="dollars"
|
||||
:watch='true'>
|
||||
<div class="usa-input usa-input--validation--dollars noMaxWidth">
|
||||
<label :for="name">
|
||||
<div class="usa-input__title">{{ 'task_orders.form.obligated_funds_label' | translate }}</div>
|
||||
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||
</label>
|
||||
|
||||
<masked-input
|
||||
v-on:input='onInput'
|
||||
v-on:blur='onBlur'
|
||||
v-on:change='onChange'
|
||||
v-bind:value='value'
|
||||
v-bind:mask='mask'
|
||||
v-bind:pipe='pipe'
|
||||
v-bind:keep-char-positions='keepCharPositions'
|
||||
v-bind:aria-invalid='showError'
|
||||
v-bind:show-mask='false'
|
||||
type='text'
|
||||
:id='name'
|
||||
ref='input'>
|
||||
</masked-input>
|
||||
|
||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||
|
||||
<template v-if='showError'>
|
||||
<span class='usa-input__message' v-html='validationError'></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class='usa-input__message'></span>
|
||||
</template>
|
||||
</div>
|
||||
</textinput>
|
||||
</div>
|
||||
</clin-fields>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="icon-link icon-link__add-another-clin"
|
||||
v-on:click="addClin"
|
||||
type="button">
|
||||
{{ Icon('plus') }}
|
||||
<span>{{ 'task_orders.form.add_clin' | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</to-form>
|
||||
{% endblock %}
|
17
templates/task_orders/step_4.html
Normal file
17
templates/task_orders/step_4.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends "task_orders/builder_base.html" %}
|
||||
|
||||
{% set action = url_for('task_orders.form_step_five_confirm_signature', task_order_id=task_order_id) %}
|
||||
{% set previous_button_link = url_for("task_orders.form_step_three_add_clins", task_order_id=task_order_id) %}
|
||||
{% set step = "4" %}
|
||||
|
||||
{% block next_button %}
|
||||
<a
|
||||
href="{{ action }}"
|
||||
class="usa-button usa-button-primary">
|
||||
Next: Submit Task Order
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block to_builder_form_field %}
|
||||
{% include "fragments/task_order_review.html" %}
|
||||
{% endblock %}
|
22
templates/task_orders/step_5.html
Normal file
22
templates/task_orders/step_5.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "task_orders/builder_base.html" %}
|
||||
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||
|
||||
{% set action = url_for("task_orders.submit_task_order", task_order_id=task_order_id) %}
|
||||
{% set next_button_text = "Next: Confirm & Submit" %}
|
||||
{% set previous_button_link = url_for("task_orders.form_step_four_review", task_order_id=task_order_id) %}
|
||||
{% set step = "5" %}
|
||||
|
||||
{% block to_builder_form_field %}
|
||||
<div>
|
||||
<h1>Signature confirmation: <em>Task Order #{{task_order.number}}</em></h1>
|
||||
</div>
|
||||
|
||||
{% call Alert('',
|
||||
message="All task orders require a Contracting Officer signature."
|
||||
) %}
|
||||
|
||||
{{ CheckboxInput(form.signature) }}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
@ -51,30 +51,7 @@ def test_review_task_order_draft(client, user_session, task_order):
|
||||
url_for("task_orders.review_task_order", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert url_for("task_orders.edit", task_order_id=task_order.id) in response.location
|
||||
|
||||
|
||||
def test_submit_task_order(client, user_session, task_order):
|
||||
user_session(task_order.portfolio.owner)
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=task_order.id)
|
||||
assert (
|
||||
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
|
||||
in response.location
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
active_start_date = date.today() - timedelta(days=1)
|
||||
active_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
|
||||
CLINFactory(task_order=active_task_order, start_date=active_start_date)
|
||||
assert active_task_order.status == TaskOrderStatus.UNSIGNED
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=active_task_order.id)
|
||||
)
|
||||
assert active_task_order.status == TaskOrderStatus.ACTIVE
|
||||
|
||||
upcoming_start_date = date.today() + timedelta(days=1)
|
||||
upcoming_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
|
||||
CLINFactory(task_order=upcoming_task_order, start_date=upcoming_start_date)
|
||||
assert upcoming_task_order.status == TaskOrderStatus.UNSIGNED
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=upcoming_task_order.id)
|
||||
)
|
||||
assert upcoming_task_order.status == TaskOrderStatus.UPCOMING
|
||||
|
@ -1,12 +1,15 @@
|
||||
import pytest
|
||||
from flask import url_for
|
||||
from datetime import timedelta, date
|
||||
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.models.task_order import Status as TaskOrderStatus
|
||||
from atst.models import Attachment, TaskOrder
|
||||
from atst.utils.localization import translate
|
||||
|
||||
from tests.factories import (
|
||||
CLINFactory,
|
||||
PortfolioFactory,
|
||||
PortfolioRoleFactory,
|
||||
TaskOrderFactory,
|
||||
@ -18,7 +21,6 @@ from tests.factories import (
|
||||
def task_order():
|
||||
user = UserFactory.create()
|
||||
portfolio = PortfolioFactory.create(owner=user)
|
||||
attachment = Attachment(filename="sample_attachment", object_name="sample")
|
||||
|
||||
return TaskOrderFactory.create(creator=user, portfolio=portfolio)
|
||||
|
||||
@ -33,17 +35,60 @@ def user():
|
||||
return UserFactory.create()
|
||||
|
||||
|
||||
def test_task_orders_edit(client, user_session, portfolio):
|
||||
def test_task_orders_form_step_one_add_pdf(client, user_session, portfolio):
|
||||
user_session(portfolio.owner)
|
||||
response = client.get(url_for("task_orders.edit", portfolio_id=portfolio.id))
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_one_add_pdf", portfolio_id=portfolio.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_update(client, user_session, portfolio):
|
||||
def test_task_orders_upload_pdf(client, user_session, portfolio, pdf_upload, session):
|
||||
user_session(portfolio.owner)
|
||||
form_data = {"pdf": pdf_upload}
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_form_step_one_add_pdf", portfolio_id=portfolio.id),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
task_order = portfolio.task_orders[0]
|
||||
assert task_order.pdf.filename == pdf_upload.filename
|
||||
|
||||
|
||||
def test_task_orders_form_step_two_add_number(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_two_add_number", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
form_data = {"number": "1234567890"}
|
||||
response = client.post(
|
||||
url_for(
|
||||
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert task_order.number == "1234567890"
|
||||
|
||||
|
||||
def test_task_orders_form_step_three_add_clins(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_three_add_clins", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_submit_form_step_three_add_clins(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
form_data = {
|
||||
"number": "0123456789",
|
||||
"pdf": pdf_upload,
|
||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||
"clins-0-clin_number": "12312",
|
||||
"clins-0-start_date": "01/01/2020",
|
||||
@ -59,55 +104,166 @@ def test_task_orders_update(client, user_session, portfolio):
|
||||
"clins-1-loas-0": "78979087",
|
||||
}
|
||||
response = client.post(
|
||||
url_for("task_orders.update", portfolio_id=portfolio.id), data=form_data
|
||||
url_for(
|
||||
"task_orders.submit_form_step_three_add_clins", task_order_id=task_order.id
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
task_order = session.query(TaskOrder).filter_by(number=data["number"]).one()
|
||||
assert task_order.pdf.filename == pdf_upload.filename
|
||||
assert len(task_order.clins) == 2
|
||||
|
||||
|
||||
def test_task_orders_save_incomplete(client, user_session, portfolio):
|
||||
user_session(portfolio.owner)
|
||||
form_data = {
|
||||
"number": "0123456789",
|
||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||
"clins-0-clin_number": "12312",
|
||||
}
|
||||
response = client.post(
|
||||
url_for("task_orders.update", portfolio_id=portfolio.id), data=form_data
|
||||
)
|
||||
assert response.status_code == 302
|
||||
task_order = portfolio.task_orders[0]
|
||||
expected_url = url_for(
|
||||
"task_orders.edit", task_order_id=task_order.id, _external=True
|
||||
)
|
||||
assert response.location == expected_url
|
||||
|
||||
|
||||
def test_task_orders_edit_existing_to(client, user_session, task_order):
|
||||
def test_task_orders_form_step_four_review(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
response = client.get(url_for("task_orders.edit", task_order_id=task_order.id))
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_four_review", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_update_existing_to(client, user_session, task_order):
|
||||
def test_task_orders_form_step_five_confirm_signature(client, user_session, task_order):
|
||||
user_session(task_order.creator)
|
||||
response = client.get(
|
||||
url_for(
|
||||
"task_orders.form_step_five_confirm_signature", task_order_id=task_order.id
|
||||
)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_form_step_one_add_pdf_existing_to(
|
||||
client, user_session, task_order
|
||||
):
|
||||
user_session(task_order.creator)
|
||||
response = client.get(
|
||||
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_task_orders_upload_pdf_existing_to(
|
||||
client, user_session, task_order, pdf_upload, pdf_upload2
|
||||
):
|
||||
task_order.pdf = pdf_upload
|
||||
assert task_order.pdf.filename == pdf_upload.filename
|
||||
|
||||
user_session(task_order.creator)
|
||||
form_data = {"pdf": pdf_upload2}
|
||||
response = client.post(
|
||||
url_for(
|
||||
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert task_order.pdf.filename == pdf_upload2.filename
|
||||
|
||||
|
||||
def test_task_orders_submit_form_step_one_add_pdf_delete_pdf(
|
||||
client, user_session, portfolio, pdf_upload
|
||||
):
|
||||
user_session(portfolio.owner)
|
||||
task_order = TaskOrderFactory.create(pdf=pdf_upload, portfolio=portfolio)
|
||||
data = {"pdf": ""}
|
||||
response = client.post(
|
||||
url_for(
|
||||
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
|
||||
),
|
||||
data=data,
|
||||
)
|
||||
assert task_order.pdf is None
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
def test_task_orders_submit_form_step_two_add_number_existing_to(
|
||||
client, user_session, task_order
|
||||
):
|
||||
user_session(task_order.creator)
|
||||
form_data = {"number": "0000000000"}
|
||||
original_number = task_order.number
|
||||
response = client.post(
|
||||
url_for(
|
||||
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert task_order.number == "0000000000"
|
||||
assert task_order.number != original_number
|
||||
|
||||
|
||||
def test_task_orders_submit_form_step_three_add_clins_existing_to(
|
||||
client, user_session, task_order
|
||||
):
|
||||
clin_list = [
|
||||
{
|
||||
"jedi_clin_type": "JEDI_CLIN_1",
|
||||
"number": "12312",
|
||||
"start_date": "01/01/2020",
|
||||
"end_date": "01/01/2021",
|
||||
"obligated_amount": "5000",
|
||||
"loas": ["123123123123", "345345234"],
|
||||
},
|
||||
{
|
||||
"jedi_clin_type": "JEDI_CLIN_1",
|
||||
"number": "12312",
|
||||
"start_date": "01/01/2020",
|
||||
"end_date": "01/01/2021",
|
||||
"obligated_amount": "5000",
|
||||
"loas": ["78979087"],
|
||||
},
|
||||
]
|
||||
TaskOrders.create_clins(task_order.id, clin_list)
|
||||
assert len(task_order.clins) == 2
|
||||
|
||||
user_session(task_order.creator)
|
||||
form_data = {
|
||||
"number": "0123456789",
|
||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||
"clins-0-number": "12312",
|
||||
"clins-0-clin_number": "12312",
|
||||
"clins-0-start_date": "01/01/2020",
|
||||
"clins-0-end_date": "01/01/2021",
|
||||
"clins-0-obligated_amount": "5000",
|
||||
"clins-0-loas-0": "123123123123",
|
||||
}
|
||||
response = client.post(
|
||||
url_for("task_orders.update", task_order_id=task_order.id), data=form_data
|
||||
url_for(
|
||||
"task_orders.submit_form_step_three_add_clins", task_order_id=task_order.id
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert len(task_order.clins) == 1
|
||||
|
||||
|
||||
def test_submit_task_order(client, user_session, task_order):
|
||||
user_session(task_order.portfolio.owner)
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=task_order.id)
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert task_order.number == "0123456789"
|
||||
|
||||
active_start_date = date.today() - timedelta(days=1)
|
||||
active_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
|
||||
CLINFactory(task_order=active_task_order, start_date=active_start_date)
|
||||
assert active_task_order.status == TaskOrderStatus.UNSIGNED
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=active_task_order.id)
|
||||
)
|
||||
assert active_task_order.status == TaskOrderStatus.ACTIVE
|
||||
|
||||
upcoming_start_date = date.today() + timedelta(days=1)
|
||||
upcoming_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
|
||||
CLINFactory(task_order=upcoming_task_order, start_date=upcoming_start_date)
|
||||
assert upcoming_task_order.status == TaskOrderStatus.UNSIGNED
|
||||
response = client.post(
|
||||
url_for("task_orders.submit_task_order", task_order_id=upcoming_task_order.id)
|
||||
)
|
||||
assert upcoming_task_order.status == TaskOrderStatus.UPCOMING
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Reevaluate how form handles invalid data")
|
||||
def test_task_orders_update_invalid_data(client, user_session, portfolio):
|
||||
user_session(portfolio.owner)
|
||||
num_task_orders = len(portfolio.task_orders)
|
||||
@ -119,41 +275,7 @@ def test_task_orders_update_invalid_data(client, user_session, portfolio):
|
||||
assert "There were some errors" in response.data.decode()
|
||||
|
||||
|
||||
def test_task_orders_update(client, user_session, portfolio, pdf_upload):
|
||||
user_session(portfolio.owner)
|
||||
data = {"number": "0123456789", "pdf": pdf_upload}
|
||||
task_order = TaskOrderFactory.create(number="0987654321", portfolio=portfolio)
|
||||
response = client.post(
|
||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||
)
|
||||
assert task_order.number == data["number"]
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
def test_task_orders_update_pdf(
|
||||
client, user_session, portfolio, pdf_upload, pdf_upload2
|
||||
):
|
||||
user_session(portfolio.owner)
|
||||
task_order = TaskOrderFactory.create(pdf=pdf_upload, portfolio=portfolio)
|
||||
data = {"number": "0123456789", "pdf": pdf_upload2}
|
||||
response = client.post(
|
||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||
)
|
||||
assert task_order.pdf.filename == pdf_upload2.filename
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_upload):
|
||||
user_session(portfolio.owner)
|
||||
task_order = TaskOrderFactory.create(pdf=pdf_upload, portfolio=portfolio)
|
||||
data = {"number": "0123456789", "pdf": ""}
|
||||
response = client.post(
|
||||
url_for("task_orders.update", task_order_id=task_order.id), data=data
|
||||
)
|
||||
assert task_order.pdf is None
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Reevaluate if user can see review page w/ incomplete TO")
|
||||
def test_cannot_get_to_review_screen_with_incomplete_data(
|
||||
client, user_session, portfolio
|
||||
):
|
||||
|
@ -448,30 +448,67 @@ def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monke
|
||||
get_url_assert_status(rando, url, 404)
|
||||
|
||||
|
||||
# task_orders.update
|
||||
def test_task_orders_update_access(post_url_assert_status):
|
||||
# task_orders.form_step_one_add_pdf
|
||||
# task_orders.form_step_two_add_number
|
||||
# task_orders.form_step_three_add_clins
|
||||
# task_orders.form_step_four_review
|
||||
# task_orders.form_step_five_confirm_signature
|
||||
def test_task_orders_new_get_routes(get_url_assert_status):
|
||||
get_routes = [
|
||||
"task_orders.form_step_one_add_pdf",
|
||||
"task_orders.form_step_two_add_number",
|
||||
"task_orders.form_step_three_add_clins",
|
||||
"task_orders.form_step_four_review",
|
||||
"task_orders.form_step_five_confirm_signature",
|
||||
]
|
||||
|
||||
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING)
|
||||
owner = user_with()
|
||||
rando = user_with()
|
||||
|
||||
portfolio = PortfolioFactory.create(owner=owner)
|
||||
data = {"number": 1234567890}
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio, creator=owner)
|
||||
|
||||
url = url_for("task_orders.update", portfolio_id=portfolio.id)
|
||||
post_url_assert_status(owner, url, 302, data=data)
|
||||
post_url_assert_status(ccpo, url, 302, data=data)
|
||||
post_url_assert_status(rando, url, 404, data=data)
|
||||
for route in get_routes:
|
||||
url = url_for(route, task_order_id=task_order.id)
|
||||
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio)
|
||||
get_url_assert_status(ccpo, url, 200)
|
||||
get_url_assert_status(owner, url, 200)
|
||||
get_url_assert_status(rando, url, 404)
|
||||
|
||||
url = url_for("task_orders.update", task_order_id=task_order.id)
|
||||
post_url_assert_status(owner, url, 302, data=data)
|
||||
post_url_assert_status(ccpo, url, 302, data=data)
|
||||
post_url_assert_status(rando, url, 404, data=data)
|
||||
|
||||
url = url_for("task_orders.update", portfolio_id=portfolio.id)
|
||||
post_url_assert_status(owner, url, 302, data=data)
|
||||
post_url_assert_status(ccpo, url, 302, data=data)
|
||||
post_url_assert_status(rando, url, 404, data=data)
|
||||
# task_orders.submit_form_step_one_add_pdf
|
||||
# task_orders.submit_form_step_two_add_number
|
||||
# task_orders.submit_form_step_three_add_clins
|
||||
def test_task_orders_new_post_routes(post_url_assert_status):
|
||||
post_routes = [
|
||||
("task_orders.submit_form_step_one_add_pdf", {"pdf": ""}),
|
||||
("task_orders.submit_form_step_two_add_number", {"number": "1234567890"}),
|
||||
(
|
||||
"task_orders.submit_form_step_three_add_clins",
|
||||
{
|
||||
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
|
||||
"clins-0-clin_number": "12312",
|
||||
"clins-0-start_date": "01/01/2020",
|
||||
"clins-0-end_date": "01/01/2021",
|
||||
"clins-0-obligated_amount": "5000",
|
||||
"clins-0-loas-0": "123123123123",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING)
|
||||
owner = user_with()
|
||||
rando = user_with()
|
||||
|
||||
portfolio = PortfolioFactory.create(owner=owner)
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio, creator=owner)
|
||||
|
||||
for route, data in post_routes:
|
||||
url = url_for(route, task_order_id=task_order.id)
|
||||
post_url_assert_status(owner, url, 302, data=data)
|
||||
post_url_assert_status(ccpo, url, 302, data=data)
|
||||
post_url_assert_status(rando, url, 404, data=data)
|
||||
|
||||
|
||||
def test_applications_application_team_access(get_url_assert_status):
|
||||
|
@ -353,7 +353,7 @@ task_orders:
|
||||
supporting_docs_header: Upload your supporting documentation
|
||||
supporting_docs_size_limit: Your file may not exceed 1MB
|
||||
supporting_docs_text: Upload a single PDF containing all relevant information.
|
||||
sticky_header_text: Add Funding
|
||||
sticky_header_text: 'Add Funding ({step} of 5)'
|
||||
new:
|
||||
form_help_text: Before you can begin work in the cloud, you'll need to complete the information below and upload your approved task order for reference by the CCPO.
|
||||
app_info:
|
||||
|
Loading…
x
Reference in New Issue
Block a user