diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/AggregateOfDayAssignments.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/AggregateOfDayAssignments.java new file mode 100644 index 000000000..49b163fa7 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/AggregateOfDayAssignments.java @@ -0,0 +1,79 @@ +/* + * 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 . + */ + +package org.libreplan.business.planner.entities; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.joda.time.LocalDate; + +import org.libreplan.business.planner.entities.DayAssignment; + +/** + * Computes aggregate values on a set{@link DayAssignment}. + *

+ * @author Nacho Barrientos + */ +public class AggregateOfDayAssignments { + + public static AggregateOfDayAssignments createByDataRange( + List assignments, + Date start, + Date end) { + + Collections.sort(assignments, new Comparator() { + @Override + public int compare(DayAssignment arg0, DayAssignment arg1) { + return arg0.getDay().compareTo(arg1.getDay()); + } + }); + + return new AggregateOfDayAssignments( + DayAssignment.getAtInterval(assignments, + new LocalDate(start), new LocalDate(end))); + } + + private Set dayAssignments; + + private AggregateOfDayAssignments( + Collection assignments) { + Validate.notNull(assignments); + Validate.noNullElements(assignments); + this.dayAssignments = new HashSet( + assignments); + } + + public int getTotalHours() { + int sum = 0; + for (DayAssignment dayAssignment : dayAssignments) { + sum += dayAssignment.getDuration().getHours(); + } + return sum; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/Task.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/Task.java index 2ba3993ba..027c6c7fc 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/Task.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/Task.java @@ -27,6 +27,7 @@ import static org.libreplan.business.workingday.EffortDuration.min; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -1085,4 +1086,11 @@ public class Task extends TaskElement implements ITaskPositionConstrained { return true; } + @Override + public Integer getTheoreticalCompletedHoursUntilDate(Date date) { + return AggregateOfDayAssignments.createByDataRange( + this.getDayAssignments(), + this.getStartDate(), + date).getTotalHours(); + } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java index 440da27e7..1d81595eb 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java @@ -701,4 +701,10 @@ public abstract class TaskElement extends BaseEntity { return result; } + public abstract Integer getTheoreticalCompletedHoursUntilDate(Date date); + + public BigDecimal getTheoreticalAdvancePercentageUntilDate(Date date) { + BigDecimal result = new BigDecimal(this.getTheoreticalCompletedHoursUntilDate(date)); + return result.divide(new BigDecimal(this.getWorkHours())); + } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskGroup.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskGroup.java index c59be320a..16cd02843 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskGroup.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskGroup.java @@ -24,6 +24,7 @@ package org.libreplan.business.planner.entities; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.ListIterator; @@ -304,4 +305,13 @@ public class TaskGroup extends TaskElement { return false; } + @Override + public Integer getTheoreticalCompletedHoursUntilDate(Date date) { + int sum = 0; + for(TaskElement each: taskElements) { + sum += each.getTheoreticalCompletedHoursUntilDate(date); + } + return sum; + } + } \ No newline at end of file diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskMilestone.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskMilestone.java index 616cca7c5..1e3a4eebd 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskMilestone.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskMilestone.java @@ -181,4 +181,10 @@ public class TaskMilestone extends TaskElement implements ITaskPositionConstrain return false; } + @Override + public Integer getTheoreticalCompletedHoursUntilDate(Date date) { + // TODO Auto-generated method stub FIXME + return null; + } + } \ No newline at end of file diff --git a/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/TaskTest.java b/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/TaskTest.java index ace799677..a9534d40f 100644 --- a/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/TaskTest.java +++ b/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/TaskTest.java @@ -34,20 +34,25 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; import static org.libreplan.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE; +import static org.libreplan.business.test.planner.entities.DayAssignmentMatchers.haveHours; import static org.libreplan.business.workingday.EffortDuration.hours; +import java.math.BigDecimal; import java.util.Arrays; import java.util.Date; - import javax.annotation.Resource; import org.easymock.IAnswer; +import org.easymock.classextension.EasyMock; import org.joda.time.LocalDate; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.libreplan.business.IDataBootstrap; +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.orders.entities.HoursGroup; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderLine; @@ -57,10 +62,12 @@ import org.libreplan.business.planner.entities.SpecificResourceAllocation; import org.libreplan.business.planner.entities.Task; import org.libreplan.business.planner.entities.TaskElement; import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement; +import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.scenarios.entities.OrderVersion; 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; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -82,11 +89,15 @@ public class TaskTest { return result; } + private BaseCalendar taskCalendar; + private Task task; private HoursGroup hoursGroup; - private BaseCalendar calendar; + private ResourceCalendar workerCalendar; + + private Worker worker; @Resource private IDataBootstrap defaultAdvanceTypesBootstrapListener; @@ -113,11 +124,13 @@ public class TaskTest { } private BaseCalendar stubCalendar() { - calendar = createNiceMock(BaseCalendar.class); - expect(calendar.getCapacityOn(isA(PartialDay.class))) + taskCalendar = createNiceMock(BaseCalendar.class); + expect(taskCalendar.getCapacityOn(isA(PartialDay.class))) .andReturn(hours(8)).anyTimes(); - replay(calendar); - return calendar; + expect(taskCalendar.getAvailability()).andReturn( + AvailabilityTimeLine.allValid()).anyTimes(); + replay(taskCalendar); + return taskCalendar; } @Test @@ -236,8 +249,8 @@ public class TaskTest { public void ifSomeDayIsNotWorkableIsNotCounted() { final LocalDate start = new LocalDate(2010, 1, 13); - resetToNice(calendar); - expect(calendar.getCapacityOn(isA(PartialDay.class))).andAnswer( + resetToNice(taskCalendar); + expect(taskCalendar.getCapacityOn(isA(PartialDay.class))).andAnswer( new IAnswer() { @Override public EffortDuration answer() throws Throwable { @@ -247,7 +260,7 @@ public class TaskTest { : hours(8); } }).anyTimes(); - replay(calendar); + replay(taskCalendar); task.setIntraDayStartDate(IntraDayDate.create(start, EffortDuration.hours(3))); @@ -294,4 +307,119 @@ public class TaskTest { assertTrue(task.getNonLimitingResourceAllocations().size() == 1); } + @Test + public void theoreticalHoursIsZeroIfNoResourcesAreAllocated() { + assertThat(task.getTheoreticalCompletedHoursUntilDate(new Date()), equalTo(0)); + } + + @Test + public void theoreticalHoursIsTotalIfDateIsLaterThanEndDate() { + prepareTaskForTheoreticalAdvanceTesting(); + assertThat(task.getTheoreticalCompletedHoursUntilDate(task.getEndDate()), equalTo(task.getTotalHours())); + + } + + @Test + public void theoreticalHoursIsZeroIfDateIsEarlierThanStartDate() { + prepareTaskForTheoreticalAdvanceTesting(); + assertThat(task.getTheoreticalCompletedHoursUntilDate(task.getStartDate()), equalTo(0)); + + } + + @Test + public void theoreticalHoursWithADateWithinStartAndEndDateHead() { + prepareTaskForTheoreticalAdvanceTesting(); + LocalDate limit = task.getStartAsLocalDate().plusDays(1); + assertThat(task.getTheoreticalCompletedHoursUntilDate(limit.toDateTimeAtStartOfDay().toDate()), equalTo(8)); + } + + @Test + public void theoreticalHoursWithADateWithinStartAndEndDateTail() { + prepareTaskForTheoreticalAdvanceTesting(); + LocalDate limit = task.getEndAsLocalDate().minusDays(1); + assertThat(task.getTheoreticalCompletedHoursUntilDate(limit.toDateTimeAtStartOfDay().toDate()), equalTo(32)); + } + + @Test + public void theoreticalAdvancePercentageIsZeroIfNoResourcesAreAllocated() { + assertThat(task.getTheoreticalAdvancePercentageUntilDate(new Date()), equalTo(new BigDecimal(0))); + } + + @Test + public void theoreticalPercentageIsOneIfDateIsLaterThanEndDate() { + prepareTaskForTheoreticalAdvanceTesting(); + assertThat(task.getTheoreticalAdvancePercentageUntilDate(task.getEndDate()), equalTo(new BigDecimal(1))); + + } + + @Test + public void theoreticalPercentageWithADateWithinStartAndEndDateHead() { + prepareTaskForTheoreticalAdvanceTesting(); + LocalDate limit = task.getStartAsLocalDate().plusDays(1); + assertThat(task.getTheoreticalAdvancePercentageUntilDate(limit.toDateTimeAtStartOfDay().toDate()), + equalTo(new BigDecimal("0.2"))); + } + + private void prepareTaskForTheoreticalAdvanceTesting() { + task.getHoursGroup().setWorkingHours(40); + assertThat(task.getTotalHours(), equalTo(40)); + task.setEndDate(task.getStartAsLocalDate().plusDays(5).toDateTimeAtStartOfDay().toDate()); + + SpecificResourceAllocation resourceAllocation = SpecificResourceAllocation.create(task); + + givenWorker(8); + resourceAllocation.setResource(this.worker); + assertTrue(resourceAllocation.getResource() != null); + + resourceAllocation.allocate(ResourcesPerDay.amount(1)); + assertThat(resourceAllocation.getAssignments().size(), equalTo(5)); + assertThat(resourceAllocation.getAssignments(), haveHours(8, 8, 8, 8, 8)); + + assertThat(task.getAssignedHours(), equalTo(0)); + task.addResourceAllocation(resourceAllocation); + assertTrue(task.getNonLimitingResourceAllocations().size() == 1); + assertThat(task.getAssignedHours(), equalTo(40)); + assertTrue(task.getDayAssignments().size() == 5); + } + + private void givenWorker(int hoursPerDay) { + this.worker = createNiceMock(Worker.class); + givenResourceCalendarAlwaysReturning(hoursPerDay); + expect(this.worker.getCalendar()).andReturn(this.workerCalendar).anyTimes(); + replay(this.worker); + } + + /* Taken from: SpecificResourceAllocationTest (TODO: Refactor) */ + private void givenResourceCalendarAlwaysReturning(final int hours) { + this.workerCalendar = createNiceMock(ResourceCalendar.class); + expect(this.workerCalendar.getCapacityOn(isA(PartialDay.class))) + .andReturn(EffortDuration.hours(hours)).anyTimes(); + IAnswer asDurationAnswer = asDurationOnAnswer(hours(hours)); + expect( + this.workerCalendar.asDurationOn(isA(PartialDay.class), + isA(ResourcesPerDay.class))) + .andAnswer(asDurationAnswer).anyTimes(); + expect(this.workerCalendar.getCapacityWithOvertime(isA(LocalDate.class))) + .andReturn( + Capacity.create(hours(hours)) + .overAssignableWithoutLimit()).anyTimes(); + expect(this.workerCalendar.getAvailability()).andReturn( + AvailabilityTimeLine.allValid()).anyTimes(); + replay(this.workerCalendar); + } + + /* Taken from: SpecificResourceAllocationTest (TODO: Refactor) */ + private IAnswer asDurationOnAnswer( + final EffortDuration duration) { + return new IAnswer() { + + @Override + public EffortDuration answer() throws Throwable { + ResourcesPerDay perDay = (ResourcesPerDay) EasyMock + .getCurrentArguments()[1]; + return perDay.asDurationGivenWorkingDayOf(duration); + } + }; + } + } \ No newline at end of file