From 2d96c15d73fac8d46c12f97e6ded21b30b74cdbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Gonz=C3=A1lez=20Fern=C3=A1ndez?= Date: Thu, 11 Aug 2011 19:41:49 +0200 Subject: [PATCH] [Bug #1136] Fix bug The distribution of the effort must consider the extra hours and the resources satisfying the generic allocation. FEA: ItEr75S04BugFixing --- .../business/calendars/entities/Capacity.java | 38 ++++++++++++ .../planner/entities/EffortDistributor.java | 18 ++++++ .../entities/GenericResourceAllocation.java | 6 ++ .../planner/entities/ResourceAllocation.java | 27 +++++---- .../entities/SpecificResourceAllocation.java | 7 +++ .../business/workingday/IntraDayDate.java | 21 +++++++ .../test/calendars/entities/CapacityTest.java | 59 +++++++++++++++++++ .../SpecificResourceAllocationTest.java | 18 ++++++ 8 files changed, 183 insertions(+), 11 deletions(-) diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/Capacity.java b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/Capacity.java index 0195140b9..d9caefd1d 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/Capacity.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/Capacity.java @@ -21,6 +21,8 @@ package org.navalplanner.business.calendars.entities; import static org.navalplanner.business.i18n.I18nHelper._; +import java.util.Arrays; +import java.util.Collection; import java.util.EnumMap; import org.apache.commons.lang.Validate; @@ -38,6 +40,21 @@ import org.navalplanner.business.workingday.EffortDuration.Granularity; */ public class Capacity { + public static Capacity sum(Capacity... capacities) { + return sum(Arrays.asList(capacities)); + } + + public static Capacity sum(Collection capacities) { + EffortDuration standard = EffortDuration.zero(); + EffortDuration extra = EffortDuration.zero(); + for (Capacity each : capacities) { + standard = standard.plus(each.getStandardEffort()); + extra = extra == null || each.isOverAssignableWithoutLimit() ? null + : extra.plus(each.getAllowedExtraEffort()); + } + return Capacity.create(standard).withAllowedExtraEffort(extra); + } + public static Capacity min(Capacity a, Capacity b) { return new Capacity(EffortDuration.min(a.getStandardEffort(), b.getStandardEffort()), minExtraEffort(a, b)); @@ -72,6 +89,11 @@ public class Capacity { return new Capacity(standardEffort, null); } + private static Capacity noCapacity() { + return Capacity.create(EffortDuration.hours(0)) + .notOverAssignableWithoutLimit(); + } + public static Capacity zero() { return new Capacity(EffortDuration.zero(), EffortDuration.zero()); } @@ -211,6 +233,22 @@ public class Capacity { .plus(allowedExtraEffort)) < 0; } + public Capacity minus(EffortDuration assignment) { + if (!hasSpareSpaceForMoreAllocations(assignment)) { + return noCapacity(); + } + + EffortDuration newStandard = standardEffort.minus(EffortDuration.min( + assignment, standardEffort)); + EffortDuration pending = assignment.minus(EffortDuration.min( + standardEffort, assignment)); + EffortDuration newExtra = allowedExtraEffort == null ? null + : allowedExtraEffort.minus(EffortDuration.min(pending, + allowedExtraEffort)); + return Capacity.create(newStandard).withAllowedExtraEffort(newExtra); + + } + public boolean allowsWorking() { return !getStandardEffort().isZero() || isOverAssignableWithoutLimit() || !getAllowedExtraEffort().isZero(); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java index 7eb79fdc1..f87446228 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/EffortDistributor.java @@ -238,6 +238,15 @@ public class EffortDistributor { return new ResourceWithAvailableCapacity(resource, available); } + Capacity getAvailableCapacityOn(PartialDay day, + IAssignedEffortForResource assignedEffort) { + Capacity originalCapacity = day.limitCapacity(calendar + .getCapacityWithOvertime(day.getDate())); + EffortDuration alreadyAssigned = assignedEffort + .getAssignedDurationAt(resource, day.getDate()); + return originalCapacity.minus(alreadyAssigned); + } + } /** @@ -316,6 +325,15 @@ public class EffortDistributor { new OnlyCanWork(), selector) : new OnlyCanWork(); } + public Capacity getCapacityAt(PartialDay day) { + List capacities = new ArrayList(); + for (ResourceWithDerivedData each : resourcesAssignableAt(day.getDate())) { + capacities.add(each.getAvailableCapacityOn(day, + assignedEffortForResource)); + } + return Capacity.sum(capacities); + } + public List distributeForDay(PartialDay day, EffortDuration totalDuration) { 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 7bb653038..df863d05b 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 @@ -34,6 +34,7 @@ import org.apache.commons.lang.Validate; import org.hibernate.validator.Valid; import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; +import org.navalplanner.business.calendars.entities.Capacity; import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.planner.entities.EffortDistributor.IResourceSelector; import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration; @@ -229,6 +230,11 @@ public class GenericResourceAllocation extends getCriterions(), resources); } + @Override + protected Capacity getCapacityAt(PartialDay day) { + return hoursDistributor.getCapacityAt(day); + } + } private IAssignedEffortForResource assignedEffortCalculatorOverriden = null; 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 e10fbedf0..0c13b4e50 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 @@ -22,7 +22,6 @@ package org.navalplanner.business.planner.entities; import static org.navalplanner.business.workingday.EffortDuration.hours; -import static org.navalplanner.business.workingday.EffortDuration.seconds; import static org.navalplanner.business.workingday.EffortDuration.zero; import java.math.BigDecimal; @@ -46,6 +45,7 @@ import org.hibernate.validator.NotNull; import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.BaseCalendar; +import org.navalplanner.business.calendars.entities.Capacity; import org.navalplanner.business.calendars.entities.CombinedWorkHours; import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay; @@ -55,6 +55,7 @@ import org.navalplanner.business.common.BaseEntity; import org.navalplanner.business.common.Registry; import org.navalplanner.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder; import org.navalplanner.business.planner.entities.allocationalgorithms.AllocatorForTaskDurationAndSpecifiedResourcesPerDay; +import org.navalplanner.business.planner.entities.allocationalgorithms.Distributor; import org.navalplanner.business.planner.entities.allocationalgorithms.EffortModification; import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification; import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator; @@ -974,23 +975,27 @@ public abstract class ResourceAllocation extends private EffortDuration[] secondsDistribution( AvailabilityTimeLine availability, Iterable days, EffortDuration duration) { - List shares = new ArrayList(); + List capacities = new ArrayList(); for (PartialDay each : days) { - shares.add(getShareAt(each, availability)); + capacities.add(getCapacity(availability, each)); } - ShareDivision original = ShareDivision.create(shares); - ShareDivision newShare = original.plus(duration.getSeconds()); - return fromSecondsToDurations(original.to(newShare)); + Distributor distributor = Distributor.among(capacities); + return distributor.distribute(duration).toArray( + new EffortDuration[0]); } - private EffortDuration[] fromSecondsToDurations(int[] seconds) { - EffortDuration[] result = new EffortDuration[seconds.length]; - for (int i = 0; i < result.length; i++) { - result[i] = seconds(seconds[i]); + private Capacity getCapacity(AvailabilityTimeLine availability, + PartialDay day) { + if (availability.isValid(day.getDate())) { + return getCapacityAt(day); + } else { + return Capacity.create(hours(0)) + .notOverAssignableWithoutLimit(); } - return result; } + protected abstract Capacity getCapacityAt(PartialDay each); + private Share getShareAt(PartialDay day, AvailabilityTimeLine availability) { if (availability.isValid(day.getDate())) { 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 7abd0fec6..6af078558 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 @@ -37,6 +37,7 @@ import org.joda.time.LocalDate; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.Interval; +import org.navalplanner.business.calendars.entities.Capacity; import org.navalplanner.business.calendars.entities.CombinedWorkHours; import org.navalplanner.business.calendars.entities.ICalendar; import org.navalplanner.business.common.ProportionalDistributor; @@ -199,6 +200,12 @@ public class SpecificResourceAllocation extends protected AvailabilityTimeLine getResourcesAvailability() { return AvailabilityCalculator.getCalendarAvailabilityFor(resource); } + + @Override + protected Capacity getCapacityAt(PartialDay day) { + return day.limitCapacity(getAllocationCalendar() + .getCapacityWithOvertime(day.getDate())); + } } public IEffortDistributor createEffortDistributor() { diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/workingday/IntraDayDate.java b/navalplanner-business/src/main/java/org/navalplanner/business/workingday/IntraDayDate.java index ed2b30558..aee197118 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/workingday/IntraDayDate.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/workingday/IntraDayDate.java @@ -39,6 +39,7 @@ import org.hibernate.validator.NotNull; import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.LocalDate; +import org.navalplanner.business.calendars.entities.Capacity; /** *

@@ -273,6 +274,26 @@ public class IntraDayDate implements Comparable { return durationLimitedByEnd.minus(alreadyElapsedInDay); } + public Capacity limitCapacity(Capacity capacity) { + if (capacity.getAllowedExtraEffort() == null) { + EffortDuration effort = limitWorkingDay(capacity + .getStandardEffort()); + return Capacity.create(effort).overAssignableWithoutLimit( + capacity.isOverAssignableWithoutLimit()); + } + EffortDuration allEffort = capacity.getStandardEffort().plus( + capacity.getAllowedExtraEffort()); + EffortDuration limited = limitWorkingDay(allEffort); + EffortDuration newStandard = EffortDuration.min(limited, + capacity.getStandardEffort()); + return Capacity + .create(newStandard) + .withAllowedExtraEffort( + EffortDuration.min(limited.minus(newStandard))) + .overAssignableWithoutLimit( + capacity.isOverAssignableWithoutLimit()); + } + private boolean isWholeDay() { return start.getEffortDuration().isZero() && end.getEffortDuration().isZero(); diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/CapacityTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/CapacityTest.java index 1349059a2..b7bd4b73a 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/CapacityTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/CapacityTest.java @@ -208,4 +208,63 @@ public class CapacityTest { assertTrue(withSomeExtraHours .hasSpareSpaceForMoreAllocations(hours(8))); } + + @Test + public void testMinusWithZeroExtraHours() { + Capacity c = Capacity.create(hours(8)).notOverAssignableWithoutLimit(); + + assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2))); + assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0))); + assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0))); + assertFalse(c.minus(hours(6)).isOverAssignableWithoutLimit()); + + } + + @Test + public void testMinusWithExtraHours() { + Capacity c = Capacity.create(hours(8)).withAllowedExtraEffort(hours(2)); + + assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2))); + assertThat(c.minus(hours(6)).getAllowedExtraEffort(), equalTo(hours(2))); + assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0))); + assertThat(c.minus(hours(8)).getAllowedExtraEffort(), equalTo(hours(2))); + assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0))); + assertThat(c.minus(hours(10)).getAllowedExtraEffort(), + equalTo(hours(0))); + assertThat(c.minus(hours(12)).getAllowedExtraEffort(), + equalTo(hours(0))); + assertFalse(c.minus(hours(10)).isOverAssignableWithoutLimit()); + } + + @Test + public void testMinusWithUnlimitedExtraHours() { + Capacity c = Capacity.create(hours(8)).overAssignableWithoutLimit(); + + assertThat(c.minus(hours(6)).getStandardEffort(), equalTo(hours(2))); + assertTrue(c.minus(hours(6)).isOverAssignableWithoutLimit()); + assertThat(c.minus(hours(8)).getStandardEffort(), equalTo(hours(0))); + assertTrue(c.minus(hours(8)).isOverAssignableWithoutLimit()); + assertThat(c.minus(hours(10)).getStandardEffort(), equalTo(hours(0))); + assertTrue(c.minus(hours(10)).isOverAssignableWithoutLimit()); + } + + @Test + public void capacitiesCanBeSummed() { + Capacity result = Capacity.sum( + Capacity.create(hours(8)).withAllowedExtraEffort(hours(2)), + Capacity.create(hours(8)).notOverAssignableWithoutLimit()); + + assertThat(result.getStandardEffort(), equalTo(hours(16))); + assertThat(result.getAllowedExtraEffort(), equalTo(hours(2))); + } + + @Test + public void ifSomeOfTheSumandsHasUnlimitedExtraHoursTheSumToo() { + Capacity result = Capacity.sum(Capacity.create(hours(8)) + .withAllowedExtraEffort(hours(2)), Capacity.create(hours(8)) + .overAssignableWithoutLimit()); + + assertThat(result.getStandardEffort(), equalTo(hours(16))); + assertTrue(result.isOverAssignableWithoutLimit()); + } } 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 c58dd8084..47a211037 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 @@ -87,6 +87,10 @@ public class SpecificResourceAllocationTest { this.calendar.asDurationOn(isA(PartialDay.class), isA(ResourcesPerDay.class))) .andAnswer(asDurationAnswer).anyTimes(); + expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class))) + .andReturn( + Capacity.create(hours(hours)) + .overAssignableWithoutLimit()).anyTimes(); expect(this.calendar.getAvailability()).andReturn( AvailabilityTimeLine.allValid()).anyTimes(); replay(this.calendar); @@ -124,6 +128,20 @@ public class SpecificResourceAllocationTest { .getStandardEffort()); } }).anyTimes(); + + expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class))) + .andAnswer(new IAnswer() { + + @Override + public Capacity answer() throws Throwable { + LocalDate date = (LocalDate) EasyMock + .getCurrentArguments()[0]; + if (answersForDates.containsKey(date)) { + return answersForDates.get(date); + } + return defaultAnswer; + } + }).anyTimes(); final IAnswer effortAnswer = new IAnswer() { @Override