Merge pull request #640 from dod-ccpo/update-task-order-show

Update view task order page
This commit is contained in:
patricksmithdds 2019-02-13 11:40:55 -05:00 committed by GitHub
commit baa0afe106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 206 additions and 100 deletions

View File

@ -30,6 +30,8 @@ def justCents(value):
def usPhone(number):
if not number:
return ""
phone = re.sub(r"\D", "", number)
return "+1 ({}) {} - {}".format(phone[0:3], phone[3:6], phone[6:])

View File

@ -21,6 +21,7 @@ from atst.models import Attachment, Base, types, mixins
class Status(Enum):
STARTED = "Started"
PENDING = "Pending"
ACTIVE = "Active"
EXPIRED = "Expired"
@ -142,7 +143,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
return Status.EXPIRED
return Status.ACTIVE
else:
return Status.PENDING
return Status.STARTED
@property
def display_status(self):

View File

@ -45,7 +45,10 @@ def portfolio_funding(portfolio_id):
return render_template(
"portfolios/task_orders/index.html",
portfolio=portfolio,
pending_task_orders=task_orders_by_status.get(TaskOrderStatus.PENDING, []),
pending_task_orders=(
task_orders_by_status.get(TaskOrderStatus.STARTED, [])
+ task_orders_by_status.get(TaskOrderStatus.PENDING, [])
),
active_task_orders=active_task_orders,
expired_task_orders=task_orders_by_status.get(TaskOrderStatus.EXPIRED, []),
total_balance=total_balance,

View File

@ -20,20 +20,29 @@ def download_summary(task_order_id):
)
def send_file(attachment):
generator = app.csp.files.download(attachment.object_name)
return Response(
generator,
headers={
"Content-Disposition": "attachment; filename={}".format(attachment.filename)
},
)
@task_orders_bp.route("/task_orders/csp_estimate/<task_order_id>")
def download_csp_estimate(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
if task_order.csp_estimate:
estimate = task_order.csp_estimate
generator = app.csp.files.download(estimate.object_name)
return Response(
generator,
headers={
"Content-Disposition": "attachment; filename={}".format(
estimate.filename
)
},
)
return send_file(task_order.csp_estimate)
else:
raise NotFoundError("task_order CSP estimate")
@task_orders_bp.route("/task_orders/pdf/<task_order_id>")
def download_task_order_pdf(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
if task_order.pdf:
return send_file(task_order.pdf)
else:
raise NotFoundError("task_order pdf")

View File

@ -63,6 +63,9 @@
}
.task-order-summary {
.panel {
@include shadow-panel;
}
.alert .alert__actions {
margin-top: 2 * $gap;
@ -72,7 +75,7 @@
width: 100%;
}
.label--pending {
.label--pending, .label--started {
background-color: $color-gold;
}
@ -117,6 +120,11 @@
.task-order-next-steps {
flex-grow: 1;
.panel {
padding-bottom: 0;
}
@include media($xlarge-screen) {
padding-right: $gap;
}
@ -140,8 +148,17 @@
width: 100%;
}
.alert {
margin-top: 3 * $gap;
margin-bottom: 0;
padding: 2 * $gap;
.alert__message {
font-style: italic;
}
}
.task-order-next-steps__icon {
width: 8%;
padding: $gap $gap 0 0;
justify-content: center;
.complete {
@ -152,34 +169,29 @@
}
}
.task-order-next-steps__text {
width: 60%;
}
.task-order-next-steps__action {
min-width: 10 * $gap;
padding: $gap 0 0 $gap;
width: 32%;
a.usa-button {
width: 100%;
}
}
.task-order-next-steps__heading {
h4 {
@include ie-only {
width: 100%;
.task-order-next-steps__text {
display: flex;
.task-order-next-steps__heading {
display: block;
max-width: 100%;
flex-shrink: 1;
}
margin: $gap $gap 0 0;
}
}
.task-order-next-steps__description {
font-style: italic;
}
}
}
.task-order-sidebar {
@include media($xlarge-screen) {
padding-left: 3 * $gap;
}
min-width: 35rem;
hr {
@ -198,18 +210,33 @@
}
}
.task-order-invitation-status {
.invited {
color: $color-green;
@include icon-color($color-green);
}
.uninvited {
color: $color-red;
@include icon-color($color-red);
.task-order-invitations {
.task-order-invitations__heading {
justify-content: space-between;
}
.task-order-invitation-status__icon {
padding: 0 0.5rem;
.task-order-invitation-status {
margin-bottom: 3 * $gap;
.task-order-invitation-status__title {
font-weight: $font-bold;
}
.invited {
color: $color-green;
@include icon-color($color-green);
}
.uninvited {
color: $color-red;
@include icon-color($color-red);
}
.task-order-invitation-status__icon {
padding: 0 0.5rem;
}
}
.task-order-invitation-details {
font-style: italic;
}
}
}

View File

@ -6,19 +6,42 @@
{% block portfolio_content %}
{% macro Step(title="", description="", link_text=None, complete=True) %}
<div class="task-order-next-steps__step panel__content row">
<div class="task-order-next-steps__icon col">
<span>{{ Icon("ok", classes="complete" if complete else "incomplete") }}</span>
</div>
<div class="task-order-next-steps__text col">
<div class="task-order-next-steps__heading row">
<h4>{{ title }}</h4>
{% macro officer_name(officer) -%}
{%- if not officer -%}
Not specified
{%- elif officer == g.current_user -%}
You
{%- else -%}
{{ officer.full_name }}
{%- endif -%}
{%- endmacro -%}
{% macro Step(description="", complete=True, button_text=None, button_url=None) %}
<div class="task-order-next-steps__step panel__content">
<div class="row">
<div class="task-order-next-steps__icon col">
{% if complete %}
<span class="label label--success">Completed</span>
{% else %}
<span class="label">Not Started</span>
{% endif %}
</div>
<div class="task-order-next-steps__description">
{{ description }}
<div class="task-order-next-steps__text col col--grow">
<div class="task-order-next-steps__heading row">
<span>{{ description }}</span>
</div>
</div>
<div class="task-order-next-steps__action col">
{% if not task_order.is_active and button_text and button_url %}
<a
href="{{ button_url }}"
class="usa-button usa-button-primary">
{{ button_text }}
</a>
{% endif %}
</div>
</div>
{% if caller %}
{{ caller() }}
{% endif %}
@ -46,14 +69,27 @@
</div>
{% endmacro %}
{% macro InvitationStatus(title, officer) %}
{% macro InvitationStatus(title, officer, officer_info) %}
{% set class = "invited" if officer else "uninvited" %}
<div class="task-order-invitation-status row">
<div class="task-order-invitation-status__icon col">
<span>{{ Icon("ok" if officer else "alert", classes=class) }}</span>
<span>{{ Icon("avatar" if officer else "alert", classes=class) }}</span>
</div>
<div class="task-order-invitation-status__title col {{ class }}">
{{ title }}
<div class="col">
<div class="task-order-invitation-status__title {{ class }}">
{{ title }}
</div>
<div class="task-order-invitation-details">
{% if officer_info %}
<div class="col">
<div>{{ officer_info.first_name }} {{ officer_info.last_name }}</div>
<div>{{ officer_info.email }}</div>
<div>{{ officer_info.phone_number | usPhone }}</div>
</div>
{% else %}
<span>Not specified</span>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
@ -87,42 +123,42 @@
<div class="task-order-details">
<div id="next-steps" class="task-order-next-steps">
<div class="panel">
<h3 class="task-order-next-steps__panel-head panel__content">What's next?</h3>
<h3 class="task-order-next-steps__panel-head panel__content">{{ "task_orders.view.whats_next" | translate }}</h3>
{% call Step(
title="Submit draft Task Order",
description="Complete initial task order request form.",
link_text="edit",
description="task_orders.view.steps.draft" | translate({
"contact": officer_name(task_order.creator)
})| safe,
button_url=url_for("task_orders.new", screen=1, task_order_id=task_order.id),
button_text='Edit',
complete=all_sections_complete) %}
<div class="task-order-next-steps__action col">
{% if user == task_order.contracting_officer %}
{% set url=url_for("portfolios.ko_review", portfolio_id=portfolio.id, task_order_id=task_order.id) %}
{% else %}
{% set url = url_for("task_orders.new", screen=1, task_order_id=task_order.id) %}
{% endif %}
<a
href="{{ url }}"
class="usa-button usa-button-primary">
Edit
</a>
</div>
{% endcall %}
{{ Step(
title="Complete a Security Requirements Document",
description="The IA Security Official you specified received an email invitation to complete and sign a DD-254: Security Requirements document that's been customized for the JEDI program here.",
description="task_orders.view.steps.security" | translate({
"security_officer": officer_name(task_order.security_officer)
}) | safe,
complete=False) }}
{% call Step(
description="task_orders.view.steps.record" | translate({
"contracting_officer": officer_name(task_order.contracting_officer),
"contracting_officer_representative": officer_name(task_order.contracting_officer_representative)
}) | safe,
complete=False) %}
<div class='alert alert--warning'>
<div class='alert__content'>
<div class='alert__message'>
{{ "task_orders.view.steps.record_description" | translate | safe }}
</div>
</div>
</div>
{% endcall %}
{% set is_ko = user == task_order.contracting_officer %}
{{ Step(
title="Prepare the Task Order Documents for your organization's contracting system",
description="You'll file your task order in your organization's contracting system. Change the formatting based on your office prefers.",
description="task_orders.view.steps.sign" | translate({
"contracting_officer": officer_name(task_order.contracting_officer)
}) | safe,
button_url=is_ko and url_for("portfolios.ko_review", portfolio_id=portfolio.id, task_order_id=task_order.id),
button_text=is_ko and 'Sign',
complete=False) }}
{{ Step(
title="Get a funding document",
description="User your organization's normal process to get a funding document, typically from your financial manager. Your Contracting Officer's Representative (COR) or Contracting Officer (KO) can help with this, too.",
complete=False) }}
{{ Step(
title="Have your KO submit your final task order",
description="Your KO will submit the final task order into your organization's contracting system and receive an official task order number. Your KO should enter your task order number in this system, along with a copy of the submitted task order.",
complete=False) }}
<h4 class="panel__content">Once your required information is submitted in this system, you're funded and ready to start using JEDI cloud services!</h4>
</div>
</div>
<div class="task-order-sidebar col">
@ -135,13 +171,23 @@
format="M/D/YYYY">
</local-datetime>
{%- endset %}
{{ DocumentLink(
title="Task Order Draft",
link_url=all_sections_complete and url_for('task_orders.download_summary', task_order_id=task_order.id),
description=description) }}
{% if task_order.pdf %}
{{ DocumentLink(
title="Task Order",
link_url=url_for('task_orders.download_task_order_pdf', task_order_id=task_order.id),
description=description) }}
{% else %}
{{ DocumentLink(
title="Task Order Draft",
link_url=all_sections_complete and url_for('task_orders.download_summary', task_order_id=task_order.id),
description=description) }}
{% endif %}
</div>
<hr />
<div class="panel__content">
{{ DocumentLink(
title="Instruction Sheet",
link_url="#") }}
{{ DocumentLink(
title="Cloud Services Estimate",
link_url=task_order.csp_estimate and url_for("task_orders.download_csp_estimate", task_order_id=task_order.id) ) }}
@ -155,15 +201,16 @@
</div>
<div class="task-order-invitations panel">
<div class="panel__content">
<h3>Invitations</h3>
{{ InvitationStatus('Contracting Officer', task_order.contracting_officer) }}
{{ InvitationStatus('Contracting Officer Representative', task_order.contracting_officer_representative) }}
{{ InvitationStatus('IA Security Officer', officer=task_order.security_officer) }}
<a href="{{ url_for('portfolios.task_order_invitations', portfolio_id=portfolio.id, task_order_id=task_order.id) }}" class="icon-link">
{{ Icon("edit") }}
<span>manage invitations</span>
</a>
<div class="task-order-invitations__heading row">
<h3>Invitations</h3>
<a href="{{ url_for('portfolios.task_order_invitations', portfolio_id=portfolio.id, task_order_id=task_order.id) }}" class="icon-link">
<span>manage</span>
{{ Icon("edit") }}
</a>
</div>
{{ InvitationStatus('Contracting Officer', task_order.contracting_officer, officer_info=task_order.officer_dictionary('contracting_officer')) }}
{{ InvitationStatus('Contracting Officer Representative', task_order.contracting_officer_representative, officer_info=task_order.officer_dictionary('contracting_officer_representative')) }}
{{ InvitationStatus('IA Security Officer', officer=task_order.security_officer, officer_info=task_order.officer_dictionary('security_officer')) }}
</div>
</div>
</div>

View File

@ -9,11 +9,14 @@ from tests.mocks import PDF_FILENAME
class TestTaskOrderStatus:
def test_pending_status(self):
def test_started_status(self):
to = TaskOrder()
assert to.status == Status.PENDING
assert to.status == Status.STARTED
to = TaskOrder(number="42", start_date=random_future_date())
def test_pending_status(self):
to = TaskOrder(
number="42", start_date=random_future_date(), end_date=random_future_date()
)
assert to.status == Status.PENDING
def test_active_status(self):

View File

@ -1,6 +1,6 @@
import pytest
from atst.filters import dollars, renderAuditEvent
from atst.filters import dollars, renderAuditEvent, usPhone
from atst.models import AuditEvent
@ -28,3 +28,9 @@ def test_render_audit_event_with_unknown_resource_type():
event = AuditEvent(resource_type="boat")
result = renderAuditEvent(event)
assert "<article" in result
def test_usPhone():
assert usPhone("1234567890") == "+1 (123) 456 - 7890"
assert usPhone(number=None) == ""
assert usPhone(number="") == ""

View File

@ -412,6 +412,14 @@ task_orders:
title: Sign Task Order
unlimited_level_of_warrant_description: Unlimited Level of Warrant funds
verify_warrant_level_paragraph: Verify your level of warrant and provide your digital signature to authorize this Task Order.
view:
whats_next: Here are the remaining tasks to get your Task Order approved.
steps:
draft: '<strong>Primary Point of contact ({contact})</strong> completes initial Task Order form.'
security: '<strong>IA Security Officer ({security_officer})</strong> completes a Security Requirements Document. <a href="#">Send a reminder</a>'
record: '<strong>Contracting Officer ({contracting_officer}) or Contracting Officer Representative ({contracting_officer_representative})</strong> records Task Order information. <a href="#">Send a reminder</a>'
record_description: Obtain a funding document and file a Task Order in the appropriate system of record. Once this is complete, come back here and record the task order information.
sign: '<strong>Contracting Officer ({contracting_officer})</strong> verifies funding to unlock cloud services.'
new:
app_info:
section_title: "What You're Making"