Task Order number not found #159087193
This commit is contained in:
dandds 2018-08-21 16:16:19 -04:00 committed by GitHub
commit a2c1e12bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 231 additions and 73 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ config/dev.ini
# CRLs # CRLs
/crl /crl
/crl-tmp /crl-tmp
*.bk

View File

@ -18,6 +18,7 @@ from atst.routes.dev import bp as dev_routes
from atst.routes.errors import make_error_pages from atst.routes.errors import make_error_pages
from atst.domain.authnid.crl import CRLCache from atst.domain.authnid.crl import CRLCache
from atst.domain.auth import apply_authentication from atst.domain.auth import apply_authentication
from atst.eda_client import MockEDAClient
ENV = os.getenv("FLASK_ENV", "dev") ENV = os.getenv("FLASK_ENV", "dev")
@ -41,6 +42,7 @@ def make_app(config):
make_flask_callbacks(app) make_flask_callbacks(app)
make_crl_validator(app) make_crl_validator(app)
register_filters(app) register_filters(app)
make_eda_client(app)
db.init_app(app) db.init_app(app)
csrf.init_app(app) csrf.init_app(app)
@ -139,3 +141,5 @@ def make_crl_validator(app):
crl_locations.append(filename.absolute()) crl_locations.append(filename.absolute())
app.crl_cache = CRLCache(app.config["CA_CHAIN"], crl_locations, logger=app.logger) app.crl_cache = CRLCache(app.config["CA_CHAIN"], crl_locations, logger=app.logger)
def make_eda_client(app):
app.eda_client = MockEDAClient()

View File

@ -1,4 +1,5 @@
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from flask import current_app as app
from atst.database import db from atst.database import db
from atst.models.task_order import TaskOrder from atst.models.task_order import TaskOrder
@ -8,12 +9,36 @@ from .exceptions import NotFoundError
class TaskOrders(object): class TaskOrders(object):
@classmethod @classmethod
def get(self, order_number): def get(cls, order_number):
try: try:
task_order = ( task_order = (
db.session.query(TaskOrder).filter_by(number=order_number).one() db.session.query(TaskOrder).filter_by(number=order_number).one()
) )
except NoResultFound: except NoResultFound:
raise NotFoundError("task_order") if TaskOrders._client():
task_order = TaskOrders._get_from_eda(order_number)
else:
raise NotFoundError("task_order")
return task_order return task_order
@classmethod
def _get_from_eda(cls, order_number):
to_data = TaskOrders._client().get_contract(order_number, status="y")
if to_data:
return TaskOrders.create(to_data["contract_no"])
else:
raise NotFoundError("task_order")
@classmethod
def create(cls, order_number):
task_order = TaskOrder(number=order_number)
db.session.add(task_order)
db.session.commit()
return task_order
@classmethod
def _client(cls):
return app.eda_client

View File

