From aa0a27aaa02fa2140fac1344083d28d4dc87fb93 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 12 Feb 2019 21:18:09 -0500 Subject: [PATCH 01/12] Update panel styles on task order show page --- styles/sections/_task_order.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 47eadcfb..5d8e27fb 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -63,6 +63,9 @@ } .task-order-summary { + .panel { + @include shadow-panel; + } .alert .alert__actions { margin-top: 2 * $gap; @@ -180,6 +183,7 @@ } .task-order-sidebar { + padding-left: 3 * $gap; min-width: 35rem; hr { From ba3000dcccfb25c830e4607ab70ad542ed210903 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 12 Feb 2019 21:27:53 -0500 Subject: [PATCH 02/12] Add started status to task order model --- atst/models/task_order.py | 3 ++- styles/sections/_task_order.scss | 2 +- tests/models/test_task_order.py | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 4e338735..0e58ed60 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -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): diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 5d8e27fb..3c056572 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -75,7 +75,7 @@ width: 100%; } - .label--pending { + .label--pending, .label--started { background-color: $color-gold; } diff --git a/tests/models/test_task_order.py b/tests/models/test_task_order.py index 908daec2..fdb7e327 100644 --- a/tests/models/test_task_order.py +++ b/tests/models/test_task_order.py @@ -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): From 64bff0f4789ec1d4e61d611133f1c42873cd0c48 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 12 Feb 2019 22:09:13 -0500 Subject: [PATCH 03/12] Initial pass at updating next steps for a task order --- styles/sections/_task_order.scss | 31 ++++---- templates/portfolios/task_orders/show.html | 82 ++++++++++++---------- translations.yaml | 8 +++ 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 3c056572..965978a2 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -120,6 +120,11 @@ .task-order-next-steps { flex-grow: 1; + + .panel { + padding-bottom: 0; + } + @include media($xlarge-screen) { padding-right: $gap; } @@ -143,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 { @@ -155,27 +169,14 @@ } } - .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%; - } - margin: $gap $gap 0 0; - } - } .task-order-next-steps__description { font-style: italic; } diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index fb7d98eb..6ae041d4 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -6,19 +6,32 @@ {% block portfolio_content %} -{% macro Step(title="", description="", link_text=None, complete=True) %} -
-
- {{ Icon("ok", classes="complete" if complete else "incomplete") }} -
-
-
-

{{ title }}

+{% macro Step(description="", complete=True, button_text=None, button_url=None) %} +
+
+
+ {% if complete %} + Completed + {% else %} + Not Started + {% endif %}
-
- {{ description }} +
+
+ {{ description }} +
+
+
+ {% if button_text and button_url %} + + {{ button_text }} + + {% endif %}
+ {% if caller %} {{ caller() }} {% endif %} @@ -87,42 +100,33 @@
-

What's next?

+

{{ "task_orders.view.whats_next" | translate }}

{% call Step( - title="Submit draft Task Order", - description="Complete initial task order request form.", - link_text="edit", + description="task_orders.view.steps.draft" | translate | safe, + button_url=url_for("task_orders.new", screen=1, task_order_id=task_order.id), + button_text='Edit', complete=all_sections_complete) %} -
- {% 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 %} - - Edit - -
{% 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 | safe, complete=False) }} + {% call Step( + description="task_orders.view.steps.record" | translate | safe, + complete=False) %} +
+
+
+ {{ "task_orders.view.steps.record_description" | translate | safe }} +
+
+
+ {% 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 | 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) }} -

Once your required information is submitted in this system, you're funded and ready to start using JEDI cloud services!

diff --git a/translations.yaml b/translations.yaml index 7c8ee7ed..8dc81185 100644 --- a/translations.yaml +++ b/translations.yaml @@ -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: 'Primary Point of contact (you) completes initial Task Order form.' + security: 'IA Security Officer (Frank Fontaine) completes a Security Requirements Document. Send a reminder' + record: 'Contracting Officer (Steward Kayou) or Contracting Officer Representative (Not specified) records Task Order information. Send a reminder' + 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: 'Contracting Officer (Steward Kayou) verifies funding to unlock cloud services.' new: app_info: section_title: "What You're Making" From ff9628cff3bd7ddc6df1e6ea441865267ea9fa69 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 08:03:33 -0500 Subject: [PATCH 04/12] Put correct officer names in TO next steps --- templates/portfolios/task_orders/show.html | 27 ++++++++++++++++++---- translations.yaml | 8 +++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 6ae041d4..3a8b504c 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -6,6 +6,16 @@ {% block portfolio_content %} +{% 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) %}
@@ -102,16 +112,23 @@

{{ "task_orders.view.whats_next" | translate }}

