Code refactoring

Create utility class 'CompanyEarnedValueCalculator' for calculating all Earned Value indicators related with Company

FEA: ItEr76S15OrganizingPerProjectDashboard
This commit is contained in:
Diego Pino 2012-05-07 12:43:23 +02:00
parent f0ab50f78f
commit a68efea35b
6 changed files with 245 additions and 101 deletions

View file

@ -0,0 +1,159 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.Interval;
import org.libreplan.business.hibernate.notification.PredefinedDatabaseSnapshots;
import org.libreplan.business.workreports.entities.WorkReportLine;
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 <dpino@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class CompanyEarnedValueCalculator implements ICompanyEarnedValueCalculator {
@Autowired
private PredefinedDatabaseSnapshots databaseSnapshots;
@Override
@Transactional(readOnly = true)
public SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkScheduled(AvailabilityTimeLine.Interval interval) {
Map<TaskElement, SortedMap<LocalDate, BigDecimal>> estimatedCostPerTask = databaseSnapshots
.snapshotEstimatedCostPerTask();
Collection<TaskElement> list = filterTasksByDate(
estimatedCostPerTask.keySet(), interval);
SortedMap<LocalDate, BigDecimal> estimatedCost = new TreeMap<LocalDate, BigDecimal>();
for (TaskElement each : list) {
addCost(estimatedCost, estimatedCostPerTask.get(each));
}
return accumulateResult(estimatedCost);
}
private List<TaskElement> filterTasksByDate(
Collection<TaskElement> tasks,
AvailabilityTimeLine.Interval interval) {
List<TaskElement> result = new ArrayList<TaskElement>();
for(TaskElement task : tasks) {
if (interval.includes(task.getStartAsLocalDate())
|| interval.includes(task.getEndAsLocalDate())) {
result.add(task);
}
}
return result;
}
private List<WorkReportLine> filterWorkReportLinesByDate(
Collection<WorkReportLine> lines,
AvailabilityTimeLine.Interval interval) {
List<WorkReportLine> result = new ArrayList<WorkReportLine>();
for(WorkReportLine line: lines) {
if (interval.includes(line.getLocalDate())) {
result.add(line);
}
}
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 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;
}
@Override
public SortedMap<LocalDate, BigDecimal> calculateActualCostWorkPerformed(
Interval interval) {
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
Collection<WorkReportLine> workReportLines = filterWorkReportLinesByDate(
databaseSnapshots.snapshotWorkReportLines(),
interval);
if (workReportLines.isEmpty()) {
return result;
}
for (WorkReportLine workReportLine : workReportLines) {
LocalDate day = new LocalDate(workReportLine.getDate());
BigDecimal cost = workReportLine.getEffort()
.toHoursAsDecimalWithScale(2);
if (!result.containsKey(day)) {
result.put(day, BigDecimal.ZERO);
}
result.put(day, result.get(day).add(cost));
}
return accumulateResult(result);
}
@Override
public SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkPerformed(
Interval interval) {
Map<TaskElement, SortedMap<LocalDate, BigDecimal>> advanceCostPerTask = databaseSnapshots
.snapshotAdvanceCostPerTask();
Collection<TaskElement> tasks = filterTasksByDate(
advanceCostPerTask.keySet(), interval);
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
for (TaskElement each : tasks) {
addCost(result, advanceCostPerTask.get(each));
}
return result;
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 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.business.planner.entities;
import java.math.BigDecimal;
import java.util.SortedMap;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
/**
* @author Diego Pino García <dpino@igalia.com>
*
* Utility class for calculating all 'Earned Value' measurements
*/
public interface ICompanyEarnedValueCalculator {
SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkScheduled(
AvailabilityTimeLine.Interval interval);
SortedMap<LocalDate, BigDecimal> calculateActualCostWorkPerformed(
AvailabilityTimeLine.Interval interval);
SortedMap<LocalDate, BigDecimal> calculateBudgetedCostWorkPerformed(
AvailabilityTimeLine.Interval interval);
}

View file

@ -508,6 +508,12 @@ public abstract class ChartFiller implements IChartFiller {
return result;
}
protected SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
SortedMap<LocalDate, BigDecimal> values, Interval interval) {
return calculatedValueForEveryDay(values, interval.getStart(),
interval.getFinish());
}
protected SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
SortedMap<LocalDate, BigDecimal> map, Date start, Date finish) {
return calculatedValueForEveryDay(map, new LocalDate(start),

View file

@ -418,4 +418,10 @@ public abstract class EarnedValueChartFiller extends ChartFiller {
}
}
public void setIndicatorInInterval(EarnedValueType type,
Interval interval, SortedMap<LocalDate, BigDecimal> values) {
addZeroBeforeTheFirstValue(values);
indicators.put(type, calculatedValueForEveryDay(values, interval));
}
}

