447 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			447 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| {% extends "portfolios/base.html" %}
 | |
| 
 | |
| {% from "components/alert.html" import Alert %}
 | |
| {% from "components/icon.html" import Icon %}
 | |
| {% from "components/empty_state.html" import EmptyState %}
 | |
| 
 | |
| {% block portfolio_content %}
 | |
| 
 | |
|   {{ Alert("Budget Report for Portfolio " + portfolio.name,
 | |
|     message="<p>Track your monthly and cumulative expenditures for your portfolio, applications, and environments below.</p>\
 | |
|       <p>Please note that the projected spend is based on the <em>average expense over the last three completed months</em> and therefore does not account for future changes that might be made in scale or configuration of your cloud services.</p>",
 | |
|     actions=[
 | |
|       {"label": "Learn More", "href": url_for('atst.helpdocs'), "icon": "info"}
 | |
|     ]  ) }}
 | |
| 
 | |
|   <div v-cloak class='funding-summary-row'>
 | |
| 
 | |
|     <div class='funding-summary-row__col'>
 | |
|       <div class='panel spend-summary'>
 | |
|         <div class='row'>
 | |
|           <h2 class='spend-summary__heading col'>Portfolio Total Spend</h2>
 | |
|           <dl class='spend-summary__budget'>
 | |
|             {% set budget = portfolio_totals['budget'] %}
 | |
|             {% set spent = portfolio_totals['spent'] %}
 | |
|             {% set remaining = budget - spent %}
 | |
|             <div>
 | |
|               <dt>Budget </dt>
 | |
|               <dd>{{ budget | dollars }}</dd>
 | |
|             </div>
 | |
| 
 | |
|             <div>
 | |
|               <dt>Remaining</dt>
 | |
|               <dd>{{ remaining | dollars }}</dd>
 | |
|             </div>
 | |
|           </dl>
 | |
|         </div>
 | |
| 
 | |
|         <div>
 | |
|           <meter value='{{ spent }}' min='0' max='{{ budget }}' title='{{ spent | dollars }} Total spend to date'>
 | |
|             <div class='meter__fallback' style='width:{{ (spent / budget) * 100 if budget else 0 }}%;'></div>
 | |
|           </meter>
 | |
| 
 | |
|           <dl class='spend-summary__spent'>
 | |
|             <dt>Total spend to date</dt>
 | |
|             <dd>{{ spent | dollars }}</dd>
 | |
|           </dl>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
| 
 | |
|     <div class='funding-summary-row__col'>
 | |
|       <div class='panel to-summary'>
 | |
|         <div class='to-summary__row'>
 | |
| 
 | |
|           <div class='to-summary__to'>
 | |
|             <h2 class='to-summary__heading'>Task Order</h2>
 | |
|             <dl class='to-summary__to-number'>
 | |
|               <dt class='usa-sr-only'>Task Order Number</dt>
 | |
|               <dd>{{ legacy_task_order.number }}</dd>
 | |
|             </dl>
 | |
|           </div>
 | |
| 
 | |
|           <div class='to-summary__expiration'>
 | |
|             <dl>
 | |
|               <div>
 | |
|                 <dt>Expires</dt>
 | |
|                 <dd>
 | |
|                   {% if expiration_date %}
 | |
|                     <local-datetime
 | |
|                       timestamp='{{ expiration_date }}'
 | |
|                       format='MMMM D, YYYY'>
 | |
|                       </local-datetime>
 | |
|                   {% else %}
 | |
|                       -
 | |
|                   {% endif %}
 | |
|                 </dd>
 | |
|               </div>
 | |
| 
 | |
|               <div>
 | |
|                 <dt>Remaining</dt>
 | |
|                 <dd>
 | |
|                   {% if remaining_days is not none %}
 | |
|                     {{ remaining_days }} days
 | |
|                   {% else %}
 | |
|                     -
 | |
|                   {% endif %}
 | |
|                 </dd>
 | |
|               </div>
 | |
|             </dl>
 | |
| 
 | |
|             <a href='{{ url_for("portfolios.portfolio", portfolio_id=portfolio.id) }}' class='icon-link'>
 | |
|               Manage Task Order
 | |
|             </a>
 | |
|           </div>
 | |
|         </div>
 | |
| 
 | |
|         <dl class='to-summary__co'>
 | |
