diff --git a/alembic/versions/c19d6129cca1_add_jedi_clin_type_to_clin_table.py b/alembic/versions/c19d6129cca1_add_jedi_clin_type_to_clin_table.py new file mode 100644 index 00000000..4b916ea5 --- /dev/null +++ b/alembic/versions/c19d6129cca1_add_jedi_clin_type_to_clin_table.py @@ -0,0 +1,28 @@ +"""add jedi clin type to clin table + +Revision ID: c19d6129cca1 +Revises: 988f8b23fbf6 +Create Date: 2019-06-04 11:30:25.283028 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c19d6129cca1' +down_revision = '988f8b23fbf6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('clins', sa.Column('jedi_clin_type', sa.Enum('JEDI_CLIN_1', 'JEDI_CLIN_2', 'JEDI_CLIN_3', 'JEDI_CLIN_4', name='jediclintype', native_enum=False), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('clins', 'jedi_clin_type') + # ### end Alembic commands ### diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 525c71ed..049b7d85 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -6,7 +6,8 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship from werkzeug.datastructures import FileStorage -from atst.models import Attachment, Base, types, mixins +from atst.models import Attachment, Base, mixins, types +from atst.models.clin import JEDICLINType class Status(Enum): @@ -33,6 +34,8 @@ class TaskOrder(Base, mixins.TimestampsMixin): signer_dod_id = Column(String) signed_at = Column(DateTime) + clins = relationship("CLIN") + @hybrid_property def pdf(self): return self._pdf @@ -83,9 +86,26 @@ class TaskOrder(Base, mixins.TimestampsMixin): return (self.end_date - date.today()).days @property + def total_obligated_funds(self): + total = 0 + for clin in self.clins: + if clin.jedi_clin_type in [ + JEDICLINType.JEDI_CLIN_1, + JEDICLINType.JEDI_CLIN_3, + ]: + total += clin.obligated_amount + return total + + @property + def total_contract_amount(self): + total = 0 + for clin in self.clins: + total += clin.obligated_amount + return total + + @property + # TODO delete when we delete task_order_review flow def budget(self): - # TODO: fix task order -- reimplement using CLINs - # Faked for display purposes return 100000 @property diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 0b887352..bdcaf40e 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -93,7 +93,7 @@ margin-top: $gap * 2; } - .to-sidebar { + .totals-box { padding: $gap * 4; padding-top: $gap * 2; flex-grow: unset; diff --git a/templates/components/to_sidebar.html b/templates/components/totals_box.html similarity index 64% rename from templates/components/to_sidebar.html rename to templates/components/totals_box.html index 1b495b7b..18afeedf 100644 --- a/templates/components/to_sidebar.html +++ b/templates/components/totals_box.html @@ -1,14 +1,14 @@ -{% macro TOSidebar() -%} +{% macro TotalsBox(task_order) -%} -
- {{ TOSidebar() }} + {{ TotalsBox(task_order=task_order) }} diff --git a/tests/factories.py b/tests/factories.py index 341887c0..e91ec26b 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -269,6 +269,18 @@ class TaskOrderFactory(Base): number = factory.LazyFunction(random_task_order_number) +class CLINFactory(Base): + class Meta: + model = CLIN + + task_order = factory.SubFactory(TaskOrderFactory) + number = factory.LazyFunction(random_task_order_number) + start_date = datetime.date.today() + end_date = factory.LazyFunction(random_future_date) + obligated_amount = random.randint(100, 999999) + jedi_clin_type = random.choice([e.value for e in clin.JEDICLINType]) + + class NotificationRecipientFactory(Base): class Meta: model = NotificationRecipient diff --git a/tests/models/test_task_order.py b/tests/models/test_task_order.py index 3a25781a..d6ae9e1c 100644 --- a/tests/models/test_task_order.py +++ b/tests/models/test_task_order.py @@ -1,10 +1,16 @@ from werkzeug.datastructures import FileStorage import pytest, datetime -from atst.models.attachment import Attachment +from atst.models import * +from atst.models.clin import JEDICLINType from atst.models.task_order import TaskOrder, Status -from tests.factories import random_future_date, random_past_date +from tests.factories import ( + CLINFactory, + random_future_date, + random_past_date, + TaskOrderFactory, +) from tests.mocks import PDF_FILENAME @@ -30,6 +36,33 @@ class TestTaskOrderStatus: assert to.status == Status.EXPIRED +class TestBudget: + def test_total_contract_amount(self): + to = TaskOrder() + assert to.total_contract_amount == 0 + + clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1) + clin2 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_2) + clin3 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_3) + + assert ( + to.total_contract_amount + == clin1.obligated_amount + clin2.obligated_amount + clin3.obligated_amount + ) + + def test_total_obligated_funds(self): + to = TaskOrder() + clin4 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_4) + assert to.total_obligated_funds == 0 + + clin1 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_1) + clin2 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_2) + clin3 = CLINFactory(task_order=to, jedi_clin_type=JEDICLINType.JEDI_CLIN_3) + assert ( + to.total_obligated_funds == clin1.obligated_amount + clin3.obligated_amount + ) + + class TestPDF: def test_setting_pdf_with_attachment(self): to = TaskOrder()