View file

@ -38,8 +38,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
@ -55,6 +53,7 @@ import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.planner.chart.ILoadChartData;
import org.libreplan.business.planner.chart.ResourceLoadChartData;
import org.libreplan.business.planner.entities.ICompanyEarnedValueCalculator;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskMilestone;
@ -62,7 +61,6 @@ import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.workreports.entities.WorkReportLine;
import org.libreplan.web.planner.TaskElementAdapter;
import org.libreplan.web.planner.TaskGroupPredicate;
import org.libreplan.web.planner.chart.Chart;
@ -132,6 +130,9 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
@Autowired
private IAdHocTransactionService transactionService;
@Autowired
private ICompanyEarnedValueCalculator earnedValueCalculator;
private List<IZoomLevelChangedListener> keepAliveZoomListeners = new ArrayList<IZoomLevelChangedListener>();
private List<Checkbox> earnedValueChartConfigurationCheckboxes = new ArrayList<Checkbox>();
@ -275,6 +276,7 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
setupChart(chartLoadTimeplot, new CompanyLoadChartFiller(), planner);
chartComponent.getTabs().getLastChild().addEventListener(Events.ON_SELECT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
createOnDemandEarnedValueTimePlot(chartComponent, planner);
event.getTarget().removeEventListener(Events.ON_SELECT, this);
@ -783,75 +785,33 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
}
/**
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
*
*/
private class CompanyEarnedValueChartFiller extends EarnedValueChartFiller {
@Override
protected void calculateBudgetedCostWorkScheduled(Interval interval) {
Map<TaskElement, SortedMap<LocalDate, BigDecimal>> estimatedCostPerTask =
databaseSnapshots.snapshotEstimatedCostPerTask();
Collection<TaskElement> list = filterTasksByDate(
estimatedCostPerTask.keySet(), getFilterInterval());
SortedMap<LocalDate, BigDecimal> estimatedCost = new TreeMap<LocalDate, BigDecimal>();
for (TaskElement taskElement : list) {
addCost(estimatedCost, estimatedCostPerTask.get(taskElement));
}
estimatedCost = accumulateResult(estimatedCost);
addZeroBeforeTheFirstValue(estimatedCost);
indicators.put(EarnedValueType.BCWS, calculatedValueForEveryDay(
estimatedCost, interval.getStart(), interval.getFinish()));
setIndicatorInInterval(EarnedValueType.BCWS, interval,
earnedValueCalculator
.calculateBudgetedCostWorkScheduled(getFilterInterval()));
}
@Override
protected void calculateActualCostWorkPerformed(Interval interval) {
SortedMap<LocalDate, BigDecimal> workReportCost = getWorkReportCost();
workReportCost = accumulateResult(workReportCost);
addZeroBeforeTheFirstValue(workReportCost);
indicators.put(EarnedValueType.ACWP, calculatedValueForEveryDay(
workReportCost, interval.getStart(), interval.getFinish()));
}
private SortedMap<LocalDate, BigDecimal> getWorkReportCost() {
SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
Collection<WorkReportLine> workReportLines = filterWorkReportLinesByDate(
databaseSnapshots.snapshotWorkReportLines(),
getFilterInterval());
if (workReportLines.isEmpty()) {
return result;
}
for (WorkReportLine workReportLine : workReportLines) {
LocalDate day = new LocalDate(workReportLine.getDate());
BigDecimal cost = workReportLine.getEffort()
.toHoursAsDecimalWithScale(2);
if (!result.containsKey(day)) {
result.put(day, BigDecimal.ZERO);
}
result.put(day, result.get(day).add(cost));
}
return result;
setIndicatorInInterval(EarnedValueType.ACWP, interval,
earnedValueCalculator
.calculateActualCostWorkPerformed(getFilterInterval()));
}
@Override
protected void calculateBudgetedCostWorkPerformed(Interval interval) {
Map<TaskElement, SortedMap<LocalDate, BigDecimal>> advanceCostPerTask =
databaseSnapshots.snapshotAdvanceCostPerTask();
Collection<TaskElement> list = filterTasksByDate(
advanceCostPerTask.keySet(), getFilterInterval());
SortedMap<LocalDate, BigDecimal> advanceCost = new TreeMap<LocalDate, BigDecimal>();
for (TaskElement taskElement : list) {
addCost(advanceCost, advanceCostPerTask.get(taskElement));
}
addZeroBeforeTheFirstValue(advanceCost);
indicators.put(EarnedValueType.BCWP, calculatedValueForEveryDay(
advanceCost, interval.getStart(), interval.getFinish()));
setIndicatorInInterval(EarnedValueType.BCWP, interval,
earnedValueCalculator
.calculateBudgetedCostWorkPerformed(getFilterInterval()));
}
@Override
@ -859,33 +819,9 @@ public class CompanyPlanningModel implements ICompanyPlanningModel {
return getEarnedValueSelectedIndicators();
}
private List<TaskElement> filterTasksByDate(
Collection<TaskElement> tasks,
AvailabilityTimeLine.Interval interval) {
List<TaskElement> result = new ArrayList<TaskElement>();
for(TaskElement task : tasks) {
if (interval.includes(task.getStartAsLocalDate())
|| interval.includes(task.getEndAsLocalDate())) {
result.add(task);
}
}
return result;
}
private List<WorkReportLine> filterWorkReportLinesByDate(
Collection<WorkReportLine> lines,
AvailabilityTimeLine.Interval interval) {
List<WorkReportLine> result = new ArrayList<WorkReportLine>();
for(WorkReportLine line: lines) {
if (interval.includes(line.getLocalDate())) {
result.add(line);
}
}
return result;
}
}
@Override
@Transactional(readOnly=true)
public ProgressType getProgressTypeFromConfiguration() {
return configurationDAO.getConfiguration().getProgressType();

View file

@ -38,7 +38,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
@ -1389,6 +1388,12 @@ public class OrderPlanningModel implements IOrderPlanningModel {
}
/**
*
* @author Manuel Rego Casasnovas <mrego@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
*
*/
class OrderEarnedValueChartFiller extends EarnedValueChartFiller {
private Order order;
@ -1418,18 +1423,6 @@ public class OrderPlanningModel implements IOrderPlanningModel {
.calculateBudgetedCostWorkPerformed(order));
}
private void setIndicatorInInterval(EarnedValueType type,
Interval interval, SortedMap<LocalDate, BigDecimal> values) {
addZeroBeforeTheFirstValue(values);
indicators.put(type, calculatedValueForEveryDay(values, interval));
}
private SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
SortedMap<LocalDate, BigDecimal> values, Interval interval) {
return calculatedValueForEveryDay(values, interval.getStart(),
interval.getFinish());
}
@Override
protected Set<EarnedValueType> getSelectedIndicators() {
return getEarnedValueSelectedIndicators();