view SO review screen / DD-254 page

This commit is contained in:
dandds 2019-02-18 13:51:01 -05:00
parent ad05c448cd
commit 99ef82e78b
8 changed files with 156 additions and 9 deletions

View File

@ -39,7 +39,13 @@ class Authorization(object):
@classmethod
def check_is_ko(cls, user, task_order):
if task_order.contracting_officer != user:
message = "review Task Order {}".format(task_order.id)
message = "review task order {}".format(task_order.id)
raise UnauthorizedError(user, message)
@classmethod
def check_is_so(cls, user, task_order):
if task_order.security_officer != user:
message = "review task order {}".format(task_order.id)
raise UnauthorizedError(user, message)
@classmethod

38
atst/forms/dd_254.py Normal file
View File

@ -0,0 +1,38 @@
from wtforms.fields import SelectMultipleField, StringField
from wtforms.fields.html5 import TelField
from wtforms.widgets import ListWidget, CheckboxInput
from wtforms.validators import Required
from atst.forms.validators import PhoneNumber
from .forms import CacheableForm
from .data import REQUIRED_DISTRIBUTIONS
from atst.utils.localization import translate
class DD254Form(CacheableForm):
certifying_official = StringField(
translate("forms.dd_254.certifying_official.label"),
description=translate("forms.dd_254.certifying_official.description"),
validators=[Required()],
)
co_title = StringField(
translate("forms.dd_254.co_title.label"), validators=[Required()]
)
co_address = StringField(
translate("forms.dd_254.co_address.label"),
description=translate("forms.dd_254.co_address.description"),
validators=[Required()],
)
co_phone = TelField(
translate("forms.dd_254.co_phone.label"),
description=translate("forms.dd_254.co_phone.description"),
validators=[Required(), PhoneNumber()],
)
required_distribution = SelectMultipleField(
translate("forms.dd_254.required_distribution.label"),
choices=REQUIRED_DISTRIBUTIONS,
default="",
widget=ListWidget(prefix_label=False),
option_widget=CheckboxInput(),
)

View File

@ -11,6 +11,7 @@ from atst.domain.authz import Authorization
from atst.forms.officers import EditTaskOrderOfficersForm
from atst.models.task_order import Status as TaskOrderStatus
from atst.forms.ko_review import KOReviewForm
from atst.forms.dd_254 import DD254Form
@portfolios_bp.route("/portfolios/<portfolio_id>/task_orders")
@ -154,3 +155,12 @@ def edit_task_order_invitations(portfolio_id, task_order_id):
task_order=task_order,
form=form,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>/dd254")
def so_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
form = DD254Form()
Authorization.check_is_so(g.current_user, task_order)
return render_template("portfolios/task_orders/so_review.html", form=form)

View File

@ -1,31 +1,39 @@
{% from "components/icon.html" import Icon %}
{% from "components/tooltip.html" import Tooltip %}
{% macro MultiCheckboxInput(field, other_input_field, tooltip, inline=False) -%}
{% macro MultiCheckboxInput(field, other_input_field=None, tooltip=None, inline=False) -%}
<multicheckboxinput
v-cloak
name='{{ field.name }}'
inline-template
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
{% if field.data and field.data != "None" %}v-bind:initial-value="{{ field.data }}"{% endif %}
{% if other_input_field.data and other_input_field.data != "None" %}initial-other-value="{{ other_input_field.data }}"{% endif %}
{% if other_input_field and other_input_field.data and other_input_field.data != "None" %}
initial-other-value="{{ other_input_field.data }}"
{% endif %}
key='{{ field.name }}'>
<div
v-bind:class="['usa-input', { 'usa-input--error': showError, 'usa-input--success': showValid }]">
{% set validation_icons %}
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
{% endset %}
<fieldset v-on:change="onInput" class="usa-input__choices {% if inline %}usa-input__choices--inline{% endif %}">
<legend>
<div class="usa-input__title">
{{ field.label | striptags}}
{% if tooltip %}{{ Tooltip(tooltip) }}{% endif %}
{% if not field.description %}
{{ validation_icons }}
{% endif %}
</div>
{% if field.description %}
<span class='usa-input__help'>{{ field.description | safe }}</span>
{{ validation_icons }}
{% endif %}
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
</legend>
<ul>
@ -38,9 +46,11 @@
<input @click="otherToggle" type='checkbox' name='{{ field.name }}' id='{{ field.name }}-{{ loop.index0 }}' value='other' v-model="selections"/>
<label for='{{ field.name }}-{{ loop.index0 }}'>{{ choice[1] | safe }}</label>
<div v-show="otherChecked">
<input type='text' name='{{ other_input_field.name}}' id='{{ field.name }}-other' v-model:value="otherText" aria-expanded='false' />
</div>
{% if other_input_field %}
<div v-show="otherChecked">
<input type='text' name='{{ other_input_field.name}}' id='{{ field.name }}-other' v-model:value="otherText" aria-expanded='false' />
</div>
{% endif %}
{% endif %}
</li>
{% endfor %}

