diff --git a/atst/domain/csp/reports.py b/atst/domain/csp/reports.py
index 88c30482..683c1139 100644
--- a/atst/domain/csp/reports.py
+++ b/atst/domain/csp/reports.py
@@ -1,6 +1,7 @@
from itertools import groupby
-from collections import OrderedDict
+from atst.utils.localization import translate
import pendulum
+from decimal import Decimal
class ReportingInterface:
@@ -35,14 +36,16 @@ def generate_sample_dates(_max=8):
current = pendulum.now()
sample_dates = []
for _i in range(_max):
- current = current.subtract(months=1)
sample_dates.append(current.strftime("%m/%Y"))
+ current = current.subtract(months=1)
reversed(sample_dates)
return sample_dates
class MockReportingProvider(ReportingInterface):
+ MOCK_PERCENT_EXPENDED_FUNDS = 0.75
+
FIXTURE_MONTHS = generate_sample_dates()
MONTHLY_SPEND_BY_ENVIRONMENT = {
@@ -163,25 +166,8 @@ class MockReportingProvider(ReportingInterface):
"FM_Prod": {FIXTURE_MONTHS[0]: 5686},
}
- CUMULATIVE_BUDGET_A_WING = {
- FIXTURE_MONTHS[7]: {"spend": 9857, "cumulative": 9857},
- FIXTURE_MONTHS[6]: {"spend": 7881, "cumulative": 17738},
- FIXTURE_MONTHS[5]: {"spend": 14010, "cumulative": 31748},
- FIXTURE_MONTHS[4]: {"spend": 43510, "cumulative": 75259},
- FIXTURE_MONTHS[3]: {"spend": 41725, "cumulative": 116_984},
- FIXTURE_MONTHS[2]: {"spend": 41328, "cumulative": 158_312},
- FIXTURE_MONTHS[1]: {"spend": 47491, "cumulative": 205_803},
- FIXTURE_MONTHS[0]: {"spend": 36028, "cumulative": 241_831},
- }
-
- CUMULATIVE_BUDGET_B_WING = {
- FIXTURE_MONTHS[1]: {"spend": 4838, "cumulative": 4838},
- FIXTURE_MONTHS[0]: {"spend": 14500, "cumulative": 19338},
- }
-
REPORT_FIXTURE_MAP = {
"A-Wing": {
- "cumulative": CUMULATIVE_BUDGET_A_WING,
"applications": [
MockApplication("LC04", ["Integ", "PreProd", "Prod"]),
MockApplication("SF18", ["Integ", "PreProd", "Prod"]),
@@ -202,7 +188,6 @@ class MockReportingProvider(ReportingInterface):
"budget": 500_000,
},
"B-Wing": {
- "cumulative": CUMULATIVE_BUDGET_B_WING,
"applications": [
MockApplication("NP02", ["Integ", "PreProd", "Prod"]),
MockApplication("FM", ["Integ", "Prod"]),
@@ -211,28 +196,6 @@ class MockReportingProvider(ReportingInterface):
},
}
- def _sum_monthly_spend(self, data):
- return sum(
- [
- spend
- for application in data
- for env in application.environments
- for spend in self.MONTHLY_SPEND_BY_ENVIRONMENT[env.id].values()
- ]
- )
-
- def get_budget(self, portfolio):
- if portfolio.name in self.REPORT_FIXTURE_MAP:
- return self.REPORT_FIXTURE_MAP[portfolio.name]["budget"]
- return 0
-
- def get_total_spending(self, portfolio):
- if portfolio.name in self.REPORT_FIXTURE_MAP:
- return self._sum_monthly_spend(
- self.REPORT_FIXTURE_MAP[portfolio.name]["applications"]
- )
- return 0
-
def _rollup_application_totals(self, data):
application_totals = {}
for application, environments in data.items():
@@ -270,7 +233,14 @@ class MockReportingProvider(ReportingInterface):
{ "01/2018": 79.85, "02/2018": 86.54 }
"""
- return self.MONTHLY_SPEND_BY_ENVIRONMENT.get(environment_id, {})
+ environment_monthly_totals = self.MONTHLY_SPEND_BY_ENVIRONMENT.get(
+ environment_id, {}
+ ).copy()
+
+ environment_monthly_totals["total_spend_to_date"] = sum(
+ monthly_total for monthly_total in environment_monthly_totals.values()
+ )
+ return environment_monthly_totals
def monthly_totals(self, portfolio):
"""Return month totals rolled up by environment, application, and portfolio.
@@ -309,19 +279,46 @@ class MockReportingProvider(ReportingInterface):
"portfolio": portfolio_totals,
}
- def cumulative_budget(self, portfolio):
+ def get_obligated_funds_by_JEDI_clin(self, portfolio):
+ """
+ Returns a dictionary of obligated funds and spending per JEDI CLIN
+ {
+ JEDI_CLIN: {
+ obligated_funds,
+ expended_funds
+ }
+ }
+ """
if portfolio.name in self.REPORT_FIXTURE_MAP:
- budget_months = self.REPORT_FIXTURE_MAP[portfolio.name]["cumulative"]
- else:
- budget_months = {}
+ return_dict = {}
+ for jedi_clin, clins in groupby(
+ portfolio.active_clins, lambda clin: clin.jedi_clin_type
+ ):
+ obligated_funds = sum(clin.obligated_amount for clin in clins)
+ return_dict[translate(f"JEDICLINType.{jedi_clin.value}")] = {
+ "obligated_funds": obligated_funds,
+ "expended_funds": (
+ obligated_funds * Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
+ ),
+ }
+ return return_dict
+ return {}
- end = pendulum.now()
- start = end.subtract(months=12)
- period = pendulum.period(start, end)
-
- all_months = OrderedDict()
- for t in period.range("months"):
- month_str = "{month:02d}/{year}".format(month=t.month, year=t.year)
- all_months[month_str] = budget_months.get(month_str, None)
-
- return {"months": all_months}
+ def get_expired_task_orders(self, portfolio):
+ return [
+ {
+ "id": task_order.id,
+ "number": task_order.number,
+ "period_of_performance": {
+ "start_date": task_order.start_date,
+ "end_date": task_order.end_date,
+ },
+ "total_obligated_funds": task_order.total_obligated_funds,
+ "expended_funds": (
+ task_order.total_obligated_funds
+ * Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
+ ),
+ }
+ for task_order in portfolio.task_orders
+ if task_order.is_expired
+ ]
diff --git a/atst/domain/reports.py b/atst/domain/reports.py
index 96085afb..94f6c54e 100644
--- a/atst/domain/reports.py
+++ b/atst/domain/reports.py
@@ -2,16 +2,14 @@ from flask import current_app
class Reports:
- @classmethod
- def portfolio_totals(cls, portfolio):
- budget = current_app.csp.reports.get_budget(portfolio)
- spent = current_app.csp.reports.get_total_spending(portfolio)
- return {"budget": budget, "spent": spent}
-
@classmethod
def monthly_totals(cls, portfolio):
return current_app.csp.reports.monthly_totals(portfolio)
@classmethod
- def cumulative_budget(cls, portfolio):
- return current_app.csp.reports.cumulative_budget(portfolio)
+ def expired_task_orders(cls, portfolio):
+ return current_app.csp.reports.get_expired_task_orders(portfolio)
+
+ @classmethod
+ def obligated_funds_by_JEDI_clin(cls, portfolio):
+ return current_app.csp.reports.get_obligated_funds_by_JEDI_clin(portfolio)
diff --git a/atst/models/portfolio.py b/atst/models/portfolio.py
index 86f3c57f..08a65f1c 100644
--- a/atst/models/portfolio.py
+++ b/atst/models/portfolio.py
@@ -114,7 +114,7 @@ class Portfolio(
for task_order in self.task_orders
if task_order.is_active
),
- default=None,
+ default=0,
)
@property
diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py
index 9c2ec1a0..336cd2e6 100644
--- a/atst/routes/portfolios/index.py
+++ b/atst/routes/portfolios/index.py
@@ -46,34 +46,24 @@ def create_portfolio():
def reports(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
today = date.today()
- month = http_request.args.get("month", today.month)
- year = http_request.args.get("year", today.year)
- current_month = date(int(year), int(month), 15)
+ current_month = date(int(today.year), int(today.month), 15)
prev_month = current_month - timedelta(days=28)
- two_months_ago = prev_month - timedelta(days=28)
-
- task_order = next(
- (task_order for task_order in portfolio.task_orders if task_order.is_active),
- None,
+ # wrapped in str() because the sum of obligated funds returns a Decimal object
+ total_portfolio_value = str(
+ sum(
+ task_order.total_obligated_funds
+ for task_order in portfolio.active_task_orders
+ )
)
- expiration_date = task_order and task_order.end_date
- if expiration_date:
- remaining_difference = expiration_date - today
- remaining_days = remaining_difference.days
- else:
- remaining_days = None
-
return render_template(
"portfolios/reports/index.html",
- cumulative_budget=Reports.cumulative_budget(portfolio),
- portfolio_totals=Reports.portfolio_totals(portfolio),
+ portfolio=portfolio,
+ total_portfolio_value=total_portfolio_value,
+ current_obligated_funds=Reports.obligated_funds_by_JEDI_clin(portfolio),
+ expired_task_orders=Reports.expired_task_orders(portfolio),
monthly_totals=Reports.monthly_totals(portfolio),
- task_order=task_order,
current_month=current_month,
prev_month=prev_month,
- two_months_ago=two_months_ago,
- expiration_date=expiration_date,
- remaining_days=remaining_days,
)
diff --git a/js/components/tables/spend_table.js b/js/components/tables/spend_table.js
index f8d663a3..c3a1c90f 100644
--- a/js/components/tables/spend_table.js
+++ b/js/components/tables/spend_table.js
@@ -6,11 +6,9 @@ export default {
props: {
applications: Object,
- portfolio: Object,
environments: Object,
currentMonthIndex: String,
prevMonthIndex: String,
- twoMonthsAgoIndex: String,
},
data: function() {
@@ -40,9 +38,5 @@ export default {
formatDollars: function(value) {
return formatDollars(value, false)
},
-
- round: function(value) {
- return Math.round(value)
- },
},
}
diff --git a/templates/components/accordion.html b/templates/components/accordion.html
index 7bf03c6f..8e508321 100644
--- a/templates/components/accordion.html
+++ b/templates/components/accordion.html
@@ -1,36 +1,23 @@
-{% macro Accordion(title, id) %}
+{% macro Accordion(title, id, heading_level="h2") %}
Applications and Environments | +Current Month | +Last Month | +Total Spent | +
---|---|---|---|
+ + | ++ + | ++ + | ++ + | +
+ + | ++ + | ++ + | ++ + | +
Period of Performance
++ {{ task_order["period_of_performance"].start_date | formattedDate(formatter="%B %d, %Y") }} + - + {{ task_order["period_of_performance"].end_date | formattedDate(formatter="%B %d, %Y") }} +
+Total Obligated
+{{ task_order["total_obligated_funds"] | dollars }}
+Total Expended
+{{ task_order["expended_funds"] | dollars }}
+Total Unused
+{{ (task_order["total_obligated_funds"] - task_order["expended_funds"]) | dollars }}
+Spending scope | -{{ two_months_ago.strftime('%B %Y') }} | -{{ prev_month.strftime('%B %Y') }} | -{{ current_month.strftime('%B %Y') }} | -- - - - |
---|---|---|---|---|
Portfolio Total | -{{ portfolio_totals.get(two_months_ago_index, 0) | dollars }} | -{{ portfolio_totals.get(prev_month_index, 0) | dollars }} | -{{ portfolio_totals.get(current_month_index, 0) | dollars }} | -
- |
-
- - | -- - | - -- - | - -- - | - -
-
-
-
- |
-
- | - - -- - | - -- - | - -- - | - -- |
Remaining funds:
+{{ (funds["obligated_funds"] - funds["expended_funds"]) | dollars }}
+Funds expended to date:
+{{ funds["expended_funds"] | dollars }}
++ Total Portfolio Value + {{Tooltip(("common.lorem" | translate), title="")}} +
+{{ total_portfolio_value | dollars }}
++ Funding Duration + {{Tooltip(("common.lorem" | translate), title="")}} +
+ {% set earliest_pop_start_date, latest_pop_end_date = portfolio.funding_duration %} + {% if earliest_pop_start_date and latest_pop_end_date %} ++ {{ earliest_pop_start_date | formattedDate(formatter="%B %d, %Y") }} + - + {{ latest_pop_end_date | formattedDate(formatter="%B %d, %Y") }} +
+ {% else %} +-
+ {% endif %} ++ Days Remaining + {{Tooltip(("common.lorem" | translate), title="")}} +
+{{ portfolio.days_to_funding_expiration }} days
+