diff --git a/atst/forms/ccpo_review.py b/atst/forms/ccpo_review.py new file mode 100644 index 00000000..6db8c9a5 --- /dev/null +++ b/atst/forms/ccpo_review.py @@ -0,0 +1,22 @@ +from wtforms.fields.html5 import EmailField, TelField +from wtforms.fields import StringField, TextAreaField +from wtforms.validators import Required, Email + +from .forms import ValidatedForm +from .validators import Alphabet, PhoneNumber + + +class CCPOReviewForm(ValidatedForm): + comments = TextAreaField( + "Comments", + description="Add notes or comments explaining what changes are being requested or why further discussion is needed about this request.", + ) + fname_mao = StringField("First Name", validators=[Required(), Alphabet()]) + lname_mao = StringField("Last Name", validators=[Required(), Alphabet()]) + email_mao = EmailField("Mission Owner e-mail (optional)", validators=[Email()]) + phone_mao = TelField( + "Mission Owner phone number (optional)", + validators=[Required(), PhoneNumber()], + ) + fname_ccpo = StringField("First Name", validators=[Required(), Alphabet()]) + lname_ccpo = StringField("Last Name", validators=[Required(), Alphabet()]) diff --git a/atst/routes/requests/approval.py b/atst/routes/requests/approval.py index 20b9c890..f9385dbd 100644 --- a/atst/routes/requests/approval.py +++ b/atst/routes/requests/approval.py @@ -1,10 +1,11 @@ -from flask import render_template, g, Response +from flask import render_template, g, Response, request as http_request, redirect, url_for from flask import current_app as app from . import requests_bp from atst.domain.requests import Requests from atst.domain.exceptions import NotFoundError from atst.domain.authz import Authorization +from atst.forms.ccpo_review import CCPOReviewForm def task_order_dictionary(task_order): @@ -15,11 +16,7 @@ def task_order_dictionary(task_order): } -@requests_bp.route("/requests/approval/", methods=["GET"]) -def approval(request_id): - request = Requests.get(g.current_user, request_id) - Authorization.check_can_approve_request(g.current_user) - +def render_approval(request, form=None): data = request.body if request.task_order: data["task_order"] = task_order_dictionary(request.task_order) @@ -27,13 +24,35 @@ def approval(request_id): return render_template( "requests/approval.html", data=data, - request_id=request_id, + request_id=request.id, status=request.status.value, financial_review=True, pdf_available=request.task_order and request.task_order.pdf, + f=form or CCPOReviewForm(), ) +@requests_bp.route("/requests/approval/", methods=["GET"]) +def approval(request_id): + request = Requests.get(g.current_user, request_id) + Authorization.check_can_approve_request(g.current_user) + + return render_approval(request) + + +@requests_bp.route("/requests/submit_approval/", methods=["POST"]) +def submit_approval(request_id): + request = Requests.get(g.current_user, request_id) + Authorization.check_can_approve_request(g.current_user) + + form = CCPOReviewForm(http_request.form) + if form.validate(): + Requests.approve_for_financial_verification(request, form.data) + return redirect(url_for("requests.requests_index")) + else: + return render_approval(request, form) + + @requests_bp.route("/requests/task_order_download/", methods=["GET"]) def task_order_pdf_download(request_id): request = Requests.get(g.current_user, request_id) diff --git a/templates/requests/approval.html b/templates/requests/approval.html index e67bd935..d9952981 100644 --- a/templates/requests/approval.html +++ b/templates/requests/approval.html @@ -2,12 +2,14 @@ {% from "components/icon.html" import Icon %} {% from "components/alert.html" import Alert %} +{% from "components/text_input.html" import TextInput %} {% block content %}
-
+ + {{ f.csrf_token }}

Request #{{ request_id }}

@@ -58,21 +60,11 @@
- -
- - -
- + {{ TextInput(f.fname_mao, placeholder="First name of mission authorizing official") }}
- -
- - -
- + {{ TextInput(f.lname_mao, placeholder="Last name of mission authorizing official") }}
@@ -80,23 +72,12 @@
- - -
- - -
- + {{ TextInput(f.email_mao, placeholder="name@mail.mil") }}
- -
- - -
- + {{ TextInput(f.phone_mao, placeholder="(123) 456-7890", validation='usPhone') }}
@@ -107,21 +88,11 @@
- -
- - -
- + {{ TextInput(f.fname_ccpo, placeholder="First name of CCPO authorizing official") }}
- -
- - -
- + {{ TextInput(f.lname_ccpo, placeholder="Last name of CCPO authorizing official") }}
@@ -135,12 +106,7 @@
- -
- - -
- + {{ TextInput(f.comments, paragraph=True, placeholder="Add notes or comments for internal CCPO reference.") }}
@@ -150,7 +116,7 @@
- Approve Request + Mark as Changes Requested {{ Icon('x') }} diff --git a/tests/factories.py b/tests/factories.py index e8165393..fa1036c9 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -70,7 +70,7 @@ class RequestReviewFactory(Base): fname_mao = factory.Faker("first_name") lname_mao = factory.Faker("last_name") email_mao = factory.Faker("email") - phone_mao = factory.Faker("phone_number") + phone_mao = factory.LazyFunction(lambda: "".join(random.choices(string.digits, k=10))) fname_ccpo = factory.Faker("first_name") lname_ccpo = factory.Faker("last_name") diff --git a/tests/routes/test_request_approval.py b/tests/routes/test_request_approval.py index 5680e30c..0198f816 100644 --- a/tests/routes/test_request_approval.py +++ b/tests/routes/test_request_approval.py @@ -4,7 +4,9 @@ from flask import url_for from atst.models.attachment import Attachment from atst.domain.roles import Roles -from tests.factories import RequestFactory, TaskOrderFactory, UserFactory +from tests.factories import ( + RequestFactory, TaskOrderFactory, UserFactory, RequestReviewFactory +) def test_ccpo_can_view_approval(user_session, client): @@ -59,3 +61,14 @@ def test_task_order_download_does_not_exist(client, user_session): url_for("requests.task_order_pdf_download", request_id=request.id) ) assert response.status_code == 404 + + +def test_can_submit_request_approval(client, user_session): + user = UserFactory.from_atat_role("ccpo") + user_session(user) + request = RequestFactory.create() + review_data = RequestReviewFactory.dictionary() + response = client.post( + url_for("requests.submit_approval", request_id=request.id), data=review_data + ) + assert response.status_code == 301