diff --git a/alembic/versions/0845b2f0f401_add_request_task_order_relationship.py b/alembic/versions/0845b2f0f401_add_request_task_order_relationship.py new file mode 100644 index 00000000..4b00c41f --- /dev/null +++ b/alembic/versions/0845b2f0f401_add_request_task_order_relationship.py @@ -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 ### diff --git a/atst/domain/requests.py b/atst/domain/requests.py index 4f956b6c..45de8c53 100644 --- a/atst/domain/requests.py +++ b/atst/domain/requests.py @@ -5,9 +5,11 @@ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.attributes import flag_modified from atst.models.request import Request +from atst.models.task_order import TaskOrder, Source as TaskOrderSource from atst.models.request_status_event import RequestStatusEvent, RequestStatus from atst.domain.workspaces import Workspaces from atst.database import db +from atst.domain.task_orders import TaskOrders from .exceptions import NotFoundError @@ -107,14 +109,20 @@ class Requests(object): except NoResultFound: return + request = Requests._merge_body(request, request_delta) + + db.session.add(request) + db.session.commit() + + @classmethod + def _merge_body(cls, request, request_delta): request.body = deep_merge(request_delta, request.body) # Without this, sqlalchemy won't notice the change to request.body, # since it doesn't track dictionary mutations by default. flag_modified(request, "body") - db.session.add(request) - db.session.commit() + return request return request @@ -215,3 +223,25 @@ WHERE requests_with_status.status = :status def completed_count(cls): return Requests.status_count(RequestStatus.APPROVED) + _TASK_ORDER_DATA = [col.name for col in TaskOrder.__table__.c if col.name != "id"] + + @classmethod + def update_financial_verification(cls, request_id, financial_data): + request = Requests.get(request_id) + + request_data = financial_data.copy() + task_order_data = {k: request_data.pop(k) for (k,v) in financial_data.items() if k in Requests._TASK_ORDER_DATA} + + task_order = None + if task_order_data: + task_order_data["number"] = request_data.pop("task_order_number") + task_order = TaskOrders.create(**task_order_data, source=TaskOrderSource.MANUAL) + else: + task_order = TaskOrders.get(financial_data["task_order_number"]) + + request.task_order = task_order + Requests._merge_body(request, {"financial_verification": request_data}) + + db.session.add(task_order) + db.session.add(request) + db.session.commit() diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index fbc587a1..3a07195d 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -2,7 +2,7 @@ from sqlalchemy.orm.exc import NoResultFound from flask import current_app as app from atst.database import db -from atst.models.task_order import TaskOrder +from atst.models.task_order import TaskOrder, Source from .exceptions import NotFoundError @@ -26,13 +26,14 @@ class TaskOrders(object): 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"]) + # 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: raise NotFoundError("task_order") @classmethod - def create(cls, order_number): - task_order = TaskOrder(number=order_number) + def create(cls, **kwargs): + task_order = TaskOrder(**kwargs) db.session.add(task_order) db.session.commit() diff --git a/atst/models/request.py b/atst/models/request.py index 44474ca3..3d79a706 100644 --- a/atst/models/request.py +++ b/atst/models/request.py @@ -20,6 +20,9 @@ class Request(Base): user_id = Column(ForeignKey("users.id"), nullable=False) creator = relationship("User") + task_order_id = Column(ForeignKey("task_order.id")) + task_order = relationship("TaskOrder") + @property def status(self): return self.status_events[-1].new_status diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 7494ac35..5c34935e 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -20,7 +20,7 @@ class TaskOrder(Base): __tablename__ = "task_order" 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) diff --git a/tests/domain/test_requests.py b/tests/domain/test_requests.py index 575697ba..a2992ae8 100644 --- a/tests/domain/test_requests.py +++ b/tests/domain/test_requests.py @@ -5,8 +5,9 @@ from atst.domain.exceptions import NotFoundError from atst.domain.requests import Requests from atst.models.request import Request 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") @@ -91,3 +92,48 @@ def test_status_count_scoped_to_creator(session): assert Requests.status_count(RequestStatus.STARTED) == 2 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, +} + + +# without a matching task order, should create one with status "MANUAL"; +# with a matching task order, should associate to an existing one +def test_update_financial_verification(): + request1 = RequestFactory.create() + financial_data = { **request_financial_data, **task_order_financial_data } + Requests.update_financial_verification(request1.id, financial_data) + assert request1.task_order + assert request1.task_order.clin_0001 == task_order_financial_data["clin_0001"] + assert request1.task_order.source == TaskOrderSource.MANUAL + + task_order = TaskOrderFactory.create(source=TaskOrderSource.EDA) + new_financial_verification_data = { **request_financial_data, "task_order_number": task_order.number } + request2 = RequestFactory.create() + Requests.update_financial_verification(request2.id, new_financial_verification_data) + assert request2.task_order == task_order + diff --git a/tests/domain/test_task_orders.py b/tests/domain/test_task_orders.py index 27b5d2de..9f37e30a 100644 --- a/tests/domain/test_task_orders.py +++ b/tests/domain/test_task_orders.py @@ -1,5 +1,6 @@ import pytest +from atst.models.task_order import Source as TaskOrderSource from atst.domain.exceptions import NotFoundError from atst.domain.task_orders import TaskOrders 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) assert to.number == MockEDAClient.MOCK_CONTRACT_NUMBER + assert to.source == TaskOrderSource.EDA def test_nonexistent_task_order_raises_without_client():