|           <dt>Contracting Officer</dt>
 | |
|           <dd>
 | |
|             {{ jedi_request.contracting_officer_full_name }}
 | |
|             <a class='icon-link' href='mailto:{{ jedi_request.contracting_officer_email }}'>{{ jedi_request.contracting_officer_email }}</a>
 | |
|           </dd>
 | |
|         </dl>
 | |
| 
 | |
|       </div>
 | |
|     </div>
 | |
| 
 | |
|   </div>
 | |
| 
 | |
|   {% set portfolio_totals = monthly_totals['portfolio'] %}
 | |
|   {% set current_month_index = current_month.strftime('%m/%Y') %}
 | |
|   {% set prev_month_index = prev_month.strftime('%m/%Y') %}
 | |
|   {% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
 | |
|   {% set reports_url = url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id) %}
 | |
| 
 | |
|   {% if not portfolio.applications %}
 | |
| 
 | |
|     {% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_PORTFOLIO) %}
 | |
|     {% set message = 'This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started.'
 | |
|       if can_create_applications
 | |
|       else 'This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments.'
 | |
|     %}
 | |
| 
 | |
|     {{ EmptyState(
 | |
|       'Nothing to report',
 | |
|       action_label='Add a New Application' if can_create_applications else None,
 | |
|       action_href=url_for('portfolios.new_application', portfolio_id=portfolio.id) if can_create_applications else None,
 | |
|       icon='chart',
 | |
|       sub_message=message
 | |
|     ) }}
 | |
|   {% else %}
 | |
| 
 | |
|     <budget-chart
 | |
|       v-cloak
 | |
|       budget={{ budget }}
 | |
|       current-month='{{ current_month_index }}'
 | |
|       expiration-date='{{ expiration_date }}'
 | |
|       v-bind:months='{{ cumulative_budget.months | tojson }}'
 | |
|       inline-template>
 | |
| 
 | |
|       <div class='budget-chart panel' ref='panel'>
 | |
|         <header class='budget-chart__header panel__heading panel__heading--tight'>
 | |
|           <h2 class='h3'>Cumulative Budget</h2>
 | |
| 
 | |
|           <div class='budget-chart__legend'>
 | |
|             <dl class='budget-chart__legend__spend'>
 | |
|               <div>
 | |
|                 <dt>Monthly Spend</dt>
 | |
|                 <dd class='budget-chart__legend__dot monthly'><span class='usa-sr-only'>Monthly spend visual key</span></dd>
 | |
|               </div>
 | |
| 
 | |
|               <div>
 | |
|                 <dt>Accumulated Spend</dt>
 | |
|                 <dd class='budget-chart__legend__dot accumulated'><span class='usa-sr-only'>Accumulated spend visual key</span></dd>
 | |
|               </div>
 | |
|             </dl>
 | |
|             <dl class='budget-chart__legend__projected'>
 | |
|               <div>
 | |
|                 <dt>Projected</dt>
 | |
|                 <dd>
 | |
|                   <div class='budget-chart__legend__line spend'><span class='usa-sr-only'>Projected monthly spend visual key</span></div>
 | |
|                   <div class='budget-chart__legend__line accumulated'><span class='usa-sr-only'>Projected accumulated spend visual key</span></div>
 | |
|                 </dd>
 | |
|               </div>
 | |
|             </dl>
 | |
|           </div>
 | |
|         </header>
 | |
| 
 | |
|         <svg v-bind:height='height' v-bind:width='width'>
 | |
| 
 | |
|           <defs>
 | |
|             <filter x="-0.04" y="0" width="1.08" height="1" class='filter__text-background' id="text-background">
 | |
|               <feFlood/>
 | |
|               <feComposite in="SourceGraphic"/>
 | |
|             </filter>
 | |
|           </defs>
 | |
| 
 | |
