[Bug #1136] Fix bug

The distribution of the effort must consider the extra hours and the
resources satisfying the generic allocation.

FEA: ItEr75S04BugFixing
This commit is contained in:
Óscar González Fernández 2011-08-11 19:41:49 +02:00
parent 5f5275f62f
commit 2d96c15d73
8 changed files with 183 additions and 11 deletions

View file

@ -21,6 +21,8 @@ package org.navalplanner.business.calendars.entities;
import static org.navalplanner.business.i18n.I18nHelper._; import static org.navalplanner.business.i18n.I18nHelper._;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
@ -38,6 +40,21 @@ import org.navalplanner.business.workingday.EffortDuration.Granularity;
*/ */
public class Capacity { public class Capacity {
public static Capacity sum(Capacity... capacities) {
return sum(Arrays.asList(capacities));
}
public static Capacity sum(Collection<? extends Capacity> capacities) {
EffortDuration standard = EffortDuration.zero();
EffortDuration extra = EffortDuration.zero();
for (Capacity each : capacities) {
standard = standard.plus(each.getStandardEffort());
extra = extra == null || each.isOverAssignableWithoutLimit() ? null
: extra.plus(each.getAllowedExtraEffort());
}
return Capacity.create(standard).withAllowedExtraEffort(extra);
}
public static Capacity min(Capacity a, Capacity b) { public static Capacity min(Capacity a, Capacity b) {
return new Capacity(EffortDuration.min(a.getStandardEffort(), return new Capacity(EffortDuration.min(a.getStandardEffort(),
b.getStandardEffort()), minExtraEffort(a, b)); b.getStandardEffort()), minExtraEffort(a, b));
@ -72,6 +89,11 @@ public class Capacity {
return new Capacity(standardEffort, null); return new Capacity(standardEffort, null);
} }
private static Capacity noCapacity() {
return Capacity.create(EffortDuration.hours(0))
.notOverAssignableWithoutLimit();
}
public static Capacity zero() { public static Capacity zero() {
return new Capacity(EffortDuration.zero(), EffortDuration.zero()); return new Capacity(EffortDuration.zero(), EffortDuration.zero());
} }
@ -211,6 +233,22 @@ public class Capacity {
.plus(allowedExtraEffort)) < 0; .plus(allowedExtraEffort)) < 0;
} }
public Capacity minus(EffortDuration assignment) {
if (!hasSpareSpaceForMoreAllocations(assignment)) {
return noCapacity();
}
EffortDuration newStandard = standardEffort.minus(EffortDuration.min(
assignment, standardEffort));
EffortDuration pending = assignment.minus(EffortDuration.min(
standardEffort, assignment));
EffortDuration newExtra = allowedExtraEffort == null ? null
: allowedExtraEffort.minus(EffortDuration.min(pending,
allowedExtraEffort));
return Capacity.create(newStandard).withAllowedExtraEffort(newExtra);
}
public boolean allowsWorking() { public boolean allowsWorking() {
return !getStandardEffort().isZero() || isOverAssignableWithoutLimit() return !getStandardEffort().isZero() || isOverAssignableWithoutLimit()
|| !getAllowedExtraEffort().isZero(); || !getAllowedExtraEffort().isZero();

View file

@ -238,6 +238,15 @@ public class EffortDistributor {
return new ResourceWithAvailableCapacity(resource, available); return new ResourceWithAvailableCapacity(resource, available);
} }
Capacity getAvailableCapacityOn(PartialDay day,
IAssignedEffortForResource assignedEffort) {
Capacity originalCapacity = day.limitCapacity(calendar
.getCapacityWithOvertime(day.getDate()));
EffortDuration alreadyAssigned = assignedEffort
.getAssignedDurationAt(resource, day.getDate());
return originalCapacity.minus(alreadyAssigned);
}
} }
/** /**
@ -316,6 +325,15 @@ public class EffortDistributor {
new OnlyCanWork(), selector) : new OnlyCanWork(); new OnlyCanWork(), selector) : new OnlyCanWork();
} }
public Capacity getCapacityAt(PartialDay day) {
List<Capacity> capacities = new ArrayList<Capacity>();
for (ResourceWithDerivedData each : resourcesAssignableAt(day.getDate())) {
capacities.add(each.getAvailableCapacityOn(day,
assignedEffortForResource));
}
return Capacity.sum(capacities);
}
public List<ResourceWithAssignedDuration> distributeForDay(PartialDay day, public List<ResourceWithAssignedDuration> distributeForDay(PartialDay day,
EffortDuration totalDuration) { EffortDuration totalDuration) {

View file

@ -34,6 +34,7 @@ import org.apache.commons.lang.Validate;
import org.hibernate.validator.Valid; import org.hibernate.validator.Valid;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
import org.navalplanner.business.calendars.entities.Capacity;
import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.calendars.entities.ICalendar;
import org.navalplanner.business.planner.entities.EffortDistributor.IResourceSelector; import org.navalplanner.business.planner.entities.EffortDistributor.IResourceSelector;
import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration; import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration;
@ -229,6 +230,11 @@ public class GenericResourceAllocation extends
getCriterions(), resources); getCriterions(), resources);
} }
@Override
protected Capacity getCapacityAt(PartialDay day) {
return hoursDistributor.getCapacityAt(day);
}
} }
private IAssignedEffortForResource assignedEffortCalculatorOverriden = null; private IAssignedEffortForResource assignedEffortCalculatorOverriden = null;

View file

@ -22,7 +22,6 @@
package org.navalplanner.business.planner.entities; package org.navalplanner.business.planner.entities;
import static org.navalplanner.business.workingday.EffortDuration.hours; import static org.navalplanner.business.workingday.EffortDuration.hours;
import static org.navalplanner.business.workingday.EffortDuration.seconds;
import static org.navalplanner.business.workingday.EffortDuration.zero; import static org.navalplanner.business.workingday.EffortDuration.zero;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -46,6 +45,7 @@ import org.hibernate.validator.NotNull;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
import org.navalplanner.business.calendars.entities.BaseCalendar; import org.navalplanner.business.calendars.entities.BaseCalendar;
import org.navalplanner.business.calendars.entities.Capacity;
import org.navalplanner.business.calendars.entities.CombinedWorkHours; import org.navalplanner.business.calendars.entities.CombinedWorkHours;
import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.calendars.entities.ICalendar;
import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay; import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay;
@ -55,6 +55,7 @@ import org.navalplanner.business.common.BaseEntity;
import org.navalplanner.business.common.Registry; import org.navalplanner.business.common.Registry;
import org.navalplanner.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder; import org.navalplanner.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
import org.navalplanner.business.planner.entities.allocationalgorithms.AllocatorForTaskDurationAndSpecifiedResourcesPerDay; import org.navalplanner.business.planner.entities.allocationalgorithms.AllocatorForTaskDurationAndSpecifiedResourcesPerDay;
import org.navalplanner.business.planner.entities.allocationalgorithms.Distributor;
import org.navalplanner.business.planner.entities.allocationalgorithms.EffortModification; import org.navalplanner.business.planner.entities.allocationalgorithms.EffortModification;
import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification; import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator; import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator;
@ -974,23 +975,27 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
private EffortDuration[] secondsDistribution( private EffortDuration[] secondsDistribution(
AvailabilityTimeLine availability, Iterable<PartialDay> days, AvailabilityTimeLine availability, Iterable<PartialDay> days,
EffortDuration duration) { EffortDuration duration) {
List<Share> shares = new ArrayList<Share>(); List<Capacity> capacities = new ArrayList<Capacity>();
for (PartialDay each : days) { for (PartialDay each : days) {
shares.add(getShareAt(each, availability)); capacities.add(getCapacity(availability, each));
} }
ShareDivision original = ShareDivision.create(shares); Distributor distributor = Distributor.among(capacities);
ShareDivision newShare = original.plus(duration.getSeconds()); return distributor.distribute(duration).toArray(
return fromSecondsToDurations(original.to(newShare)); new EffortDuration[0]);
} }
private EffortDuration[] fromSecondsToDurations(int[] seconds) { private Capacity getCapacity(AvailabilityTimeLine availability,
EffortDuration[] result = new EffortDuration[seconds.length]; PartialDay day) {
for (int i = 0; i < result.length; i++) { if (availability.isValid(day.getDate())) {
result[i] = seconds(seconds[i]); return getCapacityAt(day);
} else {
return Capacity.create(hours(0))
.notOverAssignableWithoutLimit();
} }
return result;
} }
protected abstract Capacity getCapacityAt(PartialDay each);
private Share getShareAt(PartialDay day, private Share getShareAt(PartialDay day,
AvailabilityTimeLine availability) { AvailabilityTimeLine availability) {
if (availability.isValid(day.getDate())) { if (availability.isValid(day.getDate())) {

View file

@ -37,6 +37,7 @@ import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.Interval; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.Interval;
import org.navalplanner.business.calendars.entities.Capacity;
import org.navalplanner.business.calendars.entities.CombinedWorkHours; import org.navalplanner.business.calendars.entities.CombinedWorkHours;
import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.calendars.entities.ICalendar;
import org.navalplanner.business.common.ProportionalDistributor; import org.navalplanner.business.common.ProportionalDistributor;
@ -199,6 +200,12 @@ public class SpecificResourceAllocation extends
protected AvailabilityTimeLine getResourcesAvailability() { protected AvailabilityTimeLine getResourcesAvailability() {
return AvailabilityCalculator.getCalendarAvailabilityFor(resource); return AvailabilityCalculator.getCalendarAvailabilityFor(resource);
} }
@Override
protected Capacity getCapacityAt(PartialDay day) {
return day.limitCapacity(getAllocationCalendar()
.getCapacityWithOvertime(day.getDate()));
}
} }
public IEffortDistributor<SpecificDayAssignment> createEffortDistributor() { public IEffortDistributor<SpecificDayAssignment> createEffortDistributor() {

View file

@ -39,6 +39,7 @@ import org.hibernate.validator.NotNull;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.Capacity;
/** /**
* <p> * <p>
@ -273,6 +274,26 @@ public class IntraDayDate implements Comparable<IntraDayDate> {
return durationLimitedByEnd.minus(alreadyElapsedInDay); return durationLimitedByEnd.minus(alreadyElapsedInDay);
} }
public Capacity limitCapacity(Capacity capacity) {
if (capacity.getAllowedExtraEffort() == null) {
EffortDuration effort = limitWorkingDay(capacity
.getStandardEffort());
return Capacity.create(effort).overAssignableWithoutLimit(
capacity.isOverAssignableWithoutLimit());
}
EffortDuration allEffort = capacity.getStandardEffort().plus(
capacity.getAllowedExtraEffort());
EffortDuration limited = limitWorkingDay(allEffort);
EffortDuration newStandard = EffortDuration.min(limited,
capacity.getStandardEffort());
return Capacity
.create(newStandard)
.withAllowedExtraEffort(
EffortDuration.min(limited.minus(newStandard)))
.overAssignableWithoutLimit(
capacity.isOverAssignableWithoutLimit());
}
private boolean isWholeDay() { private boolean isWholeDay() {
return start.getEffortDuration().isZero() return start.getEffortDuration().isZero()
&& end.getEffortDuration().isZero(); && end.getEffortDuration().isZero();

View file

@ -208,4 +208,63 @@ public class CapacityTest {
assertTrue(withSomeExtraHours assertTrue(withSomeExtraHours
.hasSpareSpaceForMoreAllocations(hours(8))); .hasSpareSpaceForMoreAllocations(hours(8)));
} }
@Test
public void testMinusWithZeroExtraHours() {
Capacity c = Capacity.create(hours(8)).notOverAssignableWithoutLimit();
assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2)));
assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0)));
assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0)));
assertFalse(c.minus(hours(6)).isOverAssignableWithoutLimit());
}
@Test
public void testMinusWithExtraHours() {
Capacity c = Capacity.create(hours(8)).withAllowedExtraEffort(hours(2));
assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2)));
assertThat(c.minus(hours(6)).getAllowedExtraEffort(), equalTo(hours(2)));
assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0)));
assertThat(c.minus(hours(8)).getAllowedExtraEffort(), equalTo(hours(2)));
assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0)));
assertThat(c.minus(hours(10)).getAllowedExtraEffort(),
equalTo(hours(0)));
assertThat(c.minus(hours(12)).getAllowedExtraEffort(),
equalTo(hours(0)));
assertFalse(c.minus(hours(10)).isOverAssignableWithoutLimit());
}
@Test
public void testMinusWithUnlimitedExtraHours() {
Capacity c = Capacity.create(hours(8)).overAssignableWithoutLimit();
assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2)));
assertTrue(c.minus(hours(6)).isOverAssignableWithoutLimit());
assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0)));
assertTrue(c.minus(hours(8)).isOverAssignableWithoutLimit());
assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0)));
assertTrue(c.minus(hours(10)).isOverAssignableWithoutLimit());
}
@Test
public void capacitiesCanBeSummed() {
Capacity result = Capacity.sum(
Capacity.create(hours(8)).withAllowedExtraEffort(hours(2)),
Capacity.create(hours(8)).notOverAssignableWithoutLimit());
assertThat(result.getStandardEffort(), equalTo(hours(16)));
assertThat(result.getAllowedExtraEffort(), equalTo(hours(2)));
}
@Test
public void ifSomeOfTheSumandsHasUnlimitedExtraHoursTheSumToo() {
Capacity result = Capacity.sum(Capacity.create(hours(8))
.withAllowedExtraEffort(hours(2)), Capacity.create(hours(8))
.overAssignableWithoutLimit());
assertThat(result.getStandardEffort(), equalTo(hours(16)));
assertTrue(result.isOverAssignableWithoutLimit());
}
} }

View file

@ -87,6 +87,10 @@ public class SpecificResourceAllocationTest {
this.calendar.asDurationOn(isA(PartialDay.class), this.calendar.asDurationOn(isA(PartialDay.class),
isA(ResourcesPerDay.class))) isA(ResourcesPerDay.class)))
.andAnswer(asDurationAnswer).anyTimes(); .andAnswer(asDurationAnswer).anyTimes();
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
.andReturn(
Capacity.create(hours(hours))
.overAssignableWithoutLimit()).anyTimes();
expect(this.calendar.getAvailability()).andReturn( expect(this.calendar.getAvailability()).andReturn(
AvailabilityTimeLine.allValid()).anyTimes(); AvailabilityTimeLine.allValid()).anyTimes();
replay(this.calendar); replay(this.calendar);
@ -124,6 +128,20 @@ public class SpecificResourceAllocationTest {
.getStandardEffort()); .getStandardEffort());
} }
}).anyTimes(); }).anyTimes();
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
.andAnswer(new IAnswer<Capacity>() {
@Override
public Capacity answer() throws Throwable {
LocalDate date = (LocalDate) EasyMock
.getCurrentArguments()[0];
if (answersForDates.containsKey(date)) {
return answersForDates.get(date);
}
return defaultAnswer;
}
}).anyTimes();
final IAnswer<EffortDuration> effortAnswer = new IAnswer<EffortDuration>() { final IAnswer<EffortDuration> effortAnswer = new IAnswer<EffortDuration>() {
@Override @Override