View File

@ -132,10 +132,13 @@
button_text='Edit',
complete=all_sections_complete) %}
{% endcall %}
{% set is_so = user == task_order.security_officer %}
{{ Step(
description="task_orders.view.steps.security" | translate({
"security_officer": officer_name(task_order.security_officer)
}) | safe,
button_url=is_so and url_for("portfolios.so_review", portfolio_id=portfolio.id, task_order_id=task_order.id),
button_text=is_so and 'Edit',
complete=False) }}
{% call Step(
description="task_orders.view.steps.record" | translate({

View File

@ -0,0 +1,31 @@
{% extends 'portfolios/base.html' %}
{% from "components/text_input.html" import TextInput %}
{% from "components/multi_checkbox_input.html" import MultiCheckboxInput %}
{% block content %}
<div class="col">
<div class="panel">
<div class="panel__heading">
<h1 class="subheading">
<div class="h2">{{ "task_orders.so_review.title" | translate }}</div>
</h1>
</div>
<div class="panel__content">
<h3 class="subheading">{{ "task_orders.so_review.certification" | translate }}</h3>
{{ TextInput(form.certifying_official) }}
{{ TextInput(form.co_title) }}
{{ TextInput(form.co_phone, placeholder='(123) 456-7890', validation='usPhone') }}
{{ TextInput(form.co_address, paragraph=True) }}
<hr>
{{ MultiCheckboxInput(form.required_distribution) }}
</div>
</div>
</div>
{% endblock %}

View File

@ -314,3 +314,35 @@ def test_submit_completed_ko_review_page(client, user_session, pdf_upload):
assert response.headers["Location"] == url_for(
"task_orders.signature_requested", task_order_id=task_order.id, _external=True
)
def test_so_can_view_so_review_page(client, user_session):
portfolio = PortfolioFactory.create()
so = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=so,
status=PortfolioStatus.ACTIVE,
)
task_order = TaskOrderFactory.create(portfolio=portfolio, security_officer=so)
user_session(portfolio.owner)
owner_response = client.get(
url_for(
"portfolios.so_review",
portfolio_id=portfolio.id,
task_order_id=task_order.id,
)
)
assert owner_response.status_code == 404
user_session(so)
so_response = client.get(
url_for(
"portfolios.so_review",
portfolio_id=portfolio.id,
task_order_id=task_order.id,
)
)
assert so_response.status_code == 200

View File

@ -246,6 +246,20 @@ forms:
so_invite_label: Invite Security Officer to Task Order Builder
skip_invite_description: |
<i>An invitation won't actually be sent until you click Done on the Review page. You can skip this for now and invite them later.</i>
dd_254:
certifying_official:
label: Name of Certifying Official
description: (First, Last, Middle)
co_title:
label: Title
co_address:
label: Address
description: (Include ZIP Code)
co_phone:
label: Telephone
description: (Include Area Code)
required_distribution:
label: Required Distribution by the Certifying Official
validators:
is_number_message: Please enter a valid number.
list_item_required_message: Please provide at least one.
@ -510,6 +524,9 @@ task_orders:
message: Grant your team access to the cloud by verifying the Task Order info below.
review_title: Task Order Builder
task_order_information: Task Order Information
so_review:
title: DD-254 Information
certification: Certification
portfolios:
task_orders:
available_budget_help_description: The available budget shown includes the available budget of all active task orders