Merge pull request #1416 from dod-ccpo/reporting-integration
Bugfix: Accurately report Total Portfolio Value
This commit is contained in:
commit
bad916dfb0
@ -100,6 +100,16 @@ class Portfolio(
|
|||||||
(task_order.total_obligated_funds for task_order in self.active_task_orders)
|
(task_order.total_obligated_funds for task_order in self.active_task_orders)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def upcoming_obligated_funds(self):
|
||||||
|
return sum(
|
||||||
|
(
|
||||||
|
task_order.total_obligated_funds
|
||||||
|
for task_order in self.task_orders
|
||||||
|
if task_order.is_upcoming
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def funding_duration(self):
|
def funding_duration(self):
|
||||||
"""
|
"""
|
||||||
|
@ -87,6 +87,10 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
|||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
return self.status == Status.EXPIRED
|
return self.status == Status.EXPIRED
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_upcoming(self):
|
||||||
|
return self.status == Status.UPCOMING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clins_are_completed(self):
|
def clins_are_completed(self):
|
||||||
return all([len(self.clins), (clin.is_completed for clin in self.clins)])
|
return all([len(self.clins), (clin.is_completed for clin in self.clins)])
|
||||||
|
@ -50,8 +50,10 @@ def reports(portfolio_id):
|
|||||||
return render_template(
|
return render_template(
|
||||||
"portfolios/reports/index.html",
|
"portfolios/reports/index.html",
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
# wrapped in str() because this sum returns a Decimal object
|
||||||
total_portfolio_value=str(portfolio.total_obligated_funds),
|
total_portfolio_value=str(
|
||||||
|
portfolio.total_obligated_funds + portfolio.upcoming_obligated_funds
|
||||||
|
),
|
||||||
current_obligated_funds=current_obligated_funds,
|
current_obligated_funds=current_obligated_funds,
|
||||||
expired_task_orders=Reports.expired_task_orders(portfolio),
|
expired_task_orders=Reports.expired_task_orders(portfolio),
|
||||||
retrieved=datetime.now(), # mocked datetime of reporting data retrival
|
retrieved=datetime.now(), # mocked datetime of reporting data retrival
|
||||||
|
@ -195,7 +195,7 @@ def add_task_orders_to_portfolio(portfolio):
|
|||||||
task_order=unsigned_to, start_date=(today - five_days), end_date=today
|
task_order=unsigned_to, start_date=(today - five_days), end_date=today
|
||||||
),
|
),
|
||||||
CLINFactory.build(
|
CLINFactory.build(
|
||||||
task_order=upcoming_to, start_date=future, end_date=(today + five_days)
|
task_order=upcoming_to, start_date=(today + five_days), end_date=future
|
||||||
),
|
),
|
||||||
CLINFactory.build(
|
CLINFactory.build(
|
||||||
task_order=expired_to, start_date=(today - five_days), end_date=yesterday
|
task_order=expired_to, start_date=(today - five_days), end_date=yesterday
|
||||||
|
@ -7,6 +7,51 @@ from tests.factories import (
|
|||||||
random_past_date,
|
random_past_date,
|
||||||
)
|
)
|
||||||
import datetime
|
import datetime
|
||||||
|
import pendulum
|
||||||
|
from decimal import Decimal
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def upcoming_task_order():
|
||||||
|
return dict(
|
||||||
|
signed_at=pendulum.today().subtract(days=3),
|
||||||
|
create_clins=[
|
||||||
|
dict(
|
||||||
|
start_date=pendulum.today().add(days=2),
|
||||||
|
end_date=pendulum.today().add(days=3),
|
||||||
|
obligated_amount=Decimal(700.0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def current_task_order():
|
||||||
|
return dict(
|
||||||
|
signed_at=pendulum.today().subtract(days=3),
|
||||||
|
create_clins=[
|
||||||
|
dict(
|
||||||
|
start_date=pendulum.today().subtract(days=1),
|
||||||
|
end_date=pendulum.today().add(days=1),
|
||||||
|
obligated_amount=Decimal(1000.0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def past_task_order():
|
||||||
|
return dict(
|
||||||
|
signed_at=pendulum.today().subtract(days=3),
|
||||||
|
create_clins=[
|
||||||
|
dict(
|
||||||
|
start_date=pendulum.today().subtract(days=3),
|
||||||
|
end_date=pendulum.today().subtract(days=2),
|
||||||
|
obligated_amount=Decimal(500.0),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_portfolio_applications_excludes_deleted():
|
def test_portfolio_applications_excludes_deleted():
|
||||||
@ -85,3 +130,53 @@ def test_active_task_orders(session):
|
|||||||
portfolio=portfolio, signed_at=random_past_date(), clins=[CLINFactory.create()]
|
portfolio=portfolio, signed_at=random_past_date(), clins=[CLINFactory.create()]
|
||||||
)
|
)
|
||||||
assert len(portfolio.active_task_orders) == 1
|
assert len(portfolio.active_task_orders) == 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestCurrentObligatedFunds:
|
||||||
|
"""
|
||||||
|
Tests the current_obligated_funds property
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_no_task_orders(self):
|
||||||
|
portfolio = PortfolioFactory()
|
||||||
|
assert portfolio.total_obligated_funds == Decimal(0)
|
||||||
|
|
||||||
|
def test_with_current(self, current_task_order):
|
||||||
|
portfolio = PortfolioFactory(
|
||||||
|
task_orders=[current_task_order, current_task_order]
|
||||||
|
)
|
||||||
|
assert portfolio.total_obligated_funds == Decimal(2000.0)
|
||||||
|
|
||||||
|
def test_with_others(
|
||||||
|
self, past_task_order, current_task_order, upcoming_task_order
|
||||||
|
):
|
||||||
|
portfolio = PortfolioFactory(
|
||||||
|
task_orders=[past_task_order, current_task_order, upcoming_task_order,]
|
||||||
|
)
|
||||||
|
# Only sums the current task order
|
||||||
|
assert portfolio.total_obligated_funds == Decimal(1000.0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpcomingObligatedFunds:
|
||||||
|
"""
|
||||||
|
Tests the upcoming_obligated_funds property
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_no_task_orders(self):
|
||||||
|
portfolio = PortfolioFactory()
|
||||||
|
assert portfolio.upcoming_obligated_funds == Decimal(0)
|
||||||
|
|
||||||
|
def test_with_upcoming(self, upcoming_task_order):
|
||||||
|
portfolio = PortfolioFactory(
|
||||||
|
task_orders=[upcoming_task_order, upcoming_task_order]
|
||||||
|
)
|
||||||
|
assert portfolio.upcoming_obligated_funds == Decimal(1400.0)
|
||||||
|
|
||||||
|
def test_with_others(
|
||||||
|
self, past_task_order, current_task_order, upcoming_task_order
|
||||||
|
):
|
||||||
|
portfolio = PortfolioFactory(
|
||||||
|
task_orders=[past_task_order, current_task_order, upcoming_task_order]
|
||||||
|
)
|
||||||
|
# Only sums the upcoming task order
|
||||||
|
assert portfolio.upcoming_obligated_funds == Decimal(700.0)
|
||||||
|
@ -508,7 +508,7 @@ portfolios:
|
|||||||
estimate_warning: Reports displayed in JEDI are estimates and not a system of record.
|
estimate_warning: Reports displayed in JEDI are estimates and not a system of record.
|
||||||
total_value:
|
total_value:
|
||||||
header: Total Portfolio Value
|
header: Total Portfolio Value
|
||||||
tooltip: Total portfolio value is all obligated and projected funds for all task orders in this portfolio.
|
tooltip: Total portfolio value is all obligated funds for current and upcoming task orders in this portfolio.
|
||||||
task_orders:
|
task_orders:
|
||||||
add_new_button: Add New Task Order
|
add_new_button: Add New Task Order
|
||||||
review:
|
review:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user