Merge pull request #200 from dod-ccpo/manual-to-#158436096
Manually enter task order information #158436096
This commit is contained in:
commit
917ca5b9a4
@ -0,0 +1,30 @@
|
|||||||
|
"""add request -> task order relationship
|
||||||
|
|
||||||
|
Revision ID: 0845b2f0f401
|
||||||
|
Revises: 875e4b8a05fc
|
||||||
|
Create Date: 2018-08-22 09:58:43.770718
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0845b2f0f401'
|
||||||
|
down_revision = '875e4b8a05fc'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('requests', sa.Column('task_order_id', sa.Integer(), nullable=True))
|
||||||
|
op.create_foreign_key(None, 'requests', 'task_order', ['task_order_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'requests', type_='foreignkey')
|
||||||
|
op.drop_column('requests', 'task_order_id')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,44 @@
|
|||||||
|
"""add additional task order fields
|
||||||
|
|
||||||
|
Revision ID: 875e4b8a05fc
|
||||||
|
Revises: f36f130622b9
|
||||||
|
Create Date: 2018-08-21 15:52:46.636928
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '875e4b8a05fc'
|
||||||
|
down_revision = 'f36f130622b9'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('task_order', sa.Column('clin_0001', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('clin_0003', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('clin_1001', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('clin_1003', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('clin_2001', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('clin_2003', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('funding_type', sa.String(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('funding_type_other', sa.String(), nullable=True))
|
||||||
|
op.add_column('task_order', sa.Column('source', sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('task_order', 'source')
|
||||||
|
op.drop_column('task_order', 'funding_type_other')
|
||||||
|
op.drop_column('task_order', 'funding_type')
|
||||||
|
op.drop_column('task_order', 'clin_2003')
|
||||||
|
op.drop_column('task_order', 'clin_2001')
|
||||||
|
op.drop_column('task_order', 'clin_1003')
|
||||||
|
op.drop_column('task_order', 'clin_1001')
|
||||||
|
op.drop_column('task_order', 'clin_0003')
|
||||||
|
op.drop_column('task_order', 'clin_0001')
|
||||||
|
# ### end Alembic commands ###
|
@ -8,6 +8,7 @@ from atst.models.request import Request
|
|||||||
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
||||||
from atst.domain.workspaces import Workspaces
|
from atst.domain.workspaces import Workspaces
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
|
from atst.domain.task_orders import TaskOrders
|
||||||
|
|
||||||
from .exceptions import NotFoundError
|
from .exceptions import NotFoundError
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ class Requests(object):
|
|||||||
and_(Request.id == request_id, Request.creator == creator)
|
and_(Request.id == request_id, Request.creator == creator)
|
||||||
)
|
)
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
except exc.DataError:
|
except exc.DataError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -71,10 +73,9 @@ class Requests(object):
|
|||||||
filters.append(Request.creator == creator)
|
filters.append(Request.creator == creator)
|
||||||
|
|
||||||
requests = (
|
requests = (
|
||||||
db.session.query(Request)
|
db.session.query(Request).filter(*filters).order_by(
|
||||||
.filter(*filters)
|
Request.time_created.desc()
|
||||||
.order_by(Request.time_created.desc())
|
).all()
|
||||||
.all()
|
|
||||||
)
|
)
|
||||||
return requests
|
return requests
|
||||||
|
|
||||||
@ -95,27 +96,39 @@ class Requests(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, request_id, request_delta):
|
def update(cls, request_id, request_delta):
|
||||||
|
request = Requests._get_with_lock(request_id)
|
||||||
|
if not request:
|
||||||
|
return
|
||||||
|
|
||||||
|
request = Requests._merge_body(request, request_delta)
|
||||||
|
|
||||||
|
db.session.add(request)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_with_lock(cls, request_id):
|
||||||
try:
|
try:
|
||||||
# Query for request matching id, acquiring a row-level write lock.
|
# Query for request matching id, acquiring a row-level write lock.
|
||||||
# https://www.postgresql.org/docs/10/static/sql-select.html#SQL-FOR-UPDATE-SHARE
|
# https://www.postgresql.org/docs/10/static/sql-select.html#SQL-FOR-UPDATE-SHARE
|
||||||
request = (
|
return (
|
||||||
db.session.query(Request)
|
db.session.query(Request).filter_by(id=request_id).with_for_update(
|
||||||
.filter_by(id=request_id)
|
of=Request
|
||||||
.with_for_update(of=Request)
|
).one()
|
||||||
.one()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _merge_body(cls, request, request_delta):
|
||||||
request.body = deep_merge(request_delta, request.body)
|
request.body = deep_merge(request_delta, request.body)
|
||||||
|
|
||||||
# Without this, sqlalchemy won't notice the change to request.body,
|
# Without this, sqlalchemy won't notice the change to request.body,
|
||||||
# since it doesn't track dictionary mutations by default.
|
# since it doesn't track dictionary mutations by default.
|
||||||
flag_modified(request, "body")
|
flag_modified(request, "body")
|
||||||
|
|
||||||
db.session.add(request)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -139,8 +152,10 @@ class Requests(object):
|
|||||||
return {
|
return {
|
||||||
RequestStatus.STARTED: "mission_owner",
|
RequestStatus.STARTED: "mission_owner",
|
||||||
RequestStatus.PENDING_FINANCIAL_VERIFICATION: "mission_owner",
|
RequestStatus.PENDING_FINANCIAL_VERIFICATION: "mission_owner",
|
||||||
RequestStatus.PENDING_CCPO_APPROVAL: "ccpo"
|
RequestStatus.PENDING_CCPO_APPROVAL: "ccpo",
|
||||||
}.get(request.status)
|
}.get(
|
||||||
|
request.status
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def should_auto_approve(cls, request):
|
def should_auto_approve(cls, request):
|
||||||
@ -152,16 +167,13 @@ class Requests(object):
|
|||||||
return dollar_value < cls.AUTO_APPROVE_THRESHOLD
|
return dollar_value < cls.AUTO_APPROVE_THRESHOLD
|
||||||
|
|
||||||
_VALID_SUBMISSION_STATUSES = [
|
_VALID_SUBMISSION_STATUSES = [
|
||||||
RequestStatus.STARTED,
|
RequestStatus.STARTED, RequestStatus.CHANGES_REQUESTED
|
||||||
RequestStatus.CHANGES_REQUESTED,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def should_allow_submission(cls, request):
|
def should_allow_submission(cls, request):
|
||||||
all_request_sections = [
|
all_request_sections = [
|
||||||
"details_of_use",
|
"details_of_use", "information_about_you", "primary_poc"
|
||||||
"information_about_you",
|
|
||||||
"primary_poc",
|
|
||||||
]
|
]
|
||||||
existing_request_sections = request.body.keys()
|
existing_request_sections = request.body.keys()
|
||||||
return request.status in Requests._VALID_SUBMISSION_STATUSES and all(
|
return request.status in Requests._VALID_SUBMISSION_STATUSES and all(
|
||||||
@ -201,11 +213,13 @@ WHERE requests_with_status.status = :status
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def in_progress_count(cls):
|
def in_progress_count(cls):
|
||||||
return sum([
|
return sum(
|
||||||
|
[
|
||||||
Requests.status_count(RequestStatus.STARTED),
|
Requests.status_count(RequestStatus.STARTED),
|
||||||
Requests.status_count(RequestStatus.PENDING_FINANCIAL_VERIFICATION),
|
Requests.status_count(RequestStatus.PENDING_FINANCIAL_VERIFICATION),
|
||||||
Requests.status_count(RequestStatus.CHANGES_REQUESTED),
|
Requests.status_count(RequestStatus.CHANGES_REQUESTED),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pending_ccpo_count(cls):
|
def pending_ccpo_count(cls):
|
||||||
@ -215,3 +229,43 @@ WHERE requests_with_status.status = :status
|
|||||||
def completed_count(cls):
|
def completed_count(cls):
|
||||||
return Requests.status_count(RequestStatus.APPROVED)
|
return Requests.status_count(RequestStatus.APPROVED)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_financial_verification(cls, request_id, financial_data):
|
||||||
|
request = Requests._get_with_lock(request_id)
|
||||||
|
if not request:
|
||||||
|
return
|
||||||
|
|
||||||
|
request_data = financial_data.copy()
|
||||||
|
task_order_data = {
|
||||||
|
k: request_data.pop(k)
|
||||||
|
for (k, v) in financial_data.items()
|
||||||
|
if k in TaskOrders.TASK_ORDER_DATA
|
||||||
|
}
|
||||||
|
task_order_number = request_data.pop("task_order_number")
|
||||||
|
|
||||||
|
task_order = TaskOrders.get_or_create_task_order(
|
||||||
|
task_order_number, task_order_data
|
||||||
|
)
|
||||||
|
|
||||||
|
if task_order:
|
||||||
|
request.task_order = task_order
|
||||||
|
|
||||||
|
request = Requests._merge_body(
|
||||||
|
request, {"financial_verification": request_data}
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(request)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def submit_financial_verification(cls, request_id):
|
||||||
|
request = Requests._get_with_lock(request_id)
|
||||||
|
if not request:
|
||||||
|
return
|
||||||
|
|
||||||
|
Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
|
||||||
|
|
||||||
|
db.session.add(request)
|
||||||
|
db.session.commit()
|
||||||
|
@ -2,11 +2,12 @@ from sqlalchemy.orm.exc import NoResultFound
|
|||||||
from flask import current_app as app
|
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, Source
|
||||||
from .exceptions import NotFoundError
|
from .exceptions import NotFoundError
|
||||||
|
|
||||||
|
|
||||||
class TaskOrders(object):
|
class TaskOrders(object):
|
||||||
|
TASK_ORDER_DATA = [col.name for col in TaskOrder.__table__.c if col.name != "id"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, order_number):
|
def get(cls, order_number):
|
||||||
@ -26,13 +27,15 @@ class TaskOrders(object):
|
|||||||
def _get_from_eda(cls, order_number):
|
def _get_from_eda(cls, order_number):
|
||||||
to_data = TaskOrders._client().get_contract(order_number, status="y")
|
to_data = TaskOrders._client().get_contract(order_number, status="y")
|
||||||
if to_data:
|
if to_data:
|
||||||
return TaskOrders.create(to_data["contract_no"])
|
# TODO: we need to determine exactly what we're getting and storing from the EDA client
|
||||||
|
return TaskOrders.create(number=to_data["contract_no"], source=Source.EDA)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise NotFoundError("task_order")
|
raise NotFoundError("task_order")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, order_number):
|
def create(cls, **kwargs):
|
||||||
task_order = TaskOrder(number=order_number)
|
task_order = TaskOrder(**kwargs)
|
||||||
|
|
||||||
db.session.add(task_order)
|
db.session.add(task_order)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -42,3 +45,14 @@ class TaskOrders(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _client(cls):
|
def _client(cls):
|
||||||
return app.eda_client
|
return app.eda_client
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_or_create_task_order(cls, number, task_order_data=None):
|
||||||
|
try:
|
||||||
|
return TaskOrders.get(number)
|
||||||
|
|
||||||
|
except NotFoundError:
|
||||||
|
if task_order_data:
|
||||||
|
return TaskOrders.create(
|
||||||
|
**task_order_data, number=number, source=Source.MANUAL
|
||||||
|
)
|
||||||
|
@ -73,6 +73,9 @@ class MockEDAClient(EDAClientBase):
|
|||||||
|
|
||||||
MOCK_CONTRACT_NUMBER = "DCA10096D0052"
|
MOCK_CONTRACT_NUMBER = "DCA10096D0052"
|
||||||
|
|
||||||
|
# TODO: It seems likely that this will have to supply CLIN data form the
|
||||||
|
# EDA returnclinXML API call, in addition to the basic task order data
|
||||||
|
# below. See the EDA docs.
|
||||||
def get_contract(self, contract_number, status):
|
def get_contract(self, contract_number, status):
|
||||||
if contract_number == self.MOCK_CONTRACT_NUMBER and status == "y":
|
if contract_number == self.MOCK_CONTRACT_NUMBER and status == "y":
|
||||||
return {
|
return {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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, ValidationError
|
from wtforms.validators import Required, Email, Regexp
|
||||||
|
|
||||||
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
|
||||||
@ -58,6 +58,21 @@ def validate_pe_id(field, existing_request):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_task_order_number(field):
|
||||||
|
try:
|
||||||
|
TaskOrders.get(field.data)
|
||||||
|
except NotFoundError:
|
||||||
|
field.errors += ("Task Order number not found",)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def number_to_int(num):
|
||||||
|
if num:
|
||||||
|
return int(num)
|
||||||
|
|
||||||
|
|
||||||
class BaseFinancialForm(ValidatedForm):
|
class BaseFinancialForm(ValidatedForm):
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
@ -119,11 +134,10 @@ class BaseFinancialForm(ValidatedForm):
|
|||||||
|
|
||||||
|
|
||||||
class FinancialForm(BaseFinancialForm):
|
class FinancialForm(BaseFinancialForm):
|
||||||
def validate_task_order_number(form, field):
|
def perform_extra_validation(self, existing_request):
|
||||||
try:
|
previous_valid = super().perform_extra_validation(existing_request)
|
||||||
TaskOrders.get(field.data)
|
task_order_valid = validate_task_order_number(self.task_order_number)
|
||||||
except NotFoundError:
|
return previous_valid and task_order_valid
|
||||||
raise ValidationError("Task Order number not found")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_missing_task_order_number(self):
|
def is_missing_task_order_number(self):
|
||||||
@ -154,35 +168,41 @@ class ExtendedFinancialForm(BaseFinancialForm):
|
|||||||
clin_0001 = StringField(
|
clin_0001 = StringField(
|
||||||
"<dl><dt>CLIN 0001</dt> - <dd>Unclassified IaaS and PaaS Amount</dd></dl>",
|
"<dl><dt>CLIN 0001</dt> - <dd>Unclassified IaaS and PaaS Amount</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
|
||||||
clin_0003 = StringField(
|
clin_0003 = StringField(
|
||||||
"<dl><dt>CLIN 0003</dt> - <dd>Unclassified Cloud Support Package</dd></dl>",
|
"<dl><dt>CLIN 0003</dt> - <dd>Unclassified Cloud Support Package</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
|
||||||
clin_1001 = StringField(
|
clin_1001 = StringField(
|
||||||
"<dl><dt>CLIN 1001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 1</dd></dl>",
|
"<dl><dt>CLIN 1001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 1</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
|
||||||
clin_1003 = StringField(
|
clin_1003 = StringField(
|
||||||
"<dl><dt>CLIN 1003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 1</dd></dl>",
|
"<dl><dt>CLIN 1003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 1</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
|
||||||
clin_2001 = StringField(
|
clin_2001 = StringField(
|
||||||
"<dl><dt>CLIN 2001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 2</dd></dl>",
|
"<dl><dt>CLIN 2001</dt> - <dd>Unclassified IaaS and PaaS Amount <br> OPTION PERIOD 2</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
|
||||||
clin_2003 = StringField(
|
clin_2003 = StringField(
|
||||||
"<dl><dt>CLIN 2003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 2</dd></dl>",
|
"<dl><dt>CLIN 2003</dt> - <dd>Unclassified Cloud Support Package <br> OPTION PERIOD 2</dd></dl>",
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
description="Review your task order document, the amounts for each CLIN must match exactly here"
|
description="Review your task order document, the amounts for each CLIN must match exactly here",
|
||||||
|
filters=[number_to_int]
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,9 @@ class Request(Base):
|
|||||||
user_id = Column(ForeignKey("users.id"), nullable=False)
|
user_id = Column(ForeignKey("users.id"), nullable=False)
|
||||||
creator = relationship("User")
|
creator = relationship("User")
|
||||||
|
|
||||||
|
task_order_id = Column(ForeignKey("task_order.id"))
|
||||||
|
task_order = relationship("TaskOrder")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self):
|
||||||
return self.status_events[-1].new_status
|
return self.status_events[-1].new_status
|
||||||
|
@ -1,10 +1,32 @@
|
|||||||
from sqlalchemy import Column, Integer, String
|
from enum import Enum
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Enum as SQLAEnum
|
||||||
|
|
||||||
from atst.models import Base
|
from atst.models import Base
|
||||||
|
|
||||||
|
class Source(Enum):
|
||||||
|
MANUAL = "Manual"
|
||||||
|
EDA = "eda"
|
||||||
|
|
||||||
|
|
||||||
|
class FundingType(Enum):
|
||||||
|
RDTE = "RDTE"
|
||||||
|
OM = "OM"
|
||||||
|
PROC = "PROC"
|
||||||
|
OTHER = "OTHER"
|
||||||
|
|
||||||
|
|
||||||
class TaskOrder(Base):
|
class TaskOrder(Base):
|
||||||
__tablename__ = "task_order"
|
__tablename__ = "task_order"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
number = Column(String)
|
number = Column(String, unique=True)
|
||||||
|
source = Column(SQLAEnum(Source))
|
||||||
|
funding_type = Column(SQLAEnum(FundingType))
|
||||||
|
funding_type_other = Column(String)
|
||||||
|
clin_0001 = Column(Integer)
|
||||||
|
clin_0003 = Column(Integer)
|
||||||
|
clin_1001 = Column(Integer)
|
||||||
|
clin_1003 = Column(Integer)
|
||||||
|
clin_2001 = Column(Integer)
|
||||||
|
clin_2003 = Column(Integer)
|
||||||
|
@ -36,14 +36,15 @@ def update_financial_verification(request_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
request_data = {"financial_verification": form.data}
|
|
||||||
valid = form.perform_extra_validation(
|
valid = form.perform_extra_validation(
|
||||||
existing_request.body.get("financial_verification")
|
existing_request.body.get("financial_verification")
|
||||||
)
|
)
|
||||||
updated_request = Requests.update(request_id, request_data)
|
updated_request = Requests.update_financial_verification(request_id, form.data)
|
||||||
if valid:
|
if valid:
|
||||||
|
Requests.submit_financial_verification(request_id)
|
||||||
new_workspace = Requests.approve_and_create_workspace(updated_request)
|
new_workspace = Requests.approve_and_create_workspace(updated_request)
|
||||||
return redirect(url_for("workspaces.workspace_projects", workspace_id=new_workspace.id, newWorkspace=True))
|
return redirect(url_for("workspaces.workspace_projects", workspace_id=new_workspace.id, newWorkspace=True))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form.reset()
|
form.reset()
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -5,8 +5,9 @@ from atst.domain.exceptions import NotFoundError
|
|||||||
from atst.domain.requests import Requests
|
from atst.domain.requests import Requests
|
||||||
from atst.models.request import Request
|
from atst.models.request import Request
|
||||||
from atst.models.request_status_event import RequestStatus
|
from atst.models.request_status_event import RequestStatus
|
||||||
|
from atst.models.task_order import Source as TaskOrderSource
|
||||||
|
|
||||||
from tests.factories import RequestFactory, UserFactory, RequestStatusEventFactory
|
from tests.factories import RequestFactory, UserFactory, RequestStatusEventFactory, TaskOrderFactory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
@ -91,3 +92,54 @@ def test_status_count_scoped_to_creator(session):
|
|||||||
|
|
||||||
assert Requests.status_count(RequestStatus.STARTED) == 2
|
assert Requests.status_count(RequestStatus.STARTED) == 2
|
||||||
assert Requests.status_count(RequestStatus.STARTED, creator=user) == 1
|
assert Requests.status_count(RequestStatus.STARTED, creator=user) == 1
|
||||||
|
|
||||||
|
|
||||||
|
request_financial_data = {
|
||||||
|
"pe_id": "123",
|
||||||
|
"task_order_number": "021345",
|
||||||
|
"fname_co": "Contracting",
|
||||||
|
"lname_co": "Officer",
|
||||||
|
"email_co": "jane@mail.mil",
|
||||||
|
"office_co": "WHS",
|
||||||
|
"fname_cor": "Officer",
|
||||||
|
"lname_cor": "Representative",
|
||||||
|
"email_cor": "jane@mail.mil",
|
||||||
|
"office_cor": "WHS",
|
||||||
|
"uii_ids": "1234",
|
||||||
|
"treasury_code": "00123456",
|
||||||
|
"ba_code": "024A",
|
||||||
|
}
|
||||||
|
task_order_financial_data = {
|
||||||
|
"funding_type": "RDTE",
|
||||||
|
"funding_type_other": "other",
|
||||||
|
"clin_0001": 50000,
|
||||||
|
"clin_0003": 13000,
|
||||||
|
"clin_1001": 30000,
|
||||||
|
"clin_1003": 7000,
|
||||||
|
"clin_2001": 30000,
|
||||||
|
"clin_2003": 7000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_financial_verification_without_task_order():
|
||||||
|
request = RequestFactory.create()
|
||||||
|
financial_data = { **request_financial_data, **task_order_financial_data }
|
||||||
|
Requests.update_financial_verification(request.id, financial_data)
|
||||||
|
assert request.task_order
|
||||||
|
assert request.task_order.clin_0001 == task_order_financial_data["clin_0001"]
|
||||||
|
assert request.task_order.source == TaskOrderSource.MANUAL
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_financial_verification_with_task_order():
|
||||||
|
task_order = TaskOrderFactory.create(source=TaskOrderSource.EDA)
|
||||||
|
financial_data = { **request_financial_data, "task_order_number": task_order.number }
|
||||||
|
request = RequestFactory.create()
|
||||||
|
Requests.update_financial_verification(request.id, financial_data)
|
||||||
|
assert request.task_order == task_order
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_financial_verification_with_invalid_task_order():
|
||||||
|
request = RequestFactory.create()
|
||||||
|
Requests.update_financial_verification(request.id, request_financial_data)
|
||||||
|
assert not request.task_order
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from atst.models.task_order import Source as TaskOrderSource
|
||||||
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 atst.eda_client import MockEDAClient
|
||||||
@ -19,6 +20,7 @@ def test_can_get_task_order_from_eda(monkeypatch):
|
|||||||
to = TaskOrders.get(MockEDAClient.MOCK_CONTRACT_NUMBER)
|
to = TaskOrders.get(MockEDAClient.MOCK_CONTRACT_NUMBER)
|
||||||
|
|
||||||
assert to.number == MockEDAClient.MOCK_CONTRACT_NUMBER
|
assert to.number == MockEDAClient.MOCK_CONTRACT_NUMBER
|
||||||
|
assert to.source == TaskOrderSource.EDA
|
||||||
|
|
||||||
|
|
||||||
def test_nonexistent_task_order_raises_without_client():
|
def test_nonexistent_task_order_raises_without_client():
|
||||||
|
@ -71,13 +71,14 @@ def test_ba_code_validation(input_, expected):
|
|||||||
|
|
||||||
def test_task_order_number_validation(monkeypatch):
|
def test_task_order_number_validation(monkeypatch):
|
||||||
monkeypatch.setattr("atst.domain.task_orders.TaskOrders._client", lambda: MockEDAClient())
|
monkeypatch.setattr("atst.domain.task_orders.TaskOrders._client", lambda: MockEDAClient())
|
||||||
|
monkeypatch.setattr("atst.forms.financial.validate_pe_id", lambda *args: True)
|
||||||
form_invalid = FinancialForm(data={"task_order_number": "1234"})
|
form_invalid = FinancialForm(data={"task_order_number": "1234"})
|
||||||
form_invalid.validate()
|
form_invalid.perform_extra_validation({})
|
||||||
|
|
||||||
assert "task_order_number" in form_invalid.errors
|
assert "task_order_number" in form_invalid.errors
|
||||||
|
|
||||||
form_valid = FinancialForm(data={"task_order_number": MockEDAClient.MOCK_CONTRACT_NUMBER}, eda_client=MockEDAClient())
|
form_valid = FinancialForm(data={"task_order_number": MockEDAClient.MOCK_CONTRACT_NUMBER}, eda_client=MockEDAClient())
|
||||||
form_valid.validate()
|
form_valid.perform_extra_validation({})
|
||||||
|
|
||||||
assert "task_order_number" not in form_valid.errors
|
assert "task_order_number" not in form_valid.errors
|
||||||
|
|
||||||
|
@ -27,12 +27,12 @@ class TestPENumberInForm:
|
|||||||
extended_data = {
|
extended_data = {
|
||||||
"funding_type": "RDTE",
|
"funding_type": "RDTE",
|
||||||
"funding_type_other": "other",
|
"funding_type_other": "other",
|
||||||
"clin_0001": "50,000",
|
"clin_0001": "50000",
|
||||||
"clin_0003": "13,000",
|
"clin_0003": "13000",
|
||||||
"clin_1001": "30,000",
|
"clin_1001": "30000",
|
||||||
"clin_1003": "7,000",
|
"clin_1003": "7000",
|
||||||
"clin_2001": "30,000",
|
"clin_2001": "30000",
|
||||||
"clin_2003": "7,000",
|
"clin_2003": "7000",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _set_monkeypatches(self, monkeypatch):
|
def _set_monkeypatches(self, monkeypatch):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user