Now can schedule backwards when the resources per day and the hours are fixed
FEA: ItEr62OTS04PlanificacionHaciaAtras
This commit is contained in:
parent
eb597ac7ce
commit
e129fdfec2
5 changed files with 230 additions and 47 deletions
|
|
@ -144,6 +144,41 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
};
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
FORWARD {
|
||||
@Override
|
||||
public IntraDayDate getDateFromWhichToAllocate(Task task) {
|
||||
return IntraDayDate.max(task.getFirstDayNotConsolidated(),
|
||||
task.getIntraDayStartDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
void limitAvailabilityOn(AvailabilityTimeLine availability,
|
||||
IntraDayDate dateFromWhichToAllocate) {
|
||||
availability.invalidUntil(dateFromWhichToAllocate
|
||||
.asExclusiveEnd());
|
||||
}
|
||||
},
|
||||
BACKWARD {
|
||||
@Override
|
||||
public IntraDayDate getDateFromWhichToAllocate(Task task) {
|
||||
return task.getIntraDayEndDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
void limitAvailabilityOn(AvailabilityTimeLine availability,
|
||||
IntraDayDate dateFromWhichToAllocate) {
|
||||
availability.invalidFrom(dateFromWhichToAllocate.getDate());
|
||||
}
|
||||
};
|
||||
|
||||
public abstract IntraDayDate getDateFromWhichToAllocate(Task task);
|
||||
|
||||
abstract void limitAvailabilityOn(AvailabilityTimeLine availability,
|
||||
IntraDayDate dateFromWhichToAllocate);
|
||||
|
||||
}
|
||||
|
||||
public static AllocationsSpecified allocating(
|
||||
List<ResourcesPerDayModification> resourceAllocations) {
|
||||
return new AllocationsSpecified(resourceAllocations);
|
||||
|
|
@ -229,7 +264,12 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
}
|
||||
|
||||
public IntraDayDate untilAllocating(int hoursToAllocate) {
|
||||
return untilAllocating(hoursToAllocate, doNothing());
|
||||
return untilAllocating(Direction.FORWARD, hoursToAllocate);
|
||||
}
|
||||
|
||||
public IntraDayDate untilAllocating(Direction direction,
|
||||
int hoursToAllocate) {
|
||||
return untilAllocating(direction, hoursToAllocate, doNothing());
|
||||
}
|
||||
|
||||
private static INotFulfilledReceiver doNothing() {
|
||||
|
|
@ -244,7 +284,13 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
|
||||
public IntraDayDate untilAllocating(int hoursToAllocate,
|
||||
final INotFulfilledReceiver receiver) {
|
||||
return untilAllocating(Direction.FORWARD, hoursToAllocate, receiver);
|
||||
}
|
||||
|
||||
public IntraDayDate untilAllocating(Direction direction,
|
||||
int hoursToAllocate, final INotFulfilledReceiver receiver) {
|
||||
UntilFillingHoursAllocator allocator = new UntilFillingHoursAllocator(
|
||||
direction,
|
||||
task, allocations) {
|
||||
|
||||
@Override
|
||||
|
|
@ -256,17 +302,23 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
|
||||
@Override
|
||||
protected <T extends DayAssignment> void setNewDataForAllocation(
|
||||
ResourceAllocation<T> allocation, IntraDayDate end,
|
||||
ResourceAllocation<T> allocation,
|
||||
IntraDayDate resultDate,
|
||||
ResourcesPerDay resourcesPerDay, List<T> dayAssignments) {
|
||||
Task task = AllocationsSpecified.this.task;
|
||||
allocation.resetAssignmentsTo(dayAssignments,
|
||||
task.getIntraDayStartDate(), end);
|
||||
if (isForwardScheduling()) {
|
||||
allocation.resetAssignmentsTo(dayAssignments,
|
||||
task.getIntraDayStartDate(), resultDate);
|
||||
} else {
|
||||
allocation.resetAssignmentsTo(dayAssignments,
|
||||
resultDate, task.getIntraDayEndDate());
|
||||
}
|
||||
allocation.updateResourcesPerDay();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CapacityResult thereAreAvailableHoursFrom(
|
||||
IntraDayDate start,
|
||||
IntraDayDate dateFromWhichToAllocate,
|
||||
ResourcesPerDayModification resourcesPerDayModification,
|
||||
EffortDuration effortToAllocate) {
|
||||
ICalendar calendar = getCalendar(resourcesPerDayModification);
|
||||
|
|
@ -274,7 +326,8 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
|||
.getGoal();
|
||||
AvailabilityTimeLine availability = resourcesPerDayModification
|
||||
.getAvailability();
|
||||
availability.invalidUntil(start.asExclusiveEnd());
|
||||
getDirection().limitAvailabilityOn(availability,
|
||||
dateFromWhichToAllocate);
|
||||
return ThereAreHoursOnWorkHoursCalculator
|
||||
.thereIsAvailableCapacityFor(calendar,
|
||||
availability, resourcesPerDay,
|
||||
|
|
|
|||
|
|
@ -254,4 +254,9 @@ public abstract class ResourcesPerDayModification extends
|
|||
return dayDuration.compareTo(date.getEffortDuration()) > 0;
|
||||
}
|
||||
|
||||
public EffortDuration durationAtDay(PartialDay day) {
|
||||
return getBeingModified().getAllocationCalendar().asDurationOn(day,
|
||||
goal);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.navalplanner.business.calendars.entities.ThereAreHoursOnWorkHoursCalculator.CapacityResult;
|
||||
import org.navalplanner.business.common.ProportionalDistributor;
|
||||
import org.navalplanner.business.planner.entities.DayAssignment;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation.Direction;
|
||||
import org.navalplanner.business.planner.entities.Task;
|
||||
import org.navalplanner.business.workingday.EffortDuration;
|
||||
import org.navalplanner.business.workingday.IntraDayDate;
|
||||
|
|
@ -43,14 +45,18 @@ import org.navalplanner.business.workingday.ResourcesPerDay;
|
|||
|
||||
public abstract class UntilFillingHoursAllocator {
|
||||
|
||||
private final Direction direction;
|
||||
|
||||
private final Task task;
|
||||
|
||||
private List<ResourcesPerDayModification> allocations;
|
||||
|
||||
private Map<ResourcesPerDayModification, List<DayAssignment>> resultAssignments = new HashMap<ResourcesPerDayModification, List<DayAssignment>>();
|
||||
|
||||
public UntilFillingHoursAllocator(Task task,
|
||||
|
||||
public UntilFillingHoursAllocator(Direction direction, Task task,
|
||||
List<ResourcesPerDayModification> allocations) {
|
||||
this.direction = direction;
|
||||
this.task = task;
|
||||
this.allocations = allocations;
|
||||
initializeResultsMap();
|
||||
|
|
@ -63,88 +69,160 @@ public abstract class UntilFillingHoursAllocator {
|
|||
}
|
||||
|
||||
public IntraDayDate untilAllocating(EffortDuration effortToAllocate) {
|
||||
final IntraDayDate start = IntraDayDate.max(
|
||||
task.getFirstDayNotConsolidated(), task.getIntraDayStartDate());
|
||||
List<EffortPerAllocation> effortPerAllocation = effortPerAllocation(start,
|
||||
effortToAllocate);
|
||||
final IntraDayDate dateFromWhichToAllocate = direction
|
||||
.getDateFromWhichToAllocate(task);
|
||||
List<EffortPerAllocation> effortPerAllocation = effortPerAllocation(
|
||||
dateFromWhichToAllocate, effortToAllocate);
|
||||
if (effortPerAllocation.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return untilAllocating(start, effortPerAllocation);
|
||||
return untilAllocating(dateFromWhichToAllocate, effortPerAllocation);
|
||||
}
|
||||
|
||||
private IntraDayDate untilAllocating(final IntraDayDate start,
|
||||
private IntraDayDate untilAllocating(final IntraDayDate dateFromWhichToAllocate,
|
||||
List<EffortPerAllocation> effortPerAllocation) {
|
||||
int i = 0;
|
||||
IntraDayDate currentEnd = start;
|
||||
IntraDayDate currentResult = dateFromWhichToAllocate;
|
||||
for (EffortPerAllocation each : effortPerAllocation) {
|
||||
IntraDayDate endCandidate = untilAllocating(start, each.allocation,
|
||||
each.duration);
|
||||
currentEnd = IntraDayDate.max(currentEnd, endCandidate);
|
||||
IntraDayDate candidate = untilAllocating(dateFromWhichToAllocate,
|
||||
each.allocation, each.duration);
|
||||
currentResult = pickCurrentOrCandidate(currentResult, candidate);
|
||||
i++;
|
||||
}
|
||||
setAssignmentsForEachAllocation(currentEnd);
|
||||
return currentEnd;
|
||||
setAssignmentsForEachAllocation(currentResult);
|
||||
return currentResult;
|
||||
}
|
||||
|
||||
private List<EffortPerAllocation> effortPerAllocation(IntraDayDate start,
|
||||
EffortDuration toBeAssigned) {
|
||||
private IntraDayDate pickCurrentOrCandidate(IntraDayDate current,
|
||||
IntraDayDate candidate) {
|
||||
if (direction == Direction.BACKWARD) {
|
||||
return IntraDayDate.min(current, candidate);
|
||||
}
|
||||
return IntraDayDate.max(current, candidate);
|
||||
}
|
||||
|
||||
private List<EffortPerAllocation> effortPerAllocation(
|
||||
IntraDayDate dateFromWhichToAllocate, EffortDuration toBeAssigned) {
|
||||
return new HoursPerAllocationCalculator(allocations)
|
||||
.calculateEffortsPerAllocation(start, toBeAssigned);
|
||||
.calculateEffortsPerAllocation(dateFromWhichToAllocate,
|
||||
toBeAssigned);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param start
|
||||
* @param dateFromWhichToAllocate
|
||||
* @param resourcesPerDayModification
|
||||
* @param effortRemaining
|
||||
* @return the moment on which the allocation would be completed
|
||||
*/
|
||||
private IntraDayDate untilAllocating(IntraDayDate start,
|
||||
private IntraDayDate untilAllocating(IntraDayDate dateFromWhichToAllocate,
|
||||
ResourcesPerDayModification resourcesPerDayModification,
|
||||
EffortDuration effortRemaining) {
|
||||
EffortDuration taken = zero();
|
||||
IntraDayDate lastDate = start;
|
||||
for (IntraDayDate current = start; effortRemaining.compareTo(zero()) > 0; current = current.nextDayAtStart()) {
|
||||
lastDate = current;
|
||||
taken = assignForDay(resourcesPerDayModification,
|
||||
dayStartingAt(current), effortRemaining);
|
||||
lastDate = IntraDayDate.create(current.getDate(),
|
||||
taken.plus(current.getEffortDuration()));
|
||||
IntraDayDate current = dateFromWhichToAllocate;
|
||||
while (effortRemaining.compareTo(zero()) > 0) {
|
||||
PartialDay day = calculateDay(current);
|
||||
taken = assignForDay(resourcesPerDayModification, day,
|
||||
effortRemaining);
|
||||
effortRemaining = effortRemaining.minus(taken);
|
||||
if (effortRemaining.compareTo(zero()) > 0) {
|
||||
current = nextDay(current);
|
||||
}
|
||||
}
|
||||
if (!resourcesPerDayModification.thereAreMoreSpaceAvailableAt(lastDate)) {
|
||||
return lastDate.nextDayAtStart();
|
||||
IntraDayDate result;
|
||||
if (isForwardScheduling()) {
|
||||
result = plusEffort(current, taken);
|
||||
if (!resourcesPerDayModification
|
||||
.thereAreMoreSpaceAvailableAt(result)) {
|
||||
result = nextDay(result);
|
||||
}
|
||||
} else {
|
||||
result = minusEffort(current, taken, resourcesPerDayModification);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private IntraDayDate nextDay(IntraDayDate current) {
|
||||
if (isForwardScheduling()) {
|
||||
return current.nextDayAtStart();
|
||||
} else {
|
||||
if (current.isStartOfDay()) {
|
||||
return current.previousDayAtStart();
|
||||
} else {
|
||||
return IntraDayDate.startOfDay(current.getDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PartialDay calculateDay(IntraDayDate current) {
|
||||
if (isForwardScheduling()) {
|
||||
return dayStartingAt(current);
|
||||
} else {
|
||||
return dayEndingAt(current);
|
||||
}
|
||||
return lastDate;
|
||||
}
|
||||
|
||||
private PartialDay dayStartingAt(IntraDayDate start) {
|
||||
return new PartialDay(start, start.nextDayAtStart());
|
||||
return new PartialDay(start, nextDay(start));
|
||||
}
|
||||
|
||||
private void setAssignmentsForEachAllocation(IntraDayDate end) {
|
||||
private PartialDay dayEndingAt(IntraDayDate current) {
|
||||
if (!current.isStartOfDay()) {
|
||||
return new PartialDay(IntraDayDate.startOfDay(current.getDate()),
|
||||
current);
|
||||
}
|
||||
return PartialDay.wholeDay(current.getDate().minusDays(1));
|
||||
}
|
||||
|
||||
protected boolean isForwardScheduling() {
|
||||
return Direction.FORWARD == direction;
|
||||
}
|
||||
|
||||
private IntraDayDate plusEffort(IntraDayDate current, EffortDuration taken) {
|
||||
return IntraDayDate.create(current.getDate(),
|
||||
taken.plus(current.getEffortDuration()));
|
||||
}
|
||||
|
||||
private IntraDayDate minusEffort(IntraDayDate current,
|
||||
EffortDuration taken,
|
||||
ResourcesPerDayModification resourcesPerDayModification) {
|
||||
if (!current.isStartOfDay()) {
|
||||
return IntraDayDate.create(current.getDate(), current
|
||||
.getEffortDuration().minus(taken));
|
||||
} else {
|
||||
LocalDate day = current.getDate().minusDays(1);
|
||||
EffortDuration effortAtDay = resourcesPerDayModification
|
||||
.durationAtDay(PartialDay.wholeDay(day));
|
||||
return IntraDayDate.create(day, effortAtDay.minus(taken));
|
||||
}
|
||||
}
|
||||
|
||||
private void setAssignmentsForEachAllocation(IntraDayDate resultDate) {
|
||||
for (Entry<ResourcesPerDayModification, List<DayAssignment>> entry : resultAssignments
|
||||
.entrySet()) {
|
||||
setNewDataForAllocation(entry, end);
|
||||
setNewDataForAllocation(entry, resultDate);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends DayAssignment> void setNewDataForAllocation(
|
||||
Entry<ResourcesPerDayModification, List<DayAssignment>> entry,
|
||||
IntraDayDate end) {
|
||||
IntraDayDate resultDate) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ResourceAllocation<T> allocation = (ResourceAllocation<T>) entry
|
||||
.getKey().getBeingModified();
|
||||
ResourcesPerDay resourcesPerDay = entry.getKey().getGoal();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<T> value = (List<T>) entry.getValue();
|
||||
setNewDataForAllocation(allocation, end, resourcesPerDay,
|
||||
setNewDataForAllocation(allocation, resultDate, resourcesPerDay,
|
||||
value);
|
||||
}
|
||||
|
||||
protected Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
protected abstract <T extends DayAssignment> void setNewDataForAllocation(
|
||||
ResourceAllocation<T> allocation, IntraDayDate explicitEnd,
|
||||
ResourceAllocation<T> allocation, IntraDayDate resultDate,
|
||||
ResourcesPerDay resourcesPerDay, List<T> dayAssignments);
|
||||
|
||||
protected abstract List<DayAssignment> createAssignmentsAtDay(
|
||||
|
|
@ -152,7 +230,7 @@ public abstract class UntilFillingHoursAllocator {
|
|||
EffortDuration limit);
|
||||
|
||||
protected abstract CapacityResult thereAreAvailableHoursFrom(
|
||||
IntraDayDate start,
|
||||
IntraDayDate dateFromWhichToAllocate,
|
||||
ResourcesPerDayModification resourcesPerDayModification,
|
||||
EffortDuration remainingDuration);
|
||||
|
||||
|
|
@ -205,13 +283,13 @@ public abstract class UntilFillingHoursAllocator {
|
|||
}
|
||||
|
||||
public List<EffortPerAllocation> calculateEffortsPerAllocation(
|
||||
IntraDayDate start, EffortDuration toAssign) {
|
||||
IntraDayDate dateFromWhichToAllocate, EffortDuration toAssign) {
|
||||
do {
|
||||
List<EffortDuration> durations = divideEffort(toAssign);
|
||||
List<EffortPerAllocation> result = EffortPerAllocation.wrap(
|
||||
allocations, durations);
|
||||
List<ResourcesPerDayModification> unsatisfied = getUnsatisfied(
|
||||
start, result);
|
||||
dateFromWhichToAllocate, result);
|
||||
if (unsatisfied.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
|
@ -221,13 +299,12 @@ public abstract class UntilFillingHoursAllocator {
|
|||
}
|
||||
|
||||
private List<ResourcesPerDayModification> getUnsatisfied(
|
||||
IntraDayDate start,
|
||||
IntraDayDate dateFromWhichToAllocate,
|
||||
List<EffortPerAllocation> hoursPerAllocations) {
|
||||
List<ResourcesPerDayModification> cannotSatisfy = new ArrayList<ResourcesPerDayModification>();
|
||||
for (EffortPerAllocation each : hoursPerAllocations) {
|
||||
CapacityResult capacityResult = thereAreAvailableHoursFrom(
|
||||
start, each.allocation,
|
||||
each.duration);
|
||||
dateFromWhichToAllocate, each.allocation, each.duration);
|
||||
if (!capacityResult.thereIsCapacityAvailable()) {
|
||||
cannotSatisfy.add(each.allocation);
|
||||
markUnsatisfied(each.allocation, capacityResult);
|
||||
|
|
|
|||
|
|
@ -341,4 +341,8 @@ public class IntraDayDate implements Comparable<IntraDayDate> {
|
|||
return IntraDayDate.startOfDay(getDate().plusDays(1));
|
||||
}
|
||||
|
||||
public IntraDayDate previousDayAtStart() {
|
||||
return IntraDayDate.startOfDay(getDate().minusDays(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import org.navalplanner.business.calendars.entities.ThereAreHoursOnWorkHoursCalc
|
|||
import org.navalplanner.business.planner.entities.GenericResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation.AllocationsSpecified.INotFulfilledReceiver;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation.Direction;
|
||||
import org.navalplanner.business.planner.entities.SpecificResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.Task;
|
||||
import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
|
||||
|
|
@ -75,6 +76,8 @@ public class UntilFillingHoursAllocatorTest {
|
|||
|
||||
private Integer initialLengthDaysForTask;
|
||||
|
||||
private IntraDayDate endDate;
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void allTasksOfAllocationsMustBeNotNull() {
|
||||
givenAllocationsWithoutTask();
|
||||
|
|
@ -131,6 +134,41 @@ public class UntilFillingHoursAllocatorTest {
|
|||
assertThat(allocation.getAssignments(), haveHours(16, 16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theAllocationCanBeDoneFromEnd() {
|
||||
givenStartDate(IntraDayDate.startOfDay(new LocalDate(2009, 1, 10)));
|
||||
givenTaskOfDaysLength(10);// so end is day 20
|
||||
givenSpecificAllocations(ResourcesPerDay.amount(1));
|
||||
IntraDayDate newStart = ResourceAllocation.allocating(allocations)
|
||||
.untilAllocating(Direction.BACKWARD, 16);
|
||||
assertThat(newStart,
|
||||
equalTo(IntraDayDate.startOfDay(new LocalDate(2009, 1, 18))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theAllocationCanBeDoneFromAnEndThatIsInTheMiddleOfTheDay() {
|
||||
givenStartDate(IntraDayDate
|
||||
.create(new LocalDate(2009, 1, 10), hours(4)));
|
||||
givenEndDate(IntraDayDate.create(new LocalDate(2009, 1, 19), hours(2)));
|
||||
givenSpecificAllocations(ResourcesPerDay.amount(1));
|
||||
IntraDayDate newStart = ResourceAllocation.allocating(allocations)
|
||||
.untilAllocating(Direction.BACKWARD, 10);
|
||||
assertThat(newStart,
|
||||
equalTo(IntraDayDate.startOfDay(new LocalDate(2009, 1, 18))));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theAllocationCanBeDoneFromEndAndTheStartDateIsCorrectlyCalculatedIfTheLastDayDoesntTakeAll() {
|
||||
givenStartDate(IntraDayDate.startOfDay(new LocalDate(2009, 1, 10)));
|
||||
givenTaskOfDaysLength(10);// so end is day 20
|
||||
givenSpecificAllocations(ResourcesPerDay.amount(1));
|
||||
IntraDayDate newStart = ResourceAllocation.allocating(allocations)
|
||||
.untilAllocating(Direction.BACKWARD, 14);
|
||||
assertThat(newStart, equalTo(IntraDayDate.create(new LocalDate(2009, 1,
|
||||
18), hours(2))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ifNoAvailableHoursTheAllocationsAreNotSatisfied() {
|
||||
AvailabilityTimeLine availability = AvailabilityTimeLine.allValid();
|
||||
|
|
@ -385,6 +423,10 @@ public class UntilFillingHoursAllocatorTest {
|
|||
this.startDate = start;
|
||||
}
|
||||
|
||||
private void givenEndDate(IntraDayDate end) {
|
||||
this.endDate = end;
|
||||
}
|
||||
|
||||
private void createTaskIfNotCreatedYet() {
|
||||
if (task != null) {
|
||||
return;
|
||||
|
|
@ -394,7 +436,9 @@ public class UntilFillingHoursAllocatorTest {
|
|||
startDate = IntraDayDate.startOfDay(new LocalDate(2009, 10, 10));
|
||||
}
|
||||
IntraDayDate end = null;
|
||||
if (initialLengthDaysForTask != null) {
|
||||
if (this.endDate != null) {
|
||||
end = endDate;
|
||||
} else if (initialLengthDaysForTask != null) {
|
||||
LocalDate startPlusDays = startDate.getDate().plusDays(
|
||||
initialLengthDaysForTask);
|
||||
end = IntraDayDate.startOfDay(startPlusDays);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue