/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 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.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.planner.entities.DayAssignment.FilterType;
import org.libreplan.business.workingday.EffortDuration;
/**
*
* @author Diego Pino García <dpino@igalia.com>
*
* This is a class, directly associated with a TaskGroup with no parent
* (TaskRoot element), which is used to store data about the whole
* scheduling
*
*/
public class PlanningData extends BaseEntity {
private static Log LOG = LogFactory.getLog(PlanningData.class);
public static PlanningData create(TaskGroup rootTask) {
return create(new PlanningData(rootTask));
}
private TaskGroup rootTask;
private BigDecimal progressAllByNumHours;
private BigDecimal progressByDuration;
private BigDecimal progressByNumHours;
private BigDecimal theoreticalProgressByNumHoursForAllTasks;
private BigDecimal theoreticalProgressByDurationForCriticalPath;
private BigDecimal theoreticalProgressByNumHoursForCriticalPath;
public PlanningData() {
}
private BigDecimal zeroIfNull(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
public BigDecimal getProgressAllByNumHours() {
return zeroIfNull(progressAllByNumHours);
}
public BigDecimal getProgressByDuration() {
return zeroIfNull(progressByDuration);
}
public BigDecimal getProgressByNumHours() {
return zeroIfNull(progressByNumHours);
}
public BigDecimal getTheoreticalProgressByNumHoursForAllTasks() {
return zeroIfNull(theoreticalProgressByNumHoursForAllTasks);
}
public BigDecimal getTheoreticalProgressByDurationForCriticalPath() {
return zeroIfNull(theoreticalProgressByDurationForCriticalPath);
}
public BigDecimal getTheoreticalProgressByNumHoursForCriticalPath() {
return zeroIfNull(theoreticalProgressByNumHoursForCriticalPath);
}
private PlanningData(TaskGroup rootTask) {
this.rootTask = rootTask;
}
public void update(List<Task> criticalPath) {
if (criticalPath.isEmpty()) {
LOG.warn("it can't be updated because the critical path provided is empty");
return;
}
progressAllByNumHours = rootTask.getOrderElement()
.getAdvancePercentageChildren();
progressByDuration = calculateByDuration(criticalPath);
progressByNumHours = calculateByNumHours(criticalPath);
Date now = new Date();
theoreticalProgressByNumHoursForAllTasks = rootTask.getTheoreticalAdvancePercentageUntilDate(now);
theoreticalProgressByDurationForCriticalPath = calculateTheoreticalAdvanceByDurationForCriticalPath(criticalPath, now);
theoreticalProgressByNumHoursForCriticalPath = calculateTheoreticalAdvanceByNumHoursForCriticalPath(criticalPath, now);
}
private BigDecimal calculateByDuration(List<Task> criticalPath) {
int totalDuration = 0;
BigDecimal totalProgress = BigDecimal.ZERO;
for (Task each : criticalPath) {
int duration = each.getWorkableDays();
BigDecimal progress = each.getAdvancePercentage().multiply(
BigDecimal.valueOf(duration));
totalDuration = totalDuration + duration;
totalProgress = totalProgress.add(progress);
}
return divide(totalProgress, totalDuration);
}
/**
* Prevents division by zero
*
* @param numerator
* @param denominator
* @return
*/
private BigDecimal divide(BigDecimal numerator, int denominator) {
if (denominator == 0) {
return BigDecimal.ZERO;
}
return numerator.divide(BigDecimal.valueOf(denominator), 8,
BigDecimal.ROUND_HALF_EVEN);
}
private BigDecimal calculateByNumHours(List<Task> criticalPath) {
int totalNumHours = 0, numHours;
BigDecimal totalProgress = BigDecimal.ZERO, progress;
for (Task each: criticalPath) {
numHours = each.getAssignedHours();
if (numHours == 0) {
numHours = each.getTotalHours();
}
progress = each.getAdvancePercentage();
progress = progress.multiply(BigDecimal.valueOf(numHours));
totalNumHours += numHours;
totalProgress = totalProgress.add(progress);
}
return divide(totalProgress, totalNumHours);
}
private BigDecimal calculateTheoreticalAdvanceByNumHoursForCriticalPath(
List<Task> criticalPath, Date limit) {
EffortDuration theoreticalCompletedTime = EffortDuration.zero();
EffortDuration totalAssignedTime = EffortDuration.zero();
for (Task each: criticalPath) {
theoreticalCompletedTime = EffortDuration.sum(
theoreticalCompletedTime,
each.getTheoreticalCompletedTimeUntilDate(limit));
totalAssignedTime = EffortDuration.sum(
totalAssignedTime,
AggregateOfDayAssignments.create(
each.getDayAssignments(FilterType.KEEP_ALL))
.getTotalTime());
}
return theoreticalCompletedTime.dividedByAndResultAsBigDecimal(totalAssignedTime);
}
private BigDecimal calculateTheoreticalAdvanceByDurationForCriticalPath(
List<Task> criticalPath, Date limit) {
int totalTheoreticalProgressDays = 0;
int totalDurationDays = 0;
LocalDate limitLocalDate = new LocalDate(limit);
for (Task each : criticalPath) {
totalTheoreticalProgressDays += each.getWorkableDaysFromLimitedByEndOfTheTask(limitLocalDate);
totalDurationDays += each.getWorkableDays();
}
return divide(new BigDecimal(totalTheoreticalProgressDays), totalDurationDays);
}
}