{% call Step( - description="task_orders.view.steps.draft" | translate | safe, + 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) %} {% endcall %} {{ Step( - description="task_orders.view.steps.security" | translate | safe, + 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 | safe, + 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) %}
@@ -123,7 +140,9 @@ {% endcall %} {% set is_ko = user == task_order.contracting_officer %} {{ Step( - description="task_orders.view.steps.sign" | translate | safe, + 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) }} diff --git a/translations.yaml b/translations.yaml index 8dc81185..fb95f021 100644 --- a/translations.yaml +++ b/translations.yaml @@ -415,11 +415,11 @@ task_orders: view: whats_next: Here are the remaining tasks to get your Task Order approved. steps: - draft: 'Primary Point of contact (you) completes initial Task Order form.' - security: 'IA Security Officer (Frank Fontaine) completes a Security Requirements Document. Send a reminder' - record: 'Contracting Officer (Steward Kayou) or Contracting Officer Representative (Not specified) records Task Order information. Send a reminder' + draft: 'Primary Point of contact ({contact}) completes initial Task Order form.' + security: 'IA Security Officer ({security_officer}) completes a Security Requirements Document. Send a reminder' + record: 'Contracting Officer ({contracting_officer}) or Contracting Officer Representative ({contracting_officer_representative}) records Task Order information. Send a reminder' 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: 'Contracting Officer (Steward Kayou) verifies funding to unlock cloud services.' + sign: 'Contracting Officer ({contracting_officer}) verifies funding to unlock cloud services.' new: app_info: section_title: "What You're Making" From 9ce17f575820e24c63c9aa783b687b5a5ba25726 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 08:04:51 -0500 Subject: [PATCH 05/12] Dont show edit/sign buttons if task order is already active --- templates/portfolios/task_orders/show.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 3a8b504c..9eb28a60 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -32,7 +32,7 @@
- {% if button_text and button_url %} + {% if not task_order.is_active and button_text and button_url %} From 7faf413e1b135ee7021ab534c5e1d35be56ced63 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 08:45:44 -0500 Subject: [PATCH 06/12] Add invited officer information to invitations panel --- styles/sections/_task_order.scss | 35 +++++++++++++------ templates/portfolios/task_orders/show.html | 40 +++++++++++++++------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 965978a2..262d8602 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -203,18 +203,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; } } } diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 9eb28a60..5f0f9419 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -69,14 +69,27 @@
{% endmacro %} -{% macro InvitationStatus(title, officer) %} +{% macro InvitationStatus(title, officer, officer_info) %} {% set class = "invited" if officer else "uninvited" %}
- {{ Icon("ok" if officer else "alert", classes=class) }} + {{ Icon("avatar" if officer else "alert", classes=class) }}
-
- {{ title }} +
+
+ {{ title }} +
+
+ {% if officer_info %} +
+
{{ officer_info.first_name }} {{ officer_info.last_name }}
+
{{ officer_info.email }}
+
{{ officer_info.phone_number | usPhone }}
+
+ {% else %} + Not specified + {% endif %} +
{% endmacro %} @@ -178,15 +191,16 @@
-

Invitations

- {{ 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) }} - -
- {{ Icon("edit") }} - manage invitations - + + {{ 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')) }}
From 1c21816810c363a6f876a5dc1543b618be8bb0c3 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 09:31:04 -0500 Subject: [PATCH 07/12] Add link to download official TO pdf --- atst/routes/task_orders/index.py | 31 ++++++++++++++-------- templates/portfolios/task_orders/show.html | 15 ++++++++--- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/atst/routes/task_orders/index.py b/atst/routes/task_orders/index.py index 6abf34fd..86f25f91 100644 --- a/atst/routes/task_orders/index.py +++ b/atst/routes/task_orders/index.py @@ -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/") 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/") +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") diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index 5f0f9419..ce0243ff 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -171,10 +171,17 @@ format="M/D/YYYY"> {%- 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 %}

From 7a7bbfc5c569efae05cd2d9fb03f581cd79d8342 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 10:17:46 -0500 Subject: [PATCH 08/12] Update task order layout in single column --- styles/sections/_task_order.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 262d8602..aa4da347 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -184,7 +184,9 @@ } .task-order-sidebar { - padding-left: 3 * $gap; + @include media($xlarge-screen) { + padding-left: 3 * $gap; + } min-width: 35rem; hr { From 545bddf47f168643c9953f1fdf511a3fbf06d892 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 10:26:28 -0500 Subject: [PATCH 09/12] Add missing link to instruction sheet --- templates/portfolios/task_orders/show.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/portfolios/task_orders/show.html b/templates/portfolios/task_orders/show.html index ce0243ff..1acbcf30 100644 --- a/templates/portfolios/task_orders/show.html +++ b/templates/portfolios/task_orders/show.html @@ -185,6 +185,9 @@

+ {{ 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) ) }} From 66a7d0d718760e2f8747fd84f057cd0614be4b32 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 10:34:31 -0500 Subject: [PATCH 10/12] Group pending and started task orders together on funding page --- atst/routes/portfolios/task_orders.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atst/routes/portfolios/task_orders.py b/atst/routes/portfolios/task_orders.py index 44edd888..15dbdd03 100644 --- a/atst/routes/portfolios/task_orders.py +++ b/atst/routes/portfolios/task_orders.py @@ -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, From a8e9430196cceb01fee260728efb0191d322107c Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 11:13:00 -0500 Subject: [PATCH 11/12] Fix text wrapping issue in IE10 --- styles/sections/_task_order.scss | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index aa4da347..25f7e780 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -177,8 +177,13 @@ } } - .task-order-next-steps__description { - font-style: italic; + .task-order-next-steps__text { + display: flex; + .task-order-next-steps__heading { + display: block; + max-width: 100%; + flex-shrink: 1; + } } } } From 3e50ce3d48db40ac01b7af8eba29503f42d422be Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 13 Feb 2019 11:36:17 -0500 Subject: [PATCH 12/12] Fix usPhone with empty phone number --- atst/filters.py | 2 ++ tests/test_filters.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/atst/filters.py b/atst/filters.py index 2b54572e..011d930b 100644 --- a/atst/filters.py +++ b/atst/filters.py @@ -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:]) diff --git a/tests/test_filters.py b/tests/test_filters.py index 5c091faa..f35bb659 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -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 "