/*
* 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.test.planner.entities;
import static java.util.Arrays.asList;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.libreplan.business.test.planner.entities.DayAssignmentMatchers.from;
import static org.libreplan.business.test.planner.entities.DayAssignmentMatchers.haveHours;
import static org.libreplan.business.test.planner.entities.DayAssignmentMatchers.haveResourceAllocation;
import static org.libreplan.business.workingday.EffortDuration.hours;
import static org.libreplan.business.workingday.EffortDuration.zero;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.Collections;
import org.apache.commons.lang3.Validate;
import org.easymock.IAnswer;
import org.easymock.EasyMock;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.joda.time.Period;
import org.junit.Test;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay;
import org.libreplan.business.planner.entities.GenericDayAssignment;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionSatisfaction;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.VirtualWorker;
import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.business.workingday.ResourcesPerDay;
public class GenericResourceAllocationTest {
public static IntraDayDate plusDays(LocalDate date, int days) {
return IntraDayDate.startOfDay(date.plusDays(days));
}
public static IntraDayDate minusDays(LocalDate date, int days) {
return IntraDayDate.startOfDay(date.minusDays(days));
}
private GenericResourceAllocation genericResourceAllocation;
private Set<Criterion> criterions;
private List<Worker> workers;
private List<ResourceCalendar> workerCalendars = null;
private Worker worker1;
private Worker worker2;
private Worker worker3;
private BaseCalendar baseCalendar;
private Task task;
private static Scenario mockScenario() {
Scenario result = createNiceMock(Scenario.class);
replay(result);
return result;
}
private void givenGenericResourceAllocation() {
task = givenTaskWithCriterions();
givenGenericResourceAllocationForTask(task);
}
private Task givenTaskWithStartAndEnd(Interval interval) {
Task task = createNiceMock(Task.class);
setupCriterions(task);
IntraDayDate start = IntraDayDate.startOfDay(interval.getStart().toLocalDate());
IntraDayDate end = IntraDayDate.startOfDay(interval.getEnd().toLocalDate());
expect(task.getStartDate()).andReturn(interval.getStart().toDate()).anyTimes();
expect(task.getIntraDayStartDate()).andReturn(start).anyTimes();
expect(task.getEndDate()).andReturn(interval.getEnd().toDate()).anyTimes();
expect(task.getIntraDayEndDate()).andReturn(end).anyTimes();
expect(task.getFirstDayNotConsolidated()).andReturn(start).anyTimes();
expect(task.getCalendar()).andReturn(baseCalendar).anyTimes();
replay(task);
return this.task = task;
}
private Task givenTaskWithCriterions() {
Task task = createNiceMock(Task.class);
setupCriterions(task);
expect(task.getCalendar()).andReturn(baseCalendar).anyTimes();
replay(task);
return this.task = task;
}
private void setupCriterions(Task task) {
expect(task.getCriterions()).andReturn(givenPredefinedCriterions()).anyTimes();
}
private void givenGenericResourceAllocationForTask(Task task) {
genericResourceAllocation = GenericResourceAllocation.create(task);
}
private Set<Criterion> givenPredefinedCriterions() {
Set<Criterion> result = new HashSet<>();
Criterion criterion1 = createNiceMock(Criterion.class);
setupIsSatisfiedByAll(criterion1);
Criterion criterion2 = createNiceMock(Criterion.class);
setupIsSatisfiedByAll(criterion2);
replay(criterion1, criterion2);
result.add(criterion1);
result.add(criterion2);
this.criterions = result;
return result;
}
private void setupIsSatisfiedByAll(Criterion criterion) {
expect(criterion.isSatisfiedBy(isA(Resource.class), isA(LocalDate.class))).andReturn(true).anyTimes();
}
private void givenWorkersWithoutLoadAndWithoutCalendar() {
worker1 = createNiceMock(Worker.class);
worker2 = createNiceMock(Worker.class);
worker3 = createNiceMock(Worker.class);
mockZeroLoad(worker1, worker2, worker3);
buildWorkersList();
replay(worker1, worker2, worker3);
}
public static void mockZeroLoad(Resource... resources) {
for (Resource each : resources) {
expect(each.getAssignedDurationDiscounting(isA(Map.class), isA(LocalDate.class))).andReturn(zero()).anyTimes();
}
}
private void buildWorkersList() {
workers = new ArrayList<>();
workers.add(worker1);
workers.add(worker2);
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<LocalDate, EffortDuration> 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(Map.class), isA(LocalDate.class)))
.andAnswer(new IAnswer<EffortDuration>() {
@Override
public EffortDuration answer() throws Throwable {
Object[] currentArguments = EasyMock.getCurrentArguments();
LocalDate date = (LocalDate) currentArguments[1];
return loadSpec.getLoad(date);
}
})
.anyTimes();
expect(result.getSatisfactionsFor(isA(Criterion.class)))
.andReturn(satisfactionsForPredefinedCriterions(result)).anyTimes();
replay(result);
return result;
}
private List<CriterionSatisfaction> satisfactionsForPredefinedCriterions(Resource resource) {
List<CriterionSatisfaction> result = new ArrayList<>();
for (Criterion each : criterions) {
result.add(CriterionSatisfaction.create(each, resource,
fromVeryEarlyTime()));
}
return result;
}
private org.libreplan.business.resources.entities.Interval fromVeryEarlyTime() {
return org.libreplan.business.resources.entities.Interval.from(new LocalDate(0, 1, 1));
}
private void givenCalendarsForResources(int capacity1, int capacity2, int capacity3) {
givenCalendarsForResources(fromHours(capacity1), fromHours(capacity2), fromHours(capacity3));
}
private Capacity fromHours(int hours) {
return Capacity.create(hours(hours)).overAssignableWithoutLimit();
}
private void givenCalendarsForResources(Capacity capacity1, Capacity capacity2, Capacity capacity3) {
workerCalendars = new ArrayList<>();
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) {
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 };
} else {
calendars =
new ResourceCalendar[] { workerCalendars.get(0), workerCalendars.get(1), workerCalendars.get(2) };
}
worker1 = createWorkerWithLoad(calendars[0], load1);
worker2 = createWorkerWithLoad(calendars[1], load2);
worker3 = createWorkerWithLoad(calendars[2], load3);
buildWorkersList();
}
private void givenBaseCalendarWithoutExceptions(int hoursPerDay) {
this.baseCalendar =
createCalendar(BaseCalendar.class, Capacity.create(hours(hoursPerDay)).overAssignableWithoutLimit());
}
private <T extends BaseCalendar> T createCalendar(Class<T> klass, final Capacity capacity) {
return createCalendar(klass, capacity, 1);
}
private <T extends BaseCalendar> T createCalendar(Class<T> klass, final Capacity capacity, int units) {
final Capacity capacityMultipliedByUnits = capacity.multiplyBy(units);
BaseCalendar baseCalendar = createNiceMock(klass);
expect(baseCalendar.getCapacityOn(isA(PartialDay.class)))
.andAnswer(new IAnswer<EffortDuration>() {
@Override
public EffortDuration answer() throws Throwable {
PartialDay day = (PartialDay) getCurrentArguments()[0];
return day.limitWorkingDay(capacityMultipliedByUnits.getStandardEffort());
}
})
.anyTimes();
expect(baseCalendar.isActive(isA(LocalDate.class))).andReturn(true).anyTimes();
expect(baseCalendar.canWorkOn(isA(LocalDate.class))).andReturn(true).anyTimes();
expect(baseCalendar.getAvailability()).andReturn(AvailabilityTimeLine.allValid()).anyTimes();
IAnswer<EffortDuration> durationAnswer = new IAnswer<EffortDuration>() {
@Override
public EffortDuration answer() throws Throwable {
PartialDay day = (PartialDay) getCurrentArguments()[0];
ResourcesPerDay resourcesPerDay = (ResourcesPerDay) getCurrentArguments()[1];
return capacityMultipliedByUnits.limitDuration(
resourcesPerDay.asDurationGivenWorkingDayOf(day.limitWorkingDay(capacity.getStandardEffort())));
}
};
expect(baseCalendar.asDurationOn(isA(PartialDay.class), isA(ResourcesPerDay.class)))
.andAnswer(durationAnswer)
.anyTimes();
expect(baseCalendar.getCapacityWithOvertime(isA(LocalDate.class)))
.andReturn(capacityMultipliedByUnits)
.anyTimes();
if ( baseCalendar instanceof ResourceCalendar ) {
ResourceCalendar resourceCalendar = (ResourceCalendar) baseCalendar;
expect(resourceCalendar.getCapacity()).andReturn(units).anyTimes();
}
replay(baseCalendar);
return klass.cast(baseCalendar);
}
@Test
public void theCriterionsAreCopied() {
givenGenericResourceAllocation();
GenericResourceAllocation copied = (GenericResourceAllocation) genericResourceAllocation.copy(mockScenario());
assertThat(copied.getCriterions(), equalTo(criterions));
}
@Test
public void hasTheCriterionsOfTheTask() {
givenGenericResourceAllocation();
assertThat(genericResourceAllocation.getCriterions(), equalTo(criterions));
}
@Test
public void getOrderedAssignmentsReturnsEmptyListIfNotExistsWorker() {
givenWorkersWithoutLoadAndWithoutCalendar();
givenGenericResourceAllocation();
List<GenericDayAssignment> assignments = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertNotNull(assignments);
assertTrue(assignments.isEmpty());
}
@Test
public void theGeneratedDayAssignmentsAreRelatedWithTheAllocation() {
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(2)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignments = genericResourceAllocation.getAssignments();
assertThat(assignments, haveResourceAllocation(genericResourceAllocation));
}
@Test
public void allocatingGeneratesDayAssignmentsForEachDay() {
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);
givenWorkersWithoutLoadAndWithoutCalendar();
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> orderedAssignmentsFor = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(orderedAssignmentsFor, from(start).consecutiveDays(TASK_DURATION_DAYS));
}
@Test
public void canAllocateSomeResourcesPerDayUntilSomeEndDate() {
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);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay resourcesPerDay = ResourcesPerDay.amount(1);
genericResourceAllocation.forResources(Collections.singletonList(worker1))
.resourcesPerDayUntil(plusDays(start, 2))
.allocate(resourcesPerDay);
List<GenericDayAssignment> orderedAssignmentsFor = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
int hoursPerDay = resourcesPerDay.asDurationGivenWorkingDayOf(EffortDuration.hours(8)).getHours();
assertThat(orderedAssignmentsFor, haveHours(hoursPerDay, hoursPerDay));
}
@Test
public void allocatingUntilSomeEndDateDeletesAssignmentsAfterThatDate() {
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);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay resourcesPerDay = ResourcesPerDay.amount(1);
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(resourcesPerDay);
genericResourceAllocation.forResources(Collections.singletonList(worker1))
.resourcesPerDayUntil(plusDays(start, 2))
.allocate(resourcesPerDay);
List<GenericDayAssignment> orderedAssignmentsFor = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
int hoursPerDay = resourcesPerDay.asDurationGivenWorkingDayOf(EffortDuration.hours(8)).getHours();
assertThat(orderedAssignmentsFor, haveHours(hoursPerDay, hoursPerDay));
}
@Test
public void whenAllocatingUntilSomeEndDateBeforeTheStartNothingIsDone() {
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(4)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay resourcesPerDay = ResourcesPerDay.amount(1);
genericResourceAllocation.forResources(Collections.singletonList(worker1))
.resourcesPerDayUntil(minusDays(start, 1))
.allocate(resourcesPerDay);
assertTrue(genericResourceAllocation.getOrderedAssignmentsFor(worker1).isEmpty());
}
@Test(expected = IllegalArgumentException.class)
public void whenAllocatingUntilSomeEndDateTheEndDateMustNotBeNull() {
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(4)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay resourcesPerDay = ResourcesPerDay.amount(1);
genericResourceAllocation.forResources(Collections.singletonList(worker1))
.resourcesPerDayUntil(null)
.allocate(resourcesPerDay);
}
@Test
public void allocatingUntilEndDateEqualToStartImpliesNoAssignmentsAndZeroResourcesPerDay() {
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(4)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay resourcesPerDay = ResourcesPerDay.amount(1);
genericResourceAllocation.forResources(Collections.singletonList(worker1))
.resourcesPerDayUntil(IntraDayDate.startOfDay(start))
.allocate(resourcesPerDay);
assertThat(genericResourceAllocation.getResourcesPerDay(), equalTo(ResourcesPerDay.amount(0)));
assertTrue(genericResourceAllocation.getOrderedAssignmentsFor(worker1).isEmpty());
}
@Test
public void theResourcesPerDayAreChangedWhenTheAllocationIsDone() {
givenTaskWithStartAndEnd(toInterval(new LocalDate(2006, 10, 5), Period.days(2)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
ResourcesPerDay assignedResourcesPerDay = ResourcesPerDay.amount(5);
genericResourceAllocation.forResources(workers).allocate(assignedResourcesPerDay);
assertThat(genericResourceAllocation.getResourcesPerDay(), equalTo(assignedResourcesPerDay));
}
@Test
public void allocatingSeveralResourcesPerDayHavingJustOneResourceProducesOvertime() {
LocalDate start = new LocalDate(2006, 10, 5);
final Integer standardHoursPerDay =
SameWorkHoursEveryDay.getDefaultWorkingDay().getCapacityOn(PartialDay.wholeDay(start)).getHours();
final int TASK_DURATION_DAYS = 4;
givenBaseCalendarWithoutExceptions(standardHoursPerDay);
givenTaskWithStartAndEnd(toInterval(start, Period.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(ResourcesPerDay.amount(2));
List<GenericDayAssignment> orderedAssignmentsFor = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(orderedAssignmentsFor.get(0).getDuration().getHours(), equalTo(standardHoursPerDay * 2));
}
@Test
public void theHoursAreGivenBasedOnTheWorkingHoursSpecifiedByTheCalendar() {
LocalDate start = new LocalDate(2006, 10, 5);
final int TASK_DURATION_DAYS = 1;
final int halfWorkingDay = 4;
givenBaseCalendarWithoutExceptions(halfWorkingDay);
givenTaskWithStartAndEnd(toInterval(start, Period.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignments = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignments, haveHours(halfWorkingDay));
}
@Test
public void ifThereIsNoTaskCalendarTheWorkingHoursAreSpecifiedbyTheDefaultWorkingDay() {
LocalDate start = new LocalDate(2006, 10, 5);
final int TASK_DURATION_DAYS = 1;
final Integer defaultWorkableHours =
SameWorkHoursEveryDay.getDefaultWorkingDay().getCapacityOn(PartialDay.wholeDay(start)).getHours();
givenBaseCalendarWithoutExceptions(defaultWorkableHours);
givenTaskWithStartAndEnd(toInterval(start, Period.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
genericResourceAllocation.forResources(Collections.singletonList(worker1)).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignments = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignments.get(0).getDuration().getHours(), equalTo(defaultWorkableHours));
}
@Test
public void itGivesAllTheLoadItCanToTheLessLoadedResource() {
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(3, 12, 1);
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(1, 1, 1, 1));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours());
List<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation.getOrderedAssignmentsFor(worker3);
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<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(1, 7, 1));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours(8));
List<GenericDayAssignment> 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<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation.getOrderedAssignmentsFor(worker3);
assertThat(assignmentsWorker3, haveHours(4));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(8, 8, 8, 4));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours());
}
@Test
public void doesNotSurpassTheExtraHours() {
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);
Capacity workingDay = Capacity.create(hours(8));
Capacity with2ExtraHours = workingDay.withAllowedExtraEffort(hours(2));
givenCalendarsForResources(with2ExtraHours, with2ExtraHours, workingDay.overAssignableWithoutLimit());
givenWorkersWithLoads(0, 0, 0);
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(4));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(10, 10, 10, 10));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours(10, 10, 10, 10));
List<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation.getOrderedAssignmentsFor(worker3);
assertThat(assignmentsWorker3, haveHours(12, 12, 12, 12));
}
@Test
public void itGivesAllTheLoadItCanToTheLessLoadedResourceAndThenToTheNextOne() {
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(0, 0, 0);
genericResourceAllocation.forResources(asList(worker1, worker2)).allocate(ResourcesPerDay.amount(2));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(8, 8, 8, 8));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, haveHours(8, 8, 8, 8));
}
@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);
givenVirtualWorkerWithCapacityAndLoad(Capacity.create(hours(8)).overAssignableWithoutLimit(), 5, hours(40));
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(1));
List<GenericDayAssignment> assignmentsWorker3 = genericResourceAllocation.getOrderedAssignmentsFor(worker3);
assertThat(assignmentsWorker3, haveHours(1, 1, 1, 1));
List<GenericDayAssignment> assignmentsWorker1 = genericResourceAllocation.getOrderedAssignmentsFor(worker1);
assertThat(assignmentsWorker1, haveHours(1, 1, 1, 1));
List<GenericDayAssignment> virtualWorkerAssignments =
genericResourceAllocation.getOrderedAssignmentsFor(workers.get(workers.size() - 1));
assertThat(virtualWorkerAssignments, haveHours(5, 5, 5, 5));
List<GenericDayAssignment> assignmentsWorker2 = genericResourceAllocation.getOrderedAssignmentsFor(worker2);
assertThat(assignmentsWorker2, 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());
}
@Test
public void theEndHourOfTheAllocationIsTheBiggestAllocationDoneIfThereIsSpareSpaceLeft() {
final int TASK_DURATION_DAYS = 4;
givenBaseCalendarWithoutExceptions(8);
givenCalendarsForResources(8, 8, 8);
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(8, 6, 2);
IntraDayDate end = ResourceAllocation.allocating(Collections.singletonList(ResourcesPerDayModification.create(
genericResourceAllocation,
ResourcesPerDay.amount(new BigDecimal(1)), workers))).untilAllocating(hours(12));
assertThat(end.getDate(), equalTo(start.plusDays(1)));
EffortDuration biggestLastAssignment = hours(4);
assertThat(end.getEffortDuration(), equalTo(biggestLastAssignment));
}
@Test
public void theEndOfTheAllocationIsTheNextDayIfThereIsNoSpareSpaceLeft() {
final int TASK_DURATION_DAYS = 4;
givenBaseCalendarWithoutExceptions(8);
givenCalendarsForResources(8, 8, 8);
LocalDate start = new LocalDate(2006, 10, 5);
givenTaskWithStartAndEnd(toInterval(start, Period.days(TASK_DURATION_DAYS)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(8, 2, 6);
IntraDayDate end = ResourceAllocation.allocating(Collections.singletonList(ResourcesPerDayModification.create(
genericResourceAllocation,
ResourcesPerDay.amount(1),
workers))).untilAllocating(hours(16));
assertThat(end.getDate(), equalTo(start.plusDays(2)));
}
private void givenVirtualWorkerWithCapacityAndLoad(Capacity capacityPerDayAndUnit, int capacityUnits,
EffortDuration load) {
VirtualWorker worker = createNiceMock(VirtualWorker.class);
expect(worker.getAssignedDurationDiscounting(isA(Map.class), isA(LocalDate.class))).andReturn(load).anyTimes();
expect(worker.getCalendar())
.andReturn(createCalendar(ResourceCalendar.class, capacityPerDayAndUnit, capacityUnits)).anyTimes();
replay(worker);
workers.add(worker);
}
private static Interval toInterval(LocalDate start, Period period) {
return new Interval(start.toDateTimeAtStartOfDay(), start.plus(period).toDateTimeAtStartOfDay());
}
@Test
public void canAllocateHoursOnInterval() {
final int workableHoursDay = 8;
givenBaseCalendarWithoutExceptions(workableHoursDay);
LocalDate start = new LocalDate(2006, 10, 5);
final int days = 4;
givenTaskWithStartAndEnd(toInterval(start, Period.days(days)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(3, 12, 1);
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(1));
assertThat(genericResourceAllocation.getAssignedHours(), equalTo(workableHoursDay * days));
final int hoursOnSubinterval = 3;
int daysSubinterval = 2;
genericResourceAllocation.forResources(workers)
.onIntervalWithinTask(start, start.plusDays(daysSubinterval))
.allocateHours(hoursOnSubinterval);
assertThat(genericResourceAllocation.getAssignedHours(),
equalTo(hoursOnSubinterval + (days - daysSubinterval) * workableHoursDay));
}
@Test
public void theRelatedResourcesCanBeRetrieved() {
givenTaskWithStartAndEnd(toInterval(new LocalDate(2006, 10, 5), Period.days(4)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithoutLoadAndWithoutCalendar();
List<Resource> resourcesGiven = Arrays.asList(worker1, worker2);
genericResourceAllocation.forResources(resourcesGiven).allocate(ResourcesPerDay.amount(1));
assertThat(asSet(genericResourceAllocation.getAssociatedResources()),
equalTo(asSet(genericResourceAllocation.getAssociatedResources())));
}
private Set<Resource> asSet(Collection<Resource> associatedResources) {
return new HashSet<>(associatedResources);
}
@Test
public void canAllocateHoursOnIntervalUsingPreviousResources() {
final int workableHoursDay = 8;
givenBaseCalendarWithoutExceptions(workableHoursDay);
LocalDate start = new LocalDate(2006, 10, 5);
final int days = 4;
givenTaskWithStartAndEnd(toInterval(start, Period.days(days)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(3, 12, 1);
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(1));
final int hoursOnSubinterval = 3;
int daysSubinterval = 2;
genericResourceAllocation.withPreviousAssociatedResources().onIntervalWithinTask(
start,
start.plusDays(daysSubinterval)).allocateHours(
hoursOnSubinterval);
assertThat(genericResourceAllocation.getAssignedHours(),
equalTo(hoursOnSubinterval + (days - daysSubinterval) * workableHoursDay));
}
@Test
public void allocatingWithPreviousAssociatedResourcesCanBeUsedSafelyWhenNoAllocationHasBeenDone() {
final int workableHoursDay = 8;
givenBaseCalendarWithoutExceptions(workableHoursDay);
LocalDate start = new LocalDate(2006, 10, 5);
final int days = 4;
givenTaskWithStartAndEnd(toInterval(start, Period.days(days)));
givenGenericResourceAllocationForTask(task);
genericResourceAllocation.withPreviousAssociatedResources().allocate(ResourcesPerDay.amount(1));
assertThat(genericResourceAllocation.getAssignedHours(), equalTo(0));
}
@Test
public void afterAllocatingMoreHoursOnIntervalTheResourcesPerDayAreIncreased() {
final int workableHoursDay = 8;
givenBaseCalendarWithoutExceptions(workableHoursDay);
LocalDate start = new LocalDate(2006, 10, 5);
final int days = 4;
givenTaskWithStartAndEnd(toInterval(start, Period.days(days)));
givenGenericResourceAllocationForTask(task);
givenWorkersWithLoads(8, 8, 8);
genericResourceAllocation.forResources(workers).allocate(ResourcesPerDay.amount(3));
ResourcesPerDay original = genericResourceAllocation.getResourcesPerDay();
genericResourceAllocation.forResources(workers).onIntervalWithinTask(start, start.plusDays(2)).allocateHours(60);
ResourcesPerDay current = genericResourceAllocation.getResourcesPerDay();
assertTrue(current.getAmount().compareTo(original.getAmount()) > 0);
}
}