diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericDayAssignmentsContainer.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericDayAssignmentsContainer.java index 05103d1ab..93a54e43b 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericDayAssignmentsContainer.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericDayAssignmentsContainer.java @@ -31,6 +31,7 @@ import org.navalplanner.business.common.BaseEntity; import org.navalplanner.business.scenarios.entities.Scenario; import org.navalplanner.business.util.deepcopy.OnCopy; import org.navalplanner.business.util.deepcopy.Strategy; +import org.navalplanner.business.workingday.TaskDate; /** * Object containing the {@link GenericDayAssignment generic day assignments} @@ -53,6 +54,11 @@ public class GenericDayAssignmentsContainer extends BaseEntity { private Set dayAssignments = new HashSet(); + /** + * It can be null + */ + private TaskDate endDateWithinADay; + private GenericDayAssignmentsContainer(GenericResourceAllocation resourceAllocation, Scenario scenario) { Validate.notNull(resourceAllocation); @@ -100,4 +106,12 @@ public class GenericDayAssignmentsContainer extends BaseEntity { return GenericDayAssignment.copy(this, assignments); } + public TaskDate getEndDateWithinADay() { + return endDateWithinADay; + } + + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + this.endDateWithinADay = endDateWithinADay; + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericResourceAllocation.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericResourceAllocation.java index cf3144b34..0ae49d01e 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericResourceAllocation.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/GenericResourceAllocation.java @@ -50,6 +50,7 @@ import org.navalplanner.business.util.deepcopy.OnCopy; import org.navalplanner.business.util.deepcopy.Strategy; import org.navalplanner.business.workingday.EffortDuration; import org.navalplanner.business.workingday.ResourcesPerDay; +import org.navalplanner.business.workingday.TaskDate; /** * Represents the relation between {@link Task} and a generic {@link Resource}. @@ -315,6 +316,16 @@ public class GenericResourceAllocation extends return new ExplicitlySpecifiedScenarioState( scenario); } + + @Override + TaskDate getEndDateWithinADay() { + return container.getEndDateWithinADay(); + } + + @Override + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + container.setEndDateWithinADay(endDateWithinADay); + } } private class TransientState extends DayAssignmentsState { @@ -322,6 +333,8 @@ public class GenericResourceAllocation extends private final Set genericDayAssignments; + private TaskDate endDateWithinADay; + TransientState(Set genericDayAssignments) { this.genericDayAssignments = genericDayAssignments; } @@ -366,6 +379,16 @@ public class GenericResourceAllocation extends result.resetTo(genericDayAssignments); return result; } + + @Override + TaskDate getEndDateWithinADay() { + return endDateWithinADay; + } + + @Override + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + this.endDateWithinADay = endDateWithinADay; + } } private Set getUnorderedForScenario( @@ -378,6 +401,15 @@ public class GenericResourceAllocation extends return container.getDayAssignments(); } + private TaskDate getEndDataWithinADayFor(Scenario scenario) { + GenericDayAssignmentsContainer container = containersByScenario().get( + scenario); + if (container == null) { + return null; + } + return container.getEndDateWithinADay(); + } + private class GenericDayAssignmentsNoExplicitlySpecifiedScenario extends NoExplicitlySpecifiedScenario { @@ -391,6 +423,11 @@ public class GenericResourceAllocation extends protected DayAssignmentsState switchTo(Scenario scenario) { return new ExplicitlySpecifiedScenarioState(scenario); } + + @Override + protected TaskDate getEndDateWithinADay(Scenario scenario) { + return getEndDataWithinADayFor(scenario); + } } @OnCopy(Strategy.IGNORE) diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceAllocation.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceAllocation.java index d60d79f41..07d6236a0 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceAllocation.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/ResourceAllocation.java @@ -21,6 +21,7 @@ package org.navalplanner.business.planner.entities; import static org.navalplanner.business.workingday.EffortDuration.hours; +import static org.navalplanner.business.workingday.EffortDuration.min; import static org.navalplanner.business.workingday.EffortDuration.seconds; import static org.navalplanner.business.workingday.EffortDuration.zero; @@ -64,6 +65,7 @@ import org.navalplanner.business.util.deepcopy.OnCopy; import org.navalplanner.business.util.deepcopy.Strategy; import org.navalplanner.business.workingday.EffortDuration; import org.navalplanner.business.workingday.ResourcesPerDay; +import org.navalplanner.business.workingday.TaskDate; /** * Resources are allocated to planner tasks. @@ -230,10 +232,10 @@ public abstract class ResourceAllocation extends @Override protected void setNewDataForAllocation( - ResourceAllocation allocation, + ResourceAllocation allocation, TaskDate end, ResourcesPerDay resourcesPerDay, List dayAssignments) { - allocation.resetGenericAssignmentsTo(dayAssignments); + allocation.resetGenericAssignmentsTo(dayAssignments, end); allocation.updateResourcesPerDay(); } @@ -264,7 +266,8 @@ public abstract class ResourceAllocation extends allocation.markAsUnsatisfied(); } }; - return allocator.untilAllocating(hours(hoursToAllocate)); + TaskDate result = allocator.untilAllocating(hours(hoursToAllocate)); + return result.getDate().plusDays(1); } public void allocateOnTaskLength() { @@ -705,18 +708,28 @@ public abstract class ResourceAllocation extends protected abstract void copyAssignments(Scenario from, Scenario to); protected void resetAssignmentsTo(List assignments) { + resetAssignmentsTo(assignments, null); + } + + protected void resetAssignmentsTo(List assignments, + TaskDate endDateWithinADay) { removingAssignments(withoutConsolidated(getAssignments())); addingAssignments(assignments); updateOriginalTotalAssigment(); + getDayAssignmentsState().setEndDateWithinADay(endDateWithinADay); } protected void resetAssigmentsForInterval(LocalDate startInclusive, LocalDate endExclusive, List assignmentsCreated) { + boolean finishedByEnd = isAlreadyFinishedBy(endExclusive); removingAssignments(withoutConsolidated(getAssignments(startInclusive, endExclusive))); addingAssignments(assignmentsCreated); updateOriginalTotalAssigment(); updateResourcesPerDay(); + if (finishedByEnd) { + getDayAssignmentsState().setEndDateWithinADay(null); + } } private static List withoutConsolidated( @@ -765,8 +778,16 @@ public abstract class ResourceAllocation extends EffortDuration sumWorkableEffort = zero(); final ResourcesPerDay ONE_RESOURCE_PER_DAY = ResourcesPerDay.amount(1); for (Entry> entry : byDay.entrySet()) { - sumWorkableEffort = sumWorkableEffort.plus(getAllocationCalendar() - .asDurationOn(entry.getKey(), ONE_RESOURCE_PER_DAY)); + LocalDate day = entry.getKey(); + EffortDuration incrementWorkable = getAllocationCalendar() + .asDurationOn(entry.getKey(), ONE_RESOURCE_PER_DAY); + TaskDate endDateWithinADay = getDayAssignmentsState().getEndDateWithinADay(); + if (endDateWithinADay != null + && day.equals(endDateWithinADay.getDate())) { + incrementWorkable = min(incrementWorkable, + endDateWithinADay.getEffortDuration()); + } + sumWorkableEffort = sumWorkableEffort.plus(incrementWorkable); sumTotalEffort = sumTotalEffort.plus(getAssignedDuration(entry .getValue())); } @@ -791,8 +812,9 @@ public abstract class ResourceAllocation extends protected abstract ICalendar getCalendarGivenTaskCalendar( ICalendar taskCalendar); - private void resetGenericAssignmentsTo(List assignments) { - resetAssignmentsTo(cast(assignments)); + private void resetGenericAssignmentsTo(List assignments, + TaskDate end) { + resetAssignmentsTo(cast(assignments), end); } private List cast(List value) { @@ -848,6 +870,21 @@ public abstract class ResourceAllocation extends return dayAssignmentsOrdered; } + /** + * It can be null. It allows to mark that the allocation is finished in + * a point within a day instead of taking the whole day + */ + abstract TaskDate getEndDateWithinADay(); + + /** + * Set a new endDateWithinADay. + * + * @param endDateWithinADay + * it can be null + * @see getEndDateWithinADay + */ + public abstract void setEndDateWithinADay(TaskDate endDateWithinADay); + protected abstract Collection getUnorderedAssignments(); protected void addingAssignments(Collection assignments) { @@ -915,6 +952,11 @@ public abstract class ResourceAllocation extends protected abstract class NoExplicitlySpecifiedScenario extends DayAssignmentsState { + @Override + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + modificationsNotAllowed(); + } + @Override protected final void removeAssignments( List assignments) { @@ -953,11 +995,20 @@ public abstract class ResourceAllocation extends @Override protected Collection getUnorderedAssignments() { - Scenario currentScenario = Registry - .getScenarioManager().getCurrent(); + Scenario currentScenario = currentScenario(); return getUnorderedAssignmentsForScenario(currentScenario); } + private Scenario currentScenario() { + return Registry.getScenarioManager().getCurrent(); + } + + TaskDate getEndDateWithinADay() { + return getEndDateWithinADay(currentScenario); + } + + protected abstract TaskDate getEndDateWithinADay(Scenario scenario); + protected abstract Collection getUnorderedAssignmentsForScenario( Scenario scenario); } @@ -1155,6 +1206,8 @@ public abstract class ResourceAllocation extends final void mergeAssignments(ResourceAllocation modifications) { getDayAssignmentsState().mergeAssignments(modifications); + getDayAssignmentsState().setEndDateWithinADay( + modifications.getDayAssignmentsState().getEndDateWithinADay()); } public void detach() { diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificDayAssignmentsContainer.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificDayAssignmentsContainer.java index ccf513cb9..f4c8b17ee 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificDayAssignmentsContainer.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificDayAssignmentsContainer.java @@ -31,6 +31,7 @@ import org.navalplanner.business.common.BaseEntity; import org.navalplanner.business.scenarios.entities.Scenario; import org.navalplanner.business.util.deepcopy.OnCopy; import org.navalplanner.business.util.deepcopy.Strategy; +import org.navalplanner.business.workingday.TaskDate; /** * Object containing the {@link SpecificDayAssignment specific day assignments} @@ -53,6 +54,11 @@ public class SpecificDayAssignmentsContainer extends BaseEntity { private Set dayAssignments = new HashSet(); + /** + * It can be null + */ + private TaskDate endDateWithinADay; + @Valid public Set getDayAssignments() { return new HashSet(dayAssignments); @@ -100,4 +106,12 @@ public class SpecificDayAssignmentsContainer extends BaseEntity { return SpecificDayAssignment.copy(this, assignments); } + public TaskDate getEndDateWithinADay() { + return endDateWithinADay; + } + + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + this.endDateWithinADay = endDateWithinADay; + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificResourceAllocation.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificResourceAllocation.java index 0b6769424..b86cb3dc6 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificResourceAllocation.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/SpecificResourceAllocation.java @@ -51,6 +51,7 @@ import org.navalplanner.business.util.deepcopy.OnCopy; import org.navalplanner.business.util.deepcopy.Strategy; import org.navalplanner.business.workingday.EffortDuration; import org.navalplanner.business.workingday.ResourcesPerDay; +import org.navalplanner.business.workingday.TaskDate; /** * Represents the relation between {@link Task} and a specific {@link Worker}. @@ -299,6 +300,16 @@ public class SpecificResourceAllocation extends container.resetTo(assignmentsCopied); } + @Override + TaskDate getEndDateWithinADay() { + return container.getEndDateWithinADay(); + } + + @Override + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + container.setEndDateWithinADay(endDateWithinADay); + } + @Override protected void setParentFor(SpecificDayAssignment each) { each.setSpecificResourceAllocation(outerSpecificAllocation); @@ -316,6 +327,8 @@ public class SpecificResourceAllocation extends private final Set specificDaysAssignment; + private TaskDate endDateWithinADay; + TransientState(Set specificDayAssignments) { this.specificDaysAssignment = specificDayAssignments; } @@ -360,6 +373,16 @@ public class SpecificResourceAllocation extends result.resetTo(specificDaysAssignment); return result; } + + @Override + TaskDate getEndDateWithinADay() { + return endDateWithinADay; + } + + @Override + public void setEndDateWithinADay(TaskDate endDateWithinADay) { + this.endDateWithinADay = endDateWithinADay; + } } private Set getUnorderedFor(Scenario scenario) { @@ -371,6 +394,15 @@ public class SpecificResourceAllocation extends return container.getDayAssignments(); } + private TaskDate getEndDataWithinADayFor(Scenario scenario) { + SpecificDayAssignmentsContainer container = containersByScenario().get( + scenario); + if (container == null) { + return null; + } + return container.getEndDateWithinADay(); + } + private class SpecificDayAssignmentsNoExplicitlySpecifiedScenario extends NoExplicitlySpecifiedScenario { @@ -385,6 +417,11 @@ public class SpecificResourceAllocation extends return new ExplicitlySpecifiedScenarioState(scenario); } + @Override + protected TaskDate getEndDateWithinADay(Scenario scenario) { + return getEndDataWithinADayFor(scenario); + } + } @OnCopy(Strategy.IGNORE) diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/AllocatorForSpecifiedResourcesPerDayAndHours.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/AllocatorForSpecifiedResourcesPerDayAndHours.java index d98d6f85c..803057154 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/AllocatorForSpecifiedResourcesPerDayAndHours.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/AllocatorForSpecifiedResourcesPerDayAndHours.java @@ -38,6 +38,7 @@ import org.navalplanner.business.planner.entities.ResourceAllocation; import org.navalplanner.business.planner.entities.Task; import org.navalplanner.business.workingday.EffortDuration; import org.navalplanner.business.workingday.ResourcesPerDay; +import org.navalplanner.business.workingday.TaskDate; public abstract class AllocatorForSpecifiedResourcesPerDayAndHours { @@ -60,22 +61,22 @@ public abstract class AllocatorForSpecifiedResourcesPerDayAndHours { } } - public LocalDate untilAllocating(EffortDuration effortToAllocate) { + public TaskDate untilAllocating(EffortDuration effortToAllocate) { LocalDate taskStart = LocalDate.fromDateFields(task.getStartDate()); LocalDate start = (task.getFirstDayNotConsolidated().compareTo( taskStart) >= 0) ? task.getFirstDayNotConsolidated() : taskStart; int i = 0; - int maxDaysElapsed = 0; + TaskDate currentEnd = TaskDate.create(start, zero()); for (EffortPerAllocation each : effortPerAllocation(start, effortToAllocate)) { - int daysElapsedForCurrent = untilAllocating(start, each.allocation, + TaskDate endCandidate = untilAllocating(start, each.allocation, each.duration); - maxDaysElapsed = Math.max(maxDaysElapsed, daysElapsedForCurrent); + currentEnd = TaskDate.max(currentEnd, endCandidate); i++; } - setAssignmentsForEachAllocation(); - return start.plusDays(maxDaysElapsed); + setAssignmentsForEachAllocation(currentEnd); + return currentEnd; } private List effortPerAllocation(LocalDate start, @@ -84,21 +85,29 @@ public abstract class AllocatorForSpecifiedResourcesPerDayAndHours { .calculateEffortsPerAllocation(start, toBeAssigned); } - private int untilAllocating(LocalDate start, + /** + * + * @param start + * @param resourcesPerDayModification + * @param effortRemaining + * @return the moment on which the allocation would be completed + */ + private TaskDate untilAllocating(LocalDate start, ResourcesPerDayModification resourcesPerDayModification, EffortDuration effortRemaining) { - int day = 0; - while (effortRemaining.compareTo(zero()) > 0) { - LocalDate current = start.plusDays(day); - EffortDuration taken = assignForDay(resourcesPerDayModification, + EffortDuration taken = zero(); + LocalDate lastDate = start; + for (LocalDate current = start; effortRemaining.compareTo(zero()) > 0; current = current + .plusDays(1)) { + lastDate = current; + taken = assignForDay(resourcesPerDayModification, current, effortRemaining); effortRemaining = effortRemaining.minus(taken); - day++; } - return day; + return TaskDate.create(lastDate, taken); } - private void setAssignmentsForEachAllocation() { + private void setAssignmentsForEachAllocation(TaskDate end) { for (Entry> entry : resultAssignments .entrySet()) { ResourceAllocation allocation = entry.getKey() @@ -106,12 +115,14 @@ public abstract class AllocatorForSpecifiedResourcesPerDayAndHours { ResourcesPerDay resourcesPerDay = entry.getKey() .getGoal(); List value = entry.getValue(); - setNewDataForAllocation(allocation, resourcesPerDay, value); + setNewDataForAllocation(allocation, end, resourcesPerDay, + value); } } protected abstract void setNewDataForAllocation( - ResourceAllocation allocation, ResourcesPerDay resourcesPerDay, + ResourceAllocation allocation, TaskDate explicitEnd, + ResourcesPerDay resourcesPerDay, List dayAssignments); protected abstract List createAssignmentsAtDay( diff --git a/navalplanner-business/src/main/resources/org/navalplanner/business/planner/entities/ResourceAllocations.hbm.xml b/navalplanner-business/src/main/resources/org/navalplanner/business/planner/entities/ResourceAllocations.hbm.xml index 2d4f0f577..8d95bf8d2 100644 --- a/navalplanner-business/src/main/resources/org/navalplanner/business/planner/entities/ResourceAllocations.hbm.xml +++ b/navalplanner-business/src/main/resources/org/navalplanner/business/planner/entities/ResourceAllocations.hbm.xml @@ -81,6 +81,14 @@ --> + + + + + + @@ -101,6 +109,14 @@ --> + + + + + + diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/AllocationUntilFillingHoursTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/AllocationUntilFillingHoursTest.java index 930c05f93..f6a6a4961 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/AllocationUntilFillingHoursTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/AllocationUntilFillingHoursTest.java @@ -89,6 +89,26 @@ public class AllocationUntilFillingHoursTest { assertThat(allocation.getAssignments(), haveHours(16, 16)); } + @Test + public void theResourcesPerDayIsCalculatedCorrectlyIfTheLastDayHasFilledAllHours() { + givenSpecificAllocations(ResourcesPerDay.amount(1)); + ResourceAllocation.allocating(allocations).untilAllocating(32); + ResourceAllocation allocation = allocations.get(0) + .getBeingModified(); + assertThat(allocation.getResourcesPerDay(), + equalTo(ResourcesPerDay.amount(1))); + } + + @Test + public void theResourcesPerDayIsCalculatedCorrectlyIfHasEndedInTheMiddleOfTheEnd() { + givenSpecificAllocations(ResourcesPerDay.amount(1)); + ResourceAllocation.allocating(allocations).untilAllocating(30); + ResourceAllocation allocation = allocations.get(0) + .getBeingModified(); + assertThat(allocation.getResourcesPerDay(), + equalTo(ResourcesPerDay.amount(1))); + } + @Test public void worksWellForSeveralSpecificAllocations() { givenSpecificAllocations(ResourcesPerDay.amount(1), ResourcesPerDay