Remove date attribute from Stretch

Date is a relative value depending on allocation dates and length
percentage defined by Stretch.

FEA: ItEr75S23FixAllocationModel
This commit is contained in:
Manuel Rego Casasnovas 2011-09-09 10:11:34 +02:00
parent 1f85934e30
commit 7da6dafc62
9 changed files with 251 additions and 261 deletions

View file

@ -39,28 +39,43 @@ import org.joda.time.LocalDate;
*/
public class Stretch {
public static Stretch create(LocalDate date, BigDecimal datePercent, BigDecimal workPercent) {
return new Stretch(date, datePercent, workPercent);
public static Stretch create(BigDecimal datePercent, BigDecimal workPercent) {
return new Stretch(datePercent, workPercent);
}
public static LocalDate getDateByLengthProportion(
ResourceAllocation<?> allocation, BigDecimal lengthProportion) {
int allocationDuration = Days.daysBetween(allocation.getStartDate(),
allocation.getEndDate()).getDays();
int days = lengthProportion.multiply(
BigDecimal.valueOf(allocationDuration)).intValue();
return allocation.getStartDate().plusDays(days);
}
public static BigDecimal getLengthProportionByDate(
ResourceAllocation<?> allocation, LocalDate date) {
int allocationDuration = Days.daysBetween(allocation.getStartDate(),
allocation.getEndDate()).getDays();
int days = Days.daysBetween(allocation.getStartDate(), date).getDays();
return daysProportion(days, allocationDuration);
}
private static BigDecimal daysProportion(int daysPartial, int daysTotal) {
if (daysTotal == 0) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(daysPartial).divide(
BigDecimal.valueOf(daysTotal), 2, BigDecimal.ROUND_HALF_EVEN);
}
/**
* Infers the datePercent based on duration of task and the date of the
* Stretch
*
* @param date
* @param task
* @param workPercent
* @return
* Infers the lengthPercent based on duration of resource allocation and the
* date of the Stretch
*/
public static Stretch create(LocalDate date, Task task,
BigDecimal workPercent) {
LocalDate start = task.getStartAsLocalDate();
LocalDate end = task.getEndAsLocalDate();
Days taskDuration = Days.daysBetween(start, end);
Days daysDuration = Days.daysBetween(start, date);
BigDecimal daysPercent = daysPercent(daysDuration, taskDuration);
return new Stretch(date, daysPercent, workPercent);
public static Stretch create(LocalDate date,
ResourceAllocation<?> allocation, BigDecimal workPercent) {
return new Stretch(getLengthProportionByDate(allocation, date),
workPercent);
}
protected static BigDecimal daysPercent(Days daysPartial, Days daysTotal) {
@ -81,7 +96,6 @@ public class Stretch {
public static Stretch copy(Stretch stretch) {
Stretch result = new Stretch();
result.date = stretch.date;
result.lengthPercentage = stretch.lengthPercentage;
result.amountWorkPercentage = stretch.amountWorkPercentage;
result.consolidated = stretch.consolidated;
@ -93,20 +107,18 @@ public class Stretch {
return ConsolidatedStretch.fromConsolidatedProgress(resourceAllocation);
}
public static List<Stretch> sortByDate(
public static List<Stretch> sortByLengthPercentage(
List<Stretch> stretches) {
Collections.sort(stretches, new Comparator<Stretch>() {
@Override
public int compare(Stretch o1, Stretch o2) {
return o1.getDate().compareTo(o2.getDate());
return o1.getLengthPercentage().compareTo(
o2.getLengthPercentage());
}
});
return stretches;
}
@NotNull
private LocalDate date = new LocalDate();
@NotNull
private BigDecimal lengthPercentage = BigDecimal.ZERO;
@ -119,8 +131,7 @@ public class Stretch {
// Transient value, a stretch is consolidated if it's a consolidated stretch
private boolean consolidated = false;
private Stretch(LocalDate date, BigDecimal lengthPercent, BigDecimal progressPercent) {
this.date = date;
private Stretch(BigDecimal lengthPercent, BigDecimal progressPercent) {
this.lengthPercentage = lengthPercent;
this.amountWorkPercentage = progressPercent;
}
@ -129,12 +140,12 @@ public class Stretch {
}
public void setDate(LocalDate date) {
this.date = date;
public void setDateIn(ResourceAllocation<?> allocation, LocalDate date) {
setLengthPercentage(getLengthProportionByDate(allocation, date));
}
public LocalDate getDate() {
return date;
public LocalDate getDateIn(ResourceAllocation<?> allocation) {
return getDateByLengthProportion(allocation, lengthPercentage);
}
/**
@ -178,7 +189,8 @@ public class Stretch {
}
public String toString() {
return String.format("(%s, %s, %s, readOnly: %s) ", date, lengthPercentage, amountWorkPercentage, readOnly);
return String.format("(%s, %s, readOnly: %s) ", lengthPercentage,
amountWorkPercentage, readOnly);
}
public boolean isReadOnly() {
@ -219,15 +231,8 @@ class ConsolidatedStretch {
final Task task = resourceAllocation.getTask();
final LocalDate consolidatedEnd = lastDay(consolidated);
return create(consolidatedEnd.plusDays(1), task.getAdvancePercentage(), task);
}
private static Stretch create(LocalDate date, BigDecimal advancePercentage,
Task task) {
Stretch result = Stretch.create(date, task, advancePercentage);
result.readOnly(true);
result.consolidated(true);
return result;
return Stretch.create(consolidatedEnd.plusDays(1), resourceAllocation,
task.getAdvancePercentage());
}
private ConsolidatedStretch() {

View file

@ -206,8 +206,8 @@ public class StretchesFunction extends AssignmentFunction {
// Transient. Calculated from resourceAllocation
private Stretch consolidatedStretch;
// Transient. Used to calculate read-only last stretch
private LocalDate taskEndDate;
// Transient. Used to calculate stretches dates
private ResourceAllocation<?> resourceAllocation;
public static StretchesFunction create() {
return (StretchesFunction) create(new StretchesFunction());
@ -220,14 +220,14 @@ public class StretchesFunction extends AssignmentFunction {
}
public static List<Interval> intervalsFor(
public static List<Interval> intervalsFor(ResourceAllocation<?> allocation,
Collection<? extends Stretch> streches) {
ArrayList<Interval> result = new ArrayList<Interval>();
LocalDate previous = null, stretchDate = null;
BigDecimal sumOfProportions = BigDecimal.ZERO, loadedProportion = BigDecimal.ZERO;
for (Stretch each : streches) {
stretchDate = each.getDate();
stretchDate = each.getDateIn(allocation);
loadedProportion = each.getAmountWorkPercentage().subtract(
sumOfProportions);
if (loadedProportion.signum() < 0) {
@ -251,7 +251,7 @@ public class StretchesFunction extends AssignmentFunction {
result.type = type;
result.desiredType = desiredType;
result.consolidatedStretch = consolidatedStretch;
result.taskEndDate = taskEndDate;
result.resourceAllocation = resourceAllocation;
return result;
}
@ -264,20 +264,28 @@ public class StretchesFunction extends AssignmentFunction {
}
public List<Stretch> getStretchesDefinedByUser() {
return Collections.unmodifiableList(Stretch.sortByDate(stretches));
return Collections.unmodifiableList(Stretch
.sortByLengthPercentage(stretches));
}
@Valid
public List<Stretch> getStretches() {
List<Stretch> result = new ArrayList<Stretch>(stretches);
if (taskEndDate != null) {
result.add(getLastStretch());
}
return Collections.unmodifiableList(Stretch.sortByDate(result));
List<Stretch> result = new ArrayList<Stretch>();
result.add(getFirstStretch());
result.addAll(stretches);
result.add(getLastStretch());
return Collections.unmodifiableList(Stretch
.sortByLengthPercentage(result));
}
private Stretch getLastStretch() {
Stretch result = Stretch.create(taskEndDate, BigDecimal.ONE, BigDecimal.ONE);
Stretch result = Stretch.create(BigDecimal.ONE, BigDecimal.ONE);
result.readOnly(true);
return result;
}
private Stretch getFirstStretch() {
Stretch result = Stretch.create(BigDecimal.ZERO, BigDecimal.ZERO);
result.readOnly(true);
return result;
}
@ -308,7 +316,8 @@ public class StretchesFunction extends AssignmentFunction {
@AssertTrue(message = "At least one stretch is needed")
public boolean checkNoEmpty() {
return !getStretchesPlusConsolidated().isEmpty();
// first 0%-0% and last 100%-100% stretches are added automatically
return getStretchesPlusConsolidated().size() > 2;
}
@AssertTrue(message = "Some stretch has lower or equal values than the "
@ -323,9 +332,6 @@ public class StretchesFunction extends AssignmentFunction {
Stretch previous = iterator.next();
while (iterator.hasNext()) {
Stretch current = iterator.next();
if (current.getDate().compareTo(previous.getDate()) <= 0) {
return false;
}
if (current.getLengthPercentage().compareTo(
previous.getLengthPercentage()) <= 0) {
return false;
@ -345,7 +351,8 @@ public class StretchesFunction extends AssignmentFunction {
if (consolidatedStretch != null) {
result.add(consolidatedStretch);
}
return Collections.unmodifiableList(Stretch.sortByDate(result));
return Collections.unmodifiableList(Stretch
.sortByLengthPercentage(result));
}
@AssertTrue(message = "Last stretch should have one hundred percent for "
@ -374,16 +381,11 @@ public class StretchesFunction extends AssignmentFunction {
if (resourceAllocation.getFirstNonConsolidatedDate() == null) {
return;
}
updateStretchesDates(resourceAllocation);
taskEndDate = getTaskEndDate(resourceAllocation);
this.resourceAllocation = resourceAllocation;
getDesiredType().applyTo(resourceAllocation, this);
type = getDesiredType();
}
private LocalDate getTaskEndDate(ResourceAllocation<?> resourceAllocation) {
return resourceAllocation.getTask().getEndAsLocalDate();
}
@Override
public String getName() {
if (StretchesFunctionTypeEnum.INTERPOLATED.equals(type)) {
@ -399,7 +401,7 @@ public class StretchesFunction extends AssignmentFunction {
return Collections.emptyList();
}
checkStretchesSumOneHundredPercent();
return intervalsFor(stretches);
return intervalsFor(resourceAllocation, stretches);
}
private List<Stretch> stretchesFor(StretchesFunctionTypeEnum type) {
@ -428,8 +430,8 @@ public class StretchesFunction extends AssignmentFunction {
}
public boolean checkFirstIntervalIsPosteriorToDate(LocalDate date) {
List<Interval> intervals = StretchesFunction
.intervalsFor(getStretchesPlusConsolidated());
List<Interval> intervals = StretchesFunction.intervalsFor(
resourceAllocation, getStretchesPlusConsolidated());
if (intervals.isEmpty()) {
return false;
}
@ -449,31 +451,8 @@ public class StretchesFunction extends AssignmentFunction {
return consolidatedStretch;
}
public void setTaskEndDate(LocalDate taskEndDate) {
this.taskEndDate = taskEndDate;
}
/**
* {@link Stretch} is storing date in an attribute. When a task is moved
* these dates have to be updated.
*
* FIXME: Maybe in the future we could remove these dates as they could be
* calculated from task information.
*/
private void updateStretchesDates(ResourceAllocation<?> resourceAllocation) {
Task task = resourceAllocation.getTask();
long startDate = task.getStartDate().getTime();
long endDate = task.getEndDate().getTime();
for (Stretch stretch : stretches) {
// startDate + (percentage * (endDate - startDate))
long stretchDate = startDate
+ stretch.getLengthPercentage()
.multiply(new BigDecimal(endDate - startDate))
.longValue();
stretch.setDate(new LocalDate(stretchDate));
}
public void setResourceAllocation(ResourceAllocation<?> resourceAllocation) {
this.resourceAllocation = resourceAllocation;
}
@Override

View file

@ -296,4 +296,8 @@
columnDataType="BIGINT" />
</changeSet>
<changeSet id="drop-column-date-in-stretches-table" author="mrego">
<dropColumn tableName="stretches" columnName="date" />
</changeSet>
</databaseChangeLog>

View file

@ -312,8 +312,6 @@
<list-index column="stretch_position" />
<composite-element class="Stretch">
<property name="date" not-null="true"
type="org.joda.time.contrib.hibernate.PersistentLocalDate" />
<property name="lengthPercentage" not-null="true" column="length_percentage" />
<property name="amountWorkPercentage" not-null="true" column="amount_work_percentage" />
</composite-element>

View file

@ -21,16 +21,22 @@
package org.navalplanner.business.test.planner.entities;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import org.joda.time.LocalDate;
import org.junit.Test;
import org.navalplanner.business.planner.entities.ResourceAllocation;
import org.navalplanner.business.planner.entities.Stretch;
import org.navalplanner.business.planner.entities.StretchesFunction;
import org.navalplanner.business.planner.entities.StretchesFunction.Interval;
@ -41,10 +47,26 @@ import org.navalplanner.business.planner.entities.StretchesFunction.Interval;
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public class StretchesFunctionTest {
private static final LocalDate START_DATE = new LocalDate();
private static final LocalDate END_DATE = START_DATE.plusDays(10);
private StretchesFunction stretchesFunction;
private ResourceAllocation<?> resourceAllocation;
private ResourceAllocation<?> givenResourceAllocation() {
resourceAllocation = createNiceMock(ResourceAllocation.class);
expect(resourceAllocation.getStartDate()).andReturn(START_DATE).anyTimes();
expect(resourceAllocation.getEndDate()).andReturn(END_DATE).anyTimes();
replay(resourceAllocation);
return resourceAllocation;
}
private StretchesFunction givenStretchesFunction() {
stretchesFunction = StretchesFunction.create();
stretchesFunction.setResourceAllocation(givenResourceAllocation());
return stretchesFunction;
}
@ -54,11 +76,9 @@ public class StretchesFunctionTest {
return result;
}
private Stretch givenStretchAsChild(LocalDate date,
BigDecimal lengthPercentage,
private Stretch givenStretchAsChild(BigDecimal lengthPercentage,
BigDecimal amountWorkPercentage) {
Stretch stretch = givenStretchAsChild();
stretch.setDate(date);
stretch.setLengthPercentage(lengthPercentage);
stretch.setAmountWorkPercentage(amountWorkPercentage);
return stretch;
@ -66,7 +86,9 @@ public class StretchesFunctionTest {
private Stretch givenStretchAsChild(LocalDate date,
BigDecimal amountWorkPercentage) {
return givenStretchAsChild(date, BigDecimal.ZERO, amountWorkPercentage);
return givenStretchAsChild(
Stretch.getLengthProportionByDate(resourceAllocation, date),
amountWorkPercentage);
}
@Test
@ -85,48 +107,27 @@ public class StretchesFunctionTest {
@Test
public void stretchesFunctionCheckOneHundredPercent1() {
givenStretchesFunction();
assertFalse(stretchesFunction.checkOneHundredPercent());
assertTrue(stretchesFunction.checkOneHundredPercent());
}
@Test
public void stretchesFunctionCheckOneHundredPercent2() {
givenStretchesFunction();
givenStretchAsChild();
assertFalse(stretchesFunction.checkOneHundredPercent());
}
@Test
public void stretchesFunctionCheckOneHundredPercent3() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate(), BigDecimal.ONE, BigDecimal.ZERO);
assertFalse(stretchesFunction.checkOneHundredPercent());
}
@Test
public void stretchesFunctionCheckOneHundredPercent4() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate(), BigDecimal.ZERO, BigDecimal.ONE);
assertFalse(stretchesFunction.checkOneHundredPercent());
}
@Test
public void stretchesFunctionCheckOneHundredPercent5() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate(), BigDecimal.ONE, BigDecimal.ONE);
assertTrue(stretchesFunction.checkOneHundredPercent());
}
@Test
public void stretchesFunctionCheckStretchesOrder1() {
givenStretchesFunction();
assertFalse(stretchesFunction.checkStretchesOrder());
assertTrue(stretchesFunction.checkStretchesOrder());
}
@Test
public void stretchesFunctionCheckStretchesOrder2() {
givenStretchesFunction();
givenStretchAsChild();
assertTrue(stretchesFunction.checkStretchesOrder());
assertFalse(stretchesFunction.checkStretchesOrder());
}
@Test
@ -141,7 +142,7 @@ public class StretchesFunctionTest {
public void stretchesFunctionCheckStretchesOrder4() {
givenStretchesFunction();
givenStretchAsChild();
givenStretchAsChild(new LocalDate(), BigDecimal.ONE, BigDecimal.ONE);
givenStretchAsChild(BigDecimal.ONE, BigDecimal.ONE);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@ -149,8 +150,7 @@ public class StretchesFunctionTest {
public void stretchesFunctionCheckStretchesOrder5() {
givenStretchesFunction();
givenStretchAsChild();
givenStretchAsChild(new LocalDate().plusMonths(1), BigDecimal.ZERO,
BigDecimal.ZERO);
givenStretchAsChild(BigDecimal.ZERO, BigDecimal.ZERO);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@ -158,8 +158,7 @@ public class StretchesFunctionTest {
public void stretchesFunctionCheckStretchesOrder7() {
givenStretchesFunction();
givenStretchAsChild();
givenStretchAsChild(new LocalDate().minusMonths(1), BigDecimal.ONE,
BigDecimal.ONE);
givenStretchAsChild(BigDecimal.ONE, BigDecimal.ONE);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@ -167,43 +166,63 @@ public class StretchesFunctionTest {
public void stretchesFunctionCheckStretchesOrder6() {
givenStretchesFunction();
givenStretchAsChild();
givenStretchAsChild(new LocalDate().plusMonths(1), BigDecimal.ONE,
BigDecimal.ONE);
assertTrue(stretchesFunction.checkStretchesOrder());
givenStretchAsChild(BigDecimal.ONE, BigDecimal.ONE);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@Test
public void stretchesFunctionCheckStretchesOrder8() {
givenStretchesFunction();
givenStretchAsChild(BigDecimal.ONE, BigDecimal.ONE);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@Test
public void stretchesFunctionCheckStretchesOrder9() {
givenStretchesFunction();
givenStretchAsChild(BigDecimal.ZERO, BigDecimal.ZERO);
assertFalse(stretchesFunction.checkStretchesOrder());
}
@Test
public void ifNoStrechesNoIntervalDefinedByStreches() {
givenStretchesFunction();
assertTrue(stretchesFunction.getIntervalsDefinedByStreches().isEmpty());
}
@Test(expected = IllegalStateException.class)
public void theLastStrechMustHaveAllTheLoad() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate().plusMonths(1), new BigDecimal(0.6));
stretchesFunction.getIntervalsDefinedByStreches();
assertThat(stretchesFunction.getIntervalsDefinedByStreches().size(),
equalTo(2));
}
@Test
public void oneStrechImpliesOneInterval() {
public void theLastStrechMustHaveAllTheLoad() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate().plusMonths(1), new BigDecimal(1));
givenStretchAsChild(new LocalDate().plusDays(1),
BigDecimal.valueOf(0.6));
List<Interval> intervals = stretchesFunction
.getIntervalsDefinedByStreches();
assertThat(intervals.size(), equalTo(3));
assertThat(intervals.get(intervals.size() - 1).getLoadProportion(),
equalTo(BigDecimal.valueOf(0.4).setScale(2)));
}
@Test
public void oneStrechImpliesThreeInterval() {
givenStretchesFunction();
givenStretchAsChild(new LocalDate().plusDays(1), new BigDecimal(1));
assertThat(stretchesFunction.getIntervalsDefinedByStreches().size(),
equalTo(1));
equalTo(3));
}
@Test
public void oneStrechImpliesOneIntervalUntilDateWithLoadSpecifiedByStrech() {
givenStretchesFunction();
LocalDate strechDate = new LocalDate().plusMonths(1);
BigDecimal amountOfWorkProportion = new BigDecimal(0.5).setScale(2);
LocalDate strechDate = new LocalDate().plusDays(1);
BigDecimal amountOfWorkProportion = BigDecimal.valueOf(0.5).setScale(2);
givenStretchAsChild(strechDate, amountOfWorkProportion);
givenStretchAsChild(new LocalDate().plusMonths(2), new BigDecimal(1.0));
givenStretchAsChild(new LocalDate().plusDays(2),
BigDecimal.valueOf(1.0));
Interval firstInterval = stretchesFunction
.getIntervalsDefinedByStreches().get(0);
.getIntervalsDefinedByStreches().get(1);
assertThat(firstInterval.getEnd(), equalTo(strechDate));
assertTrue(firstInterval.hasNoStart());
assertFalse(firstInterval.hasNoStart());
assertThat(firstInterval.getLoadProportion(),
equalTo(amountOfWorkProportion));
}
@ -211,24 +230,24 @@ public class StretchesFunctionTest {
@Test
public void theLastIntervalHasStart() {
givenStretchesFunction();
LocalDate strechDate = new LocalDate().plusMonths(1);
LocalDate strechDate = new LocalDate().plusDays(1);
givenStretchAsChild(strechDate, new BigDecimal(0.5));
givenStretchAsChild(strechDate.plusDays(20), new BigDecimal(1));
givenStretchAsChild(strechDate.plusDays(2), new BigDecimal(1));
Interval lastInterval = stretchesFunction
.getIntervalsDefinedByStreches().get(1);
.getIntervalsDefinedByStreches().get(2);
assertThat(lastInterval.getStart(), equalTo(strechDate));
}
@Test
public void aIntervalInTheMiddleHasStart() {
givenStretchesFunction();
LocalDate start = new LocalDate().plusMonths(1);
LocalDate start = new LocalDate().plusDays(1);
givenStretchAsChild(start, new BigDecimal(0.5));
LocalDate middleEnd = start.plusMonths(2);
LocalDate middleEnd = start.plusDays(2);
givenStretchAsChild(middleEnd, new BigDecimal(0.6));
givenStretchAsChild(middleEnd.plusDays(10), new BigDecimal(1));
Interval middle = stretchesFunction.getIntervalsDefinedByStreches().get(
1);
givenStretchAsChild(middleEnd.plusDays(3), new BigDecimal(1));
Interval middle = stretchesFunction.getIntervalsDefinedByStreches()
.get(2);
assertFalse(middle.hasNoStart());
assertThat(middle.getStart(), equalTo(start));
assertThat(middle.getEnd(), equalTo(middleEnd));
@ -237,17 +256,17 @@ public class StretchesFunctionTest {
@Test
public void eachIntervalHasTheCorrespondingLoadForThatInterval() {
givenStretchesFunction();
LocalDate start = new LocalDate().plusMonths(1);
LocalDate start = new LocalDate().plusDays(1);
givenStretchAsChild(start, new BigDecimal(0.5));
LocalDate middleEnd = start.plusMonths(2);
LocalDate middleEnd = start.plusDays(2);
givenStretchAsChild(middleEnd, new BigDecimal(0.8));
givenStretchAsChild(middleEnd.plusDays(10), new BigDecimal(1));
givenStretchAsChild(middleEnd.plusDays(3), new BigDecimal(1));
Interval first = stretchesFunction.getIntervalsDefinedByStreches().get(
0);
1);
Interval middle = stretchesFunction.getIntervalsDefinedByStreches()
.get(1);
Interval last = stretchesFunction.getIntervalsDefinedByStreches()
.get(2);
Interval last = stretchesFunction.getIntervalsDefinedByStreches()
.get(3);
assertThat(first.getLoadProportion(), equalTo(new BigDecimal(0.5)
.setScale(2)));
assertThat(middle.getLoadProportion(), equalTo(new BigDecimal(0.3)
@ -286,4 +305,22 @@ public class StretchesFunctionTest {
assertThat(interval.getEnd(), equalTo(end));
}
@Test
public void checkCalculatedDateForStretches() {
givenStretchesFunction();
givenStretchAsChild(BigDecimal.valueOf(0.2), BigDecimal.valueOf(0.5));
givenStretchAsChild(BigDecimal.valueOf(0.5), BigDecimal.valueOf(0.8));
List<Interval> intervals = stretchesFunction
.getIntervalsDefinedByStreches();
assertNull(intervals.get(0).getStart());
assertThat(intervals.get(0).getEnd(), equalTo(START_DATE));
assertThat(intervals.get(1).getStart(), equalTo(START_DATE));
assertThat(intervals.get(1).getEnd(), equalTo(START_DATE.plusDays(2)));
assertThat(intervals.get(2).getStart(), equalTo(START_DATE.plusDays(2)));
assertThat(intervals.get(2).getEnd(), equalTo(START_DATE.plusDays(5)));
assertThat(intervals.get(3).getStart(), equalTo(START_DATE.plusDays(5)));
assertThat(intervals.get(3).getEnd(), equalTo(END_DATE));
}
}

View file

@ -27,6 +27,7 @@ import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.BaseCalendar;
import org.navalplanner.business.planner.entities.ResourceAllocation;
import org.navalplanner.business.planner.entities.Stretch;
import org.navalplanner.business.planner.entities.StretchesFunction;
import org.navalplanner.business.planner.entities.StretchesFunction.Interval;
@ -60,7 +61,7 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
return new SimpleXYModel();
}
return getAccumulatedHoursChartData(stretches,
stretchesFunctionModel.getTaskStartDate(), new BigDecimal(
stretchesFunctionModel.getResourceAllocation(), new BigDecimal(
stretchesFunctionModel.getAllocationHours()));
}
@ -73,17 +74,18 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
return new SimpleXYModel();
}
return getDedicationChart(stretches,
stretchesFunctionModel.getTaskStartDate(),
new BigDecimal(stretchesFunctionModel.getAllocationHours()),
stretchesFunctionModel.getResourceAllocation(), new BigDecimal(
stretchesFunctionModel.getAllocationHours()),
stretchesFunctionModel.getTaskCalendar());
}
protected abstract XYModel getDedicationChart(List<Stretch> stretches,
LocalDate startDate, BigDecimal totalHours,
ResourceAllocation<?> allocation, BigDecimal totalHours,
BaseCalendar taskCalendar);
protected abstract XYModel getAccumulatedHoursChartData(
List<Stretch> stretches, LocalDate startDate, BigDecimal taskHours);
List<Stretch> stretches, ResourceAllocation<?> allocation,
BigDecimal taskHours);
private static class ForDefaultStreches extends GraphicForStreches {
@ -94,19 +96,19 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
@Override
public XYModel getAccumulatedHoursChartData(List<Stretch> stretches,
LocalDate startDate, BigDecimal taskHours) {
ResourceAllocation<?> allocation, BigDecimal taskHours) {
XYModel xymodel = new SimpleXYModel();
String title = "percentage";
xymodel.addValue(title, startDate.toDateTimeAtStartOfDay()
.getMillis(), 0);
xymodel.addValue(title, allocation.getStartDate()
.toDateTimeAtStartOfDay().getMillis(), 0);
for (Stretch stretch : stretches) {
BigDecimal amountWork = stretch.getAmountWorkPercentage()
.multiply(taskHours);
xymodel.addValue(title, stretch.getDate()
xymodel.addValue(title, stretch.getDateIn(allocation)
.toDateTimeAtStartOfDay().getMillis(), amountWork);
}
@ -114,12 +116,12 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
}
protected XYModel getDedicationChart(List<Stretch> stretches,
LocalDate startDate, BigDecimal taskHours,
ResourceAllocation<?> allocation, BigDecimal taskHours,
BaseCalendar calendar){
XYModel xymodel = new SimpleXYModel();
String title = "hours";
LocalDate previousDate = startDate;
LocalDate previousDate = allocation.getStartDate();
BigDecimal previousPercentage = BigDecimal.ZERO;
xymodel.addValue(title, previousDate.toDateTimeAtStartOfDay()
.getMillis(), 0);
@ -127,12 +129,12 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
for (Stretch stretch : stretches) {
BigDecimal amountWork = stretch.getAmountWorkPercentage()
.subtract(previousPercentage).multiply(taskHours);
Integer days = Days
.daysBetween(previousDate, stretch.getDate()).getDays();
Integer days = Days.daysBetween(previousDate,
stretch.getDateIn(allocation)).getDays();
if (calendar != null) {
days -= calendar.getNonWorkableDays(previousDate,
stretch.getDate()).size();
stretch.getDateIn(allocation)).size();
}
BigDecimal hoursPerDay = BigDecimal.ZERO;
@ -143,10 +145,10 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
xymodel.addValue(title, previousDate.toDateTimeAtStartOfDay()
.getMillis() + 1, hoursPerDay);
xymodel.addValue(title, stretch.getDate()
xymodel.addValue(title, stretch.getDateIn(allocation)
.toDateTimeAtStartOfDay().getMillis(), hoursPerDay);
previousDate = stretch.getDate();
previousDate = stretch.getDateIn(allocation);
previousPercentage = stretch.getAmountWorkPercentage();
}
@ -162,51 +164,53 @@ public abstract class GraphicForStreches implements IGraphicGenerator {
@Override
public boolean areChartsEnabled(IStretchesFunctionModel model) {
return canComputeChartFrom(model.getStretchesPlusConsolidated(),
model.getTaskStartDate());
return canComputeChartFrom(model.getResourceAllocation(),
model.getStretchesPlusConsolidated());
}
@Override
protected XYModel getAccumulatedHoursChartData(List<Stretch> stretches,
LocalDate startDate, BigDecimal taskHours) {
if (!canComputeChartFrom(stretches, startDate)) {
ResourceAllocation<?> allocation, BigDecimal taskHours) {
if (!canComputeChartFrom(allocation, stretches)) {
return new SimpleXYModel();
}
int[] hoursForEachDayUsingSplines = hoursForEachDayInterpolatedUsingSplines(
stretches, startDate, taskHours);
return createModelFrom(startDate,
stretches, allocation, taskHours);
return createModelFrom(allocation.getStartDate(),
accumulatedFrom(hoursForEachDayUsingSplines));
}
@Override
protected XYModel getDedicationChart(List<Stretch> stretches,
LocalDate startDate, BigDecimal totalHours,
ResourceAllocation<?> allocation, BigDecimal totalHours,
BaseCalendar taskCalendar) {
if (!canComputeChartFrom(stretches, startDate)) {
if (!canComputeChartFrom(allocation, stretches)) {
return new SimpleXYModel();
}
int[] dataForChart = hoursForEachDayInterpolatedUsingSplines(
stretches, startDate, totalHours);
return createModelFrom(startDate, dataForChart);
stretches, allocation, totalHours);
return createModelFrom(allocation.getStartDate(), dataForChart);
}
private boolean canComputeChartFrom(List<Stretch> stretches,
LocalDate start) {
return StretchesFunctionModel.areValidForInterpolation(stretches,
start);
private boolean canComputeChartFrom(ResourceAllocation<?> allocation,
List<Stretch> stretches) {
return StretchesFunctionModel.areValidForInterpolation(allocation,
stretches);
}
private int[] hoursForEachDayInterpolatedUsingSplines(
List<Stretch> stretches, LocalDate startDate,
List<Stretch> stretches, ResourceAllocation<?> allocation,
BigDecimal taskHours) {
List<Interval> intervals = StretchesFunction
.intervalsFor(stretches);
double[] dayPoints = Interval.getDayPointsFor(startDate, intervals);
List<Interval> intervals = StretchesFunction.intervalsFor(
allocation, stretches);
double[] dayPoints = Interval.getDayPointsFor(
allocation.getStartDate(), intervals);
double[] hourPoints = Interval.getHoursPointsFor(taskHours
.intValue(), intervals);
final Stretch lastStretch = stretches.get(stretches.size() - 1);
return StretchesFunctionTypeEnum.hoursForEachDayUsingSplines(
dayPoints, hourPoints, startDate, lastStretch.getDate());
dayPoints, hourPoints, allocation.getStartDate(),
lastStretch.getDateIn(allocation));
}
private int[] accumulatedFrom(int[] hoursForEachDayUsingSplines) {

View file

@ -67,6 +67,8 @@ public interface IStretchesFunctionModel {
AssignmentFunction getStretchesFunction();
Date getStretchDate(Stretch stretch);
void setStretchDate(Stretch stretch, Date date) throws IllegalArgumentException;
void setStretchLengthPercentage(Stretch stretch, BigDecimal lengthPercentage)
@ -78,6 +80,8 @@ public interface IStretchesFunctionModel {
BaseCalendar getTaskCalendar();
ResourceAllocation<?> getResourceAllocation();
/*
* Final conversation steps
*/

View file

@ -293,7 +293,7 @@ public class StretchesFunctionController extends GenericForwardComposer {
Datebox datebox = Util.bind(tempDatebox, new Util.Getter<Date>() {
@Override
public Date get() {
return stretch.getDate().toDateTimeAtStartOfDay().toDate();
return stretchesFunctionModel.getStretchDate(stretch);
}
}, new Util.Setter<Date>() {
@Override

View file

@ -24,9 +24,7 @@ package org.navalplanner.web.planner.allocation.streches;
import static org.navalplanner.web.I18nHelper._;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -101,7 +99,7 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
this.taskEndDate = task.getEndDate();
// Initialize stretchesFunction
stretchesFunction.setTaskEndDate(task.getEndAsLocalDate());
stretchesFunction.setResourceAllocation(resourceAllocation);
this.originalStretchesFunction = stretchesFunction;
this.stretchesFunction = stretchesFunction.copy();
this.stretchesFunction.changeTypeTo(type);
@ -150,10 +148,7 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
* @return
*/
private List<Stretch> allStretches() {
List<Stretch> result = new ArrayList<Stretch>();
result.add(firstStretch());
result.addAll(stretchesFunction.getStretchesPlusConsolidated());
return result;
return stretchesFunction.getStretchesPlusConsolidated();
}
@Override
@ -165,18 +160,6 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
.getStretchesPlusConsolidated());
}
/**
* Defines an initial read-only stretch with 0% hours worked and 0% progress
*
* @return
*/
private Stretch firstStretch() {
Stretch result = Stretch.create(task.getStartAsLocalDate(),
BigDecimal.ZERO, BigDecimal.ZERO);
result.readOnly(true);
return result;
}
@Override
public void confirm() throws ValidationException {
if (stretchesFunction != null) {
@ -214,10 +197,11 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
}
}
public static boolean areValidForInterpolation(List<Stretch> stretches,
LocalDate start) {
public static boolean areValidForInterpolation(
ResourceAllocation<?> resourceAllocation, List<Stretch> stretches) {
return atLeastTwoStreches(stretches)
&& theFirstIntervalIsPosteriorToFirstDay(stretches, start);
&& theFirstIntervalIsPosteriorToFirstDay(resourceAllocation,
stretches);
}
private static boolean atLeastTwoStreches(List<Stretch> stretches) {
@ -225,13 +209,14 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
}
private static boolean theFirstIntervalIsPosteriorToFirstDay(
List<Stretch> stretches, LocalDate start) {
List<Interval> intervals = StretchesFunction.intervalsFor(stretches);
ResourceAllocation<?> resourceAllocation, List<Stretch> stretches) {
List<Interval> intervals = StretchesFunction.intervalsFor(
resourceAllocation, stretches);
if (intervals.isEmpty()) {
return false;
}
Interval first = intervals.get(0);
return first.getEnd().compareTo(start) > 0;
return first.getEnd().compareTo(resourceAllocation.getStartDate()) > 0;
}
@Override
@ -253,10 +238,11 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
Stretch consolidatedStretch = stretchesFunction
.getConsolidatedStretch();
if (consolidatedStretch != null) {
startDate = consolidatedStretch.getDate().plusDays(1);
startDate = consolidatedStretch.getDateIn(resourceAllocation)
.plusDays(1);
amountWorkPercent = consolidatedStretch.getAmountWorkPercentage().add(BigDecimal.ONE.divide(BigDecimal.valueOf(100)));
}
return Stretch.create(startDate, task, amountWorkPercent);
return Stretch.create(startDate, resourceAllocation, amountWorkPercent);
}
@Override
@ -279,6 +265,12 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
return stretchesFunction;
}
@Override
public Date getStretchDate(Stretch stretch) {
return stretch.getDateIn(resourceAllocation).toDateTimeAtStartOfDay()
.toDate();
}
@Override
public void setStretchDate(Stretch stretch, Date date)
throws IllegalArgumentException {
@ -294,15 +286,7 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
+ sameFormatAsDefaultZK(taskEndDate)));
}
stretch.setDate(new LocalDate(date));
if ((date.compareTo(taskEndDate) > 0)
|| (stretch.getAmountWorkPercentage().compareTo(BigDecimal.ONE) == 0)) {
taskEndDate = date;
recalculateStretchesPercentages();
} else {
calculatePercentage(stretch);
}
stretch.setDateIn(resourceAllocation, new LocalDate(date));
}
private String sameFormatAsDefaultZK(Date date) {
@ -312,40 +296,10 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
return formatter.format(date);
}
private void recalculateStretchesPercentages() {
List<Stretch> stretches = stretchesFunction.getStretches();
if (!stretches.isEmpty()) {
for (Stretch stretch : stretches) {
calculatePercentage(stretch);
}
}
}
private void calculatePercentage(Stretch stretch) {
long stretchDate = stretch.getDate().toDateTimeAtStartOfDay().toDate()
.getTime();
long startDate = task.getStartDate().getTime();
long endDate = taskEndDate.getTime();
// (stretchDate - startDate) / (endDate - startDate)
BigDecimal lengthPercenage = (new BigDecimal(stretchDate - startDate)
.setScale(2)).divide(new BigDecimal(endDate - startDate),
RoundingMode.DOWN);
stretch.setLengthPercentage(lengthPercenage);
}
@Override
public void setStretchLengthPercentage(Stretch stretch,
BigDecimal lengthPercentage) throws IllegalArgumentException {
stretch.setLengthPercentage(lengthPercentage);
long startDate = task.getStartDate().getTime();
long endDate = taskEndDate.getTime();
// startDate + (percentage * (endDate - startDate))
long stretchDate = startDate + lengthPercentage.multiply(
new BigDecimal(endDate - startDate)).longValue();
stretch.setDate(new LocalDate(stretchDate));
}
@Override
@ -364,4 +318,9 @@ public class StretchesFunctionModel implements IStretchesFunctionModel {
return task.getCalendar();
}
@Override
public ResourceAllocation<?> getResourceAllocation() {
return resourceAllocation;
}
}