diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusController.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusController.java
new file mode 100644
index 000000000..47097524a
--- /dev/null
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusController.java
@@ -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 .
+ */
+
+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
+ *
+ * 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()));
+ }
+
+}
\ No newline at end of file
diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusModel.java
new file mode 100644
index 000000000..1198c5443
--- /dev/null
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/CostStatusModel.java
@@ -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 .
+ */
+
+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
+ *
+ * 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 actualCost = calculateActualCostWorkPerformed();
+ BigDecimal result = actualCost.get(date);
+ return (result != null) ? result : BigDecimal.ZERO;
+ }
+
+ private SortedMap calculateActualCostWorkPerformed() {
+ SortedMap result = new TreeMap();
+ for (TaskElement taskElement : getAllTaskElements(order)) {
+ if (taskElement instanceof Task) {
+ addCost(result, getWorkReportCost((Task) taskElement));
+ }
+ }
+ return accumulateResult(result);
+ }
+
+ private SortedMap accumulateResult(
+ SortedMap map) {
+ SortedMap result = new TreeMap();
+ 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 currentCost,
+ SortedMap 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 getAllTaskElements(Order order) {
+ List result = order.getAllChildrenAssociatedTaskElements();
+ result.add(order.getAssociatedTaskElement());
+ return result;
+ }
+
+ private SortedMap getWorkReportCost(Task taskElement) {
+ return hoursCostCalculator.getWorkReportCost(taskElement);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public BigDecimal getBudgetAtCompletion() {
+ SortedMap budgedtedCost = calculateBudgetedCostWorkScheduled();
+ LocalDate lastKey = budgedtedCost.lastKey();
+ return (lastKey) != null ? budgedtedCost.get(lastKey) : BigDecimal.ZERO;
+ }
+
+ private SortedMap calculateBudgetedCostWorkScheduled() {
+ SortedMap result = new TreeMap();
+ for (TaskElement taskElement : getAllTaskElements(order)) {
+ if (taskElement instanceof Task) {
+ addCost(result, getEstimatedCost((Task) taskElement));
+ }
+ }
+ return accumulateResult(result);
+ }
+
+ private SortedMap getEstimatedCost(Task task) {
+ return hoursCostCalculator.getEstimatedCost(task);
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public BigDecimal getBudgetedCostWorkPerformedAt(LocalDate date) {
+ SortedMap budgetedCost = calculateBudgetedCostWorkPerformed();
+ BigDecimal result = budgetedCost.get(date);
+ return (result != null) ? result : BigDecimal.ZERO;
+ }
+
+ private SortedMap calculateBudgetedCostWorkPerformed() {
+ SortedMap estimatedCost = new TreeMap();
+ for (TaskElement taskElement : getAllTaskElements(order)) {
+ if (taskElement instanceof Task) {
+ addCost(estimatedCost, getAdvanceCost((Task) taskElement));
+ }
+ }
+ return accumulateResult(estimatedCost);
+ }
+
+ private SortedMap 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;
+ }
+
+}
\ No newline at end of file
diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java
index 5d2c5a014..0702dbacc 100644
--- a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java
@@ -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
diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/ICostStatusModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/ICostStatusModel.java
new file mode 100644
index 000000000..ae75c0891
--- /dev/null
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/ICostStatusModel.java
@@ -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 .
+ */
+
+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
+ *
+ */
+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);
+
+}
\ No newline at end of file
diff --git a/libreplan-webapp/src/main/webapp/dashboard/_costStatus.zul b/libreplan-webapp/src/main/webapp/dashboard/_costStatus.zul
new file mode 100644
index 000000000..780e00fe7
--- /dev/null
+++ b/libreplan-webapp/src/main/webapp/dashboard/_costStatus.zul
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul b/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul
index 54a57f812..61cefce43 100644
--- a/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul
+++ b/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul
@@ -17,6 +17,8 @@
along with this program. If not, see .
-->
+
+
+
+
+
+
+
+
+
+
+