[Bug #1136] Fix bug
The distribution of the effort must consider the extra hours and the resources satisfying the generic allocation. FEA: ItEr75S04BugFixing
This commit is contained in:
parent
5f5275f62f
commit
2d96c15d73
8 changed files with 183 additions and 11 deletions
|
|
@ -21,6 +21,8 @@ package org.navalplanner.business.calendars.entities;
|
||||||
|
|
||||||
import static org.navalplanner.business.i18n.I18nHelper._;
|
import static org.navalplanner.business.i18n.I18nHelper._;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
|
|
@ -38,6 +40,21 @@ import org.navalplanner.business.workingday.EffortDuration.Granularity;
|
||||||
*/
|
*/
|
||||||
public class Capacity {
|
public class Capacity {
|
||||||
|
|
||||||
|
public static Capacity sum(Capacity... capacities) {
|
||||||
|
return sum(Arrays.asList(capacities));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Capacity sum(Collection<? extends Capacity> 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) {
|
public static Capacity min(Capacity a, Capacity b) {
|
||||||
return new Capacity(EffortDuration.min(a.getStandardEffort(),
|
return new Capacity(EffortDuration.min(a.getStandardEffort(),
|
||||||
b.getStandardEffort()), minExtraEffort(a, b));
|
b.getStandardEffort()), minExtraEffort(a, b));
|
||||||
|
|
@ -72,6 +89,11 @@ public class Capacity {
|
||||||
return new Capacity(standardEffort, null);
|
return new Capacity(standardEffort, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Capacity noCapacity() {
|
||||||
|
return Capacity.create(EffortDuration.hours(0))
|
||||||
|
.notOverAssignableWithoutLimit();
|
||||||
|
}
|
||||||
|
|
||||||
public static Capacity zero() {
|
public static Capacity zero() {
|
||||||
return new Capacity(EffortDuration.zero(), EffortDuration.zero());
|
return new Capacity(EffortDuration.zero(), EffortDuration.zero());
|
||||||
}
|
}
|
||||||
|
|
@ -211,6 +233,22 @@ public class Capacity {
|
||||||
.plus(allowedExtraEffort)) < 0;
|
.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() {
|
public boolean allowsWorking() {
|
||||||
return !getStandardEffort().isZero() || isOverAssignableWithoutLimit()
|
return !getStandardEffort().isZero() || isOverAssignableWithoutLimit()
|
||||||
|| !getAllowedExtraEffort().isZero();
|
|| !getAllowedExtraEffort().isZero();
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,15 @@ public class EffortDistributor {
|
||||||
return new ResourceWithAvailableCapacity(resource, available);
|
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();
|
new OnlyCanWork(), selector) : new OnlyCanWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Capacity getCapacityAt(PartialDay day) {
|
||||||
|
List<Capacity> capacities = new ArrayList<Capacity>();
|
||||||
|
for (ResourceWithDerivedData each : resourcesAssignableAt(day.getDate())) {
|
||||||
|
capacities.add(each.getAvailableCapacityOn(day,
|
||||||
|
assignedEffortForResource));
|
||||||
|
}
|
||||||
|
return Capacity.sum(capacities);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<ResourceWithAssignedDuration> distributeForDay(PartialDay day,
|
public List<ResourceWithAssignedDuration> distributeForDay(PartialDay day,
|
||||||
EffortDuration totalDuration) {
|
EffortDuration totalDuration) {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import org.apache.commons.lang.Validate;
|
||||||
import org.hibernate.validator.Valid;
|
import org.hibernate.validator.Valid;
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
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.calendars.entities.ICalendar;
|
||||||
import org.navalplanner.business.planner.entities.EffortDistributor.IResourceSelector;
|
import org.navalplanner.business.planner.entities.EffortDistributor.IResourceSelector;
|
||||||
import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration;
|
import org.navalplanner.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration;
|
||||||
|
|
@ -229,6 +230,11 @@ public class GenericResourceAllocation extends
|
||||||
getCriterions(), resources);
|
getCriterions(), resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Capacity getCapacityAt(PartialDay day) {
|
||||||
|
return hoursDistributor.getCapacityAt(day);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IAssignedEffortForResource assignedEffortCalculatorOverriden = null;
|
private IAssignedEffortForResource assignedEffortCalculatorOverriden = null;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
package org.navalplanner.business.planner.entities;
|
package org.navalplanner.business.planner.entities;
|
||||||
|
|
||||||
import static org.navalplanner.business.workingday.EffortDuration.hours;
|
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 static org.navalplanner.business.workingday.EffortDuration.zero;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -46,6 +45,7 @@ import org.hibernate.validator.NotNull;
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
||||||
import org.navalplanner.business.calendars.entities.BaseCalendar;
|
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.CombinedWorkHours;
|
||||||
import org.navalplanner.business.calendars.entities.ICalendar;
|
import org.navalplanner.business.calendars.entities.ICalendar;
|
||||||
import org.navalplanner.business.calendars.entities.SameWorkHoursEveryDay;
|
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.common.Registry;
|
||||||
import org.navalplanner.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
|
import org.navalplanner.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
|
||||||
import org.navalplanner.business.planner.entities.allocationalgorithms.AllocatorForTaskDurationAndSpecifiedResourcesPerDay;
|
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.EffortModification;
|
||||||
import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
|
import org.navalplanner.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
|
||||||
import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator;
|
import org.navalplanner.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator;
|
||||||
|
|
@ -974,23 +975,27 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
|
||||||
private EffortDuration[] secondsDistribution(
|
private EffortDuration[] secondsDistribution(
|
||||||
AvailabilityTimeLine availability, Iterable<PartialDay> days,
|
AvailabilityTimeLine availability, Iterable<PartialDay> days,
|
||||||
EffortDuration duration) {
|
EffortDuration duration) {
|
||||||
List<Share> shares = new ArrayList<Share>();
|
List<Capacity> capacities = new ArrayList<Capacity>();
|
||||||
for (PartialDay each : days) {
|
for (PartialDay each : days) {
|
||||||
shares.add(getShareAt(each, availability));
|
capacities.add(getCapacity(availability, each));
|
||||||
}
|
}
|
||||||
ShareDivision original = ShareDivision.create(shares);
|
Distributor distributor = Distributor.among(capacities);
|
||||||
ShareDivision newShare = original.plus(duration.getSeconds());
|
return distributor.distribute(duration).toArray(
|
||||||
return fromSecondsToDurations(original.to(newShare));
|
new EffortDuration[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EffortDuration[] fromSecondsToDurations(int[] seconds) {
|
private Capacity getCapacity(AvailabilityTimeLine availability,
|
||||||
EffortDuration[] result = new EffortDuration[seconds.length];
|
PartialDay day) {
|
||||||
for (int i = 0; i < result.length; i++) {
|
if (availability.isValid(day.getDate())) {
|
||||||
result[i] = seconds(seconds[i]);
|
return getCapacityAt(day);
|
||||||
|
} else {
|
||||||
|
return Capacity.create(hours(0))
|
||||||
|
.notOverAssignableWithoutLimit();
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Capacity getCapacityAt(PartialDay each);
|
||||||
|
|
||||||
private Share getShareAt(PartialDay day,
|
private Share getShareAt(PartialDay day,
|
||||||
AvailabilityTimeLine availability) {
|
AvailabilityTimeLine availability) {
|
||||||
if (availability.isValid(day.getDate())) {
|
if (availability.isValid(day.getDate())) {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import org.joda.time.LocalDate;
|
||||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
||||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
|
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
|
||||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.Interval;
|
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.CombinedWorkHours;
|
||||||
import org.navalplanner.business.calendars.entities.ICalendar;
|
import org.navalplanner.business.calendars.entities.ICalendar;
|
||||||
import org.navalplanner.business.common.ProportionalDistributor;
|
import org.navalplanner.business.common.ProportionalDistributor;
|
||||||
|
|
@ -199,6 +200,12 @@ public class SpecificResourceAllocation extends
|
||||||
protected AvailabilityTimeLine getResourcesAvailability() {
|
protected AvailabilityTimeLine getResourcesAvailability() {
|
||||||
return AvailabilityCalculator.getCalendarAvailabilityFor(resource);
|
return AvailabilityCalculator.getCalendarAvailabilityFor(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Capacity getCapacityAt(PartialDay day) {
|
||||||
|
return day.limitCapacity(getAllocationCalendar()
|
||||||
|
.getCapacityWithOvertime(day.getDate()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEffortDistributor<SpecificDayAssignment> createEffortDistributor() {
|
public IEffortDistributor<SpecificDayAssignment> createEffortDistributor() {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import org.hibernate.validator.NotNull;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Days;
|
import org.joda.time.Days;
|
||||||
import org.joda.time.LocalDate;
|
import org.joda.time.LocalDate;
|
||||||
|
import org.navalplanner.business.calendars.entities.Capacity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -273,6 +274,26 @@ public class IntraDayDate implements Comparable<IntraDayDate> {
|
||||||
return durationLimitedByEnd.minus(alreadyElapsedInDay);
|
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() {
|
private boolean isWholeDay() {
|
||||||
return start.getEffortDuration().isZero()
|
return start.getEffortDuration().isZero()
|
||||||
&& end.getEffortDuration().isZero();
|
&& end.getEffortDuration().isZero();
|
||||||
|
|
|
||||||
|
|
@ -208,4 +208,63 @@ public class CapacityTest {
|
||||||
assertTrue(withSomeExtraHours
|
assertTrue(withSomeExtraHours
|
||||||
.hasSpareSpaceForMoreAllocations(hours(8)));
|
.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,10 @@ public class SpecificResourceAllocationTest {
|
||||||
this.calendar.asDurationOn(isA(PartialDay.class),
|
this.calendar.asDurationOn(isA(PartialDay.class),
|
||||||
isA(ResourcesPerDay.class)))
|
isA(ResourcesPerDay.class)))
|
||||||
.andAnswer(asDurationAnswer).anyTimes();
|
.andAnswer(asDurationAnswer).anyTimes();
|
||||||
|
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
|
||||||
|
.andReturn(
|
||||||
|
Capacity.create(hours(hours))
|
||||||
|
.overAssignableWithoutLimit()).anyTimes();
|
||||||
expect(this.calendar.getAvailability()).andReturn(
|
expect(this.calendar.getAvailability()).andReturn(
|
||||||
AvailabilityTimeLine.allValid()).anyTimes();
|
AvailabilityTimeLine.allValid()).anyTimes();
|
||||||
replay(this.calendar);
|
replay(this.calendar);
|
||||||
|
|
@ -124,6 +128,20 @@ public class SpecificResourceAllocationTest {
|
||||||
.getStandardEffort());
|
.getStandardEffort());
|
||||||
}
|
}
|
||||||
}).anyTimes();
|
}).anyTimes();
|
||||||
|
|
||||||
|
expect(this.calendar.getCapacityWithOvertime(isA(LocalDate.class)))
|
||||||
|
.andAnswer(new IAnswer<Capacity>() {
|
||||||
|
|
||||||
|
@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<EffortDuration> effortAnswer = new IAnswer<EffortDuration>() {
|
final IAnswer<EffortDuration> effortAnswer = new IAnswer<EffortDuration>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue