From f62b661159cc448307b9d7833a360cb4ccedb599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Gonz=C3=A1lez=20Fern=C3=A1ndez?= Date: Thu, 8 Oct 2009 00:27:00 +0200 Subject: [PATCH] ItEr29S06CUAsignacionGrupoRecursosAPlanificacionItEr28S06: Implementing the allocation of some hours on interval --- .../planner/entities/DayAssignment.java | 2 +- .../planner/entities/ResourceAllocation.java | 93 +++++++++++--- .../entities/SpecificResourceAllocation.java | 25 ++++ .../SpecificResourceAllocationTest.java | 118 +++++++++++++++++- 4 files changed, 218 insertions(+), 20 deletions(-) diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DayAssignment.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DayAssignment.java index 6548a1d15..a89dc65b5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DayAssignment.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DayAssignment.java @@ -64,7 +64,7 @@ public abstract class DayAssignment extends BaseEntity { } public static Map> byDay( - Collection assignments) { + Collection assignments) { Map> result = new HashMap>(); for (T t : assignments) { LocalDate day = t.getDay(); 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 18fc11357..6fe600654 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 @@ -28,6 +28,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.lang.Validate; import org.hibernate.validator.NotNull; @@ -295,6 +296,34 @@ public abstract class ResourceAllocation extends return result; } + void allocate(LocalDate startInclusive, LocalDate endExclusive, int hours) { + Validate.isTrue(hours >= 0); + Validate.isTrue(startInclusive.compareTo(endExclusive) <= 0, + "the end must be equal or posterior than start"); + List assignmentsCreated = new ArrayList(); + List days = getDays(startInclusive, endExclusive); + int[] hoursEachDay = hoursDistribution(days, hours); + int i = 0; + for (LocalDate day : getDays(startInclusive, endExclusive)) { + assignmentsCreated.addAll(distributeForDay(day, + hoursEachDay[i++])); + } + removingAssignments(getAssignments(startInclusive, endExclusive)); + addingAssignments(assignmentsCreated); + setResourcesPerDay(calculateResourcesPerDayFromAssignments()); + } + + private int[] hoursDistribution(List days, int hoursToSum) { + List shares = new ArrayList(); + for (LocalDate day : days) { + shares.add(new Share(-getWorkHoursPerDay() + .getWorkableHours(day))); + } + ShareDivision original = ShareDivision.create(shares); + ShareDivision newShare = original.plus(hoursToSum); + return original.to(newShare); + } + protected abstract List distributeForDay(LocalDate day, int totalHours); @@ -317,6 +346,21 @@ public abstract class ResourceAllocation extends return resourcesPerDay.asHoursGivenResourceWorkingDayOf(workableHours); } + private ResourcesPerDay calculateResourcesPerDayFromAssignments() { + Map> byDay = DayAssignment + .byDay(getAssignments()); + int sumTotalHours = 0; + int sumWorkableHours = 0; + for (Entry> entry : byDay.entrySet()) { + sumWorkableHours += getWorkHoursPerDay().getWorkableHours( + entry.getKey()) + * entry.getValue().size(); + sumTotalHours += getAssignedHours(entry.getValue()); + } + return ResourcesPerDay.calculateFrom( + sumTotalHours, sumWorkableHours); + } + private IWorkHours getWorkHoursPerDay() { return getWorkHoursGivenTaskHours(getTaskWorkHoursLimit()); } @@ -413,34 +457,51 @@ public abstract class ResourceAllocation extends public int getAssignedHours(final Resource resource, LocalDate start, - LocalDate end) { - return getAssignedHours(start, end, new PredicateOnDayAssignment() { + LocalDate endExclusive) { + return getAssignedHours(filter(getAssignments(start, endExclusive),new PredicateOnDayAssignment() { @Override public boolean satisfiedBy(DayAssignment dayAssignment) { return dayAssignment.isAssignedTo(resource); } - }); + })); } - - public int getAssignedHours(LocalDate start, LocalDate end) { - return getAssignedHours(start, end, - PredicateOnDayAssignment.ALWAYS_TRUE); - } - - private int getAssignedHours(LocalDate start, LocalDate end, - PredicateOnDayAssignment predicate) { - int sum = 0; + public List getAssignments(LocalDate start, + LocalDate endExclusive) { + List result = new ArrayList(); for (DayAssignment dayAssignment : getAssignments()) { - if (dayAssignment.getDay().compareTo(end) >= 0) { + if (dayAssignment.getDay().compareTo(endExclusive) >= 0) { break; } - if (dayAssignment.includedIn(start, end) - && predicate.satisfiedBy(dayAssignment)) { - sum += dayAssignment.getHours(); + if (dayAssignment.includedIn(start, endExclusive)) { + result.add(dayAssignment); } } + return result; + } + + + public int getAssignedHours(LocalDate start, LocalDate endExclusive) { + return getAssignedHours(getAssignments(start, endExclusive)); + } + + private List filter(List assignments, + PredicateOnDayAssignment predicate) { + List result = new ArrayList(); + for (DayAssignment dayAssignment : assignments) { + if (predicate.satisfiedBy(dayAssignment)) { + result.add(dayAssignment); + } + } + return result; + } + + private int getAssignedHours(List assignments) { + int sum = 0; + for (DayAssignment dayAssignment : assignments) { + sum += dayAssignment.getHours(); + } return sum; } 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 e86624ab1..427995880 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 @@ -123,6 +123,31 @@ public class SpecificResourceAllocation extends } } + public interface IAllocateHoursOnInterval { + void allocateHours(int hours); + } + + private class AllocateHoursOnInterval implements IAllocateHoursOnInterval { + + private final LocalDate start; + private final LocalDate end; + + AllocateHoursOnInterval(LocalDate start, LocalDate end) { + this.start = start; + this.end = end; + } + + public void allocateHours(int hours) { + new SpecificAssignmentsAllocation().allocate(start, end, hours); + } + } + + public IAllocateHoursOnInterval onInterval(LocalDate start, LocalDate end) { + Validate.isTrue(start.compareTo(end) <= 0, + "the end must be equal or posterior than start"); + return new AllocateHoursOnInterval(start, end); + } + @Override protected IWorkHours getWorkHoursGivenTaskHours(IWorkHours taskWorkHours) { if (getResource().getCalendar() == null) { diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/SpecificResourceAllocationTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/SpecificResourceAllocationTest.java index 4370e6070..b8f86aca8 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/SpecificResourceAllocationTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/SpecificResourceAllocationTest.java @@ -25,13 +25,18 @@ import static org.easymock.EasyMock.isA; import static org.easymock.classextension.EasyMock.createNiceMock; import static org.easymock.classextension.EasyMock.replay; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.navalplanner.business.test.planner.entities.DayAssignmentMatchers.consecutiveDays; import static org.navalplanner.business.test.planner.entities.DayAssignmentMatchers.from; import static org.navalplanner.business.test.planner.entities.DayAssignmentMatchers.haveHours; import static org.navalplanner.business.test.planner.entities.DayAssignmentMatchers.haveResourceAllocation; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.easymock.IAnswer; +import org.easymock.classextension.EasyMock; import org.joda.time.LocalDate; import org.junit.Test; import org.navalplanner.business.calendars.entities.BaseCalendar; @@ -55,7 +60,7 @@ public class SpecificResourceAllocationTest { private int assignedHours = 0; - private void givenAssignedHours(int assignedHours){ + private void givenAssignedHours(int assignedHours) { this.assignedHours = assignedHours; } @@ -68,10 +73,40 @@ public class SpecificResourceAllocationTest { replay(this.calendar); } - private void givenWorker(){ + private void givenResourceCalendar(final int defaultAnswer, final Map answersForDates){ + this.calendar = createNiceMock(ResourceCalendar.class); + expect(this.calendar.getWorkableHours(isA(LocalDate.class))).andAnswer(new IAnswer() { + + @Override + public Integer answer() throws Throwable { + LocalDate date = (LocalDate) EasyMock.getCurrentArguments()[0]; + if(answersForDates.containsKey(date)){ + return answersForDates.get(date); + } + return defaultAnswer; + } + }).anyTimes(); + expect(this.calendar.getWorkableHours(isA(Date.class))) + .andAnswer(new IAnswer() { + + @Override + public Integer answer() throws Throwable { + Date date = (Date) EasyMock.getCurrentArguments()[0]; + LocalDate localDate = new LocalDate(date.getTime()); + if(answersForDates.containsKey(localDate)){ + return answersForDates.get(localDate); + } + return defaultAnswer; + } + }).anyTimes(); + replay(this.calendar); + } + + private void givenWorker() { this.worker = createNiceMock(Worker.class); expect(this.worker.getCalendar()).andReturn(calendar).anyTimes(); - expect(this.worker.getAssignedHours(isA(LocalDate.class))).andReturn(assignedHours).anyTimes(); + expect(this.worker.getAssignedHours(isA(LocalDate.class))).andReturn( + assignedHours).anyTimes(); replay(this.worker); } @@ -138,4 +173,81 @@ public class SpecificResourceAllocationTest { assertThat(specificResourceAllocation.getAssignments(), haveHours(4, 4)); } + @Test(expected = IllegalArgumentException.class) + public void cantAllocateOnAnWrongInterval() { + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + LocalDate dayBefore = start.plusDays(-1); + specificResourceAllocation.onInterval(start, dayBefore).allocateHours( + 10); + } + + @Test + public void canAllocateZeroHours() { + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + specificResourceAllocation.onInterval(start, start.plusDays(2)) + .allocateHours(0); + } + + @Test(expected = IllegalArgumentException.class) + public void cantAllocateNegativeHours() { + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + specificResourceAllocation.onInterval(start, start.plusDays(1)) + .allocateHours(-1); + } + + @Test + public void someHoursInAnIntervalCanBeAssigned() { + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + specificResourceAllocation.onInterval(start, start.plusDays(2)) + .allocateHours(10); + assertThat(specificResourceAllocation.getAssignments(), haveHours(5, 5)); + } + + @Test + public void thePreviousAssignmentsAreReplacedWhenAllocationHoursOnInterval() { + givenResourceCalendarAlwaysReturning(3); + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + specificResourceAllocation.allocate(ResourcesPerDay.amount(1)); + specificResourceAllocation.onInterval(start, start.plusDays(2)) + .allocateHours(10); + assertThat(specificResourceAllocation.getAssignments(), haveHours(5, 5, + 3, 3)); + } + + @Test + public void theResourcesPerDayAreRecalculatedWhenAllocationHoursOnInterval() { + givenResourceCalendarAlwaysReturning(3); + LocalDate start = new LocalDate(2000, 2, 4); + givenSpecificResourceAllocation(start, 4); + ResourcesPerDay original = ResourcesPerDay.amount(1); + specificResourceAllocation.allocate(original); + specificResourceAllocation.onInterval(start, start.plusDays(2)) + .allocateHours(10); + ResourcesPerDay newResourcesPerDay = specificResourceAllocation + .getResourcesPerDay(); + assertTrue("Expecting that the resources per day is increased", + newResourcesPerDay + .getAmount().compareTo(original.getAmount()) > 0); + } + + @SuppressWarnings("serial") + @Test + public void theHoursAreDistributedTakingIntoAccountTheWorkableHours() { + final LocalDate start = new LocalDate(2000, 2, 4); + givenResourceCalendar(8, new HashMap() { + { + put(start, 2); + } + }); + givenSpecificResourceAllocation(start, 4); + specificResourceAllocation.onInterval(start, start.plusDays(2)) + .allocateHours(10); + assertThat(specificResourceAllocation.getAssignments(), haveHours(2, 8)); + } + }