view SO review screen / DD-254 page
This commit is contained in:
parent
ad05c448cd
commit
99ef82e78b
@ -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
38
atst/forms/dd_254.py
Normal 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(),
|
||||
)
|
@ -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)
|
||||
|
@ -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 %}
|
||||
|
@ -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({
|
||||
|
31
templates/portfolios/task_orders/so_review.html
Normal file
31
templates/portfolios/task_orders/so_review.html
Normal 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 %}
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user