/*
* 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.orders.entities;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Diego Pino Garcia <dpino@igalia.com>
*/
public abstract class HoursGroupHandler<T> implements IHoursGroupHandler<T> {
public boolean isTotalHoursValid(Integer total, final Set<HoursGroup> hoursGroups) {
if (total == null) {
return false;
}
int newTotal = 0;
for (HoursGroup hoursGroup : hoursGroups) {
if (hoursGroup.isFixedPercentage()) {
newTotal += hoursGroup.getPercentage().multiply(
new BigDecimal(total).setScale(2)).toBigInteger()
.intValue();
}
}
if (newTotal > total) {
return false;
}
return true;
}
public boolean isPercentageValid(final Set<HoursGroup> hoursGroups) {
BigDecimal newPercentage = new BigDecimal(0).setScale(2);
for (HoursGroup hoursGroup : hoursGroups) {
if (hoursGroup.isFixedPercentage()) {
newPercentage = newPercentage.add(hoursGroup.getPercentage());
}
}
if (newPercentage.compareTo(new BigDecimal(1).setScale(2)) > 0) {
return false;
}
return true;
}
public Integer calculateTotalHours(Set<HoursGroup> hoursGroups) {
Integer result = 0;
for (HoursGroup hoursGroup : hoursGroups) {
Integer workingHours = hoursGroup.getWorkingHours();
if (workingHours != null) {
result += workingHours;
}
}
return result;
}
/**
* Calculates the total number of working hours in a set of
* {@link HoursGroup} taking into account just {@link HoursGroup} with
* NO_FIXED as policy.
* @param hoursGroups
* A {@link HoursGroup} set
* @return The sum of NO_FIXED {@link HoursGroup}
*/
private Integer calculateTotalHoursNoFixed(Set<HoursGroup> hoursGroups) {
Integer result = 0;
for (HoursGroup hoursGroup : hoursGroups) {
if (!hoursGroup.isFixedPercentage()) {
result += hoursGroup.getWorkingHours();
}
}
return result;
}
public void recalculateHoursGroups(T orderLine) {
Set<HoursGroup> hoursGroups = getHoursGroup(orderLine);
Integer total = calculateTotalHours(hoursGroups);
BigDecimal totalBigDecimal = new BigDecimal(total).setScale(2);
// For each HoursGroup with FIXED_PERCENTAGE, the workingHours are
// calculated
for (HoursGroup hoursGroup : hoursGroups) {
if (hoursGroup.isFixedPercentage()) {
Integer workingHours = hoursGroup.getPercentage().multiply(
totalBigDecimal).toBigInteger().intValue();
hoursGroup.setWorkingHours(workingHours);
}
}
Integer newTotal = calculateTotalHours(hoursGroups);
// If the total was modified
if (!newTotal.equals(total)) {
Integer totalNoFixed = calculateTotalHoursNoFixed(hoursGroups);
// For each HoursGroup without FIXED_PERCENTAGE, the hours are
// proportionally shared
for (HoursGroup hoursGroup : hoursGroups) {
if (!hoursGroup.isFixedPercentage()) {
Integer hours = hoursGroup.getWorkingHours();
Integer newHours = (int) (((float) hours / totalNoFixed) * (total - (newTotal - totalNoFixed)));
hoursGroup.setWorkingHours(newHours);
}
}
}
newTotal = calculateTotalHours(hoursGroups);
// If there's still some remaining hours
if (newTotal.compareTo(total) < 0) {
// Add a new HourGroup with the remaining hours
HoursGroup hoursGroup = createHoursGroup(orderLine);
hoursGroup.updateMyCriterionRequirements();
hoursGroup.setWorkingHours(total - newTotal);
hoursGroups.add(hoursGroup);
}
// Then the percentages for the HoursGroup without FIXED_PERCENTAGE are
// recalculated.
for (HoursGroup hoursGroup : hoursGroups) {
if (!hoursGroup.isFixedPercentage()) {
if (totalBigDecimal.equals(new BigDecimal(0).setScale(2))) {
hoursGroup.setPercentage(new BigDecimal(0).setScale(2));
} else {
BigDecimal hoursBigDecimal = new BigDecimal(hoursGroup
.getWorkingHours()).setScale(2);
BigDecimal percentage = hoursBigDecimal.divide(
totalBigDecimal, BigDecimal.ROUND_DOWN);
hoursGroup.setPercentage(percentage);
}
}
}
}
protected abstract Set<HoursGroup> getHoursGroup(T orderLine);
protected abstract HoursGroup createHoursGroup(T orderLine);
private void updateHoursGroups(T orderLine, Integer workHours) {
final Set<HoursGroup> hoursGroups = getHoursGroup(orderLine);
Set<HoursGroup> newHoursGroups = new HashSet<HoursGroup>();
// Divide HourGroup depending on policy
Set<HoursGroup> fixedPercentageGroups = new HashSet<HoursGroup>();
Set<HoursGroup> noFixedGroups = new HashSet<HoursGroup>();
for (HoursGroup hoursGroup : hoursGroups) {
if (hoursGroup.isFixedPercentage()) {
fixedPercentageGroups.add(hoursGroup);
} else {
noFixedGroups.add(hoursGroup);
}
}
// For every HourGroup with FIXED_PERCENTAGE, workingHours will be
// calculated
for (HoursGroup hoursGroup : fixedPercentageGroups) {
Integer hours = hoursGroup.getPercentage().multiply(
new BigDecimal(workHours).setScale(2)).toBigInteger()
.intValue();
hoursGroup.setWorkingHours(hours);
newHoursGroups.add(hoursGroup);
}
Integer newTotal = calculateTotalHours(newHoursGroups);
if (newTotal.compareTo(workHours) > 0) {
throw new RuntimeException("Unreachable code");
} else if (newTotal.compareTo(workHours) == 0) {
for (HoursGroup hoursGroup : noFixedGroups) {
hoursGroup.setWorkingHours(0);
newHoursGroups.add(hoursGroup);
}
} else if (newTotal.compareTo(workHours) < 0) {
// Proportional sharing
Integer oldNoFixed = calculateTotalHoursNoFixed(hoursGroups);
Integer newNoFixed = workHours - newTotal;
for (HoursGroup hoursGroup : noFixedGroups) {
Integer newHours;
if (oldNoFixed == 0) {
newHours = (int) ((float) newNoFixed / hoursGroups.size());
} else {
newHours = (int) ((float) hoursGroup.getWorkingHours()
/ oldNoFixed * newNoFixed);
}
hoursGroup.setWorkingHours(newHours);
newHoursGroups.add(hoursGroup);
}
}
// If there're remaining hours
newTotal = calculateTotalHours(newHoursGroups);
if (newTotal.compareTo(workHours) < 0) {
// Add a new HourGroup with the remaining hours
HoursGroup hoursGroup = createHoursGroup(orderLine);
hoursGroup.setWorkingHours(workHours - newTotal);
newHoursGroups.add(hoursGroup);
}
// Set the attribute with the new hours group calculated
setHoursGroups(orderLine, newHoursGroups);
// Re-calculate percentages
recalculateHoursGroups(orderLine);
}
protected abstract void setHoursGroups(T orderLine, Set<HoursGroup> hoursGroups);
/**
* Set the total working hours of the {@link OrderLine} taking into account
* the {@link HoursGroup} policies.
* @param workHours
* The desired value to set as total working hours
* @throws IllegalArgumentException
* If parameter is less than 0 or if it's not possible to set
* this value taking into account {@link HoursGroup} policies.
*/
@Override
public void setWorkHours(T orderLine, Integer workHours) throws IllegalArgumentException {
if (workHours == null) {
workHours = 0;
}
if (workHours < 0) {
throw new IllegalArgumentException(
"workHours should be greater or equals to 0");
}
if (hoursGroupsIsEmpty(orderLine)) {
HoursGroup hoursGroup = createHoursGroup(orderLine);
hoursGroup.setWorkingHours(workHours);
hoursGroup.setPercentage((new BigDecimal(1).setScale(2)));
addHoursGroup(orderLine, hoursGroup);
} else {
if (!isTotalHoursValid(workHours, getHoursGroup(orderLine))) {
throw new IllegalArgumentException(
"\"workHours\" value is not valid, taking into "
+ "account the current list of HoursGroup");
}
updateHoursGroups(orderLine, workHours);
}
}
protected abstract boolean hoursGroupsIsEmpty(T orderLine);
protected abstract void addHoursGroup(T orderLine, HoursGroup hoursGroup);
}