diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java index 03169bf06..604b056b5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java @@ -26,11 +26,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import org.apache.commons.collections.ComparatorUtils; import org.apache.commons.lang.Validate; import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.Capacity; @@ -103,6 +107,15 @@ public class EffortDistributor { return result; } + static List resources( + Collection collection) { + List result = new ArrayList(); + for (ResourceWithAssignedDuration each : collection) { + result.add(each.resource); + } + return result; + } + static Map byResource( Collection durations) { Map result = new HashMap(); @@ -244,6 +257,30 @@ public class EffortDistributor { public int compareTo(ResourceWithAvailableCapacity o) { return available.compareTo(o.available); } + + @SuppressWarnings("unchecked") + static Comparator getComparatorConsidering( + final Set lastResourcesUsed) { + return ComparatorUtils.chainedComparator( + new Comparator() { + + @Override + public int compare(ResourceWithAvailableCapacity o1, + ResourceWithAvailableCapacity o2) { + boolean resource1Used = lastResourcesUsed + .contains(o1.resource); + boolean resource2Used = lastResourcesUsed + .contains(o2.resource); + return asInt(resource1Used) - asInt(resource2Used); + } + + int asInt(boolean b) { + return b ? 1 : 0; + } + + }, ComparatorUtils.naturalComparator()); + } + } private final List resources; @@ -252,6 +289,8 @@ public class EffortDistributor { private final IResourceSelector resourceSelector; + private Set resourcesAlreadyPicked = new HashSet(); + public EffortDistributor(List resources, IAssignedEffortForResource assignedHoursForResource) { this(resources, assignedHoursForResource, null); @@ -269,6 +308,19 @@ public class EffortDistributor { public List distributeForDay(LocalDate date, EffortDuration totalDuration) { + return withCaptureOfResourcesPicked(distributeForDay_(date, + totalDuration)); + } + + private List withCaptureOfResourcesPicked( + List result) { + resourcesAlreadyPicked.addAll(ResourceWithAssignedDuration + .resources(result)); + return result; + } + + private List distributeForDay_( + LocalDate date, EffortDuration totalDuration) { List resourcesAssignable = resourcesAssignableAt(date); List withoutOvertime = assignAllPossibleWithoutOvertime( date, totalDuration, resourcesAssignable); @@ -282,7 +334,8 @@ public class EffortDistributor { date, remaining, ResourceWithAssignedDuration.sumAssignedEffort(withoutOvertime, assignedEffortForResource), resourcesAssignable); - return ResourceWithAssignedDuration.join(withoutOvertime, withOvertime); + return ResourceWithAssignedDuration + .join(withoutOvertime, withOvertime); } private List resourcesAssignableAt(LocalDate day) { @@ -298,7 +351,7 @@ public class EffortDistributor { private List assignAllPossibleWithoutOvertime( LocalDate date, EffortDuration totalDuration, List resourcesAssignable) { - List fromMoreToLessCapacity = resourcesFromMoreToLessCapacityAvailable( + List fromMoreToLessCapacity = resourcesFromMoreDesirableToLess( resourcesAssignable, date); EffortDuration remaining = totalDuration; List result = new ArrayList(); @@ -315,14 +368,16 @@ public class EffortDistributor { return result; } - private List resourcesFromMoreToLessCapacityAvailable( + private List resourcesFromMoreDesirableToLess( List resourcesAssignable, LocalDate date) { List result = new ArrayList(); for (ResourceWithDerivedData each : resourcesAssignable) { result.add(each.withAvailableCapacityOn(date, assignedEffortForResource)); } - Collections.sort(result, Collections.reverseOrder()); + Collections.sort(result, Collections + .reverseOrder(ResourceWithAvailableCapacity + .getComparatorConsidering(resourcesAlreadyPicked))); return result; } diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/GenericResourceAllocationTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/GenericResourceAllocationTest.java index 4f5cb08c7..fed6457fb 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/GenericResourceAllocationTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/GenericResourceAllocationTest.java @@ -42,11 +42,15 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import org.apache.commons.lang.Validate; import org.easymock.IAnswer; +import org.easymock.classextension.EasyMock; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.Period; @@ -178,13 +182,60 @@ public class GenericResourceAllocationTest { workers.add(worker3); } + private static class LoadSpec { + + public static LoadSpec withHours(int hours) { + return new LoadSpec(hours(hours)); + } + + private final EffortDuration defaultLoad; + + private LoadSpec(EffortDuration defaultLoad) { + Validate.notNull(defaultLoad); + this.defaultLoad = defaultLoad; + } + + private Map exceptions = new HashMap(); + + LoadSpec withException(LocalDate date, EffortDuration loadAtThatDate) { + Validate.notNull(date); + Validate.notNull(loadAtThatDate); + exceptions.put(date, loadAtThatDate); + return this; + } + + EffortDuration getLoad(LocalDate date) { + if (exceptions.containsKey(date)) { + return exceptions.get(date); + } + return defaultLoad; + } + + } + private Worker createWorkerWithLoad(ResourceCalendar resourceCalendar, int hours) { + return createWorkerWithLoad(resourceCalendar, + new LoadSpec(hours(hours))); + } + + private Worker createWorkerWithLoad(ResourceCalendar resourceCalendar, + final LoadSpec loadSpec) { Worker result = createNiceMock(Worker.class); expect(result.getCalendar()).andReturn(resourceCalendar).anyTimes(); expect( result.getAssignedDurationDiscounting(isA(Object.class), - isA(LocalDate.class))).andReturn(hours(hours)).anyTimes(); + isA(LocalDate.class))).andAnswer( + new IAnswer() { + + @Override + public EffortDuration answer() throws Throwable { + Object[] currentArguments = EasyMock + .getCurrentArguments(); + LocalDate date = (LocalDate) currentArguments[1]; + return loadSpec.getLoad(date); + } + }).anyTimes(); expect(result.getSatisfactionsFor(isA(ICriterion.class))).andReturn( satisfactionsForPredefinedCriterions(result)).anyTimes(); replay(result); @@ -225,6 +276,12 @@ public class GenericResourceAllocationTest { } private void givenWorkersWithLoads(int hours1, int hours2, int hours3) { + givenWorkersWithLoads(LoadSpec.withHours(hours1), + LoadSpec.withHours(hours2), LoadSpec.withHours(hours3)); + } + + private void givenWorkersWithLoads(LoadSpec load1, LoadSpec load2, + LoadSpec load3) { ResourceCalendar[] calendars; if (workerCalendars == null) { calendars = new ResourceCalendar[] { null, null, null }; @@ -232,9 +289,9 @@ public class GenericResourceAllocationTest { calendars = new ResourceCalendar[] { workerCalendars.get(0), workerCalendars.get(1), workerCalendars.get(2) }; } - worker1 = createWorkerWithLoad(calendars[0], hours1); - worker2 = createWorkerWithLoad(calendars[1], hours2); - worker3 = createWorkerWithLoad(calendars[2], hours3); + worker1 = createWorkerWithLoad(calendars[0], load1); + worker2 = createWorkerWithLoad(calendars[1], load2); + worker3 = createWorkerWithLoad(calendars[2], load3); buildWorkersList(); } @@ -540,6 +597,66 @@ public class GenericResourceAllocationTest { assertThat(assignmentsWorker3, haveHours(7, 7, 7, 7)); } + @Test + public void itTakesIntoAccountTheLoadForEachDay() { + final int TASK_DURATION_DAYS = 4; + givenBaseCalendarWithoutExceptions(8); + LocalDate start = new LocalDate(2006, 10, 5); + givenTaskWithStartAndEnd(toInterval(start, + Period.days(TASK_DURATION_DAYS))); + givenGenericResourceAllocationForTask(task); + givenWorkersWithLoads( + LoadSpec.withHours(3) + .withException(start.plusDays(1), hours(1)) + .withException(start.plusDays(3), hours(8)), + LoadSpec.withHours(12).withException(start.plusDays(3), zero()), + LoadSpec.withHours(1) + .withException(start.plusDays(1), hours(3)) + .withException(start.plusDays(3), hours(8))); + + genericResourceAllocation.forResources(workers).allocate( + ResourcesPerDay.amount(1)); + + List assignmentsWorker1 = genericResourceAllocation + .getOrderedAssignmentsFor(worker1); + assertThat(assignmentsWorker1, haveHours(1, 7, 1)); + List assignmentsWorker2 = genericResourceAllocation + .getOrderedAssignmentsFor(worker2); + assertThat(assignmentsWorker2, haveHours(8)); + List assignmentsWorker3 = genericResourceAllocation + .getOrderedAssignmentsFor(worker3); + assertThat(assignmentsWorker3, haveHours(7, 1, 7)); + } + + @Test + public void previouslyPickedResourcesHaveMorePriority() { + final int TASK_DURATION_DAYS = 4; + givenBaseCalendarWithoutExceptions(8); + LocalDate start = new LocalDate(2006, 10, 5); + givenTaskWithStartAndEnd(toInterval(start, + Period.days(TASK_DURATION_DAYS))); + givenGenericResourceAllocationForTask(task); + givenWorkersWithLoads( + LoadSpec.withHours(0) + .withException(start.plusDays(3), hours(4)), + LoadSpec.withHours(12), + LoadSpec.withHours(1) + .withException(start.plusDays(3), hours(0))); + + genericResourceAllocation.forResources(workers).allocate( + ResourcesPerDay.amount(1)); + + List assignmentsWorker3 = genericResourceAllocation + .getOrderedAssignmentsFor(worker3); + assertThat(assignmentsWorker3, haveHours(4)); + List assignmentsWorker1 = genericResourceAllocation + .getOrderedAssignmentsFor(worker1); + assertThat(assignmentsWorker1, haveHours(8, 8, 8, 4)); + List assignmentsWorker2 = genericResourceAllocation + .getOrderedAssignmentsFor(worker2); + assertThat(assignmentsWorker2, haveHours()); + } + @Test public void doesntSurpassTheExtraHours() { final int TASK_DURATION_DAYS = 4;