ItEr41S09RFSoporteRecursosVirtuaisItEr40S12: Prorating virtual workers so they receive more overload

This commit is contained in:
Óscar González Fernández 2009-12-28 20:39:10 +01:00
parent d53a64f2bc
commit d2333b6312
2 changed files with 209 additions and 28 deletions

View file

@ -20,6 +20,7 @@
package org.navalplanner.business.planner.entities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
@ -47,12 +48,26 @@ public class HoursDistributor {
private final List<IWorkHours> workHours;
private final List<Integer> capacities;
public HoursDistributor(List<Resource> resources) {
this.resources = resources;
this.workHours = new ArrayList<IWorkHours>();
for (Resource resource : resources) {
this.workHours.add(generateWorkHoursFor(resource));
}
this.capacities = new ArrayList<Integer>();
for (Resource resource : resources) {
this.capacities.add(getCapacityFor(resource));
}
}
private Integer getCapacityFor(Resource resource) {
if (resource.getCalendar() != null) {
return resource.getCalendar().getCapacity();
} else {
return 1;
}
}
private final IWorkHours generateWorkHoursFor(Resource resource) {
@ -65,31 +80,78 @@ public class HoursDistributor {
public List<ResourceWithAssignedHours> distributeForDay(LocalDate day,
int totalHours) {
List<ResourceWithAssignedHours> result = new ArrayList<ResourceWithAssignedHours>();
List<Share> shares = currentSharesFor(day);
ShareDivision currentDivision = ShareDivision.create(shares);
List<ShareSource> shares = divisionAt(day);
ShareDivision currentDivision = ShareSource.all(shares);
ShareDivision newDivison = currentDivision.plus(totalHours);
int[] differences = currentDivision.to(newDivison);
for (int i = 0; i < differences.length; i++) {
assert differences[i] >= 0;
result.add(new ResourceWithAssignedHours(differences[i], resources
.get(i)));
return ShareSource.hoursForEachResource(shares, differences, resources);
}
private static final ResourcesPerDay ONE = ResourcesPerDay.amount(1);
private static class ShareSource {
public static ShareDivision all(Collection<ShareSource> sources) {
List<Share> shares = new ArrayList<Share>();
for (ShareSource shareSource : sources) {
shares.addAll(shareSource.shares);
}
return ShareDivision.create(shares);
}
public static List<ResourceWithAssignedHours> hoursForEachResource(
List<ShareSource> sources, int[] differences,
List<Resource> resources) {
List<ResourceWithAssignedHours> result = new ArrayList<ResourceWithAssignedHours>();
int differencesIndex = 0;
for (int i = 0; i < resources.size(); i++) {
Resource resource = resources.get(i);
ShareSource shareSource = sources.get(i);
final int differencesToTake = shareSource.shares.size();
int sum = sumDifferences(differences, differencesIndex,
differencesToTake);
differencesIndex += differencesToTake;
result.add(new ResourceWithAssignedHours(sum, resource));
}
return result;
}
private static int sumDifferences(int[] differences, int start,
final int toTake) {
int sum = 0;
for (int i = 0; i < toTake; i++) {
sum += differences[start + i];
}
return sum;
}
private final List<Share> shares;
private ShareSource(List<Share> shares) {
this.shares = shares;
}
}
public List<ShareSource> divisionAt(LocalDate day) {
List<ShareSource> result = new ArrayList<ShareSource>();
for (int i = 0; i < resources.size(); i++) {
List<Share> shares = new ArrayList<Share>();
Resource resource = resources.get(i);
IWorkHours workHoursForResource = workHours.get(i);
int alreadyAssignedHours = resource.getAssignedHours(day);
Integer capacityEachOne = workHoursForResource.toHours(day, ONE);
final int capacityUnits = capacities.get(i);
assert capacityUnits >= 1;
final int assignedForEach = alreadyAssignedHours / capacityUnits;
final int remainder = alreadyAssignedHours % capacityUnits;
for (int j = 0; j < capacityUnits; j++) {
int assigned = assignedForEach + (j < remainder ? 1 : 0);
shares.add(new Share(assigned - capacityEachOne));
}
result.add(new ShareSource(shares));
}
return result;
}
public List<Share> currentSharesFor(LocalDate day) {
List<Share> shares = new ArrayList<Share>();
for (int i = 0; i < resources.size(); i++) {
Resource resource = resources.get(i);
IWorkHours workHoursForResource = workHours.get(i);
int alreadyAssignedHours = resource.getAssignedHours(day);
Integer workableHours = workHoursForResource.getCapacityAt(day);
// a resource would have a zero share if all it's hours for a
// given day are filled
shares.add(new Share(alreadyAssignedHours - workableHours));
}
return shares;
}
}

View file

@ -21,6 +21,7 @@
package org.navalplanner.business.test.planner.entities;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.isA;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
@ -40,11 +41,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.easymock.IAnswer;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.joda.time.Period;
import org.junit.Test;
import org.navalplanner.business.calendars.entities.BaseCalendar;
import org.navalplanner.business.calendars.entities.ResourceCalendar;
import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay;
import org.navalplanner.business.planner.entities.GenericDayAssignment;
import org.navalplanner.business.planner.entities.GenericResourceAllocation;
@ -52,6 +55,7 @@ import org.navalplanner.business.planner.entities.ResourcesPerDay;
import org.navalplanner.business.planner.entities.Task;
import org.navalplanner.business.resources.entities.Criterion;
import org.navalplanner.business.resources.entities.Resource;
import org.navalplanner.business.resources.entities.VirtualWorker;
import org.navalplanner.business.resources.entities.Worker;
public class GenericResourceAllocationTest {
@ -60,6 +64,7 @@ public class GenericResourceAllocationTest {
private Set<Criterion> criterions;
private List<Worker> workers;
private List<ResourceCalendar> workerCalendars = null;
private Worker worker1;
private Worker worker2;
private Worker worker3;
@ -129,19 +134,35 @@ public class GenericResourceAllocationTest {
workers.add(worker3);
}
private Worker createWorkerWithLoad(int hours) {
private Worker createWorkerWithLoad(ResourceCalendar resourceCalendar,
int hours) {
Worker result = createNiceMock(Worker.class);
setupCalendarIsNull(result);
expect(result.getCalendar()).andReturn(resourceCalendar).anyTimes();
expect(result.getAssignedHours(isA(LocalDate.class))).andReturn(hours)
.anyTimes();
replay(result);
return result;
}
private void givenCalendarsForResources(int capacity1, int capacity2,
int capacity3) {
workerCalendars = new ArrayList<ResourceCalendar>();
workerCalendars.add(createCalendar(ResourceCalendar.class, capacity1));
workerCalendars.add(createCalendar(ResourceCalendar.class, capacity2));
workerCalendars.add(createCalendar(ResourceCalendar.class, capacity3));
}
private void givenWorkersWithLoads(int hours1, int hours2, int hours3) {
worker1 = createWorkerWithLoad(hours1);
worker2 = createWorkerWithLoad(hours2);
worker3 = createWorkerWithLoad(hours3);
ResourceCalendar[] calendars;
if(workerCalendars ==null){
calendars = new ResourceCalendar[] { null, null, null };
} else {
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);
buildWorkersList();
}
@ -150,13 +171,37 @@ public class GenericResourceAllocationTest {
}
private void givenBaseCalendarWithoutExceptions(int hoursPerDay) {
BaseCalendar baseCalendar = createNiceMock(BaseCalendar.class);
BaseCalendar baseCalendar = createCalendar(BaseCalendar.class,
hoursPerDay);
this.baseCalendar = baseCalendar;
}
private <T extends BaseCalendar> T createCalendar(Class<T> klass,
final int hoursPerDay) {
BaseCalendar baseCalendar = createNiceMock(klass);
expect(baseCalendar.getWorkableHours(isA(Date.class))).andReturn(
hoursPerDay).anyTimes();
expect(baseCalendar.getCapacityAt(isA(LocalDate.class))).andReturn(
hoursPerDay).anyTimes();
expect(
baseCalendar.toHours(isA(LocalDate.class),
isA(ResourcesPerDay.class))).andAnswer(
new IAnswer<Integer>() {
@Override
public Integer answer() throws Throwable {
ResourcesPerDay resourcesPerDay = (ResourcesPerDay) getCurrentArguments()[1];
return resourcesPerDay
.asHoursGivenResourceWorkingDayOf(hoursPerDay);
}
})
.anyTimes();
if (baseCalendar instanceof ResourceCalendar) {
ResourceCalendar resourceCalendar = (ResourceCalendar) baseCalendar;
expect(resourceCalendar.getCapacity()).andReturn(1);
}
replay(baseCalendar);
this.baseCalendar = baseCalendar;
return klass.cast(baseCalendar);
}
@Test
@ -399,6 +444,80 @@ public class GenericResourceAllocationTest {
assertThat(assignmentsWorker3, haveHours(5, 5, 5, 5));
}
@Test
public void virtualWorkersAreGivenMoreLoad() {
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(8, 8, 8);
givenVirtualWorkerWithCapacity(5);
genericResourceAllocation.forResources(workers).allocate(
ResourcesPerDay.amount(1));
List<GenericDayAssignment> virtualWorkerAssignments = genericResourceAllocation
.getOrderedAssignmentsFor(workers.get(workers.size() - 1));
assertThat(virtualWorkerAssignments, haveHours(5, 5, 5, 5));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation
.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(1, 1, 1, 1));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation
.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours(1, 1, 1, 1));
List<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation
.getOrderedAssignmentsFor(worker3);
assertThat(assignmentsWorker3, haveHours(1, 1, 1, 1));
}
@Test
public void itWorksWithCalendarsReturningZeroHours() {
final int TASK_DURATION_DAYS = 4;
givenBaseCalendarWithoutExceptions(8);
givenCalendarsForResources(4, 4, 0);
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period
.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(4, 4, 4);
genericResourceAllocation.forResources(workers).allocate(
ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation
.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(4, 4, 4, 4));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation
.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours(4, 4, 4, 4));
List<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation
.getOrderedAssignmentsFor(worker3);
assertThat(assignmentsWorker3, haveHours(0, 0, 0, 0));
}
private void givenVirtualWorkerWithCapacity(int capacity) {
VirtualWorker worker = createNiceMock(VirtualWorker.class);
final int fullLoadForAll = 8 * capacity;
expect(worker.getAssignedHours(isA(LocalDate.class))).andReturn(
fullLoadForAll)
.anyTimes();
expect(worker.getCalendar()).andReturn(createCalendar(capacity, 8))
.anyTimes();
replay(worker);
workers.add(worker);
}
private ResourceCalendar createCalendar(int capacity, int unit) {
ResourceCalendar calendar = createNiceMock(ResourceCalendar.class);
expect(calendar.getCapacityAt(isA(LocalDate.class))).andReturn(
capacity * unit).anyTimes();
expect(calendar.toHours(isA(LocalDate.class),
isA(ResourcesPerDay.class))).andReturn(unit).anyTimes();
expect(calendar.getCapacity()).andReturn(capacity).anyTimes();
replay(calendar);
return calendar;
}
private static Interval toInterval(LocalDate start, Period period) {
return new Interval(start.toDateTimeAtStartOfDay(), start.plus(period)
.toDateTimeAtStartOfDay());