|           {# spend/projected budget path lines #}
 | |
|           <path class='budget-chart__projected-path' v-bind:d='projectedPath'></path>
 | |
|           <path class='budget-chart__spend-path' v-bind:d='spendPath'></path>
 | |
| 
 | |
|           {# max budget line #}
 | |
|           <line
 | |
|             class='budget-chart__budget-line'
 | |
|             x1='0'
 | |
|             v-bind:x2='width'
 | |
|             v-bind:y1='budgetHeight'
 | |
|             v-bind:y2='budgetHeight'></line>
 | |
| 
 | |
|           <g v-for='month in displayedMonths' >
 | |
| 
 | |
|             {# make this clickable to focus on that month #}
 | |
|             <a v-bind:href='"{{ reports_url }}?month=" + month.date.monthIndex + "&year=" + month.date.year'>
 | |
| 
 | |
|               <defs>
 | |
|                 <filter
 | |
|                   x="-0.04"
 | |
|                   y="0"
 | |
|                   width="1.08"
 | |
|                   height="1"
 | |
|                   class='filter__text-background'
 | |
|                   v-bind:class='{ "filter__text-background--highlighted": month.isHighlighted }'
 | |
|                   v-bind:id="'text-background__' +month.date.month + month.date.year">
 | |
|                   <feFlood/>
 | |
|                   <feComposite in="SourceGraphic"/>
 | |
|                 </filter>
 | |
|               </defs>
 | |
| 
 | |
|               <title>
 | |
|                 <span v-html='month.date.month + " " + month.date.year'></span> | <!--
 | |
|                 --><template v-if='month.cumulativeTotal'><!--
 | |
|                   --><template v-if='month.budget && month.budget.spend'>Spend:</template><!--
 | |
|                   --><template v-else>Projected Spend:</template><!--
 | |
|                   --><span v-html='month.spendAmount'></span><!--
 | |
|                   --> | <!--
 | |
|                   --><template v-if='month.budget'>Total:</template><!--
 | |
|                   --><template v-else>Projected Total:</template><!--
 | |
|                   --><span v-html='month.cumulativeAmount'></span><!--
 | |
|                 --></template><!--
 | |
| 
 | |
|                 --><template v-else>No spend for this month</template>
 | |
|               </title>
 | |
| 
 | |
|               {# container block #}
 | |
|               <rect
 | |
|                 class='budget-chart__block'
 | |
|                 v-bind:class='{ "budget-chart__block--highlighted": month.isHighlighted, "budget-chart__block-is-expiration": month.isExpirationMonth }'
 | |
|                 v-bind:width='month.metrics.blockWidth'
 | |
|                 v-bind:x='month.metrics.blockX'
 | |
|                 v-bind:height='height'></rect>
 | |
| 
 | |
|               {# budget bar #}
 | |
|               <rect
 | |
|                 v-if='month.budget'
 | |
|                 class='budget-chart__bar'
 | |
|                 v-bind:class='{ "budget-chart__bar--projected": month.budget.projected }'
 | |
|                 v-bind:width='month.metrics.barWidth'
 | |
|                 v-bind:height='month.metrics.barHeight'
 | |
|                 v-bind:x='month.metrics.barX'
 | |
|                 v-bind:y='month.metrics.barY'></rect>
 | |
| 
 | |
|               {# projected budget bar #}
 | |
|               <rect
 | |
|                 v-if='!month.budget'
 | |
|                 class='budget-chart__bar budget-chart__bar--projected'
 | |
|                 v-bind:width='month.metrics.barWidth'
 | |
|                 v-bind:height='month.metrics.barHeight'
 | |
|                 v-bind:x='month.metrics.barX'
 | |
|                 v-bind:y='month.metrics.barY'></rect>
 | |
| 
 | |
|               {# task order expiration line #}
 | |
|               <line
 | |
|                 v-if='month.isExpirationMonth'
 | |
|                 class='budget-chart__expiration-line'
 | |
|                 v-bind:x1='month.metrics.cumulativeX'
 | |
|                 v-bind:x2='month.metrics.cumulativeX'
 | |
|                 y1='0'
 | |
|                 v-bind:y2='baseHeight'></line>
 | |
| 
 | |
|               {# task order expiration label #}
 | |
|               <text
 | |
|                 v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
 | |
|                 v-if='month.isExpirationMonth'
 | |
|                 text-anchor='middle'
 | |
|                 v-bind:x='month.metrics.cumulativeX'
 | |
|                 v-bind:y='budgetHeight + 20'
 | |
|                 class='budget-chart__label'>T.O. Expires</text>
 | |
| 
 | |
|               {# cumulative dot #}
 | |
|               <circle
 | |
|                 v-if='month.cumulativeTotal'
 | |
|                 class='budget-chart__cumulative__dot'
 | |
|                 v-bind:r='month.metrics.cumulativeR'
 | |
|                 v-bind:cx='month.metrics.cumulativeX'
 | |
|                 v-bind:cy='month.metrics.cumulativeY'></circle>
 | |
| 
 | |
|               {# abbreviated cumulative label #}
 | |
|               <text
 | |
|                 v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
 | |
|                 v-if='month.cumulativeTotal'
 | |
|                 v-bind:x='month.metrics.cumulativeX'
 | |
|                 v-bind:y='month.metrics.cumulativeY - 10'
 | |
|                 text-anchor='middle'
 | |
|                 class='budget-chart__label'
 | |
|                 v-html='month.abbreviatedCumulative'></text>
 | |
| 
 | |
|               {# abbreviated spend label #}
 | |
|               <text
 | |
|                 v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
 | |
|                 v-bind:x='month.metrics.cumulativeX'
 | |
|                 v-bind:y='baseHeight + 20'
 | |
|                 text-anchor='middle'
 | |
|                 class='budget-chart__label'
 | |
|                 v-html='"+" + month.abbreviatedSpend'></text>
 | |
| 
 | |
|               {# month label #}
 | |
|               <text
 | |
|                 v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
 | |
|                 v-bind:x='month.metrics.cumulativeX'
 | |
|                 v-bind:y='baseHeight + 40'
 | |
|                 text-anchor='middle'
 | |
|                 class='budget-chart__label budget-chart__label--strong'
 | |
|                 v-html='month.date.month'></text>
 | |
| 
 | |
|               {# year label #}
 | |
|               <text
 | |
|                 v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
 | |
|                 v-if='month.showYear'
 | |
|                 v-bind:x='month.metrics.cumulativeX'
 | |
|                 v-bind:y='baseHeight + 55'
 | |
|                 text-anchor='middle'
 | |
|                 class='budget-chart__label budget-chart__label--strong'
 | |
|                 v-html='month.date.year'></text>
 | |
|             </g>
 | |
|           </a>
 | |
| 
 | |
|           <text
 | |
|             x='20'
 | |
|             v-bind:y='budgetHeight + 20'
 | |
|             class='budget-chart__label'>Total Budget</text>
 | |
|           <text
 | |
|             x='20'
 | |
|             v-bind:y='budgetHeight + 40'
 | |
|             class='budget-chart__label'
 | |
|             v-html='displayBudget'></text>
 | |
|         </svg>
 | |
|       </div>
 | |
|     </budget-chart>
 | |
| 
 | |
|     <div class='spend-table responsive-table-wrapper'>
 | |
|       <div class='spend-table__header'>
 | |
|         <h2 class='spend-table__title'>Total spend per month </h2>
 | |
| 
 | |
|         <select name='month' id='month' onchange='location = this.value' class='spend-table__month-select'>
 | |
|           {% for m in cumulative_budget["months"] %}
 | |
|             {% set month = m | dateFromString %}
 | |
|             <option
 | |
|               {% if month.month == current_month.month and month.year == current_month.year %}
 | |
|                 selected='selected'
 | |
|               {% endif %}
 | |
|               value='{{ url_for("portfolios.portfolio_reports",
 | |
|                                 portfolio_id=portfolio.id,
 | |
|                                 month=month.month,
 | |
|                                 year=month.year) }}'
 | |
|             >
 | |
|               {{ month.strftime('%B %Y') }}
 | |
|             </option>
 | |
|           {% endfor %}
 | |
|           {% if not cumulative_budget["months"] %}
 | |
|             <option>{{ current_month.strftime('%B %Y') }}</option>
 | |
|           {% endif %}
 | |
|         </select>
 | |
|       </div>
 | |
| 
 | |
|       <spend-table
 | |
|         v-bind:applications='{{ monthly_totals['applications'] | tojson }}'
 | |
|         v-bind:portfolio='{{ portfolio_totals | tojson }}'
 | |
|         v-bind:environments='{{ monthly_totals['environments'] | tojson }}'
 | |
|         current-month-index='{{ current_month_index }}'
 | |
|         prev-month-index='{{ prev_month_index }}'
 | |
|         two-months-ago-index='{{ two_months_ago_index }}'
 | |
|         inline-template>
 | |
|         <table>
 | |
|           <thead>
 | |
|             <th scope='col'><span class='usa-sr-only'>Spending scope</span></th>
 | |
|             <th scope='col' class='table-cell--align-right previous-month'>{{ two_months_ago.strftime('%B %Y') }}</th>
 | |
|             <th scope='col' class='table-cell--align-right previous-month'>{{ prev_month.strftime('%B %Y') }}</th>
 | |
|             <th scope='col' class='table-cell--align-right current-month'>{{ current_month.strftime('%B %Y') }}</th>
 | |
|             <th class='current-month'>% of total spend this month</th>
 | |
|           </thead>
 | |
| 
 | |
|           <tbody class='spend-table__portfolio'>
 | |
|             <tr>
 | |
|               <th scope='row'>Total</th>
 | |
|               <td class='table-cell--align-right previous-month'>{{ portfolio_totals.get(two_months_ago_index, 0) | dollars }}</td>
 | |
|               <td class='table-cell--align-right previous-month'>{{ portfolio_totals.get(prev_month_index, 0) | dollars }}</td>
 | |
|               <td class='table-cell--align-right current-month'>{{ portfolio_totals.get(current_month_index, 0) | dollars }}</td>
 | |
|               <td class='table-cell--expand current-month meter-cell'>
 | |
|                 <meter value='{{ portfolio_totals.get(current_month_index, 0) }}' min='0' max='{{ portfolio_totals.get(current_month_index, 0) }}'>
 | |
|                   <div class='meter__fallback' style='width: 100%'></div>
 | |
|                 </meter>
 | |
|               </td>
 | |
|             </tr>
 | |
|           </tbody>
 | |
| 
 | |
|           <tbody v-for='(application, name) in applicationsState' class='spend-table__application'>
 | |
|             <tr>
 | |
|               <th scope='rowgroup'>
 | |
|                 <button v-on:click='toggle($event, name)' class='icon-link icon-link--large spend-table__application__toggler'>
 | |
|                   <template v-if='application.isVisible'>{{ Icon('caret_down') }}</template>
 | |
|                   <template v-else>{{ Icon('caret_right') }}</template>
 | |
|                   <span v-html='name'></span>
 | |
|                 </button>
 | |
|               </th>
 | |
|               <td class='table-cell--align-right previous-month'>
 | |
|                 <span v-html='formatDollars(application[twoMonthsAgoIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--align-right previous-month'>
 | |
|                 <span v-html='formatDollars(application[prevMonthIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--align-right current-month'>
 | |
|                 <span v-html='formatDollars(application[currentMonthIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--expand current-month meter-cell'>
 | |
|                 <span class='spend-table__meter-value'>
 | |
|                   <span v-html='round( 100 * ((application[currentMonthIndex] || 0) / (portfolio[currentMonthIndex] || 1) )) + "%"'></span>
 | |
|                 </span>
 | |
|                 <meter v-bind:value='application[currentMonthIndex] || 0' min='0' v-bind:max='portfolio[currentMonthIndex] || 1'>
 | |
|                   <div class='meter__fallback' v-bind:style='"width:" + round( 100 * ((application[currentMonthIndex] || 0) / (portfolio[currentMonthIndex] || 1) )) + "%;"'></div>
 | |
|                 </meter>
 | |
|               </td>
 | |
|             </tr>
 | |
| 
 | |
|             <tr v-for='(environment, envName) in environments[name]' v-show='application.isVisible' class='spend-table__application__env'>
 | |
|               <th scope='rowgroup'>
 | |
|                 <a href='#' class='icon-link spend-table__application__env'>
 | |
|                   {{ Icon('link') }}
 | |
|                   <span v-html='envName'></span>
 | |
|                 </a>
 | |
|               </th>
 | |
| 
 | |
|               <td class='table-cell--align-right previous-month'>
 | |
|                 <span v-html='formatDollars(environment[twoMonthsAgoIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--align-right previous-month'>
 | |
|                 <span v-html='formatDollars(environment[prevMonthIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--align-right current-month'>
 | |
|                 <span v-html='formatDollars(environment[currentMonthIndex] || 0)'></span>
 | |
|               </td>
 | |
| 
 | |
|               <td class='table-cell--expand current-month'></td>
 | |
|             </tr>
 | |
|           </tbody>
 | |
|         </table>
 | |
|       </spend-table>
 | |
|     </div>
 | |
| 
 | |
|   {% endif %}
 | |
| 
 | |
| {% endblock %}
 |