diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py index b5d4fb11..86f3c57f 100644 --- a/atst/models/portfolio.py +++ b/atst/models/portfolio.py @@ -74,6 +74,49 @@ class Portfolio( if clin.is_active ] + @property + def active_task_orders(self): + return [task_order for task_order in self.task_orders if task_order.is_active] + + @property + def funding_duration(self): + """ + Return the earliest period of performance start date and latest period + of performance end date for all active task orders in a portfolio. + @return: (datetime.date or None, datetime.date or None) + """ + start_dates = ( + task_order.start_date + for task_order in self.task_orders + if task_order.is_active + ) + + end_dates = ( + task_order.end_date + for task_order in self.task_orders + if task_order.is_active + ) + + earliest_pop_start_date = min(start_dates, default=None) + latest_pop_end_date = max(end_dates, default=None) + + return (earliest_pop_start_date, latest_pop_end_date) + + @property + def days_to_funding_expiration(self): + """ + Returns the number of days between today and the lastest period performance + end date of all active Task Orders + """ + return max( + ( + task_order.days_to_expiration + for task_order in self.task_orders + if task_order.is_active + ), + default=None, + ) + @property def members(self): return ( diff --git a/tests/models/test_portfolio.py b/tests/models/test_portfolio.py index 613b6435..af082876 100644 --- a/tests/models/test_portfolio.py +++ b/tests/models/test_portfolio.py @@ -1,4 +1,12 @@ -from tests.factories import ApplicationFactory, PortfolioFactory +from tests.factories import ( + ApplicationFactory, + PortfolioFactory, + TaskOrderFactory, + CLINFactory, + random_future_date, + random_past_date, +) +import datetime def test_portfolio_applications_excludes_deleted(): @@ -7,3 +15,73 @@ def test_portfolio_applications_excludes_deleted(): ApplicationFactory.create(portfolio=portfolio, deleted=True) assert len(portfolio.applications) == 1 assert portfolio.applications[0].id == app.id + + +def test_funding_duration(session): + # portfolio with active task orders + portfolio = PortfolioFactory() + + funding_start_date = random_past_date() + funding_end_date = random_future_date(year_min=2) + + TaskOrderFactory.create( + signed_at=random_past_date(), + portfolio=portfolio, + create_clins=[ + { + "start_date": funding_start_date, + "end_date": random_future_date(year_max=1), + } + ], + ) + TaskOrderFactory.create( + portfolio=portfolio, + signed_at=random_past_date(), + create_clins=[ + {"start_date": datetime.datetime.now(), "end_date": funding_end_date,} + ], + ) + + assert portfolio.funding_duration == (funding_start_date, funding_end_date) + + # empty portfolio + empty_portfolio = PortfolioFactory() + assert empty_portfolio.funding_duration == (None, None) + + +def test_days_remaining(session): + # portfolio with task orders + funding_end_date = random_future_date(year_min=2) + portfolio = PortfolioFactory() + TaskOrderFactory.create( + portfolio=portfolio, + signed_at=random_past_date(), + create_clins=[{"end_date": funding_end_date}], + ) + + assert ( + portfolio.days_to_funding_expiration + == (funding_end_date - datetime.date.today()).days + ) + + # empty portfolio + empty_portfolio = PortfolioFactory() + assert empty_portfolio.days_to_funding_expiration == 0 + + +def test_active_task_orders(session): + portfolio = PortfolioFactory() + TaskOrderFactory.create( + portfolio=portfolio, + signed_at=random_past_date(), + create_clins=[ + { + "start_date": datetime.date(2019, 1, 1), + "end_date": datetime.date(2019, 10, 31), + } + ], + ) + TaskOrderFactory.create( + portfolio=portfolio, signed_at=random_past_date(), clins=[CLINFactory.create()] + ) + assert len(portfolio.active_task_orders) == 1