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 extends EffortDuration> 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 extends EffortDuration> 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