diff --git a/atst/models/task_order.py b/atst/models/task_order.py index ed0b394d..d34632cb 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -1,5 +1,5 @@ from enum import Enum -from datetime import date +from datetime import date, datetime from sqlalchemy import Column, DateTime, ForeignKey, String from sqlalchemy.ext.hybrid import hybrid_property @@ -15,6 +15,9 @@ class Status(Enum): PENDING = "Pending" ACTIVE = "Active" EXPIRED = "Expired" + DRAFT = "Draft" + UPCOMING = "Upcoming" + UNSIGNED = "Unsigned" class TaskOrder(Base, mixins.TimestampsMixin): @@ -62,19 +65,36 @@ class TaskOrder(Base, mixins.TimestampsMixin): def is_expired(self): return self.status == Status.EXPIRED + @property + def is_completed(self): + return True + + @property + def is_signed(self): + return self.signed_at is not None + @property def status(self): - # TODO: fix task order -- implement correctly using CLINs - # Faked for display purposes - return Status.ACTIVE + today = date.today() + + if not self.is_completed and not self.is_signed: + return Status.DRAFT + elif self.is_completed and not self.is_signed: + return Status.UNSIGNED + elif today < self.start_date: + return Status.UPCOMING + elif today >= self.end_date: + return Status.EXPIRED + elif self.start_date <= today < self.end_date: + return Status.ACTIVE @property def start_date(self): - return min(c.start_date for c in self.clins) + return min((c.start_date for c in self.clins), default=None) @property def end_date(self): - return max(c.end_date for c in self.clins) + return max((c.end_date for c in self.clins), default=None) @property def days_to_expiration(self): diff --git a/tests/models/test_task_order.py b/tests/models/test_task_order.py index 59d3beba..a9593281 100644 --- a/tests/models/test_task_order.py +++ b/tests/models/test_task_order.py @@ -1,6 +1,8 @@ from werkzeug.datastructures import FileStorage import pytest -from datetime import date +from datetime import date, datetime +from unittest.mock import Mock, patch, PropertyMock +import pendulum from atst.models import * from atst.models.clin import JEDICLINType @@ -48,26 +50,74 @@ class TestPeriodOfPerformance: class TestTaskOrderStatus: - @pytest.mark.skip(reason="Reimplement after adding CLINs") - def test_started_status(self): + @patch("atst.models.TaskOrder.is_completed", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) + def test_draft_status(self, is_signed, is_completed): + # Given that I have a TO that is neither completed nor signed to = TaskOrder() - assert to.status == Status.STARTED + is_signed.return_value = False + is_completed.return_value = False - @pytest.mark.skip(reason="See if still needed after implementing CLINs") - def test_pending_status(self): - to = TaskOrder(number="42") - assert to.status == Status.PENDING + assert to.status == Status.DRAFT - @pytest.mark.skip(reason="See if still needed after implementing CLINs") - def test_active_status(self): - to = TaskOrder(number="42") + @patch("atst.models.TaskOrder.end_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.start_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_completed", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) + def test_active_status(self, is_signed, is_completed, start_date, end_date): + # Given that I have a signed TO and today is within its start_date and end_date + today = pendulum.today().date() + to = TaskOrder() + + start_date.return_value = today.subtract(days=1) + end_date.return_value = today.add(days=1) + is_signed.return_value = True + is_completed.return_value = True + + # Its status should be active assert to.status == Status.ACTIVE - @pytest.mark.skip(reason="See if still needed after implementing CLINs") - def test_expired_status(self): - to = TaskOrder(number="42") + @patch("atst.models.TaskOrder.end_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.start_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_completed", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) + def test_upcoming_status(self, is_signed, is_completed, start_date, end_date): + # Given that I have a signed TO and today is before its start_date + to = TaskOrder() + start_date.return_value = pendulum.today().add(days=1).date() + end_date.return_value = pendulum.today().add(days=2).date() + is_signed.return_value = True + is_completed.return_value = True + + # Its status should be upcoming + assert to.status == Status.UPCOMING + + @patch("atst.models.TaskOrder.start_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.end_date", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_completed", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) + def test_expired_status(self, is_signed, is_completed, end_date, start_date): + # Given that I have a signed TO and today is after its expiration date + to = TaskOrder() + end_date.return_value = pendulum.today().subtract(days=1).date() + start_date.return_value = pendulum.today().subtract(days=2).date() + is_signed.return_value = True + is_completed.return_value = True + + # Its status should be expired assert to.status == Status.EXPIRED + @patch("atst.models.TaskOrder.is_completed", new_callable=PropertyMock) + @patch("atst.models.TaskOrder.is_signed", new_callable=PropertyMock) + def test_unsigned_status(self, is_signed, is_completed): + # Given that I have a TO that is completed but not signed + to = TaskOrder(signed_at=pendulum.now().subtract(days=1)) + is_completed.return_value = True + is_signed.return_value = False + + # Its status should be unsigned + assert to.status == Status.UNSIGNED + class TestBudget: def test_total_contract_amount(self):