[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:
parent
5f5275f62f
commit
2d96c15d73
8 changed files with 183 additions and 11 deletions
|
|
@ -21,6 +21,8 @@ package org.navalplanner.business.calendars.entities;
|
|||
|
||||
import static org.navalplanner.business.i18n.I18nHelper._;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
|
@ -38,6 +40,21 @@ import org.navalplanner.business.workingday.EffortDuration.Granularity;
|
|||
*/
|
||||
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) {
|
||||
return new Capacity(EffortDuration.min(a.getStandardEffort(),
|
||||
b.getStandardEffort()), minExtraEffort(a, b));
|
||||
|
|
@ -72,6 +89,11 @@ public class Capacity {
|
|||
return new Capacity(standardEffort, null);
|
||||
}
|
||||
|
||||
private static Capacity noCapacity() {
|
||||
return Capacity.create(EffortDuration.hours(0))
|
||||
.notOverAssignableWithoutLimit();
|
||||
}
|
||||
|
||||
public static Capacity zero() {
|
||||
return new Capacity(EffortDuration.zero(), EffortDuration.zero());
|
||||
}
|
||||
|
|
@ -211,6 +233,22 @@ public class Capacity {
|
|||
.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() {
|
||||
return !getStandardEffort().isZero() || isOverAssignableWithoutLimit()
|
||||
|| !getAllowedExtraEffort().isZero();
|
||||
|
|
|
|||
|
|
@ -238,6 +238,15 @@ public class EffortDistributor {
|
|||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
EffortDuration totalDuration) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import org.apache.commons.lang.Validate;
|
|||
import org.hibernate.validator.Valid;
|
||||
import org.joda.time.LocalDate;
|
||||
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.planner.entities.EffortDistributor.IResourceSelector;
|
||||
import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration;
|
||||
|
|
@ -229,6 +230,11 @@ public class GenericResourceAllocation extends
|
|||
getCriterions(), resources);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Capacity getCapacityAt(PartialDay day) {
|
||||
return hoursDistributor.getCapacityAt(day);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IAssignedEffortForResource assignedEffortCalculatorOverriden = null;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
package org.navalplanner.business.planner.entities;
|
||||
|
||||
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 java.math.BigDecimal;
|
||||
|
|
@ -46,6 +45,7 @@ import org.hibernate.validator.NotNull;
|
|||
import org.joda.time.LocalDate;
|
||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
||||
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.ICalendar;
|
||||
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.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
|
||||
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.ResourcesPerDayModification;
|
||||
import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator;
|
||||
|
|
@ -974,23 +975,27 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
private EffortDuration[] secondsDistribution(
|
||||
AvailabilityTimeLine availability, Iterable<PartialDay> days,
|
||||
EffortDuration duration) {
|
||||
List<Share> shares = new ArrayList<Share>();
|
||||
List<Capacity> capacities = new ArrayList<Capacity>();
|
||||
for (PartialDay each : days) {
|
||||
shares.add(getShareAt(each, availability));
|
||||
capacities.add(getCapacity(availability, each));
|
||||
}
|
||||
ShareDivision original = ShareDivision.create(shares);
|
||||
ShareDivision newShare = original.plus(duration.getSeconds());
|
||||
return fromSecondsToDurations(original.to(newShare));
|
||||
Distributor distributor = Distributor.among(capacities);
|
||||
return distributor.distribute(duration).toArray(
|
||||
new EffortDuration[0]);
|
||||
}
|
||||
|
||||
private EffortDuration[] fromSecondsToDurations(int[] seconds) {
|
||||
EffortDuration[] result = new EffortDuration[seconds.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = seconds(seconds[i]);
|
||||
private Capacity getCapacity(AvailabilityTimeLine availability,
|
||||
PartialDay day) {
|
||||
if (availability.isValid(day.getDate())) {
|
||||
return getCapacityAt(day);
|
||||
} else {
|
||||
return Capacity.create(hours(0))
|
||||
.notOverAssignableWithoutLimit();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract Capacity getCapacityAt(PartialDay each);
|
||||
|
||||
private Share getShareAt(PartialDay day,
|
||||
AvailabilityTimeLine availability) {
|
||||
if (availability.isValid(day.getDate())) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.joda.time.LocalDate;
|
|||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
|
||||
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.ICalendar;
|
||||
import org.navalplanner.business.common.ProportionalDistributor;
|
||||
|
|
@ -199,6 +200,12 @@ public class SpecificResourceAllocation extends
|
|||
protected AvailabilityTimeLine getResourcesAvailability() {
|
||||
return AvailabilityCalculator.getCalendarAvailabilityFor(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Capacity getCapacityAt(PartialDay day) {
|
||||
return day.limitCapacity(getAllocationCalendar()
|
||||
.getCapacityWithOvertime(day.getDate()));
|
||||
}
|
||||
}
|
||||
|
||||
public IEffortDistributor<SpecificDayAssignment> createEffortDistributor() {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.hibernate.validator.NotNull;
|
|||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.navalplanner.business.calendars.entities.Capacity;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
|
@ -273,6 +274,26 @@ public class IntraDayDate implements Comparable<IntraDayDate> {
|
|||
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() {
|
||||
return start.getEffortDuration().isZero()
|
||||
&& end.getEffortDuration().isZero();
|
||||
|
|
|
|||
|
|
@ -208,4 +208,63 @@ public class CapacityTest {
|
|||
assertTrue(withSomeExtraHours
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ public class SpecificResourceAllocationTest {
|
|||
this.calendar.asDurationOn(isA(PartialDay.class),
|
||||
isA(ResourcesPerDay.class)))
|
||||
.andAnswer(asDurationAnswer).anyTimes();
|
||||
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
|
||||
.andReturn(
|
||||
Capacity.create(hours(hours))
|
||||
.overAssignableWithoutLimit()).anyTimes();
|
||||
expect(this.calendar.getAvailability()).andReturn(
|
||||
AvailabilityTimeLine.allValid()).anyTimes();
|
||||
replay(this.calendar);
|
||||
|
|
@ -124,6 +128,20 @@ public class SpecificResourceAllocationTest {
|
|||
.getStandardEffort());
|
||||
}
|
||||
}).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>() {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue