diff --git a/alembic/versions/53ab3edd334b_request_reviews_comment_column_name.py b/alembic/versions/53ab3edd334b_request_reviews_comment_column_name.py new file mode 100644 index 00000000..35b52d1d --- /dev/null +++ b/alembic/versions/53ab3edd334b_request_reviews_comment_column_name.py @@ -0,0 +1,30 @@ +"""request_reviews comment column name + +Revision ID: 53ab3edd334b +Revises: 777ded5c57a0 +Create Date: 2018-09-10 13:29:02.648359 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '53ab3edd334b' +down_revision = '777ded5c57a0' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('request_reviews', sa.Column('comment', sa.String(), nullable=True)) + op.drop_column('request_reviews', 'comments') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('request_reviews', sa.Column('comments', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.drop_column('request_reviews', 'comment') + # ### end Alembic commands ### diff --git a/alembic/versions/777ded5c57a0_bigint_for_request_review_id.py b/alembic/versions/777ded5c57a0_bigint_for_request_review_id.py new file mode 100644 index 00000000..c76ca8e4 --- /dev/null +++ b/alembic/versions/777ded5c57a0_bigint_for_request_review_id.py @@ -0,0 +1,36 @@ +"""bigint for request review id + +Revision ID: 777ded5c57a0 +Revises: 7bdb2055d7c7 +Create Date: 2018-09-10 13:24:36.328610 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '777ded5c57a0' +down_revision = '7bdb2055d7c7' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('request_reviews', 'id', + existing_type=sa.Integer(), + type_=sa.BigInteger(), + nullable=False) + pass + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('request_reviews', 'id', + existing_type=sa.BigInteger(), + type_=sa.Integer(), + nullable=False) + pass + # ### end Alembic commands ### diff --git a/alembic/versions/7bdb2055d7c7_add_request_review_table.py b/alembic/versions/7bdb2055d7c7_add_request_review_table.py new file mode 100644 index 00000000..ba669921 --- /dev/null +++ b/alembic/versions/7bdb2055d7c7_add_request_review_table.py @@ -0,0 +1,46 @@ +"""add request review table + +Revision ID: 7bdb2055d7c7 +Revises: ad30159ef19b +Create Date: 2018-09-06 15:15:40.666840 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = '7bdb2055d7c7' +down_revision = 'ad30159ef19b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('request_reviews', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('comments', sa.String(), nullable=True), + sa.Column('fname_mao', sa.String(), nullable=True), + sa.Column('lname_mao', sa.String(), nullable=True), + sa.Column('email_mao', sa.String(), nullable=True), + sa.Column('phone_mao', sa.String(), nullable=True), + sa.Column('fname_ccpo', sa.String(), nullable=True), + sa.Column('lname_ccpo', sa.String(), nullable=True), + sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('request_status_events', sa.Column('request_review_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'request_status_events', 'request_reviews', ['request_review_id'], ['id']) + op.create_foreign_key(None, 'request_reviews', 'users', ['user_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'request_status_events', type_='foreignkey') + op.drop_constraint(None, 'request_reviews', type_='foreignkey') + op.drop_column('request_status_events', 'request_review_id') + op.drop_table('request_reviews') + # ### end Alembic commands ### diff --git a/atst/domain/requests.py b/atst/domain/requests.py index 5f7e68bc..f87a8382 100644 --- a/atst/domain/requests.py +++ b/atst/domain/requests.py @@ -12,6 +12,7 @@ from atst.domain.workspaces import Workspaces from atst.models.request import Request from atst.models.request_revision import RequestRevision from atst.models.request_status_event import RequestStatusEvent, RequestStatus +from atst.models.request_review import RequestReview from atst.utils import deep_merge from .exceptions import NotFoundError, UnauthorizedError @@ -90,7 +91,7 @@ class Requests(object): if Requests.should_auto_approve(request): new_status = RequestStatus.PENDING_FINANCIAL_VERIFICATION else: - new_status = RequestStatus.PENDING_CCPO_APPROVAL + new_status = RequestStatus.PENDING_CCPO_ACCEPTANCE request = Requests.set_status(request, new_status) @@ -175,6 +176,10 @@ class Requests(object): def is_pending_financial_verification(cls, request): return request.status == RequestStatus.PENDING_FINANCIAL_VERIFICATION + @classmethod + def is_pending_ccpo_acceptance(cls, request): + return request.status == RequestStatus.PENDING_CCPO_ACCEPTANCE + @classmethod def is_pending_ccpo_approval(cls, request): return request.status == RequestStatus.PENDING_CCPO_APPROVAL @@ -214,7 +219,12 @@ WHERE requests_with_status.status = :status @classmethod def pending_ccpo_count(cls): - return Requests.status_count(RequestStatus.PENDING_CCPO_APPROVAL) + return sum( + [ + Requests.status_count(RequestStatus.PENDING_CCPO_ACCEPTANCE), + Requests.status_count(RequestStatus.PENDING_CCPO_APPROVAL), + ] + ) @classmethod def completed_count(cls): @@ -260,3 +270,24 @@ WHERE requests_with_status.status = :status db.session.commit() return request + + @classmethod + def _add_review(cls, user, request, review_data): + request.latest_status.review = RequestReview(reviewer=user, **review_data) + + db.session.add(request) + db.session.commit() + + return request + + @classmethod + def accept_for_financial_verification(cls, user, request, review_data): + Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION) + + return Requests._add_review(user, request, review_data) + + @classmethod + def request_changes(cls, user, request, review_data): + Requests.set_status(request, RequestStatus.CHANGES_REQUESTED) + + return Requests._add_review(user, request, review_data) diff --git a/atst/forms/ccpo_review.py b/atst/forms/ccpo_review.py new file mode 100644 index 00000000..8b6ea64d --- /dev/null +++ b/atst/forms/ccpo_review.py @@ -0,0 +1,26 @@ +from wtforms.fields.html5 import EmailField, TelField +from wtforms.fields import StringField, TextAreaField +from wtforms.validators import Email, Optional + +from .forms import ValidatedForm +from .validators import Alphabet, PhoneNumber + + +class CCPOReviewForm(ValidatedForm): + comment = TextAreaField("Comments (optional)") + fname_mao = StringField( + "First Name (optional)", validators=[Optional(), Alphabet()] + ) + lname_mao = StringField("Last Name (optional)", validators=[Optional(), Alphabet()]) + email_mao = EmailField( + "Mission Owner e-mail (optional)", validators=[Optional(), Email()] + ) + phone_mao = TelField( + "Mission Owner phone number (optional)", validators=[Optional(), PhoneNumber()] + ) + fname_ccpo = StringField( + "First Name (optional)", validators=[Optional(), Alphabet()] + ) + lname_ccpo = StringField( + "Last Name (optional)", validators=[Optional(), Alphabet()] + ) diff --git a/atst/models/__init__.py b/atst/models/__init__.py index 4bedebfe..245e3806 100644 --- a/atst/models/__init__.py +++ b/atst/models/__init__.py @@ -15,3 +15,4 @@ from .project import Project from .environment import Environment from .attachment import Attachment from .request_revision import RequestRevision +from .request_review import RequestReview diff --git a/atst/models/request.py b/atst/models/request.py index 4e155504..05f52610 100644 --- a/atst/models/request.py +++ b/atst/models/request.py @@ -115,13 +115,17 @@ class Request(Base): return body + @property + def latest_status(self): + return self.status_events[-1] + @property def status(self): - return self.status_events[-1].new_status + return self.latest_status.new_status @property def status_displayname(self): - return self.status_events[-1].displayname + return self.latest_status.displayname @property def annual_spend(self): @@ -152,5 +156,11 @@ class Request(Base): def action_required_by(self): return { RequestStatus.PENDING_FINANCIAL_VERIFICATION: "mission_owner", + RequestStatus.CHANGES_REQUESTED: "mission_owner", RequestStatus.PENDING_CCPO_APPROVAL: "ccpo", + RequestStatus.PENDING_CCPO_ACCEPTANCE: "ccpo", }.get(self.status) + + @property + def reviews(self): + return [status.review for status in self.status_events if status.review] diff --git a/atst/models/request_review.py b/atst/models/request_review.py new file mode 100644 index 00000000..3c991ea5 --- /dev/null +++ b/atst/models/request_review.py @@ -0,0 +1,22 @@ +from sqlalchemy import Column, BigInteger, String, ForeignKey +from sqlalchemy.orm import relationship + +from atst.models import Base + + +class RequestReview(Base): + __tablename__ = "request_reviews" + + id = Column(BigInteger, primary_key=True) + status = relationship("RequestStatusEvent", back_populates="review") + + user_id = Column(ForeignKey("users.id"), nullable=False) + reviewer = relationship("User") + + comment = Column(String) + fname_mao = Column(String) + lname_mao = Column(String) + email_mao = Column(String) + phone_mao = Column(String) + fname_ccpo = Column(String) + lname_ccpo = Column(String) diff --git a/atst/models/request_status_event.py b/atst/models/request_status_event.py index ba13c242..a5373bdd 100644 --- a/atst/models/request_status_event.py +++ b/atst/models/request_status_event.py @@ -13,6 +13,7 @@ class RequestStatus(Enum): STARTED = "Started" SUBMITTED = "Submitted" PENDING_FINANCIAL_VERIFICATION = "Pending Financial Verification" + PENDING_CCPO_ACCEPTANCE = "Pending CCPO Acceptance" PENDING_CCPO_APPROVAL = "Pending CCPO Approval" CHANGES_REQUESTED = "Changes Requested" APPROVED = "Approved" @@ -35,6 +36,9 @@ class RequestStatusEvent(Base): request_revision_id = Column(ForeignKey("request_revisions.id"), nullable=False) revision = relationship("RequestRevision") + request_review_id = Column(ForeignKey("request_reviews.id"), nullable=True) + review = relationship("RequestReview", back_populates="status") + @property def displayname(self): return self.new_status.value diff --git a/atst/routes/requests/approval.py b/atst/routes/requests/approval.py index 20b9c890..ca02933f 100644 --- a/atst/routes/requests/approval.py +++ b/atst/routes/requests/approval.py @@ -1,10 +1,18 @@ -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,25 +23,54 @@ 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: + pending_final_approval = Requests.is_pending_ccpo_approval(request) + pending_review = ( + Requests.is_pending_ccpo_acceptance(request) or pending_final_approval + ) + if pending_final_approval and request.task_order: data["task_order"] = task_order_dictionary(request.task_order) return render_template( "requests/approval.html", data=data, - request_id=request_id, + request_id=request.id, status=request.status.value, - financial_review=True, + pending_review=pending_review, + financial_review=pending_final_approval, 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(): + if http_request.form.get("approved"): + Requests.accept_for_financial_verification( + g.current_user, request, form.data + ) + else: + Requests.request_changes(g.current_user, 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/atst/routes/requests/index.py b/atst/routes/requests/index.py index 75be2c87..8553c0d6 100644 --- a/atst/routes/requests/index.py +++ b/atst/routes/requests/index.py @@ -30,7 +30,7 @@ class RequestsIndex(object): return { "requests": mapped_requests, "pending_financial_verification": False, - "pending_ccpo_approval": False, + "pending_ccpo_acceptance": False, "extended_view": True, "kpi_inprogress": Requests.in_progress_count(), "kpi_pending": Requests.pending_ccpo_count(), @@ -47,12 +47,12 @@ class RequestsIndex(object): pending_fv = any( Requests.is_pending_financial_verification(r) for r in requests ) - pending_ccpo = any(Requests.is_pending_ccpo_approval(r) for r in requests) + pending_ccpo = any(Requests.is_pending_ccpo_acceptance(r) for r in requests) return { "requests": mapped_requests, "pending_financial_verification": pending_fv, - "pending_ccpo_approval": pending_ccpo, + "pending_ccpo_acceptance": pending_ccpo, "num_action_required": num_action_required, "extended_view": False, } @@ -76,7 +76,9 @@ class RequestsIndex(object): edit_link = url_for( "requests.financial_verification", request_id=request.id ) - elif Requests.is_pending_ccpo_approval(request): + elif Requests.is_pending_ccpo_acceptance( + request + ) or Requests.is_pending_ccpo_approval(request): edit_link = url_for("requests.view_pending_request", request_id=request.id) else: edit_link = url_for( diff --git a/templates/requests/approval.html b/templates/requests/approval.html index e67bd935..8cf4ec2c 100644 --- a/templates/requests/approval.html +++ b/templates/requests/approval.html @@ -2,12 +2,21 @@ {% from "components/icon.html" import Icon %} {% from "components/alert.html" import Alert %} +{% from "components/text_input.html" import TextInput %} {% block content %}
-
+{% if f.errors %} + {{ Alert('There were some errors', + message="

Please see below.

", + level='error' + ) }} +{% endif %} + + + {{ f.csrf_token }}

Request #{{ request_id }}

@@ -24,139 +33,107 @@
-
-
-

Approval Notes

-
+ {% if pending_review %} +
+
+

Approval Notes

+
-
+
-
+
-

Instructions for the Requestor

+

Instructions for the Requestor

- Provide instructions or notes for additional information that is necessary to approve the request here. The requestor may then re-submit the updated request or initiate contact outside of AT-AT if further discussion is required. These notes will be visible to the person making the JEDI request. + Provide instructions or notes for additional information that is necessary to approve the request here. The requestor may then re-submit the updated request or initiate contact outside of AT-AT if further discussion is required. These notes will be visible to the person making the JEDI request. -
- - -
- - -
- -
-
-

Authorizing Officials

-

This section is not visible to the person making the request. It is only viewable by CCPO staff.

-

Provide the name of the key officials for both parties that have authorized this request to move forward.

+ {{ TextInput(f.comment, paragraph=True, placeholder="Add notes or comments explaining what changes are being requested or why further discussion is needed about this request.") }}
-
-

Mission Authorizing Official

-
+
+
+

Authorizing Officials

+

This section is not visible to the person making the request. It is only viewable by CCPO staff.

+

Provide the name of the key officials for both parties that have authorized this request to move forward.

-
+
+
+

Mission Authorizing Official

-
- - + +
+ +
+ {{ TextInput(f.fname_mao, placeholder="First name of mission authorizing official") }} +
+ +
+ {{ TextInput(f.lname_mao, placeholder="Last name of mission authorizing official") }}
-
+
+ +
+ {{ TextInput(f.email_mao, placeholder="name@mail.mil") }} +
+ + +
+ {{ TextInput(f.phone_mao, placeholder="(123) 456-7890", validation='usPhone') }} +
+ +
+ + +

CCPO Authorizing Official

+ +
+ +
+ {{ TextInput(f.fname_ccpo, placeholder="First name of CCPO authorizing official") }} +
+ +
+ {{ TextInput(f.lname_ccpo, placeholder="Last name of CCPO authorizing official") }} +
+ +
+ + + +

CCPO Internal Notes

+ +

You may add additional comments and notes for internal CCPO reference and follow-up here.

+ +
+ +
+ +
+ + +
-
- -
-
+
-
- - -
- - -
- -
- - -
- -
- - -
- -
- - - - -

CCPO Authorizing Official

- -
- -
- -
- - -
- -
- -
- -
- - -
- -
- -
- - - -

CCPO Internal Notes

- -

You may add additional comments and notes for internal CCPO reference and follow-up here.

- -
- -
- -
- - -
- -
- -
- - - -
- -
- Approve Request - Mark as Changes Requested - - {{ Icon('x') }} - Cancel - -
+
+ + + + {{ Icon('x') }} + Cancel + +
+ {% endif %}
diff --git a/tests/domain/test_requests.py b/tests/domain/test_requests.py index e938d7bc..34045076 100644 --- a/tests/domain/test_requests.py +++ b/tests/domain/test_requests.py @@ -13,6 +13,7 @@ from tests.factories import ( RequestStatusEventFactory, TaskOrderFactory, RequestRevisionFactory, + RequestReviewFactory, ) @@ -50,14 +51,14 @@ def test_dont_auto_approve_if_dollar_value_is_1m_or_above(): new_request = RequestFactory.create(initial_revision={"dollar_value": 1000000}) request = Requests.submit(new_request) - assert request.status == RequestStatus.PENDING_CCPO_APPROVAL + assert request.status == RequestStatus.PENDING_CCPO_ACCEPTANCE def test_dont_auto_approve_if_no_dollar_value_specified(): new_request = RequestFactory.create(initial_revision={}) request = Requests.submit(new_request) - assert request.status == RequestStatus.PENDING_CCPO_APPROVAL + assert request.status == RequestStatus.PENDING_CCPO_ACCEPTANCE def test_should_allow_submission(): @@ -178,3 +179,14 @@ def test_set_status_sets_revision(): request = RequestFactory.create() Requests.set_status(request, RequestStatus.APPROVED) assert request.latest_revision == request.status_events[-1].revision + + +def test_accept_for_financial_verification(): + request = RequestFactory.create() + review_data = RequestReviewFactory.dictionary() + Requests.accept_for_financial_verification( + UserFactory.create(), request, review_data + ) + assert request.status == RequestStatus.PENDING_FINANCIAL_VERIFICATION + current_review = request.latest_status.review + assert current_review.fname_mao == review_data["fname_mao"] diff --git a/tests/factories.py b/tests/factories.py index b73ea74a..ad8cb048 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -7,6 +7,7 @@ import datetime from atst.forms.data import SERVICE_BRANCHES from atst.models.request import Request from atst.models.request_revision import RequestRevision +from atst.models.request_review import RequestReview from atst.models.request_status_event import RequestStatusEvent, RequestStatus from atst.models.pe_number import PENumber from atst.models.task_order import TaskOrder @@ -16,14 +17,20 @@ from atst.models.workspace import Workspace from atst.domain.roles import Roles -class RoleFactory(factory.alchemy.SQLAlchemyModelFactory): +class Base(factory.alchemy.SQLAlchemyModelFactory): + @classmethod + def dictionary(cls, **attrs): + return factory.build(dict, FACTORY_CLASS=cls, **attrs) + + +class RoleFactory(Base): class Meta: model = Role permissions = [] -class UserFactory(factory.alchemy.SQLAlchemyModelFactory): +class UserFactory(Base): class Meta: model = User @@ -40,7 +47,7 @@ class UserFactory(factory.alchemy.SQLAlchemyModelFactory): return cls.create(atat_role=role, **kwargs) -class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory): +class RequestStatusEventFactory(Base): class Meta: model = RequestStatusEvent @@ -48,14 +55,29 @@ class RequestStatusEventFactory(factory.alchemy.SQLAlchemyModelFactory): sequence = 1 -class RequestRevisionFactory(factory.alchemy.SQLAlchemyModelFactory): +class RequestRevisionFactory(Base): class Meta: model = RequestRevision id = factory.Sequence(lambda x: uuid4()) -class RequestFactory(factory.alchemy.SQLAlchemyModelFactory): +class RequestReviewFactory(Base): + class Meta: + model = RequestReview + + comment = factory.Faker("sentence") + fname_mao = factory.Faker("first_name") + lname_mao = factory.Faker("last_name") + email_mao = factory.Faker("email") + phone_mao = factory.LazyFunction( + lambda: "".join(random.choices(string.digits, k=10)) + ) + fname_ccpo = factory.Faker("first_name") + lname_ccpo = factory.Faker("last_name") + + +class RequestFactory(Base): class Meta: model = Request @@ -127,17 +149,17 @@ class RequestFactory(factory.alchemy.SQLAlchemyModelFactory): return RequestRevisionFactory.build(**data) -class PENumberFactory(factory.alchemy.SQLAlchemyModelFactory): +class PENumberFactory(Base): class Meta: model = PENumber -class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory): +class TaskOrderFactory(Base): class Meta: model = TaskOrder -class WorkspaceFactory(factory.alchemy.SQLAlchemyModelFactory): +class WorkspaceFactory(Base): class Meta: model = Workspace diff --git a/tests/models/test_requests.py b/tests/models/test_requests.py index cf17afa8..a5bbef93 100644 --- a/tests/models/test_requests.py +++ b/tests/models/test_requests.py @@ -1,4 +1,9 @@ -from tests.factories import RequestFactory, UserFactory +from tests.factories import ( + RequestFactory, + UserFactory, + RequestStatusEventFactory, + RequestReviewFactory, +) from atst.domain.requests import Requests, RequestStatus @@ -69,3 +74,20 @@ def test_annual_spend(): request = RequestFactory.create() monthly = request.body.get("details_of_use").get("estimated_monthly_spend") assert request.annual_spend == monthly * 12 + + +def test_reviews(): + request = RequestFactory.create() + ccpo = UserFactory.from_atat_role("ccpo") + request.status_events = [ + RequestStatusEventFactory.create( + revision=request.latest_revision, + review=RequestReviewFactory.create(reviewer=ccpo), + ), + RequestStatusEventFactory.create( + revision=request.latest_revision, + review=RequestReviewFactory.create(reviewer=ccpo), + ), + RequestStatusEventFactory.create(revision=request.latest_revision), + ] + assert len(request.reviews) == 2 diff --git a/tests/routes/test_request_approval.py b/tests/routes/test_request_approval.py index 5680e30c..ced46240 100644 --- a/tests/routes/test_request_approval.py +++ b/tests/routes/test_request_approval.py @@ -2,9 +2,15 @@ import os from flask import url_for from atst.models.attachment import Attachment +from atst.models.request_status_event import RequestStatus 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 +65,29 @@ 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() + review_data["approved"] = True + response = client.post( + url_for("requests.submit_approval", request_id=request.id), data=review_data + ) + assert response.status_code == 302 + assert request.status == RequestStatus.PENDING_FINANCIAL_VERIFICATION + + +def test_can_submit_request_denial(client, user_session): + user = UserFactory.from_atat_role("ccpo") + user_session(user) + request = RequestFactory.create() + review_data = RequestReviewFactory.dictionary() + review_data["denied"] = True + response = client.post( + url_for("requests.submit_approval", request_id=request.id), data=review_data + ) + assert response.status_code == 302 + assert request.status == RequestStatus.CHANGES_REQUESTED diff --git a/tests/test_integration.py b/tests/test_integration.py index 51a75789..152e960b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -81,4 +81,4 @@ def test_stepthrough_request_form(user_session, screens, client): ) finished_request = Requests.get(user, req_id) - assert finished_request.status == RequestStatus.PENDING_CCPO_APPROVAL + assert finished_request.status == RequestStatus.PENDING_CCPO_ACCEPTANCE