@ -71,8 +71,10 @@ class MockEDAClient(EDAClientBase):
}, },
] ]
MOCK_CONTRACT_NUMBER = "DCA10096D0052"
def get_contract(self, contract_number, status): def get_contract(self, contract_number, status):
if contract_number == "DCA10096D0052" and status == "y": if contract_number == self.MOCK_CONTRACT_NUMBER and status == "y":
return { return {
"aco_mod": "01", "aco_mod": "01",
"admin_dodaac": None, "admin_dodaac": None,

View File

@ -1,10 +1,11 @@
import re import re
from wtforms.fields.html5 import EmailField from wtforms.fields.html5 import EmailField
from wtforms.fields import StringField from wtforms.fields import StringField
from wtforms.validators import Required, Email, Regexp from wtforms.validators import Required, Email, Regexp, ValidationError
from atst.domain.exceptions import NotFoundError from atst.domain.exceptions import NotFoundError
from atst.domain.pe_numbers import PENumbers from atst.domain.pe_numbers import PENumbers
from atst.domain.task_orders import TaskOrders
from .fields import NewlineListField, SelectField from .fields import NewlineListField, SelectField
from .forms import ValidatedForm from .forms import ValidatedForm
@ -57,12 +58,7 @@ def validate_pe_id(field, existing_request):
return True return True
class FinancialForm(ValidatedForm): class BaseFinancialForm(ValidatedForm):
def validate(self, *args, **kwargs):
if self.funding_type.data == "OTHER":
self.funding_type_other.validators.append(Required())
return super().validate(*args, **kwargs)
def reset(self): def reset(self):
""" """
Reset UII info so that it can be de-parsed rendered properly. Reset UII info so that it can be de-parsed rendered properly.
@ -76,7 +72,11 @@ class FinancialForm(ValidatedForm):
valid = validate_pe_id(self.pe_id, existing_request) valid = validate_pe_id(self.pe_id, existing_request)
return valid return valid
task_order_id = StringField( @property
def is_missing_task_order_number(self):
return False
task_order_number = StringField(
"Task Order Number associated with this request", "Task Order Number associated with this request",
description="Include the original Task Order number (including the 000X at the end). Do not include any modification numbers. Note that there may be a lag between approving a task order and when it becomes available in our system.", description="Include the original Task Order number (including the 000X at the end). Do not include any modification numbers. Note that there may be a lag between approving a task order and when it becomes available in our system.",
validators=[Required()] validators=[Required()]
@ -117,6 +117,25 @@ class FinancialForm(ValidatedForm):
"Contracting Officer Representative (COR) Office", validators=[Required()] "Contracting Officer Representative (COR) Office", validators=[Required()]
) )
class FinancialForm(BaseFinancialForm):
def validate_task_order_number(form, field):
try:
TaskOrders.get(field.data)
except NotFoundError:
raise ValidationError("Task Order number not found")
@property
def is_missing_task_order_number(self):
return "task_order_number" in self.errors
class ExtendedFinancialForm(BaseFinancialForm):
def validate(self, *args, **kwargs):
if self.funding_type.data == "OTHER":
self.funding_type_other.validators.append(Required())
return super().validate(*args, **kwargs)
funding_type = SelectField( funding_type = SelectField(
description="What is the source of funding?", description="What is the source of funding?",
choices=[ choices=[

View File

@ -3,15 +3,25 @@ from flask import request as http_request
from . import requests_bp from . import requests_bp
from atst.domain.requests import Requests from atst.domain.requests import Requests
from atst.forms.financial import FinancialForm from atst.forms.financial import FinancialForm, ExtendedFinancialForm
def financial_form(data):
if http_request.args.get("extended"):
return ExtendedFinancialForm(data=data)
else:
return FinancialForm(data=data)
@requests_bp.route("/requests/verify/<string:request_id>", methods=["GET"]) @requests_bp.route("/requests/verify/<string:request_id>", methods=["GET"])
def financial_verification(request_id=None): def financial_verification(request_id=None):
request = Requests.get(request_id) request = Requests.get(request_id)
form = FinancialForm(data=request.body.get("financial_verification")) form = financial_form(request.body.get("financial_verification"))
return render_template( return render_template(
"requests/financial_verification.html", f=form, request_id=request_id "requests/financial_verification.html",
f=form,
request_id=request_id,
extended=http_request.args.get("extended"),
) )
@ -19,9 +29,9 @@ def financial_verification(request_id=None):
def update_financial_verification(request_id): def update_financial_verification(request_id):
post_data = http_request.form post_data = http_request.form
existing_request = Requests.get(request_id) existing_request = Requests.get(request_id)
form = FinancialForm(post_data) form = financial_form(post_data)
rerender_args = dict(request_id=request_id, f=form) rerender_args = dict(request_id=request_id, f=form, extended=http_request.args.get("extended"))
if form.validate(): if form.validate():
request_data = {"financial_verification": form.data} request_data = {"financial_verification": form.data}
@ -31,11 +41,13 @@ def update_financial_verification(request_id):
Requests.update(request_id, request_data) Requests.update(request_id, request_data)
if valid: if valid:
return redirect(url_for("requests.financial_verification_submitted")) return redirect(url_for("requests.financial_verification_submitted"))
else: else:
form.reset() form.reset()
return render_template( return render_template(
"requests/financial_verification.html", **rerender_args "requests/financial_verification.html", **rerender_args
) )
else: else:
form.reset() form.reset()
return render_template("requests/financial_verification.html", **rerender_args) return render_template("requests/financial_verification.html", **rerender_args)

View File

@ -9,6 +9,24 @@
<financial inline-template v-bind:initial-data='{{ f.data|tojson }}'> <financial inline-template v-bind:initial-data='{{ f.data|tojson }}'>
<div class="col"> <div class="col">
{% if extended %}
{{ Alert('Task Order not found in EDA',
message="Since the Task Order (TO) number was not found in our system of record, EDA, please populate the additional fields in the form below.",
level='warning'
) }}
{% endif %}
{% if f.is_missing_task_order_number %}
{% set extended_url = url_for('requests.financial_verification', request_id=request_id, extended=True) %}
{{ Alert('Task Order not found in EDA',
message="We could not find your Task Order in our system of record, EDA.
Please confirm that you have entered it correctly.
<a href=\"%s\">Otherwise enter TO information manually.</a>
"|format(extended_url),
level='warning'
) }}
{% endif %}
<div class="panel"> <div class="panel">
<div class="panel__heading"> <div class="panel__heading">
@ -19,7 +37,11 @@
<div class="panel__content"> <div class="panel__content">
{% block form_action %} {% block form_action %}
<form method='POST' action="{{ url_for('requests.financial_verification', request_id=request_id) }}" autocomplete="off"> {% if extended %}
<form method='POST' action="{{ url_for('requests.financial_verification', request_id=request_id, extended=True) }}" autocomplete="off">
{% else %}
<form method='POST' action="{{ url_for('requests.financial_verification', request_id=request_id) }}" autocomplete="off">
{% endif %}
{% endblock %} {% endblock %}
{{ f.csrf_token }} {{ f.csrf_token }}
@ -35,8 +57,48 @@
<p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p> <p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p>
{% if extended %}
<fieldset class="form__sub-fields form__sub-fields--warning">
{{ OptionsInput(f.funding_type) }}
<template v-if="funding_type == 'OTHER'" v-cloak>
{{ TextInput(f.funding_type_other) }}
</template>
{{ TextInput(
f.clin_0001,placeholder="50,000",
validation='integer'
) }}
{{ TextInput(
f.clin_0003,placeholder="13,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1003,placeholder="7,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2003,placeholder="7,000",
validation='integer'
) }}
</fieldset>
{% endif %}
{{ TextInput( {{ TextInput(
f.task_order_id, f.task_order_number,
placeholder="e.g.: 1234567899C0001", placeholder="e.g.: 1234567899C0001",
tooltip="A Contracting Officer will likely be the best source for this number.", tooltip="A Contracting Officer will likely be the best source for this number.",
validation="anything" validation="anything"
@ -83,51 +145,6 @@
{{ TextInput(f.office_cor,placeholder="e.g.: WHS") }} {{ TextInput(f.office_cor,placeholder="e.g.: WHS") }}
<hr>
{{ Alert('Task Order not found in EDA',
message="Since the Task Order (TO) number was not found in our system of record, EDA, please populate the additional fields in the form below.",
level='warning'
) }}
<fieldset class="form__sub-fields form__sub-fields--warning">
{{ OptionsInput(f.funding_type) }}
<template v-if="funding_type == 'OTHER'" v-cloak>
{{ TextInput(f.funding_type_other) }}
</template>
{{ TextInput(
f.clin_0001,placeholder="50,000",
validation='integer'
) }}
{{ TextInput(
f.clin_0003,placeholder="13,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_1003,placeholder="7,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2001,placeholder="30,000",
validation='integer'
) }}
{{ TextInput(
f.clin_2003,placeholder="7,000",
validation='integer'
) }}
</fieldset>
{% endautoescape %} {% endautoescape %}
{% endblock form %} {% endblock form %}

View File

@ -15,9 +15,9 @@
<h2 id="financial-verification">Financial Verification</h2> <h2 id="financial-verification">Financial Verification</h2>
<p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p> <p>In order to get you access to the JEDI Cloud, we will need you to enter the details below that will help us verify and account for your Task Order.</p>
{{ f.task_order_id.label }} {{ f.task_order_number.label }}
{{ f.task_order_id(placeholder="Example: 1234567899C0001") }} {{ f.task_order_number(placeholder="Example: 1234567899C0001") }}
{% for e in f.task_order_id.errors %} {% for e in f.task_order_number.errors %}
<div class="usa-input-error-message"> <div class="usa-input-error-message">
{{ e }} {{ e }}
</div> </div>

View File

@ -2,6 +2,7 @@ import pytest
from atst.domain.exceptions import NotFoundError from atst.domain.exceptions import NotFoundError
from atst.domain.task_orders import TaskOrders from atst.domain.task_orders import TaskOrders
from atst.eda_client import MockEDAClient
from tests.factories import TaskOrderFactory from tests.factories import TaskOrderFactory
@ -13,6 +14,19 @@ def test_can_get_task_order():
assert to.id == to.id assert to.id == to.id
def test_nonexistent_task_order_raises(): def test_can_get_task_order_from_eda(monkeypatch):
monkeypatch.setattr("atst.domain.task_orders.TaskOrders._client", lambda: MockEDAClient())
to = TaskOrders.get(MockEDAClient.MOCK_CONTRACT_NUMBER)
assert to.number == MockEDAClient.MOCK_CONTRACT_NUMBER
def test_nonexistent_task_order_raises_without_client():
with pytest.raises(NotFoundError): with pytest.raises(NotFoundError):
TaskOrders.get("some fake number") TaskOrders.get("some fake number")
def test_nonexistent_task_order_raises_with_client(monkeypatch):
monkeypatch.setattr("atst.domain.task_orders.TaskOrders._client", lambda: MockEDAClient())
with pytest.raises(NotFoundError):
TaskOrders.get("some other fake numer")

View File

@ -1,6 +1,7 @@
import pytest import pytest
from atst.forms.financial import suggest_pe_id, FinancialForm from atst.forms.financial import suggest_pe_id, FinancialForm, ExtendedFinancialForm
from atst.eda_client import MockEDAClient
@pytest.mark.parametrize("input_,expected", [ @pytest.mark.parametrize("input_,expected", [
@ -18,7 +19,7 @@ def test_funding_type_other_not_required_if_funding_type_is_not_other():
form_data = { form_data = {
"funding_type": "PROC" "funding_type": "PROC"
} }
form = FinancialForm(data=form_data) form = ExtendedFinancialForm(data=form_data)
form.validate() form.validate()
assert "funding_type_other" not in form.errors assert "funding_type_other" not in form.errors
@ -27,7 +28,7 @@ def test_funding_type_other_required_if_funding_type_is_other():
form_data = { form_data = {
"funding_type": "OTHER" "funding_type": "OTHER"
} }
form = FinancialForm(data=form_data) form = ExtendedFinancialForm(data=form_data)
form.validate() form.validate()
assert "funding_type_other" in form.errors assert "funding_type_other" in form.errors
@ -67,3 +68,16 @@ def test_ba_code_validation(input_, expected):
is_valid = "ba_code" not in form.errors is_valid = "ba_code" not in form.errors
assert is_valid == expected assert is_valid == expected
def test_task_order_number_validation(monkeypatch):
monkeypatch.setattr("atst.domain.task_orders.TaskOrders._client", lambda: MockEDAClient())
form_invalid = FinancialForm(data={"task_order_number": "1234"})
form_invalid.validate()
assert "task_order_number" in form_invalid.errors
form_valid = FinancialForm(data={"task_order_number": MockEDAClient.MOCK_CONTRACT_NUMBER}, eda_client=MockEDAClient())
form_valid.validate()
assert "task_order_number" not in form_valid.errors

View File

@ -1,6 +1,10 @@
import re import re
import pytest import pytest
import urllib import urllib
from flask import url_for
from atst.eda_client import MockEDAClient
from tests.mocks import MOCK_REQUEST, MOCK_USER from tests.mocks import MOCK_REQUEST, MOCK_USER
from tests.factories import PENumberFactory from tests.factories import PENumberFactory
@ -9,7 +13,7 @@ class TestPENumberInForm:
required_data = { required_data = {
"pe_id": "123", "pe_id": "123",
"task_order_id": "1234567899C0001", "task_order_number": MockEDAClient.MOCK_CONTRACT_NUMBER,
"fname_co": "Contracting", "fname_co": "Contracting",
"lname_co": "Officer", "lname_co": "Officer",
"email_co": "jane@mail.mil", "email_co": "jane@mail.mil",
@ -18,6 +22,11 @@ class TestPENumberInForm:
"lname_cor": "Representative", "lname_cor": "Representative",
"email_cor": "jane@mail.mil", "email_cor": "jane@mail.mil",
"office_cor": "WHS", "office_cor": "WHS",
"uii_ids": "1234",
"treasury_code": "00123456",
"ba_code": "024A"
}
extended_data = {
"funding_type": "RDTE", "funding_type": "RDTE",
"funding_type_other": "other", "funding_type_other": "other",
"clin_0001": "50,000", "clin_0001": "50,000",
@ -33,9 +42,13 @@ class TestPENumberInForm:
monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST) monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST)
monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: MOCK_USER) monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: MOCK_USER)
def submit_data(self, client, data): def submit_data(self, client, data, extended=False):
url_kwargs = {"request_id": MOCK_REQUEST.id}
if extended:
url_kwargs["extended"] = True
response = client.post( response = client.post(
"/requests/verify/{}".format(MOCK_REQUEST.id), url_for("requests.financial_verification", **url_kwargs),
headers={"Content-Type": "application/x-www-form-urlencoded"}, headers={"Content-Type": "application/x-www-form-urlencoded"},
data=urllib.parse.urlencode(data), data=urllib.parse.urlencode(data),
follow_redirects=False, follow_redirects=False,
@ -83,3 +96,40 @@ class TestPENumberInForm:
assert "There were some errors" in response.data.decode() assert "There were some errors" in response.data.decode()
assert response.status_code == 200 assert response.status_code == 200
def test_submit_financial_form_with_invalid_task_order(self, monkeypatch, user_session, client):
monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST)
user_session()
data = dict(self.required_data)
data['pe_id'] = MOCK_REQUEST.body['financial_verification']['pe_id']
data['task_order_number'] = '1234'
response = self.submit_data(client, data)
assert "enter TO information manually" in response.data.decode()
def test_submit_financial_form_with_valid_task_order(self, monkeypatch, user_session, client):
monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST)
user_session()
data = dict(self.required_data)
data['pe_id'] = MOCK_REQUEST.body['financial_verification']['pe_id']
data['task_order_number'] = MockEDAClient.MOCK_CONTRACT_NUMBER
response = self.submit_data(client, data)
assert "enter TO information manually" not in response.data.decode()
def test_submit_extended_financial_form(self, monkeypatch, user_session, client):
monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST)
user_session()
data = { **self.required_data, **self.extended_data }
data['pe_id'] = MOCK_REQUEST.body['financial_verification']['pe_id']
data['task_order_number'] = "1234567"
response = self.submit_data(client, data, extended=True)
assert response.status_code == 302
assert "/requests/financial_verification_submitted" in response.headers.get("Location")