Implement Dashboard 'Cost Status' table
* Table contains several 'Earned Value' measures related with cost FEA: ItEr76S15OrganizingPerProjectDashboard
This commit is contained in:
parent
398355295f
commit
94aa02fed1
6 changed files with 460 additions and 0 deletions
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* This file is part of LibrePlan
|
||||
*
|
||||
* Copyright (C) 2010-2012 Igalia, S.L.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.libreplan.web.dashboard;
|
||||
|
||||
import static org.libreplan.web.I18nHelper._;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.web.common.Util;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.zkoss.zk.ui.util.GenericForwardComposer;
|
||||
import org.zkoss.zul.Label;
|
||||
|
||||
/**
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
*
|
||||
* Contains operations for calculations in the CostStatus table in the
|
||||
* Dashboard view
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||
public class CostStatusController extends GenericForwardComposer {
|
||||
|
||||
private ICostStatusModel costStatusModel;
|
||||
|
||||
// Cost Variance
|
||||
public Label lblCV;
|
||||
|
||||
// Cost Performance Index
|
||||
public Label lblCPI;
|
||||
|
||||
// Budget at Completion
|
||||
public Label lblBAC;
|
||||
|
||||
// Estimate at Completion
|
||||
public Label lblEAC;
|
||||
|
||||
// Variance at Completion
|
||||
public Label lblVAC;
|
||||
|
||||
@Override
|
||||
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
|
||||
super.doAfterCompose(comp);
|
||||
self.setAttribute("controller", this);
|
||||
Util.createBindingsFor(self);
|
||||
}
|
||||
|
||||
public void setOrder(Order order) {
|
||||
costStatusModel.setCurrentOrder(order);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
LocalDate today = LocalDate.fromDateFields(new Date());
|
||||
BigDecimal budgetedCost = costStatusModel
|
||||
.getBudgetedCostWorkPerformedAt(today);
|
||||
BigDecimal actualCost = costStatusModel
|
||||
.getActualCostWorkPerformedAt(today);
|
||||
|
||||
BigDecimal costVariance = costStatusModel.getCostVariance(budgetedCost,
|
||||
actualCost);
|
||||
setCostVariance(costVariance);
|
||||
|
||||
BigDecimal costPerformanceIndex = costStatusModel
|
||||
.getCostPerformanceIndex(budgetedCost, actualCost);
|
||||
setCostPerformanceIndex(costPerformanceIndex);
|
||||
|
||||
BigDecimal budgetAtCompletion = costStatusModel.getBudgetAtCompletion();
|
||||
setBudgetAtCompletion(budgetAtCompletion);
|
||||
|
||||
BigDecimal estimateAtCompletion = costStatusModel
|
||||
.getEstimateAtCompletion(budgetAtCompletion,
|
||||
costPerformanceIndex);
|
||||
setEstimateAtCompletion(estimateAtCompletion);
|
||||
|
||||
BigDecimal varianceAtCompletion = costStatusModel
|
||||
.getVarianceAtCompletion(budgetAtCompletion,
|
||||
estimateAtCompletion);
|
||||
setVarianceAtCompletion(varianceAtCompletion);
|
||||
}
|
||||
|
||||
private void setEstimateAtCompletion(BigDecimal value) {
|
||||
lblEAC.setValue(String.format("%.2f %%", value.doubleValue()));
|
||||
}
|
||||
|
||||
private void setCostPerformanceIndex(BigDecimal value) {
|
||||
lblCPI.setValue(String.format("%.2f %%", value.doubleValue()));
|
||||
}
|
||||
|
||||
private void setBudgetAtCompletion(BigDecimal value) {
|
||||
lblBAC.setValue(String.format(_("%s h"), value.toString()));
|
||||
}
|
||||
|
||||
private void setCostVariance(BigDecimal value) {
|
||||
lblCV.setValue(String.format(_("%s h"), value.toString()));
|
||||
}
|
||||
|
||||
private void setVarianceAtCompletion(BigDecimal value) {
|
||||
lblVAC.setValue(String.format(_("%s h"), value.toString()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* This file is part of LibrePlan
|
||||
*
|
||||
* Copyright (C) 2010-2012 Igalia, S.L.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.libreplan.web.dashboard;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.planner.entities.ICostCalculator;
|
||||
import org.libreplan.business.planner.entities.Task;
|
||||
import org.libreplan.business.planner.entities.TaskElement;
|
||||
import org.libreplan.web.planner.order.OrderPlanningModel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Diego Pino García <ltilve@igalia.com>
|
||||
*
|
||||
* Model for UI operations related to CostStatus in Dashboard view
|
||||
*
|
||||
* FIXME: This Model contains several operations for calculating 'Earned
|
||||
* Value' measures related with cost. The code for calculating the basic
|
||||
* measures: BCWP, ACWP and BCWS is copied from
|
||||
* {@link OrderPlanningModel}. At this moment this code cannot be reused
|
||||
* as it's coupled with the logic for displaying the 'Earned Value'
|
||||
* chart. We may consider to refactor this code in the future.
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||
public class CostStatusModel implements ICostStatusModel {
|
||||
|
||||
@Autowired
|
||||
private ICostCalculator hoursCostCalculator;
|
||||
|
||||
private Order order;
|
||||
|
||||
public CostStatusModel() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal getActualCostWorkPerformedAt(LocalDate date) {
|
||||
SortedMap<LocalDate, BigDecimal> actualCost = calculateActualCostWorkPerformed();
|
||||
BigDecimal result = actualCost.get(date);
|
||||
return (result != null) ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> calculateActualCostWorkPerformed() {
|
||||
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
|
||||
for (TaskElement taskElement : getAllTaskElements(order)) {
|
||||
if (taskElement instanceof Task) {
|
||||
addCost(result, getWorkReportCost((Task) taskElement));
|
||||
}
|
||||
}
|
||||
return accumulateResult(result);
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> accumulateResult(
|
||||
SortedMap<LocalDate, BigDecimal> map) {
|
||||
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
|
||||
if (map.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
BigDecimal accumulatedResult = BigDecimal.ZERO;
|
||||
for (LocalDate day : map.keySet()) {
|
||||
BigDecimal value = map.get(day);
|
||||
accumulatedResult = accumulatedResult.add(value);
|
||||
result.put(day, accumulatedResult);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addCost(SortedMap<LocalDate, BigDecimal> currentCost,
|
||||
SortedMap<LocalDate, BigDecimal> additionalCost) {
|
||||
for (LocalDate day : additionalCost.keySet()) {
|
||||
if (!currentCost.containsKey(day)) {
|
||||
currentCost.put(day, BigDecimal.ZERO);
|
||||
}
|
||||
currentCost.put(day,
|
||||
currentCost.get(day).add(additionalCost.get(day)));
|
||||
}
|
||||
}
|
||||
|
||||
private List<TaskElement> getAllTaskElements(Order order) {
|
||||
List<TaskElement> result = order.getAllChildrenAssociatedTaskElements();
|
||||
result.add(order.getAssociatedTaskElement());
|
||||
return result;
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> getWorkReportCost(Task taskElement) {
|
||||
return hoursCostCalculator.getWorkReportCost(taskElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal getBudgetAtCompletion() {
|
||||
SortedMap<LocalDate, BigDecimal> budgedtedCost = calculateBudgetedCostWorkScheduled();
|
||||
LocalDate lastKey = budgedtedCost.lastKey();
|
||||
return (lastKey) != null ? budgedtedCost.get(lastKey) : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkScheduled() {
|
||||
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
|
||||
for (TaskElement taskElement : getAllTaskElements(order)) {
|
||||
if (taskElement instanceof Task) {
|
||||
addCost(result, getEstimatedCost((Task) taskElement));
|
||||
}
|
||||
}
|
||||
return accumulateResult(result);
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> getEstimatedCost(Task task) {
|
||||
return hoursCostCalculator.getEstimatedCost(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal getBudgetedCostWorkPerformedAt(LocalDate date) {
|
||||
SortedMap<LocalDate, BigDecimal> budgetedCost = calculateBudgetedCostWorkPerformed();
|
||||
BigDecimal result = budgetedCost.get(date);
|
||||
return (result != null) ? result : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkPerformed() {
|
||||
SortedMap<LocalDate, BigDecimal> estimatedCost = new TreeMap<LocalDate, BigDecimal>();
|
||||
for (TaskElement taskElement : getAllTaskElements(order)) {
|
||||
if (taskElement instanceof Task) {
|
||||
addCost(estimatedCost, getAdvanceCost((Task) taskElement));
|
||||
}
|
||||
}
|
||||
return accumulateResult(estimatedCost);
|
||||
}
|
||||
|
||||
private SortedMap<LocalDate, BigDecimal> getAdvanceCost(Task task) {
|
||||
return hoursCostCalculator.getAdvanceCost(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCostPerformanceIndex(BigDecimal budgetedCost,
|
||||
BigDecimal actualCost) {
|
||||
if (BigDecimal.ZERO.compareTo(actualCost) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return asPercentage(budgetedCost.divide(actualCost));
|
||||
}
|
||||
|
||||
private BigDecimal asPercentage(BigDecimal value) {
|
||||
return value.multiply(BigDecimal.valueOf(100)).setScale(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCostVariance(BigDecimal budgetedCost,
|
||||
BigDecimal actualCost) {
|
||||
return budgetedCost.subtract(actualCost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getEstimateAtCompletion(BigDecimal budgetAtCompletion,
|
||||
BigDecimal costPerformanceIndex) {
|
||||
if (BigDecimal.ZERO.compareTo(costPerformanceIndex) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return asPercentage(budgetAtCompletion.divide(costPerformanceIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getVarianceAtCompletion(BigDecimal budgetAtCompletion,
|
||||
BigDecimal estimateAtCompletion) {
|
||||
return budgetAtCompletion.subtract(estimateAtCompletion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentOrder(Order order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -67,6 +67,8 @@ public class DashboardController extends GenericForwardComposer {
|
|||
private Grid gridTasksSummary;
|
||||
private Grid gridMarginWithDeadline;
|
||||
|
||||
private org.zkoss.zk.ui.Component costStatus;
|
||||
|
||||
private Div projectDashboardChartsDiv;
|
||||
private Div projectDashboardNoTasksWarningDiv;
|
||||
|
||||
|
|
@ -96,9 +98,20 @@ public class DashboardController extends GenericForwardComposer {
|
|||
renderDeadlineViolation();
|
||||
renderMarginWithDeadline();
|
||||
renderEstimationAccuracy();
|
||||
renderCostStatus(order);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderCostStatus(Order order) {
|
||||
CostStatusController costStatusController = getCostStatusController();
|
||||
costStatusController.setOrder(order);
|
||||
costStatusController.render();
|
||||
}
|
||||
|
||||
private CostStatusController getCostStatusController() {
|
||||
return (CostStatusController) costStatus.getAttribute("controller");
|
||||
}
|
||||
|
||||
private void renderMarginWithDeadline() {
|
||||
marginWithDeadline(dashboardModel.getMarginWithDeadLine());
|
||||
absoluteMarginWithDeadline(dashboardModel
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of LibrePlan
|
||||
*
|
||||
* Copyright (C) 2010-2012 Igalia, S.L.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.libreplan.web.dashboard;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Diego Pino García <dpino@igalia.com>
|
||||
*
|
||||
*/
|
||||
interface ICostStatusModel {
|
||||
|
||||
BigDecimal getActualCostWorkPerformedAt(LocalDate day);
|
||||
|
||||
// Budget at Completion (BAC)
|
||||
BigDecimal getBudgetAtCompletion();
|
||||
|
||||
BigDecimal getBudgetedCostWorkPerformedAt(LocalDate day);
|
||||
|
||||
// Cost Performance Index (CPI)
|
||||
BigDecimal getCostPerformanceIndex(BigDecimal budgetedCost,
|
||||
BigDecimal actualCost);
|
||||
|
||||
// Cost Variance (CV)
|
||||
BigDecimal getCostVariance(BigDecimal budgetedCost, BigDecimal actualCost);
|
||||
|
||||
// Estimate at Completion (EAC)
|
||||
BigDecimal getEstimateAtCompletion(BigDecimal budgetAtCompletion,
|
||||
BigDecimal costPerformanceIndex);
|
||||
|
||||
// Variance at Completion (VAC)
|
||||
BigDecimal getVarianceAtCompletion(BigDecimal budgetAtCompletion,
|
||||
BigDecimal estimateAtCompletion);
|
||||
|
||||
void setCurrentOrder(Order order);
|
||||
|
||||
}
|
||||
53
libreplan-webapp/src/main/webapp/dashboard/_costStatus.zul
Normal file
53
libreplan-webapp/src/main/webapp/dashboard/_costStatus.zul
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!--
|
||||
This file is part of LibrePlan
|
||||
|
||||
Copyright (C) 2010-2012 Igalia, S.L.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<!-- Cost status -->
|
||||
<grid id="${arg.id}"
|
||||
apply="org.libreplan.web.dashboard.CostStatusController"
|
||||
width="400px">
|
||||
<auxhead>
|
||||
<auxheader label="${i18n:_('Cost status')}" colspan="2"/>
|
||||
</auxhead>
|
||||
<columns sizable="false">
|
||||
<column width="200px"/>
|
||||
<column />
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<label value="${i18n:_('CV (Cost Variance)')}:"/>
|
||||
<label id="lblCV" />
|
||||
</row>
|
||||
<row>
|
||||
<label value="${i18n:_('EAC (Estimate At Completion)')}:"/>
|
||||
<label id="lblEAC" />
|
||||
</row>
|
||||
<row>
|
||||
<label value="${i18n:_('BAC (Budget At Completion)')}:"/>
|
||||
<label id="lblBAC" />
|
||||
</row>
|
||||
<row>
|
||||
<label value="${i18n:_('VAC (Variance At Completion)')}:"/>
|
||||
<label id="lblVAC" />
|
||||
</row>
|
||||
<row>
|
||||
<label value="${i18n:_('CPI (Cost Performance Index)')}:"/>
|
||||
<label id="lblCPI" />
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?component name="costStatus" inline="true" macroURI="_costStatus.zul"?>
|
||||
|
||||
<zk xmlns:n="http://www.zkoss.org/2005/zk/native">
|
||||
<zscript>
|
||||
<![CDATA[
|
||||
|
|
@ -114,6 +116,15 @@
|
|||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<!-- Cost -->
|
||||
<groupbox closable="false">
|
||||
<caption label="${i18n:_('Cost')}" />
|
||||
<hbox>
|
||||
<!-- Cost status -->
|
||||
<costStatus id="costStatus" />
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="projectDashboardNoTasksWarningDiv" visible="false">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue