Remove old task order edit and update routes

This commit is contained in:
leigh-mil 2019-07-16 15:33:17 -04:00
parent b9e14f8719
commit e3c6b08d34
6 changed files with 71 additions and 596 deletions

View File

@ -5,7 +5,6 @@ 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, SignatureForm
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
def render_task_orders_edit(template, portfolio_id=None, task_order_id=None, form=None):
@ -137,60 +136,3 @@ def 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("/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}
form = None
if task_order_id:
task_order = TaskOrders.get(task_order_id)
form = TaskOrderForm(form_data, obj=task_order)
else:
form = TaskOrderForm(form_data)
if form.validate():
task_order = None
if task_order_id:
task_order = TaskOrders.update(task_order_id, **form.data)
portfolio_id = task_order.portfolio_id
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(
"task_orders/step_1.html", 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))
else:
return (
render_task_orders_edit(
"task_orders/step_1.html", portfolio_id, task_order_id, form
),
400,
)

View File

@ -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.add_pdf", "task_orders.upload_pdf", "task_orders.add_number", "task_orders.update_number", "task_orders.add_clins", "task_orders.update_clins", "task_orders.review", "task_orders.confirm_signature"],
) }}
{{ Link(
icon='applications',

View File

@ -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.add_pdf', task_order_id=task_order.id) }}" class="usa-button {{ 'usa-button-secondary' if secondary else '' }}">
{{ text }}
</a>
{% endmacro %}
@ -111,7 +111,7 @@
{{ EmptyState(
'This portfolio doesnt 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.add_pdf', portfolio_id=portfolio.id),
icon='cloud',
add_perms=user_can(permissions.CREATE_TASK_ORDER)
) }}

View File

@ -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 %}

View File

@ -115,29 +115,9 @@ def test_task_orders_confirm_signature(client, user_session, task_order):
assert response.status_code == 200
def test_task_orders_new_flow():
pass
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_add_pdf_existing_to(client, user_session, task_order):
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.add_pdf", task_order_id=task_order.id))
assert response.status_code == 200
@ -158,6 +138,17 @@ def test_task_orders_upload_pdf_existing_to(
assert task_order.pdf.filename == pdf_upload2.filename
def test_task_orders_upload_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.upload_pdf", task_order_id=task_order.id), data=data
)
assert task_order.pdf is None
assert response.status_code == 302
def test_task_orders_update_number_existing_to(client, user_session, task_order):
user_session(task_order.creator)
form_data = {"number": "0000000000"}
@ -210,24 +201,6 @@ def test_task_orders_update_clins_existing_to(client, user_session, task_order):
assert len(task_order.clins) == 1
def test_task_orders_update_existing_to(client, user_session, task_order):
user_session(task_order.creator)
form_data = {
"number": "0123456789",
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
"clins-0-number": "12312",
"clins-0-start_date": "01/01/2020",
"clins-0-end_date": "01/01/2021",
"clins-0-obligated_amount": "5000",
"clins-0-loas-0": "123123123123",
}
response = client.post(
url_for("task_orders.update", task_order_id=task_order.id), data=form_data
)
assert response.status_code == 302
assert task_order.number == "0123456789"
@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)
@ -240,41 +213,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
):

View File

@ -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.add_pdf
# task_orders.add_number
# task_orders.add_clins
# task_orders.review
# task_orders.confirm_signature
def test_task_orders_new_get_routes(get_url_assert_status):
get_routes = [
"task_orders.add_pdf",
"task_orders.add_number",
"task_orders.add_clins",
"task_orders.review",
"task_orders.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.upload_pdf
# task_orders.update_number
# task_orders.update_clins
def test_task_orders_new_post_routes(post_url_assert_status):
post_routes = [
("task_orders.upload_pdf", {"pdf": ""}),
("task_orders.update_number", {"number": "1234567890"}),
(
"task_orders.update_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):