TSK-1972: calculates working time in any resolution
Prior working time calculation only happened in a full day.
This commit is contained in:
parent
4697fbe5be
commit
4a42a35a21
|
@ -1,39 +1,33 @@
|
|||
package pro.taskana.common.api;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Objects;
|
||||
|
||||
public class LocalTimeInterval {
|
||||
import pro.taskana.common.internal.Interval;
|
||||
|
||||
private LocalTime begin;
|
||||
private LocalTime end;
|
||||
/**
|
||||
* LocalTimeInterval provides a closed interval using {@link LocalTime}.
|
||||
*
|
||||
* <p>That means both begin and end must not be <code>null</code>.
|
||||
*
|
||||
* <p>Note: this class has a natural ordering that is inconsistent with equals.
|
||||
*/
|
||||
public class LocalTimeInterval extends Interval<LocalTime>
|
||||
implements Comparable<LocalTimeInterval> {
|
||||
|
||||
public LocalTimeInterval(LocalTime begin, LocalTime end) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return begin != null && end != null;
|
||||
}
|
||||
|
||||
public LocalTime getBegin() {
|
||||
return begin;
|
||||
}
|
||||
|
||||
public void setBegin(LocalTime begin) {
|
||||
this.begin = begin;
|
||||
}
|
||||
|
||||
public LocalTime getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(LocalTime end) {
|
||||
this.end = end;
|
||||
super(Objects.requireNonNull(begin), Objects.requireNonNull(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two LocalTimeInterval objects in regard to their {@link #getBegin() begin}.
|
||||
*
|
||||
* @param o the LocalTimeInterval to be compared.
|
||||
* @return a negative value if <code>o</code> begins before <code>this</code>, 0 if both have the
|
||||
* same begin and a positive value if <code>o</code> begins after <code>this</code>.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalTimeInterval [begin=" + begin + ", end=" + end + "]";
|
||||
public int compareTo(LocalTimeInterval o) {
|
||||
return begin.compareTo(o.getBegin());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,153 +1,101 @@
|
|||
package pro.taskana.common.api;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
|
||||
public class WorkingTimeCalculator {
|
||||
@SuppressWarnings("unused")
|
||||
public interface WorkingTimeCalculator {
|
||||
|
||||
public static final Map<DayOfWeek, LocalTimeInterval> WORKING_TIME;
|
||||
/**
|
||||
* Subtracts {@code workingTime} from {@code workStart}. Respects the configured working time
|
||||
* schedule and Holidays.
|
||||
*
|
||||
* <p>The returned Instant denotes the first time in point the work time starts or in short it is
|
||||
* inclusive.
|
||||
*
|
||||
* <p>E.g can be used for planned date calculation.
|
||||
*
|
||||
* @param workStart The Instant {@code workingTime} is subtracted from.
|
||||
* @param workingTime The Duration to subtract from {@code workStart}. May have any resolution
|
||||
* Duration supports, e.g. minutes or seconds.
|
||||
* @return A new Instant which represents the subtraction of working time.
|
||||
* @throws InvalidArgumentException If {@code workingTime} is negative.
|
||||
*/
|
||||
Instant subtractWorkingTime(Instant workStart, Duration workingTime)
|
||||
throws InvalidArgumentException;
|
||||
|
||||
static {
|
||||
WORKING_TIME = new EnumMap<>(DayOfWeek.class);
|
||||
WORKING_TIME.put(
|
||||
DayOfWeek.MONDAY, new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(18, 0)));
|
||||
WORKING_TIME.put(
|
||||
DayOfWeek.TUESDAY, new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(18, 0)));
|
||||
WORKING_TIME.put(
|
||||
DayOfWeek.WEDNESDAY, new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(18, 0)));
|
||||
WORKING_TIME.put(
|
||||
DayOfWeek.THURSDAY, new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(18, 0)));
|
||||
WORKING_TIME.put(
|
||||
DayOfWeek.FRIDAY, new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(18, 0)));
|
||||
WORKING_TIME.put(DayOfWeek.SATURDAY, null);
|
||||
WORKING_TIME.put(DayOfWeek.SUNDAY, null);
|
||||
}
|
||||
/**
|
||||
* Adds {@code workingTime} from {@code workStart}. Respects the configured working time schedule
|
||||
* and Holidays.
|
||||
*
|
||||
* <p>The returned Instant denotes the first time in point the work time has ended or in short it
|
||||
* is exclusive.
|
||||
*
|
||||
* <p>E.g can be used for due date calculation.
|
||||
*
|
||||
* @param workStart The Instant {@code workingTime} is added to.
|
||||
* @param workingTime The Duration to add to {@code workStart}. May have any resolution Duration
|
||||
* supports, e.g. minutes or seconds.
|
||||
* @return A new Instant which represents the addition of working time.
|
||||
* @throws InvalidArgumentException If {@code workingTime} is negative.
|
||||
*/
|
||||
Instant addWorkingTime(Instant workStart, Duration workingTime) throws InvalidArgumentException;
|
||||
|
||||
private final ZoneId zone;
|
||||
private final WorkingDaysToDaysConverter converter;
|
||||
/**
|
||||
* Calculates the working time between {@code first} and {@code second} according to the
|
||||
* configured working time schedule. The returned Duration is precise to nanoseconds.
|
||||
*
|
||||
* <p>This method does not impose any ordering on {@code first} or {@code second}.
|
||||
*
|
||||
* @param first An Instant denoting the start or end of the considered time frame.
|
||||
* @param second An Instant denoting the start or end of the considered time frame.
|
||||
* @return The Duration representing the working time between {@code first} and {@code to }.
|
||||
* @throws InvalidArgumentException If either {@code first} or {@code second} is {@code null}.
|
||||
*/
|
||||
Duration workingTimeBetween(Instant first, Instant second) throws InvalidArgumentException;
|
||||
|
||||
public WorkingTimeCalculator(WorkingDaysToDaysConverter converter) {
|
||||
this.converter = converter;
|
||||
zone = ZoneId.of("UTC");
|
||||
}
|
||||
|
||||
public Duration workingTimeBetweenTwoTimestamps(Instant from, Instant to)
|
||||
/**
|
||||
* Decides whether there is any working time between {@code first} and {@code second}.
|
||||
*
|
||||
* @see #workingTimeBetween(Instant, Instant)
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:JavadocMethod")
|
||||
default boolean isWorkingTimeBetween(Instant first, Instant second)
|
||||
throws InvalidArgumentException {
|
||||
checkValidInput(from, to);
|
||||
Instant currentTime = from;
|
||||
LocalDate currentDate = LocalDateTime.ofInstant(from, zone).toLocalDate();
|
||||
LocalDate untilDate = LocalDateTime.ofInstant(to, zone).toLocalDate();
|
||||
DayOfWeek weekDay = currentDate.getDayOfWeek();
|
||||
|
||||
if (currentDate.isEqual(untilDate)) {
|
||||
return calculateDurationWithinOneDay(from, to, weekDay, currentDate);
|
||||
}
|
||||
|
||||
Duration duration = Duration.ZERO;
|
||||
duration = duration.plus(calculateDurationOfStartDay(currentTime, weekDay, currentDate));
|
||||
currentTime = currentTime.plus(1, ChronoUnit.DAYS);
|
||||
currentDate = currentDate.plusDays(1);
|
||||
weekDay = weekDay.plus(1);
|
||||
|
||||
while (!currentDate.isEqual(untilDate)) {
|
||||
duration = duration.plus(calculateDurationOfOneWorkDay(weekDay, currentDate));
|
||||
weekDay = weekDay.plus(1);
|
||||
currentDate = currentDate.plusDays(1);
|
||||
currentTime = currentTime.plus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
|
||||
return duration.plus(calculateDurationOnEndDay(to, weekDay, currentDate));
|
||||
return !Duration.ZERO.equals(workingTimeBetween(first, second));
|
||||
}
|
||||
|
||||
private Duration calculateDurationWithinOneDay(
|
||||
Instant from, Instant to, DayOfWeek weekday, LocalDate currentDate) {
|
||||
LocalTimeInterval workHours = WORKING_TIME.get(weekday);
|
||||
if (WORKING_TIME.get(weekday) != null && !converter.isHoliday(currentDate)) {
|
||||
LocalTime start = workHours.getBegin();
|
||||
LocalTime end = workHours.getEnd();
|
||||
LocalTime fromTime = from.atZone(zone).toLocalTime();
|
||||
LocalTime toTime = to.atZone(zone).toLocalTime();
|
||||
/**
|
||||
* Decides whether {@code instant} is a working day.
|
||||
*
|
||||
* @param instant The Instant to check. May not be {@code null}.
|
||||
* @return {@code true} if {@code instant} is a working day. {@code false} otherwise.
|
||||
*/
|
||||
boolean isWorkingDay(Instant instant);
|
||||
|
||||
if (!fromTime.isBefore(start) && toTime.isBefore(end)) {
|
||||
return Duration.between(from, to);
|
||||
} else if (fromTime.isBefore(start)) {
|
||||
if (toTime.isAfter(end)) {
|
||||
return addWorkingHoursOfOneDay(weekday);
|
||||
} else if (!toTime.isBefore(start)) {
|
||||
return Duration.between(start, toTime);
|
||||
}
|
||||
} else if (fromTime.isBefore(end)) {
|
||||
return Duration.between(fromTime, end);
|
||||
}
|
||||
}
|
||||
return Duration.ZERO;
|
||||
}
|
||||
/**
|
||||
* Decides whether {@code instant} is a weekend day.
|
||||
*
|
||||
* @param instant The Instant to check. May not be {@code null}.
|
||||
* @return {@code true} if {@code instant} is a weekend day. {@code false} otherwise.
|
||||
*/
|
||||
boolean isWeekend(Instant instant);
|
||||
|
||||
private Duration calculateDurationOfOneWorkDay(DayOfWeek weekday, LocalDate date) {
|
||||
if (WORKING_TIME.get(weekday) != null && !converter.isHoliday(date)) {
|
||||
return addWorkingHoursOfOneDay(weekday);
|
||||
}
|
||||
return Duration.ZERO;
|
||||
}
|
||||
/**
|
||||
* Decides whether { @code instant} is a holiday.
|
||||
*
|
||||
* @param instant The Instant to check. May not be {@code null}.
|
||||
* @return {@code true} if {@code instant} is a holiday. {@code false} otherwise.
|
||||
*/
|
||||
boolean isHoliday(Instant instant);
|
||||
|
||||
private Duration calculateDurationOfStartDay(
|
||||
Instant startDay, DayOfWeek weekday, LocalDate date) {
|
||||
LocalTimeInterval workHours = WORKING_TIME.get(weekday);
|
||||
if (WORKING_TIME.get(weekday) != null && !converter.isHoliday(date)) {
|
||||
LocalTime fromTime = startDay.atZone(zone).toLocalTime();
|
||||
LocalTime end = workHours.getEnd();
|
||||
if (fromTime.isBefore(workHours.getBegin())) {
|
||||
return addWorkingHoursOfOneDay(weekday);
|
||||
} else if (fromTime.isBefore(end)) {
|
||||
return Duration.between(fromTime, end);
|
||||
}
|
||||
}
|
||||
return Duration.ZERO;
|
||||
}
|
||||
|
||||
private Duration calculateDurationOnEndDay(Instant endDate, DayOfWeek weekday, LocalDate date) {
|
||||
LocalTimeInterval workHours = WORKING_TIME.get(weekday);
|
||||
if (WORKING_TIME.get(weekday) != null && !converter.isHoliday(date)) {
|
||||
LocalTime start = workHours.getBegin();
|
||||
LocalTime toTime = endDate.atZone(zone).toLocalTime();
|
||||
if (toTime.isAfter(workHours.getEnd())) {
|
||||
return addWorkingHoursOfOneDay(weekday);
|
||||
} else if (!toTime.isBefore(start)) {
|
||||
return Duration.between(start, toTime);
|
||||
}
|
||||
}
|
||||
return Duration.ZERO;
|
||||
}
|
||||
|
||||
private void checkValidInput(Instant from, Instant to) throws InvalidArgumentException {
|
||||
if (from == null || to == null || from.compareTo(to) > 0) {
|
||||
throw new InvalidArgumentException("Instants are invalid.");
|
||||
}
|
||||
|
||||
for (LocalTimeInterval interval : WORKING_TIME.values()) {
|
||||
if (interval != null && !interval.isValid()) {
|
||||
throw new InvalidArgumentException(
|
||||
"The work period doesn't have two LocalTimes for start and end.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Duration addWorkingHoursOfOneDay(DayOfWeek weekday) {
|
||||
LocalTimeInterval workHours = WORKING_TIME.get(weekday);
|
||||
if (workHours.isValid()) {
|
||||
return Duration.between(workHours.getBegin(), workHours.getEnd());
|
||||
} else {
|
||||
return Duration.ZERO;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Decides whether {@code instant} is a holiday in Germany.
|
||||
*
|
||||
* @param instant The Instant to check. May not be {@code null}.
|
||||
* @return {@code true} if {@code instant} is a holiday in Germany. {@code false} otherwise.
|
||||
*/
|
||||
boolean isGermanHoliday(Instant instant);
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import java.util.Objects;
|
|||
*/
|
||||
public class Interval<T extends Comparable<? super T>> {
|
||||
|
||||
private final T begin;
|
||||
protected final T begin;
|
||||
|
||||
private final T end;
|
||||
protected final T end;
|
||||
|
||||
public Interval(T begin, T end) {
|
||||
this.begin = begin;
|
||||
|
|
|
@ -4,8 +4,10 @@ import static java.util.function.Predicate.not;
|
|||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -23,6 +25,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.common.api.CustomHoliday;
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException;
|
||||
|
@ -47,6 +50,7 @@ public class TaskanaConfigurationInitializer {
|
|||
PROPERTY_INITIALIZER_BY_CLASS.put(Duration.class, new DurationPropertyParser());
|
||||
PROPERTY_INITIALIZER_BY_CLASS.put(Instant.class, new InstantPropertyParser());
|
||||
PROPERTY_INITIALIZER_BY_CLASS.put(List.class, new ListPropertyParser());
|
||||
PROPERTY_INITIALIZER_BY_CLASS.put(Map.class, new MapPropertyParser());
|
||||
}
|
||||
|
||||
private TaskanaConfigurationInitializer() {
|
||||
|
@ -161,6 +165,109 @@ public class TaskanaConfigurationInitializer {
|
|||
TaskanaProperty taskanaProperty);
|
||||
}
|
||||
|
||||
static class MapPropertyParser implements PropertyParser<Map<?, ?>> {
|
||||
|
||||
@Override
|
||||
public Optional<Map<?, ?>> initialize(
|
||||
Map<String, String> properties,
|
||||
String separator,
|
||||
Field field,
|
||||
TaskanaProperty taskanaProperty) {
|
||||
if (!Map.class.isAssignableFrom(field.getType())) {
|
||||
throw new SystemException(
|
||||
String.format(
|
||||
"Cannot initialize field '%s' because field type '%s' is not a Map",
|
||||
field, field.getType()));
|
||||
}
|
||||
|
||||
ParameterizedType genericType = (ParameterizedType) field.getGenericType();
|
||||
Type[] actualTypeArguments = genericType.getActualTypeArguments();
|
||||
Class<?> keyClass = (Class<?>) actualTypeArguments[0];
|
||||
Type valueClass = actualTypeArguments[1];
|
||||
|
||||
// Parses property files into a Map using the following layout: <Property>.<Key> = <value>
|
||||
String propertyKey = taskanaProperty.value();
|
||||
Map<?, ?> mapFromProperties =
|
||||
properties.keySet().stream()
|
||||
.filter(it -> it.startsWith(propertyKey))
|
||||
.map(
|
||||
it -> {
|
||||
// Keys of the map entry is everything after the propertyKey + "."
|
||||
String keyAsString = it.substring(propertyKey.length() + 1);
|
||||
Object key = getStringAsObject(keyAsString, keyClass);
|
||||
|
||||
// Value of the map entry is the value from the property
|
||||
String propertyValue = properties.get(it);
|
||||
Object value = getStringAsObject(propertyValue, separator, valueClass);
|
||||
return Pair.of(key, value);
|
||||
})
|
||||
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
|
||||
|
||||
if (mapFromProperties.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(mapFromProperties);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getStringAsObject(String string, String separator, Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
if (rawType.equals(Set.class)) {
|
||||
return getStringAsSet(
|
||||
string, separator, (Class<?>) parameterizedType.getActualTypeArguments()[0]);
|
||||
}
|
||||
} else if (type instanceof Class) {
|
||||
return getStringAsObject(string, (Class<?>) type);
|
||||
}
|
||||
throw new SystemException(
|
||||
String.format(
|
||||
"Cannot parse property value '%s': It is not convertible to '%s'",
|
||||
string, type.getTypeName()));
|
||||
}
|
||||
|
||||
private Object getStringAsObject(String string, Class<?> targetClass) {
|
||||
if (targetClass.isEnum()) {
|
||||
Map<String, ?> enumConstantsByLowerCasedName =
|
||||
Arrays.stream(targetClass.getEnumConstants())
|
||||
.collect(Collectors.toMap(e -> e.toString().toLowerCase(), Function.identity()));
|
||||
Object o = enumConstantsByLowerCasedName.get(string.toLowerCase());
|
||||
if (o == null) {
|
||||
throw new SystemException(
|
||||
String.format(
|
||||
"Invalid property value '%s': Valid values are '%s' or '%s",
|
||||
string,
|
||||
enumConstantsByLowerCasedName.keySet(),
|
||||
Arrays.toString(targetClass.getEnumConstants())));
|
||||
}
|
||||
return o;
|
||||
} else if (targetClass.equals(LocalTimeInterval.class)) {
|
||||
List<String> startAndEnd = splitStringAndTrimElements(string, "-");
|
||||
if (startAndEnd.size() != 2) {
|
||||
throw new SystemException("Cannot convert " + string + " to " + LocalTimeInterval.class);
|
||||
}
|
||||
LocalTime start = LocalTime.parse(startAndEnd.get(0));
|
||||
LocalTime end = LocalTime.parse(startAndEnd.get(1));
|
||||
if (end.equals(LocalTime.MIN)) {
|
||||
end = LocalTime.MAX;
|
||||
}
|
||||
return new LocalTimeInterval(start, end);
|
||||
} else {
|
||||
throw new SystemException(
|
||||
String.format(
|
||||
"Cannot parse property value '%s': It is not convertible to '%s'",
|
||||
string, targetClass.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private Set<?> getStringAsSet(String string, String separator, Class<?> elementClass) {
|
||||
return splitStringAndTrimElements(string, separator).stream()
|
||||
.map(it -> getStringAsObject(it, elementClass))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
static class ListPropertyParser implements PropertyParser<List<?>> {
|
||||
@Override
|
||||
public Optional<List<?>> initialize(
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
package pro.taskana.common.api;
|
||||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.DAYS;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -16,15 +11,16 @@ import java.util.Set;
|
|||
import java.util.stream.LongStream;
|
||||
import java.util.stream.LongStream.Builder;
|
||||
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
import pro.taskana.common.api.CustomHoliday;
|
||||
|
||||
/**
|
||||
* The WorkingDaysToDaysConverter provides a method to convert an age in working days into an age in
|
||||
* days.
|
||||
*/
|
||||
public class WorkingDaysToDaysConverter {
|
||||
public class HolidaySchedule {
|
||||
|
||||
// offset in days from easter sunday
|
||||
private static final long OFFSET_EASTER_SUNDAY = 0;
|
||||
private static final long OFFSET_GOOD_FRIDAY = -2; // Good Friday
|
||||
private static final long OFFSET_EASTER_MONDAY = 1; // Easter Monday
|
||||
private static final long OFFSET_ASCENSION_DAY = 39; // Ascension Day
|
||||
|
@ -45,7 +41,7 @@ public class WorkingDaysToDaysConverter {
|
|||
private final Set<CustomHoliday> customHolidays;
|
||||
private final EasterCalculator easterCalculator;
|
||||
|
||||
public WorkingDaysToDaysConverter(boolean germanHolidaysEnabled, boolean corpusChristiEnabled) {
|
||||
public HolidaySchedule(boolean germanHolidaysEnabled, boolean corpusChristiEnabled) {
|
||||
this(germanHolidaysEnabled, corpusChristiEnabled, Collections.emptySet());
|
||||
}
|
||||
|
||||
|
@ -57,7 +53,7 @@ public class WorkingDaysToDaysConverter {
|
|||
* germanHolidaysEnabled and thus only validated if German holidays are enabled.
|
||||
* @param customHolidays additional custom holidays
|
||||
*/
|
||||
public WorkingDaysToDaysConverter(
|
||||
public HolidaySchedule(
|
||||
boolean germanHolidaysEnabled,
|
||||
boolean corpusChristiEnabled,
|
||||
Collection<CustomHoliday> customHolidays) {
|
||||
|
@ -67,33 +63,6 @@ public class WorkingDaysToDaysConverter {
|
|||
easterCalculator = new EasterCalculator();
|
||||
}
|
||||
|
||||
public Instant addWorkingDaysToInstant(Instant instant, Duration workingDays) {
|
||||
long days = convertWorkingDaysToDays(instant, workingDays.toDays(), ZeroDirection.ADD_DAYS);
|
||||
return instant.plus(Duration.ofDays(days));
|
||||
}
|
||||
|
||||
public Instant subtractWorkingDaysFromInstant(Instant instant, Duration workingDays) {
|
||||
long days = convertWorkingDaysToDays(instant, -workingDays.toDays(), ZeroDirection.SUB_DAYS);
|
||||
return instant.plus(Duration.ofDays(days));
|
||||
}
|
||||
|
||||
// counts working days between two dates, exclusive for both margins.
|
||||
public boolean hasWorkingDaysInBetween(Instant left, Instant right) {
|
||||
long days = Duration.between(left, right).abs().toDays();
|
||||
Instant firstInstant = left.isBefore(right) ? left : right;
|
||||
return LongStream.range(1, days).anyMatch(day -> isWorkingDay(firstInstant.plus(day, DAYS)));
|
||||
}
|
||||
|
||||
public boolean isWorkingDay(Instant referenceDate) {
|
||||
LocalDate dateToCheck = LocalDateTime.ofInstant(referenceDate, ZoneOffset.UTC).toLocalDate();
|
||||
return !isWeekend(dateToCheck) && !isHoliday(dateToCheck);
|
||||
}
|
||||
|
||||
public boolean isWeekend(LocalDate dateToCheck) {
|
||||
return dateToCheck.getDayOfWeek().equals(DayOfWeek.SATURDAY)
|
||||
|| dateToCheck.getDayOfWeek().equals(DayOfWeek.SUNDAY);
|
||||
}
|
||||
|
||||
public boolean isHoliday(LocalDate date) {
|
||||
if (germanHolidaysEnabled && isGermanHoliday(date)) {
|
||||
return true;
|
||||
|
@ -113,6 +82,7 @@ public class WorkingDaysToDaysConverter {
|
|||
|
||||
Builder builder =
|
||||
LongStream.builder()
|
||||
.add(OFFSET_EASTER_SUNDAY)
|
||||
.add(OFFSET_GOOD_FRIDAY)
|
||||
.add(OFFSET_EASTER_MONDAY)
|
||||
.add(OFFSET_ASCENSION_DAY)
|
||||
|
@ -125,29 +95,6 @@ public class WorkingDaysToDaysConverter {
|
|||
return builder.build().anyMatch(c -> c == diffFromEasterSunday);
|
||||
}
|
||||
|
||||
private long convertWorkingDaysToDays(
|
||||
final Instant startTime, long numberOfDays, ZeroDirection zeroDirection) {
|
||||
if (startTime == null) {
|
||||
throw new SystemException(
|
||||
"Internal Error: convertWorkingDaysToDays was called with a null startTime");
|
||||
}
|
||||
int direction = calculateDirection(numberOfDays, zeroDirection);
|
||||
long limit = Math.abs(numberOfDays);
|
||||
return LongStream.iterate(0, i -> i + direction)
|
||||
.filter(day -> isWorkingDay(startTime.plus(day, DAYS)))
|
||||
.skip(limit)
|
||||
.findFirst()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
private int calculateDirection(long numberOfDays, ZeroDirection zeroDirection) {
|
||||
if (numberOfDays == 0) {
|
||||
return zeroDirection.getDirection();
|
||||
} else {
|
||||
return numberOfDays >= 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WorkingDaysToDaysConverter [germanHolidaysEnabled="
|
||||
|
@ -161,22 +108,8 @@ public class WorkingDaysToDaysConverter {
|
|||
+ "]";
|
||||
}
|
||||
|
||||
private enum ZeroDirection {
|
||||
SUB_DAYS(-1),
|
||||
ADD_DAYS(1);
|
||||
|
||||
private final int direction;
|
||||
|
||||
ZeroDirection(int direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public int getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
static class EasterCalculator {
|
||||
|
||||
LocalDate cachedEasterDay;
|
||||
|
||||
/**
|
|
@ -0,0 +1,272 @@
|
|||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
|
||||
public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||
|
||||
static final ZoneOffset UTC = ZoneOffset.UTC;
|
||||
|
||||
private final HolidaySchedule holidaySchedule;
|
||||
private final WorkingTimeSchedule workingTimeSchedule;
|
||||
|
||||
public WorkingTimeCalculatorImpl(
|
||||
HolidaySchedule holidaySchedule, Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule) {
|
||||
this.holidaySchedule = holidaySchedule;
|
||||
this.workingTimeSchedule = new WorkingTimeSchedule(workingTimeSchedule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant subtractWorkingTime(Instant workStart, Duration workingTime)
|
||||
throws InvalidArgumentException {
|
||||
validatePositiveDuration(workingTime);
|
||||
WorkSlot workSlot = getWorkSlotOrPrevious(toLocalDateTime(workStart));
|
||||
return workSlot.subtractWorkingTime(workStart, workingTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant addWorkingTime(Instant workStart, Duration workingTime)
|
||||
throws InvalidArgumentException {
|
||||
validatePositiveDuration(workingTime);
|
||||
WorkSlot bestMatchingWorkSlot = getWorkSlotOrNext(toLocalDateTime(workStart));
|
||||
return bestMatchingWorkSlot.addWorkingTime(workStart, workingTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration workingTimeBetween(Instant first, Instant second)
|
||||
throws InvalidArgumentException {
|
||||
validateNonNullInstants(first, second);
|
||||
|
||||
Instant from;
|
||||
Instant to;
|
||||
if (first.isAfter(second)) {
|
||||
from = second;
|
||||
to = first;
|
||||
} else {
|
||||
from = first;
|
||||
to = second;
|
||||
}
|
||||
|
||||
WorkSlot bestMatchingWorkSlot = getWorkSlotOrNext(toLocalDateTime(from));
|
||||
Instant earliestWorkStart = max(from, bestMatchingWorkSlot.start);
|
||||
Instant endOfWorkSlot = bestMatchingWorkSlot.end;
|
||||
|
||||
if (endOfWorkSlot.compareTo(to) >= 0) {
|
||||
if (bestMatchingWorkSlot.start.compareTo(to) <= 0) {
|
||||
// easy part. _from_ and _to_ are in the same work slot
|
||||
return Duration.between(earliestWorkStart, to);
|
||||
} else {
|
||||
// _from_ and _to_ are before the bestMatchingWorkSlot aka between two work slots. We simply
|
||||
// drop it
|
||||
return Duration.ZERO;
|
||||
}
|
||||
} else {
|
||||
// Take the current duration and add the working time starting after this work slot.
|
||||
return Duration.between(earliestWorkStart, endOfWorkSlot)
|
||||
.plus(workingTimeBetween(endOfWorkSlot, to));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWorkingDay(Instant instant) {
|
||||
return workingTimeSchedule.isWorkingDay(toDayOfWeek(instant)) && !isHoliday(instant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWeekend(Instant instant) {
|
||||
DayOfWeek dayOfWeek = toDayOfWeek(instant);
|
||||
return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHoliday(Instant instant) {
|
||||
return holidaySchedule.isHoliday(toLocalDate(instant));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGermanHoliday(Instant instant) {
|
||||
return holidaySchedule.isGermanHoliday(toLocalDate(instant));
|
||||
}
|
||||
|
||||
private void validateNonNullInstants(Instant first, Instant second) {
|
||||
if (first == null || second == null) {
|
||||
throw new InvalidArgumentException("Neither first nor second may be null.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePositiveDuration(Duration workingTime) {
|
||||
if (workingTime.isNegative()) {
|
||||
throw new InvalidArgumentException("Duration must be zero or positive.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WorkSlot that matches best <code>currentDateTime</code>. If currentDateTime is
|
||||
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
||||
* next WorkSlot is returned.
|
||||
*
|
||||
* @param currentDateTime The LocalDateTime we want the best matching WorkSlot for. May not be
|
||||
* <code>null</code>.
|
||||
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to add.
|
||||
*/
|
||||
private WorkSlot getWorkSlotOrNext(LocalDateTime currentDateTime) {
|
||||
LocalDate currentDate = currentDateTime.toLocalDate();
|
||||
// We do not work on Holidays
|
||||
if (holidaySchedule.isHoliday(currentDate)) {
|
||||
return getWorkSlotOrNext(getDayAfter(currentDateTime));
|
||||
}
|
||||
SortedSet<LocalTimeInterval> workSlotsOfWorkingDay =
|
||||
workingTimeSchedule.workSlotsFor(currentDate.getDayOfWeek());
|
||||
// We are looking for the first workingSlot whose end is after the current time
|
||||
Optional<LocalTimeInterval> workSlotEndingAfterCurrentTime =
|
||||
workSlotsOfWorkingDay.stream()
|
||||
.filter(it -> it.getEnd().isAfter(currentDateTime.toLocalTime()))
|
||||
.findFirst();
|
||||
return workSlotEndingAfterCurrentTime
|
||||
.map(it -> new WorkSlot(currentDate, it))
|
||||
.orElseGet(
|
||||
() ->
|
||||
// we started after the last working slot on that day, the next start time is the
|
||||
// first working slot of the next working day.
|
||||
getWorkSlotOrNext(getDayAfter(currentDateTime)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WorkSlot that matches best <code>currentDateTime</code>. If currentDateTime is
|
||||
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
||||
* previous WorkSlot is returned.
|
||||
*
|
||||
* @param currentDateTime The LocalDateTime we want the best matching WorkSlot for. May not be
|
||||
* <code>null</code>.
|
||||
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to subtract.
|
||||
*/
|
||||
private WorkSlot getWorkSlotOrPrevious(LocalDateTime currentDateTime) {
|
||||
LocalDate currentDate = currentDateTime.toLocalDate();
|
||||
// We do not work on Holidays
|
||||
if (holidaySchedule.isHoliday(currentDate)) {
|
||||
return getWorkSlotOrPrevious(getDayBefore(currentDateTime));
|
||||
}
|
||||
|
||||
SortedSet<LocalTimeInterval> workSlotsOfWorkingDay =
|
||||
workingTimeSchedule.workSlotsForReversed(currentDate.getDayOfWeek());
|
||||
// We are looking for the last workingSlot whose begin is before or equals the current time
|
||||
Optional<LocalTimeInterval> workSlotStartingBeforeCurrentTime =
|
||||
workSlotsOfWorkingDay.stream()
|
||||
// we use beforeOrEquals because begin is inclusive
|
||||
.filter(it -> isBeforeOrEquals(it.getBegin(), currentDateTime))
|
||||
.findFirst();
|
||||
return workSlotStartingBeforeCurrentTime
|
||||
.map(it -> new WorkSlot(currentDate, it))
|
||||
.orElseGet(
|
||||
() ->
|
||||
// we started before the first working slot on that day, the next start time is the
|
||||
// last working slot of the previous working day.
|
||||
getWorkSlotOrPrevious(getDayBefore(currentDateTime)));
|
||||
}
|
||||
|
||||
private static boolean isBeforeOrEquals(LocalTime time, LocalDateTime currentDateTime) {
|
||||
return !time.isAfter(currentDateTime.toLocalTime());
|
||||
}
|
||||
|
||||
private LocalDateTime getDayAfter(LocalDateTime current) {
|
||||
return LocalDateTime.of(current.toLocalDate().plusDays(1), LocalTime.MIN);
|
||||
}
|
||||
|
||||
private LocalDateTime getDayBefore(LocalDateTime current) {
|
||||
return LocalDateTime.of(current.toLocalDate().minusDays(1), LocalTime.MAX);
|
||||
}
|
||||
|
||||
private LocalDateTime toLocalDateTime(Instant instant) {
|
||||
return LocalDateTime.ofInstant(instant, UTC);
|
||||
}
|
||||
|
||||
private LocalDate toLocalDate(Instant instant) {
|
||||
return LocalDate.ofInstant(instant, UTC);
|
||||
}
|
||||
|
||||
private DayOfWeek toDayOfWeek(Instant instant) {
|
||||
return toLocalDate(instant).getDayOfWeek();
|
||||
}
|
||||
|
||||
private static Instant max(Instant a, Instant b) {
|
||||
if (a.isAfter(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
private static Instant min(Instant a, Instant b) {
|
||||
if (a.isBefore(b)) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
class WorkSlot {
|
||||
|
||||
private final Instant start;
|
||||
private final Instant end;
|
||||
|
||||
public WorkSlot(LocalDate day, LocalTimeInterval interval) {
|
||||
this.start = LocalDateTime.of(day, interval.getBegin()).toInstant(UTC);
|
||||
if (interval.getEnd().equals(LocalTime.MAX)) {
|
||||
this.end = day.plusDays(1).atStartOfDay().toInstant(UTC);
|
||||
} else {
|
||||
this.end = LocalDateTime.of(day, interval.getEnd()).toInstant(UTC);
|
||||
}
|
||||
}
|
||||
|
||||
public Instant addWorkingTime(Instant workStart, Duration workingTime) {
|
||||
// _workStart_ might be outside the working hours. We need to adjust the start accordingly.
|
||||
Instant earliestWorkStart = max(workStart, start);
|
||||
Duration untilEndOfWorkSlot = Duration.between(earliestWorkStart, end);
|
||||
if (workingTime.compareTo(untilEndOfWorkSlot) <= 0) {
|
||||
// easy part. It is due within the same work slot
|
||||
return earliestWorkStart.plus(workingTime);
|
||||
} else {
|
||||
// we subtract the duration of the currentWorkingSlot
|
||||
Duration remainingWorkingTime = workingTime.minus(untilEndOfWorkSlot);
|
||||
// We continue to calculate the dueDate by starting from an workStart outside the current
|
||||
// working slot and the remainingWorkingTime
|
||||
return next().addWorkingTime(end, remainingWorkingTime);
|
||||
}
|
||||
}
|
||||
|
||||
public Instant subtractWorkingTime(Instant workStart, Duration workingTime) {
|
||||
// _workStart_ might be outside the working hours. We need to adjust the end accordingly.
|
||||
Instant latestWorkEnd = min(workStart, end);
|
||||
Duration untilStartOfWorkSlot = Duration.between(start, latestWorkEnd);
|
||||
if (workingTime.compareTo(untilStartOfWorkSlot) <= 0) {
|
||||
// easy part. It is due within the same work slot
|
||||
return latestWorkEnd.minus(workingTime);
|
||||
} else {
|
||||
Duration remainingWorkingTime = workingTime.minus(untilStartOfWorkSlot);
|
||||
return previous().subtractWorkingTime(start, remainingWorkingTime);
|
||||
}
|
||||
}
|
||||
|
||||
private WorkSlot previous() {
|
||||
// We need to subtract a nanosecond because start is inclusive
|
||||
return getWorkSlotOrPrevious(toLocalDateTime(start.minusNanos(1)));
|
||||
}
|
||||
|
||||
private WorkSlot next() {
|
||||
return getWorkSlotOrNext(toLocalDateTime(end));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.internal.util.Pair;
|
||||
|
||||
class WorkingTimeSchedule {
|
||||
|
||||
// Holds WorkSlots as SortedSet of LocalTimeInterval sorted ascending and descending by DayOfWeek
|
||||
private final Map<DayOfWeek, Pair<SortedSet<LocalTimeInterval>, SortedSet<LocalTimeInterval>>>
|
||||
workingTimeByDayOfWeek = new EnumMap<>(DayOfWeek.class);
|
||||
|
||||
WorkingTimeSchedule(Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeByDayOfWeek) {
|
||||
if (workingTimeByDayOfWeek.isEmpty()) {
|
||||
throw new InvalidArgumentException("At least one day of the week needs to have working time");
|
||||
}
|
||||
|
||||
for (Entry<DayOfWeek, Set<LocalTimeInterval>> dayOfWeekSetEntry :
|
||||
workingTimeByDayOfWeek.entrySet()) {
|
||||
SortedSet<LocalTimeInterval> intervalsAscending =
|
||||
sortedSetOf(dayOfWeekSetEntry.getValue(), null);
|
||||
|
||||
LocalTime previousEnd = null;
|
||||
for (LocalTimeInterval current : intervalsAscending) {
|
||||
if (previousEnd != null && current.getBegin().isBefore(previousEnd)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Working time is overlapping for " + intervalsAscending);
|
||||
}
|
||||
previousEnd = current.getEnd();
|
||||
}
|
||||
|
||||
SortedSet<LocalTimeInterval> intervalsDescending =
|
||||
sortedSetOf(intervalsAscending, Comparator.reverseOrder());
|
||||
|
||||
this.workingTimeByDayOfWeek.put(
|
||||
dayOfWeekSetEntry.getKey(), Pair.of(intervalsAscending, intervalsDescending));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether <code>dayOfWeek</code> is a working day.
|
||||
*
|
||||
* @param dayOfWeek The dayOfWeek. May not be <code>null</code>>.
|
||||
* @return <code>true</code> if it is a working day, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isWorkingDay(DayOfWeek dayOfWeek) {
|
||||
return !workSlotsFor(dayOfWeek).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all LocalTimeIntervals for <code>dayOfWeek</code> sorted ascending by their beginning.
|
||||
*
|
||||
* @param dayOfWeek The DayOfWeek to get LocalTimeIntervals for.
|
||||
* @return All LocalTimeIntervals sorted ascending by their beginning. May be empty.
|
||||
*/
|
||||
public SortedSet<LocalTimeInterval> workSlotsFor(DayOfWeek dayOfWeek) {
|
||||
return workSlotsForBySortOrder(dayOfWeek, Pair::getLeft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all LocalTimeIntervals for <code>dayOfWeek</code> sorted descending by their beginning.
|
||||
*
|
||||
* @param dayOfWeek The DayOfWeek to get LocalTimeIntervals for.
|
||||
* @return All LocalTimeIntervals sorted descending by their beginning. May be empty.
|
||||
*/
|
||||
public SortedSet<LocalTimeInterval> workSlotsForReversed(DayOfWeek dayOfWeek) {
|
||||
return workSlotsForBySortOrder(dayOfWeek, Pair::getRight);
|
||||
}
|
||||
|
||||
private SortedSet<LocalTimeInterval> workSlotsForBySortOrder(
|
||||
DayOfWeek dayOfWeek,
|
||||
Function<
|
||||
Pair<SortedSet<LocalTimeInterval>, SortedSet<LocalTimeInterval>>,
|
||||
SortedSet<LocalTimeInterval>>
|
||||
pairChooser) {
|
||||
Pair<SortedSet<LocalTimeInterval>, SortedSet<LocalTimeInterval>> bothIntervalSets =
|
||||
workingTimeByDayOfWeek.get(dayOfWeek);
|
||||
if (bothIntervalSets == null) {
|
||||
return Collections.emptySortedSet();
|
||||
}
|
||||
return pairChooser.apply(bothIntervalSets);
|
||||
}
|
||||
|
||||
private SortedSet<LocalTimeInterval> sortedSetOf(
|
||||
Set<LocalTimeInterval> original, Comparator<LocalTimeInterval> comparator) {
|
||||
SortedSet<LocalTimeInterval> sorted = new TreeSet<>(comparator);
|
||||
sorted.addAll(original);
|
||||
|
||||
return Collections.unmodifiableSortedSet(sorted);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package pro.taskana.common.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LocalTimeIntervalTest {
|
||||
|
||||
@Test
|
||||
void naturalOrderingIsDefinedByBegin() {
|
||||
LocalTimeInterval ltiOne = new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX);
|
||||
LocalTimeInterval ltiTwo =
|
||||
new LocalTimeInterval(LocalTime.MIN.plus(1, ChronoUnit.MILLIS), LocalTime.MAX);
|
||||
|
||||
assertThat(ltiOne).isLessThan(ltiTwo);
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
package pro.taskana.common.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.DynamicContainer;
|
||||
import org.junit.jupiter.api.DynamicNode;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter.EasterCalculator;
|
||||
|
||||
/** Test for the WorkingDaysToDaysConverter. */
|
||||
class WorkingDaysToDaysConverterTest {
|
||||
|
||||
private final WorkingDaysToDaysConverter converter;
|
||||
|
||||
WorkingDaysToDaysConverterTest() {
|
||||
CustomHoliday dayOfReformation = CustomHoliday.of(31, 10);
|
||||
CustomHoliday allSaintsDays = CustomHoliday.of(1, 11);
|
||||
converter =
|
||||
new WorkingDaysToDaysConverter(true, false, List.of(dayOfReformation, allSaintsDays));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> should_NotDetectCorpusChristiAsHoliday_When_CorpusChristiIsDisabled() {
|
||||
DynamicTest year1980 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 1980",
|
||||
() -> assertThat(converter.isGermanHoliday(LocalDate.parse("1980-06-05"))).isFalse());
|
||||
DynamicTest year2020 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 2020",
|
||||
() -> assertThat(converter.isGermanHoliday(LocalDate.parse("2020-06-11"))).isFalse());
|
||||
return Stream.of(year1980, year2020);
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicNode> should_DetectCorpusChristiAsHoliday_When_CorpusChristiIsEnabled() {
|
||||
WorkingDaysToDaysConverter converter = new WorkingDaysToDaysConverter(true, true);
|
||||
DynamicTest year1980 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 1980",
|
||||
() -> assertThat(converter.isGermanHoliday(LocalDate.parse("1980-06-05"))).isTrue());
|
||||
DynamicTest year2020 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 2020",
|
||||
() -> assertThat(converter.isGermanHoliday(LocalDate.parse("2020-06-11"))).isTrue());
|
||||
return Stream.of(year1980, year2020);
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicNode> testHasWorkingInBetween() {
|
||||
Instant thursday = Instant.parse("2020-04-30T07:12:00.000Z");
|
||||
Instant friday = Instant.parse("2020-05-01T07:12:00.000Z"); // german holiday
|
||||
Instant saturday = Instant.parse("2020-05-02T07:12:00.000Z");
|
||||
Instant sunday = Instant.parse("2020-05-03T07:12:00.000Z");
|
||||
Instant monday = Instant.parse("2020-05-04T07:12:00.000Z");
|
||||
Instant tuesday = Instant.parse("2020-05-05T07:12:00.000Z");
|
||||
DynamicContainer noWorkingDaysInBetween =
|
||||
DynamicContainer.dynamicContainer(
|
||||
"no working days in between",
|
||||
Stream.of(
|
||||
DynamicTest.dynamicTest(
|
||||
"tuesday <-> tuesday",
|
||||
() ->
|
||||
assertThat(converter.hasWorkingDaysInBetween(tuesday, tuesday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"thursday <-> saturday (friday is holiday)",
|
||||
() ->
|
||||
assertThat(converter.hasWorkingDaysInBetween(thursday, saturday))
|
||||
.isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"friday <-> friday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(friday, friday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"friday <-> monday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(friday, monday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"saturday <-> monday",
|
||||
() ->
|
||||
assertThat(converter.hasWorkingDaysInBetween(saturday, monday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"sunday <-> monday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(sunday, monday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"monday <-> monday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(sunday, monday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"monday <-> sunday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(monday, sunday)).isFalse()),
|
||||
DynamicTest.dynamicTest(
|
||||
"monday <-> friday",
|
||||
() ->
|
||||
assertThat(converter.hasWorkingDaysInBetween(monday, friday)).isFalse())));
|
||||
|
||||
DynamicContainer hasWorkingDaysInBetween =
|
||||
DynamicContainer.dynamicContainer(
|
||||
"has working days in between",
|
||||
Stream.of(
|
||||
DynamicTest.dynamicTest(
|
||||
"friday <-> tuesday",
|
||||
() -> assertThat(converter.hasWorkingDaysInBetween(friday, tuesday)).isTrue()),
|
||||
DynamicTest.dynamicTest(
|
||||
"sunday <-> tuesday",
|
||||
() ->
|
||||
assertThat(converter.hasWorkingDaysInBetween(sunday, tuesday)).isTrue())));
|
||||
|
||||
return Stream.of(noWorkingDaysInBetween, hasWorkingDaysInBetween);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertWorkingDaysToDaysForTasks() {
|
||||
Instant thursday0201 = Instant.parse("2018-02-01T07:00:00.000Z");
|
||||
Instant days =
|
||||
converter.subtractWorkingDaysFromInstant(
|
||||
thursday0201, Duration.ofDays(7)); // = tuesday (sat + sun)
|
||||
assertThat(days).isEqualTo(thursday0201.minus(9, ChronoUnit.DAYS));
|
||||
days =
|
||||
converter.subtractWorkingDaysFromInstant(
|
||||
thursday0201, Duration.ofDays(6)); // = wednesday (sat + sun)
|
||||
assertThat(days).isEqualTo(thursday0201.minus(8, ChronoUnit.DAYS));
|
||||
days =
|
||||
converter.subtractWorkingDaysFromInstant(
|
||||
thursday0201, Duration.ofDays(5)); // = thursday (sat + sun)
|
||||
assertThat(days).isEqualTo(thursday0201.minus(7, ChronoUnit.DAYS));
|
||||
days = converter.subtractWorkingDaysFromInstant(thursday0201, Duration.ofDays(4)); // = friday
|
||||
assertThat(days).isEqualTo(thursday0201.minus(6, ChronoUnit.DAYS));
|
||||
days = converter.subtractWorkingDaysFromInstant(thursday0201, Duration.ofDays(3)); // monday
|
||||
assertThat(days).isEqualTo(thursday0201.minus(3, ChronoUnit.DAYS));
|
||||
days = converter.subtractWorkingDaysFromInstant(thursday0201, Duration.ofDays(2)); // tuesday
|
||||
assertThat(days).isEqualTo(thursday0201.minus(2, ChronoUnit.DAYS));
|
||||
days = converter.subtractWorkingDaysFromInstant(thursday0201, Duration.ofDays(1)); // wednesday
|
||||
assertThat(days).isEqualTo(thursday0201.minus(1, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(0)); // = thursday
|
||||
assertThat(days).isEqualTo(thursday0201.plus(0, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(1)); // fri
|
||||
assertThat(days).isEqualTo(thursday0201.plus(1, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(2)); // mon
|
||||
assertThat(days).isEqualTo(thursday0201.plus(4, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(3)); // tues
|
||||
assertThat(days).isEqualTo(thursday0201.plus(5, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(4)); // we
|
||||
assertThat(days).isEqualTo(thursday0201.plus(6, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(5)); // thurs
|
||||
assertThat(days).isEqualTo(thursday0201.plus(7, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(6)); // fri
|
||||
assertThat(days).isEqualTo(thursday0201.plus(8, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(7)); // mon
|
||||
assertThat(days).isEqualTo(thursday0201.plus(11, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(8)); // tue
|
||||
assertThat(days).isEqualTo(thursday0201.plus(12, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(9)); // we
|
||||
assertThat(days).isEqualTo(thursday0201.plus(13, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(10)); // thu
|
||||
assertThat(days).isEqualTo(thursday0201.plus(14, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(thursday0201, Duration.ofDays(11)); // fri
|
||||
assertThat(days).isEqualTo(thursday0201.plus(15, ChronoUnit.DAYS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertWorkingDaysToDaysForKarFreitag() {
|
||||
Instant gruenDonnerstag2018 = Instant.parse("2018-03-29T01:00:00.000Z");
|
||||
Instant days = converter.addWorkingDaysToInstant(gruenDonnerstag2018, Duration.ofDays(0));
|
||||
assertThat(days).isEqualTo(gruenDonnerstag2018.plus(0, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(gruenDonnerstag2018, Duration.ofDays(1)); // Karfreitag
|
||||
assertThat(days).isEqualTo(gruenDonnerstag2018.plus(5, ChronoUnit.DAYS)); // osterdienstag
|
||||
days = converter.addWorkingDaysToInstant(gruenDonnerstag2018, Duration.ofDays(2)); // Karfreitag
|
||||
assertThat(days).isEqualTo(gruenDonnerstag2018.plus(6, ChronoUnit.DAYS)); // ostermittwoch
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertWorkingDaysToDaysForHolidays() {
|
||||
Instant freitag0427 = Instant.parse("2018-04-27T19:00:00.000Z");
|
||||
Instant days = converter.addWorkingDaysToInstant(freitag0427, Duration.ofDays(0));
|
||||
assertThat(days).isEqualTo(freitag0427.plus(0, ChronoUnit.DAYS));
|
||||
days = converter.addWorkingDaysToInstant(freitag0427, Duration.ofDays(1));
|
||||
assertThat(days).isEqualTo(freitag0427.plus(3, ChronoUnit.DAYS)); // 30.4.
|
||||
days = converter.addWorkingDaysToInstant(freitag0427, Duration.ofDays(2));
|
||||
assertThat(days).isEqualTo(freitag0427.plus(5, ChronoUnit.DAYS)); // 2.5.
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetEasterSunday() {
|
||||
EasterCalculator easterCalculator = new EasterCalculator();
|
||||
assertThat(easterCalculator.getEasterSunday(2018)).isEqualTo(LocalDate.of(2018, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2019)).isEqualTo(LocalDate.of(2019, 4, 21));
|
||||
assertThat(easterCalculator.getEasterSunday(2020)).isEqualTo(LocalDate.of(2020, 4, 12));
|
||||
assertThat(easterCalculator.getEasterSunday(2021)).isEqualTo(LocalDate.of(2021, 4, 4));
|
||||
assertThat(easterCalculator.getEasterSunday(2022)).isEqualTo(LocalDate.of(2022, 4, 17));
|
||||
assertThat(easterCalculator.getEasterSunday(2023)).isEqualTo(LocalDate.of(2023, 4, 9));
|
||||
assertThat(easterCalculator.getEasterSunday(2024)).isEqualTo(LocalDate.of(2024, 3, 31));
|
||||
assertThat(easterCalculator.getEasterSunday(2025)).isEqualTo(LocalDate.of(2025, 4, 20));
|
||||
assertThat(easterCalculator.getEasterSunday(2026)).isEqualTo(LocalDate.of(2026, 4, 5));
|
||||
assertThat(easterCalculator.getEasterSunday(2027)).isEqualTo(LocalDate.of(2027, 3, 28));
|
||||
assertThat(easterCalculator.getEasterSunday(2028)).isEqualTo(LocalDate.of(2028, 4, 16));
|
||||
assertThat(easterCalculator.getEasterSunday(2029)).isEqualTo(LocalDate.of(2029, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2030)).isEqualTo(LocalDate.of(2030, 4, 21));
|
||||
assertThat(easterCalculator.getEasterSunday(2031)).isEqualTo(LocalDate.of(2031, 4, 13));
|
||||
assertThat(easterCalculator.getEasterSunday(2032)).isEqualTo(LocalDate.of(2032, 3, 28));
|
||||
assertThat(easterCalculator.getEasterSunday(2033)).isEqualTo(LocalDate.of(2033, 4, 17));
|
||||
assertThat(easterCalculator.getEasterSunday(2034)).isEqualTo(LocalDate.of(2034, 4, 9));
|
||||
assertThat(easterCalculator.getEasterSunday(2035)).isEqualTo(LocalDate.of(2035, 3, 25));
|
||||
assertThat(easterCalculator.getEasterSunday(2040)).isEqualTo(LocalDate.of(2040, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2050)).isEqualTo(LocalDate.of(2050, 4, 10));
|
||||
assertThat(easterCalculator.getEasterSunday(2100)).isEqualTo(LocalDate.of(2100, 3, 28));
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package pro.taskana.common.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.jupiter.api.function.ThrowingConsumer;
|
||||
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.internal.util.Quadruple;
|
||||
|
||||
class WorkingTimeCalculatorTest {
|
||||
|
||||
private final WorkingTimeCalculator calculator;
|
||||
|
||||
WorkingTimeCalculatorTest() {
|
||||
WorkingDaysToDaysConverter converter = new WorkingDaysToDaysConverter(true, false);
|
||||
calculator = new WorkingTimeCalculator(converter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throwInvalidArgumentException_When_FromTimeIsAfterUntilTime() {
|
||||
Instant from = Instant.parse("2021-09-30T12:00:00.000Z");
|
||||
Instant to = Instant.parse("2021-09-30T09:00:00.000Z");
|
||||
|
||||
assertThatThrownBy(() -> calculator.workingTimeBetweenTwoTimestamps(from, to))
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessage("Instants are invalid.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throwInvalidArgumentException_When_FromIsNull() {
|
||||
Instant to = Instant.parse("2021-09-30T09:00:00.000Z");
|
||||
|
||||
assertThatThrownBy(() -> calculator.workingTimeBetweenTwoTimestamps(null, to))
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessage("Instants are invalid.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnMultipleWorkingTimes_When_CalculatorUsedMultipleTimes() throws Exception {
|
||||
Instant from1 = Instant.parse("2021-09-30T10:02:00.000Z");
|
||||
Instant to1 = Instant.parse("2021-09-30T10:38:00.000Z");
|
||||
|
||||
Duration duration1 = calculator.workingTimeBetweenTwoTimestamps(from1, to1);
|
||||
|
||||
assertThat(duration1).isEqualTo(Duration.of(36, ChronoUnit.MINUTES));
|
||||
|
||||
Instant from2 = Instant.parse("2021-09-27T10:00:00.000Z");
|
||||
Instant to2 = Instant.parse("2021-10-02T15:00:00.000Z");
|
||||
|
||||
Duration duration2 = calculator.workingTimeBetweenTwoTimestamps(from2, to2);
|
||||
|
||||
assertThat(duration2).isEqualTo(Duration.of(56, ChronoUnit.HOURS));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> should_ReturnWorkingTime() {
|
||||
List<Quadruple<String, Instant, Instant, Duration>> valuesForTests =
|
||||
List.of(
|
||||
// Test instants that are within same day
|
||||
Quadruple.of(
|
||||
"Delta in hours",
|
||||
Instant.parse("2021-09-30T09:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T14:00:00.000Z"),
|
||||
Duration.of(5, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"Delta in minutes",
|
||||
Instant.parse("2021-09-30T09:02:00.000Z"),
|
||||
Instant.parse("2021-09-30T09:38:00.000Z"),
|
||||
Duration.of(36, ChronoUnit.MINUTES)),
|
||||
Quadruple.of(
|
||||
"Delta in seconds",
|
||||
Instant.parse("2021-09-30T15:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T15:00:01.000Z"),
|
||||
Duration.of(1, ChronoUnit.SECONDS)),
|
||||
Quadruple.of(
|
||||
"Delta in milliseconds",
|
||||
Instant.parse("2021-09-30T15:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T15:00:00.111Z"),
|
||||
Duration.of(111, ChronoUnit.MILLIS)),
|
||||
Quadruple.of(
|
||||
"Delta in all time units",
|
||||
Instant.parse("2021-09-30T15:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T16:01:01.001Z"),
|
||||
Duration.of(1, ChronoUnit.HOURS).plusMinutes(1).plusSeconds(1).plusMillis(1)),
|
||||
Quadruple.of(
|
||||
"Start time before working hours",
|
||||
Instant.parse("2021-09-30T05:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T07:00:00.000Z"),
|
||||
Duration.of(1, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"End time after working hours",
|
||||
Instant.parse("2021-09-30T17:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T19:00:00.000Z"),
|
||||
Duration.of(1, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"On holiday",
|
||||
Instant.parse("2021-01-01T11:00:00.000Z"),
|
||||
Instant.parse("2021-01-01T14:00:00.000Z"),
|
||||
Duration.ZERO),
|
||||
Quadruple.of(
|
||||
"Start and end after hours",
|
||||
Instant.parse("2021-09-30T19:00:00.000Z"),
|
||||
Instant.parse("2021-09-30T20:00:00.000Z"),
|
||||
Duration.ZERO),
|
||||
// Test instants that are over two days
|
||||
Quadruple.of(
|
||||
"Two days, start before working hours",
|
||||
Instant.parse("2021-09-30T05:00:00.000Z"),
|
||||
Instant.parse("2021-10-01T10:00:00.000Z"),
|
||||
Duration.of(12 + 4, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"Two days, start after working hours",
|
||||
Instant.parse("2021-09-30T19:00:00.000Z"),
|
||||
Instant.parse("2021-10-01T10:00:00.000Z"),
|
||||
Duration.of(4, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"Two days, end before working hours",
|
||||
Instant.parse("2021-09-30T17:00:00.000Z"),
|
||||
Instant.parse("2021-10-01T05:00:00.000Z"),
|
||||
Duration.of(1, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"Two days, end after working hours",
|
||||
Instant.parse("2021-09-30T17:00:00.000Z"),
|
||||
Instant.parse("2021-10-01T19:00:00.000Z"),
|
||||
Duration.of(1 + 12, ChronoUnit.HOURS)),
|
||||
// Test instants that are over multiple days
|
||||
Quadruple.of(
|
||||
"Separated by weekend",
|
||||
Instant.parse("2021-09-24T15:00:00.000Z"),
|
||||
Instant.parse("2021-09-27T10:00:00.000Z"),
|
||||
Duration.of(3 + 4, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"Separated by holiday",
|
||||
Instant.parse("2021-05-12T17:00:00.000Z"),
|
||||
Instant.parse("2021-05-14T07:00:00.000Z"),
|
||||
Duration.of(1 + 1, ChronoUnit.HOURS)),
|
||||
Quadruple.of(
|
||||
"From Monday to Saturday",
|
||||
Instant.parse("2021-09-27T09:00:00.000Z"),
|
||||
Instant.parse("2021-10-02T14:00:00.000Z"),
|
||||
Duration.of(9 + 12 + 12 + 12 + 12, ChronoUnit.HOURS)));
|
||||
|
||||
ThrowingConsumer<Quadruple<String, Instant, Instant, Duration>> test =
|
||||
q -> {
|
||||
Duration duration =
|
||||
calculator.workingTimeBetweenTwoTimestamps(q.getSecond(), q.getThird());
|
||||
assertThat(duration).isEqualTo(q.getFourth());
|
||||
};
|
||||
return DynamicTest.stream(valuesForTests.iterator(), Quadruple::getFirst, test);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.DynamicNode;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
import pro.taskana.common.api.CustomHoliday;
|
||||
import pro.taskana.common.internal.workingtime.HolidaySchedule.EasterCalculator;
|
||||
|
||||
/** Test for the HolidaySchedule. */
|
||||
class HolidayScheduleTest {
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> should_NotDetectCorpusChristiAsHoliday_When_CorpusChristiIsDisabled() {
|
||||
CustomHoliday dayOfReformation = CustomHoliday.of(31, 10);
|
||||
CustomHoliday allSaintsDays = CustomHoliday.of(1, 11);
|
||||
HolidaySchedule schedule =
|
||||
new HolidaySchedule(true, false, List.of(dayOfReformation, allSaintsDays));
|
||||
DynamicTest year1980 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 1980",
|
||||
() -> assertThat(schedule.isGermanHoliday(LocalDate.parse("1980-06-05"))).isFalse());
|
||||
DynamicTest year2020 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 2020",
|
||||
() -> assertThat(schedule.isGermanHoliday(LocalDate.parse("2020-06-11"))).isFalse());
|
||||
return Stream.of(year1980, year2020);
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Stream<DynamicNode> should_DetectCorpusChristiAsHoliday_When_CorpusChristiIsEnabled() {
|
||||
HolidaySchedule schedule = new HolidaySchedule(true, true);
|
||||
DynamicTest year1980 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 1980",
|
||||
() -> assertThat(schedule.isGermanHoliday(LocalDate.parse("1980-06-05"))).isTrue());
|
||||
DynamicTest year2020 =
|
||||
DynamicTest.dynamicTest(
|
||||
"year 2020",
|
||||
() -> assertThat(schedule.isGermanHoliday(LocalDate.parse("2020-06-11"))).isTrue());
|
||||
return Stream.of(year1980, year2020);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetEasterSunday() {
|
||||
EasterCalculator easterCalculator = new EasterCalculator();
|
||||
assertThat(easterCalculator.getEasterSunday(2018)).isEqualTo(LocalDate.of(2018, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2019)).isEqualTo(LocalDate.of(2019, 4, 21));
|
||||
assertThat(easterCalculator.getEasterSunday(2020)).isEqualTo(LocalDate.of(2020, 4, 12));
|
||||
assertThat(easterCalculator.getEasterSunday(2021)).isEqualTo(LocalDate.of(2021, 4, 4));
|
||||
assertThat(easterCalculator.getEasterSunday(2022)).isEqualTo(LocalDate.of(2022, 4, 17));
|
||||
assertThat(easterCalculator.getEasterSunday(2023)).isEqualTo(LocalDate.of(2023, 4, 9));
|
||||
assertThat(easterCalculator.getEasterSunday(2024)).isEqualTo(LocalDate.of(2024, 3, 31));
|
||||
assertThat(easterCalculator.getEasterSunday(2025)).isEqualTo(LocalDate.of(2025, 4, 20));
|
||||
assertThat(easterCalculator.getEasterSunday(2026)).isEqualTo(LocalDate.of(2026, 4, 5));
|
||||
assertThat(easterCalculator.getEasterSunday(2027)).isEqualTo(LocalDate.of(2027, 3, 28));
|
||||
assertThat(easterCalculator.getEasterSunday(2028)).isEqualTo(LocalDate.of(2028, 4, 16));
|
||||
assertThat(easterCalculator.getEasterSunday(2029)).isEqualTo(LocalDate.of(2029, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2030)).isEqualTo(LocalDate.of(2030, 4, 21));
|
||||
assertThat(easterCalculator.getEasterSunday(2031)).isEqualTo(LocalDate.of(2031, 4, 13));
|
||||
assertThat(easterCalculator.getEasterSunday(2032)).isEqualTo(LocalDate.of(2032, 3, 28));
|
||||
assertThat(easterCalculator.getEasterSunday(2033)).isEqualTo(LocalDate.of(2033, 4, 17));
|
||||
assertThat(easterCalculator.getEasterSunday(2034)).isEqualTo(LocalDate.of(2034, 4, 9));
|
||||
assertThat(easterCalculator.getEasterSunday(2035)).isEqualTo(LocalDate.of(2035, 3, 25));
|
||||
assertThat(easterCalculator.getEasterSunday(2040)).isEqualTo(LocalDate.of(2040, 4, 1));
|
||||
assertThat(easterCalculator.getEasterSunday(2050)).isEqualTo(LocalDate.of(2050, 4, 10));
|
||||
assertThat(easterCalculator.getEasterSunday(2100)).isEqualTo(LocalDate.of(2100, 3, 28));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
|
||||
class WorkingTimeCalculatorImplTest {
|
||||
|
||||
@Nested
|
||||
class UsualWorkingSlotsWithSingleBreak {
|
||||
|
||||
private final Set<LocalTimeInterval> standardWorkingSlots =
|
||||
Set.of(
|
||||
new LocalTimeInterval(LocalTime.of(6, 0), LocalTime.of(12, 0)),
|
||||
new LocalTimeInterval(LocalTime.of(13, 0), LocalTime.of(18, 0)));
|
||||
private final WorkingTimeCalculator cut =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
new HolidaySchedule(true, false),
|
||||
Map.of(
|
||||
DayOfWeek.MONDAY, standardWorkingSlots,
|
||||
DayOfWeek.TUESDAY, standardWorkingSlots,
|
||||
DayOfWeek.WEDNESDAY, standardWorkingSlots,
|
||||
DayOfWeek.THURSDAY, standardWorkingSlots,
|
||||
DayOfWeek.FRIDAY, standardWorkingSlots));
|
||||
|
||||
@Nested
|
||||
class WorkingTimeAddition {
|
||||
|
||||
/*
|
||||
* Examples (assuming a working day is from 06:00 - 12:00 and 13:00 - 18:00):
|
||||
* Start | duration in minutes | Due Date
|
||||
* Tue, 09:00 | 42 | Tue, 09:42
|
||||
* Wed, 09:00 | 3*60 | Wed, 12:00
|
||||
* Thu, 09:00 | 3*60 + 1 | Thu, 13:01
|
||||
* Fri, 09:00 | 11*60 | Mon, 09:00
|
||||
* Holy Thursday, 09:00 | 8*60 + 1 | Tue, 08:01
|
||||
*/
|
||||
@Test
|
||||
void withinWorkSlot() {
|
||||
Instant tuesday9oClock = Instant.parse("2022-11-15T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(tuesday9oClock, Duration.ofMinutes(42));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-15T09:42:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void untilEndOfWorkSlot() {
|
||||
Instant wednesday9oClock = Instant.parse("2022-11-16T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(wednesday9oClock, Duration.ofHours(3));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T12:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningToNextWorkSlot() {
|
||||
Instant thursday9oClock = Instant.parse("2022-11-17T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(thursday9oClock, Duration.ofMinutes(3 * 60 + 1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-17T13:01:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningOverWeekend() {
|
||||
Instant friday9oClock = Instant.parse("2022-11-18T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(friday9oClock, Duration.ofHours(11));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-21T09:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningOverHoliday() {
|
||||
Instant holyThursday = Instant.parse("2022-04-14T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(holyThursday, Duration.ofHours(11));
|
||||
|
||||
Instant tuesDayAfterEaster = Instant.parse("2022-04-19T09:00:00.000Z");
|
||||
assertThat(dueDate).isEqualTo(tuesDayAfterEaster);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startOnHoliday() {
|
||||
Instant holyFriday = Instant.parse("2022-04-15T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(holyFriday, Duration.ofHours(2));
|
||||
|
||||
Instant tuesDayAfterEaster = Instant.parse("2022-04-19T08:00:00.000Z");
|
||||
assertThat(dueDate).isEqualTo(tuesDayAfterEaster);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDurationOfZero() {
|
||||
Instant mondayHalfPastNine = Instant.parse("2022-11-14T09:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(mondayHalfPastNine, Duration.ZERO);
|
||||
|
||||
assertThat(dueDate).isEqualTo(mondayHalfPastNine);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDurationOfZeroOnHolySaturday() {
|
||||
Instant holySaturdayHalfPastNine = Instant.parse("2022-04-16T09:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(holySaturdayHalfPastNine, Duration.ZERO);
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-04-19T06:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeIsBeforeWorkingHours() {
|
||||
Instant wednesday5oClock = Instant.parse("2022-11-16T05:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(wednesday5oClock, Duration.ofHours(1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T07:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeIsAfterWorkingHours() {
|
||||
Instant wednesday19oClock = Instant.parse("2022-11-16T19:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(wednesday19oClock, Duration.ofHours(1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-17T07:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNegativeDuration() {
|
||||
assertThatExceptionOfType(InvalidArgumentException.class)
|
||||
.isThrownBy(() -> cut.addWorkingTime(Instant.now(), Duration.ofMillis(-1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullInstant() {
|
||||
assertThatExceptionOfType(NullPointerException.class)
|
||||
.isThrownBy(() -> cut.addWorkingTime(null, Duration.ofMillis(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WorkingTimeSubtraction {
|
||||
|
||||
/*
|
||||
* Examples (assuming a working day is from 06:00 - 12:00 and 13:00 - 18:00):
|
||||
* Start | duration in minutes | Due Date
|
||||
* Tue, 09:00 | 42 | Tue, 08:18
|
||||
* Wed, 09:00 | 3*60 | Wed, 06:00
|
||||
* Thu, 16:00 | 3*60 + 1 | Thu, 11:59
|
||||
* Mon, 17:00 | 11*60 | Fri, 17:00
|
||||
* Tuesday after Easter, 09:00 | 3*60 + 1 | Thu, 17:59
|
||||
*/
|
||||
@Test
|
||||
void withinWorkSlot() {
|
||||
Instant tuesday9oClock = Instant.parse("2022-11-15T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(tuesday9oClock, Duration.ofMinutes(42));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-15T08:18:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void untilEndOfWorkSlot() {
|
||||
Instant wednesday9oClock = Instant.parse("2022-11-16T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(wednesday9oClock, Duration.ofHours(3));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T06:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningToPreviousWorkSlot() {
|
||||
Instant thursday16oClock = Instant.parse("2022-11-17T16:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(thursday16oClock, Duration.ofMinutes(3 * 60 + 1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-17T11:59:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningOverWeekend() {
|
||||
Instant monday17oClock = Instant.parse("2022-11-14T17:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(monday17oClock, Duration.ofHours(11));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-11T17:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanningOverHoliday() {
|
||||
Instant tuesdayAfterEaster = Instant.parse("2022-04-19T09:00:00.000Z");
|
||||
|
||||
Instant dueDate =
|
||||
cut.subtractWorkingTime(tuesdayAfterEaster, Duration.ofMinutes(3 * 60 + 1));
|
||||
|
||||
Instant holyThursday = Instant.parse("2022-04-14T17:59:00.000Z");
|
||||
assertThat(dueDate).isEqualTo(holyThursday);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startOnHoliday() {
|
||||
Instant holyFriday = Instant.parse("2022-04-15T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(holyFriday, Duration.ofHours(2));
|
||||
|
||||
Instant tuesDayAfterEaster = Instant.parse("2022-04-14T16:00:00.000Z");
|
||||
assertThat(dueDate).isEqualTo(tuesDayAfterEaster);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDurationOfZero() {
|
||||
Instant mondayHalfPastNine = Instant.parse("2022-11-14T09:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(mondayHalfPastNine, Duration.ZERO);
|
||||
|
||||
assertThat(dueDate).isEqualTo(mondayHalfPastNine);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDurationOfZeroOnHolySaturday() {
|
||||
Instant holySaturdayHalfPastNine = Instant.parse("2022-04-16T09:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(holySaturdayHalfPastNine, Duration.ZERO);
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-04-14T18:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeIsBeforeWorkingHours() {
|
||||
Instant wednesday1230 = Instant.parse("2022-11-16T12:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(wednesday1230, Duration.ofHours(1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T11:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeIsAfterWorkingHours() {
|
||||
Instant wednesday5oClock = Instant.parse("2022-11-16T05:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(wednesday5oClock, Duration.ofHours(1));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-15T17:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNegativeDuration() {
|
||||
assertThatExceptionOfType(InvalidArgumentException.class)
|
||||
.isThrownBy(() -> cut.subtractWorkingTime(Instant.now(), Duration.ofMillis(-1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullInstant() {
|
||||
assertThatExceptionOfType(NullPointerException.class)
|
||||
.isThrownBy(() -> cut.subtractWorkingTime(null, Duration.ofMillis(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WorkingTimeBetweenTwoInstants {
|
||||
|
||||
/*
|
||||
* Examples (assuming a working day is from 06:00 - 12:00 and 13:00 - 18:00):
|
||||
* Start | duration in minutes | Due Date
|
||||
* Tue, 09:00 | 42 | Tue, 08:18
|
||||
* Wed, 09:00 | 3*60 | Wed, 06:00
|
||||
* Thu, 16:00 | 3*60 + 1 | Thu, 11:59
|
||||
* Mon, 17:00 | 11*60 | Fri, 17:00
|
||||
* Tuesday after Easter, 09:00 | 3*60 + 1 | Thu, 17:59
|
||||
*/
|
||||
|
||||
@Test
|
||||
void precisionIsLowerThanDays() {
|
||||
Instant from = Instant.parse("2022-11-15T09:00:00.000Z");
|
||||
Instant to = Instant.parse("2022-11-15T09:00:00.001Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(from, to);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofMillis(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void timestampsInSameWorkSlot() {
|
||||
Instant tuesday9oClock = Instant.parse("2022-11-15T09:00:00.000Z");
|
||||
Instant tuesday10oClock = Instant.parse("2022-11-15T10:00:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(tuesday9oClock, tuesday10oClock);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofHours(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void timestampsInDifferentWorkSlot() {
|
||||
Instant tuesday9oClock = Instant.parse("2022-11-15T09:00:00.000Z");
|
||||
Instant tuesday10oClock = Instant.parse("2022-11-15T14:00:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(tuesday9oClock, tuesday10oClock);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofHours(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void timestampsSpanningHoliday() {
|
||||
Instant holyThursday = Instant.parse("2022-04-14T17:00:00.000Z");
|
||||
Instant tuesdayAfterEaster = Instant.parse("2022-04-19T09:00:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(holyThursday, tuesdayAfterEaster);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofHours(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropTimeIfFromIsOutsideWorkSlot() {
|
||||
Instant tuesday5oClock = Instant.parse("2022-11-15T05:00:00.000Z");
|
||||
Instant tuesday7oClock = Instant.parse("2022-11-15T07:00:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(tuesday5oClock, tuesday7oClock);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofHours(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropRemainingTimeIfToIsOutsideWorkSlot() {
|
||||
Instant tuesday1100 = Instant.parse("2022-11-15T11:00:00.000Z");
|
||||
Instant tuesday1230 = Instant.parse("2022-11-15T12:30:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(tuesday1100, tuesday1230);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofHours(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void worksIfFromIsAfterTo() {
|
||||
Instant from = Instant.parse("2023-01-09T10:11:00.000Z");
|
||||
Instant to = Instant.parse("2023-01-09T10:10:00.000Z");
|
||||
|
||||
Duration workingTime = cut.workingTimeBetween(from, to);
|
||||
|
||||
assertThat(workingTime).isEqualTo(Duration.ofMinutes(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void failsIfFromIsNull() {
|
||||
assertThatExceptionOfType(InvalidArgumentException.class)
|
||||
.isThrownBy(() -> cut.workingTimeBetween(null, Instant.now()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void failsIfToIsNull() {
|
||||
assertThatExceptionOfType(InvalidArgumentException.class)
|
||||
.isThrownBy(() -> cut.workingTimeBetween(Instant.now(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WorkSlotWithTimeIntervalsWithoutBreak {
|
||||
|
||||
private final Set<LocalTimeInterval> standardWorkday =
|
||||
Set.of(
|
||||
new LocalTimeInterval(LocalTime.of(9, 0), LocalTime.of(12, 0)),
|
||||
new LocalTimeInterval(LocalTime.of(12, 0), LocalTime.of(17, 0)));
|
||||
private final WorkingTimeCalculator cut =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
new HolidaySchedule(true, false),
|
||||
Map.of(
|
||||
DayOfWeek.MONDAY, standardWorkday,
|
||||
DayOfWeek.TUESDAY, standardWorkday,
|
||||
DayOfWeek.WEDNESDAY, standardWorkday,
|
||||
DayOfWeek.THURSDAY, standardWorkday,
|
||||
DayOfWeek.FRIDAY, standardWorkday));
|
||||
|
||||
@Test
|
||||
void addTimeToMatchEndOfFirstAndStartOfSecondSlot() {
|
||||
Instant wednesday9oClock = Instant.parse("2022-11-16T09:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.addWorkingTime(wednesday9oClock, Duration.ofHours(3));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T12:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subtractTimeToMatchEndOfFirstAndStartOfSecondSlot() {
|
||||
Instant wednesday15oClock = Instant.parse("2022-11-16T15:00:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(wednesday15oClock, Duration.ofHours(3));
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-11-16T12:00:00.000Z"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WorkSlotSpansCompleteDay {
|
||||
|
||||
private final Set<LocalTimeInterval> completeWorkDay =
|
||||
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
||||
|
||||
private final WorkingTimeCalculator cut =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
new HolidaySchedule(true, false),
|
||||
Map.of(
|
||||
DayOfWeek.MONDAY, completeWorkDay,
|
||||
DayOfWeek.TUESDAY, completeWorkDay,
|
||||
DayOfWeek.WEDNESDAY, completeWorkDay,
|
||||
DayOfWeek.THURSDAY, completeWorkDay,
|
||||
DayOfWeek.FRIDAY, completeWorkDay));
|
||||
|
||||
@Test
|
||||
void withDurationOfZeroOnHolySaturday() {
|
||||
Instant holySaturdayHalfPastNine = Instant.parse("2022-04-16T09:30:00.000Z");
|
||||
|
||||
Instant dueDate = cut.subtractWorkingTime(holySaturdayHalfPastNine, Duration.ZERO);
|
||||
|
||||
assertThat(dueDate).isEqualTo(Instant.parse("2022-04-15T00:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void calculatesWorkingTimeBetweenCorrectlySpanningWorkDays() {
|
||||
Instant start = Instant.parse("2023-01-09T23:00:00.000Z");
|
||||
Instant end = Instant.parse("2023-01-10T01:00:00.000Z");
|
||||
|
||||
Duration duration = cut.workingTimeBetween(start, end);
|
||||
|
||||
assertThat(duration).isEqualTo(Duration.ofHours(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addsWorkingTimeCorrectly() {
|
||||
Instant start = Instant.parse("2023-01-09T23:00:00.000Z");
|
||||
|
||||
Instant end = cut.addWorkingTime(start, Duration.ofDays(1));
|
||||
|
||||
assertThat(end).isEqualTo(Instant.parse("2023-01-10T23:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subtractsWorkingTimeCorrectly() {
|
||||
Instant end = Instant.parse("2023-01-10T23:00:00.000Z");
|
||||
|
||||
Instant start = cut.subtractWorkingTime(end, Duration.ofDays(1));
|
||||
|
||||
assertThat(start).isEqualTo(Instant.parse("2023-01-09T23:00:00.000Z"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WorkingDayDetermination {
|
||||
|
||||
private final WorkingTimeCalculator cut =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
new HolidaySchedule(true, false),
|
||||
Map.of(DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))));
|
||||
|
||||
@Test
|
||||
void returnsTrueIfWorkingTimeScheduleIsDefinedForDayOfWeek() {
|
||||
Instant sunday = Instant.parse("2023-02-26T12:30:00.000Z");
|
||||
|
||||
boolean isWorkingDay = cut.isWorkingDay(sunday);
|
||||
|
||||
assertThat(isWorkingDay).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsFalseIfNoWorkingTimeScheduleIsDefinedForDayOfWeek() {
|
||||
Instant monday = Instant.parse("2023-02-27T12:30:00.000Z");
|
||||
|
||||
boolean isWorkingDay = cut.isWorkingDay(monday);
|
||||
|
||||
assertThat(isWorkingDay).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsFalseForHolidays() {
|
||||
Instant easterSunday = Instant.parse("2023-04-09T12:30:00.000Z");
|
||||
|
||||
boolean isWorkingDay = cut.isWorkingDay(easterSunday);
|
||||
|
||||
assertThat(isWorkingDay).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void failForNull() {
|
||||
assertThatExceptionOfType(NullPointerException.class)
|
||||
.isThrownBy(() -> cut.isWorkingDay(null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package pro.taskana.common.internal.workingtime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
|
||||
public class WorkingTimeScheduleTest {
|
||||
|
||||
@Test
|
||||
void creationFailsIfWorkingTimesOverlap() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(
|
||||
() ->
|
||||
new WorkingTimeSchedule(
|
||||
Map.of(
|
||||
DayOfWeek.MONDAY,
|
||||
Set.of(
|
||||
new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX),
|
||||
new LocalTimeInterval(LocalTime.NOON, LocalTime.MAX)))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void workSlotsForReturnsUnmodifiableSets() {
|
||||
WorkingTimeSchedule cut =
|
||||
new WorkingTimeSchedule(
|
||||
Map.of(DayOfWeek.MONDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))));
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(
|
||||
() ->
|
||||
cut.workSlotsFor(DayOfWeek.MONDAY)
|
||||
.add(new LocalTimeInterval(LocalTime.NOON, LocalTime.MIDNIGHT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void workSlotsForReversedReturnsUnmodifiableSets() {
|
||||
WorkingTimeSchedule cut =
|
||||
new WorkingTimeSchedule(
|
||||
Map.of(DayOfWeek.MONDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))));
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(
|
||||
() ->
|
||||
cut.workSlotsForReversed(DayOfWeek.MONDAY)
|
||||
.add(new LocalTimeInterval(LocalTime.NOON, LocalTime.MIDNIGHT)));
|
||||
}
|
||||
}
|
|
@ -20,5 +20,9 @@ taskana.jobs.history.cleanup.minimumAge=P15D
|
|||
taskana.german.holidays.enabled=true
|
||||
taskana.german.holidays.corpus-christi.enabled=false
|
||||
taskana.history.deletion.on.task.deletion.enabled=true
|
||||
|
||||
taskana.workingtime.schedule.MONDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.TUESDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
||||
|
||||
|
|
|
@ -67,8 +67,11 @@ import pro.taskana.common.api.exceptions.TaskanaException;
|
|||
import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
|
||||
import pro.taskana.common.internal.InternalTaskanaEngine;
|
||||
import pro.taskana.common.internal.Interval;
|
||||
import pro.taskana.common.internal.TaskanaEngineImpl;
|
||||
import pro.taskana.common.internal.logging.LoggingAspect;
|
||||
import pro.taskana.common.internal.util.MapCreator;
|
||||
import pro.taskana.common.internal.workingtime.HolidaySchedule;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.testapi.TaskanaIntegrationTest;
|
||||
|
||||
/**
|
||||
|
@ -425,6 +428,26 @@ class ArchitectureTest {
|
|||
classes().that().areAssignableTo(Throwable.class).should().bePublic().check(importedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classesShouldNotUseWorkingDaysToDaysConverter() {
|
||||
classes()
|
||||
.that()
|
||||
.areNotAssignableFrom(ArchitectureTest.class)
|
||||
.and()
|
||||
.areNotAssignableTo(WorkingTimeCalculatorImpl.class)
|
||||
.and()
|
||||
.areNotAssignableTo(TaskanaEngineImpl.class)
|
||||
.and()
|
||||
.haveSimpleNameNotEndingWith("Test")
|
||||
.should()
|
||||
.onlyDependOnClassesThat()
|
||||
.areNotAssignableTo(HolidaySchedule.class)
|
||||
.because(
|
||||
"we want to enforce the usage of the WorkingTimeCalculator"
|
||||
+ " instead of the WorkingDaysToDaysConverter")
|
||||
.check(importedClasses);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper Methods
|
||||
|
|
|
@ -34,7 +34,7 @@ import pro.taskana.classification.api.models.ClassificationSummary;
|
|||
import pro.taskana.classification.internal.models.ClassificationImpl;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.ConcurrencyException;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.api.exceptions.MismatchedRoleException;
|
||||
|
@ -60,7 +60,7 @@ class UpdateClassificationAccTest {
|
|||
@TaskanaInject TaskanaEngine taskanaEngine;
|
||||
@TaskanaInject TaskService taskService;
|
||||
@TaskanaInject WorkbasketService workbasketService;
|
||||
@TaskanaInject WorkingDaysToDaysConverter converter;
|
||||
@TaskanaInject WorkingTimeCalculator workingTimeCalculator;
|
||||
@TaskanaInject CurrentUserContext currentUserContext;
|
||||
|
||||
@WithAccessId(user = "businessadmin")
|
||||
|
@ -238,7 +238,7 @@ class UpdateClassificationAccTest {
|
|||
classificationService.updateClassification(classification);
|
||||
runAssociatedJobs();
|
||||
|
||||
validateTaskProperties(before, directLinkedTask, taskService, converter, 15, 1);
|
||||
validateTaskProperties(before, directLinkedTask, taskService, workingTimeCalculator, 15, 1);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "businessadmin")
|
||||
|
@ -257,7 +257,8 @@ class UpdateClassificationAccTest {
|
|||
classificationService.updateClassification(classification);
|
||||
runAssociatedJobs();
|
||||
|
||||
validateTaskProperties(before, directLinkedTask, taskService, converter, 13, 1000);
|
||||
validateTaskProperties(
|
||||
before, directLinkedTask, taskService, workingTimeCalculator, 13, 1000);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "businessadmin")
|
||||
|
@ -278,7 +279,8 @@ class UpdateClassificationAccTest {
|
|||
classificationService.updateClassification(classification);
|
||||
runAssociatedJobs();
|
||||
|
||||
validateTaskProperties(before, directLinkedTask, taskService, converter, 15, 1000);
|
||||
validateTaskProperties(
|
||||
before, directLinkedTask, taskService, workingTimeCalculator, 15, 1000);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "businessadmin")
|
||||
|
@ -318,7 +320,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -369,7 +371,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -422,7 +424,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -475,7 +477,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -527,7 +529,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -579,7 +581,7 @@ class UpdateClassificationAccTest {
|
|||
before,
|
||||
indirectLinkedTasks,
|
||||
taskService,
|
||||
converter,
|
||||
workingTimeCalculator,
|
||||
input.getRight().getLeft(),
|
||||
input.getRight().getRight());
|
||||
};
|
||||
|
@ -609,7 +611,7 @@ class UpdateClassificationAccTest {
|
|||
Instant before,
|
||||
List<String> tasksUpdated,
|
||||
TaskService taskService,
|
||||
WorkingDaysToDaysConverter converter,
|
||||
WorkingTimeCalculator workingTimeCalculator,
|
||||
int serviceLevel,
|
||||
int priority)
|
||||
throws Exception {
|
||||
|
@ -617,7 +619,7 @@ class UpdateClassificationAccTest {
|
|||
Task task = taskService.getTask(taskId);
|
||||
|
||||
Instant expDue =
|
||||
converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(serviceLevel));
|
||||
workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(serviceLevel));
|
||||
assertThat(task.getModified())
|
||||
.describedAs("Task " + task.getId() + " has not been refreshed.")
|
||||
.isAfter(before);
|
||||
|
|
|
@ -5,7 +5,6 @@ import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
|
|||
import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference;
|
||||
import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
@ -14,8 +13,6 @@ import org.junit.jupiter.api.Test;
|
|||
import pro.taskana.classification.api.ClassificationService;
|
||||
import pro.taskana.classification.api.models.ClassificationSummary;
|
||||
import pro.taskana.common.api.BulkOperationResults;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.exceptions.TaskanaException;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
import pro.taskana.task.api.models.Attachment;
|
||||
|
@ -41,7 +38,6 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
private static final String SMALL_CLASSIFICATION_SERVICE_LEVEL = "P2D";
|
||||
private static final String GREAT_CLASSIFICATION_SERVICE_LEVEL = "P7D";
|
||||
|
||||
@TaskanaInject TaskanaEngine taskanaEngine;
|
||||
@TaskanaInject TaskService taskService;
|
||||
@TaskanaInject WorkbasketService workbasketService;
|
||||
@TaskanaInject ClassificationService classificationService;
|
||||
|
@ -52,7 +48,6 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
Attachment attachmentSummaryGreatServiceLevel;
|
||||
WorkbasketSummary defaultWorkbasketSummary;
|
||||
ObjectReference defaultObjectReference;
|
||||
WorkingDaysToDaysConverter converter;
|
||||
|
||||
@WithAccessId(user = "businessadmin")
|
||||
@BeforeAll
|
||||
|
@ -87,13 +82,12 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
.permission(WorkbasketPermission.READ)
|
||||
.permission(WorkbasketPermission.APPEND)
|
||||
.buildAndStore(workbasketService);
|
||||
converter = taskanaEngine.getWorkingDaysToDaysConverter();
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@Test
|
||||
void should_SetPlannedOnMultipleTasks() throws Exception {
|
||||
Instant planned = Instant.parse("2020-05-03T07:00:00.000Z");
|
||||
Instant planned = Instant.parse("2020-05-03T07:00:00.000Z"); // Sunday
|
||||
TaskSummary task1 =
|
||||
createDefaultTask()
|
||||
.classificationSummary(classificationSummarySmallServiceLevel)
|
||||
|
@ -123,7 +117,7 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
@Test
|
||||
void should_ChangeDue_When_SettingPlannedAndClassificationHasSmallerServiceLevel()
|
||||
throws Exception {
|
||||
Instant planned = Instant.parse("2020-05-03T07:00:00.000Z");
|
||||
Instant planned = Instant.parse("2020-05-03T07:00:00.000Z"); // Sunday
|
||||
TaskSummary task1 =
|
||||
createDefaultTask()
|
||||
.classificationSummary(classificationSummarySmallServiceLevel)
|
||||
|
@ -141,11 +135,8 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
assertThat(bulkLog.containsErrors()).isFalse();
|
||||
List<TaskSummary> result =
|
||||
taskService.createTaskQuery().idIn(task1.getId(), task2.getId()).list();
|
||||
assertThat(result)
|
||||
.extracting(TaskSummary::getDue)
|
||||
.containsOnly(
|
||||
converter.addWorkingDaysToInstant(
|
||||
planned, Duration.parse(SMALL_CLASSIFICATION_SERVICE_LEVEL)));
|
||||
Instant expectedDue = Instant.parse("2020-05-05T23:00:00.000Z");
|
||||
assertThat(result).extracting(TaskSummary::getDue).containsOnly(expectedDue);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
|
@ -171,11 +162,8 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
assertThat(bulkLog.containsErrors()).isFalse();
|
||||
List<TaskSummary> result =
|
||||
taskService.createTaskQuery().idIn(task1.getId(), task2.getId()).list();
|
||||
assertThat(result)
|
||||
.extracting(TaskSummary::getDue)
|
||||
.containsOnly(
|
||||
converter.addWorkingDaysToInstant(
|
||||
planned, Duration.parse(SMALL_CLASSIFICATION_SERVICE_LEVEL)));
|
||||
Instant expectedDue = Instant.parse("2020-05-05T23:00:00.000Z");
|
||||
assertThat(result).extracting(TaskSummary::getDue).containsOnly(expectedDue);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
|
@ -205,13 +193,11 @@ class ServiceLevelOfAllTasksAccTest {
|
|||
assertThat(bulkLog.containsErrors()).isFalse();
|
||||
List<TaskSummary> result =
|
||||
taskService.createTaskQuery().idIn(task1.getId(), task2.getId(), task3.getId()).list();
|
||||
Instant expectedDueSmallServiceLevel = Instant.parse("2020-05-05T23:00:00.000Z");
|
||||
Instant expectedDueGreatServiceLevel = Instant.parse("2020-05-12T23:00:00.000Z");
|
||||
assertThat(result)
|
||||
.extracting(TaskSummary::getDue)
|
||||
.containsOnly(
|
||||
converter.addWorkingDaysToInstant(
|
||||
planned, Duration.parse(SMALL_CLASSIFICATION_SERVICE_LEVEL)),
|
||||
converter.addWorkingDaysToInstant(
|
||||
planned, Duration.parse(GREAT_CLASSIFICATION_SERVICE_LEVEL)));
|
||||
.containsOnly(expectedDueSmallServiceLevel, expectedDueGreatServiceLevel);
|
||||
}
|
||||
|
||||
private TaskBuilder createDefaultTask() {
|
||||
|
|
|
@ -9,8 +9,10 @@ import java.io.InputStream;
|
|||
import java.lang.reflect.Field;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
|
@ -27,6 +29,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.common.api.CustomHoliday;
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
import pro.taskana.common.internal.configuration.DB;
|
||||
|
@ -61,6 +64,7 @@ public class TaskanaConfiguration {
|
|||
private final boolean deleteHistoryOnTaskDeletionEnabled;
|
||||
// region custom configuration
|
||||
private final Map<String, String> properties;
|
||||
private final Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule;
|
||||
|
||||
@TaskanaProperty("taskana.domains")
|
||||
private List<String> domains = new ArrayList<>();
|
||||
|
@ -147,6 +151,11 @@ public class TaskanaConfiguration {
|
|||
this.deleteHistoryOnTaskDeletionEnabled = builder.deleteHistoryOnTaskDeletionEnabled;
|
||||
this.germanPublicHolidaysEnabled = builder.germanPublicHolidaysEnabled;
|
||||
this.corpusChristiEnabled = builder.corpusChristiEnabled;
|
||||
this.workingTimeSchedule =
|
||||
builder.workingTimeSchedule.entrySet().stream()
|
||||
.collect(
|
||||
Collectors.toUnmodifiableMap(
|
||||
Entry::getKey, e -> Collections.unmodifiableSet(e.getValue())));
|
||||
this.jobBatchSize = builder.jobBatchSize;
|
||||
this.maxNumberOfJobRetries = builder.maxNumberOfJobRetries;
|
||||
this.cleanupJobFirstRun = builder.cleanupJobFirstRun;
|
||||
|
@ -238,6 +247,10 @@ public class TaskanaConfiguration {
|
|||
return customHolidays;
|
||||
}
|
||||
|
||||
public Map<DayOfWeek, Set<LocalTimeInterval>> getWorkingTimeSchedule() {
|
||||
return workingTimeSchedule;
|
||||
}
|
||||
|
||||
public Map<TaskanaRole, Set<String>> getRoleMap() {
|
||||
return roleMap;
|
||||
}
|
||||
|
@ -369,6 +382,10 @@ public class TaskanaConfiguration {
|
|||
|
||||
@TaskanaProperty("taskana.german.holidays.corpus-christi.enabled")
|
||||
private boolean corpusChristiEnabled;
|
||||
|
||||
@TaskanaProperty("taskana.workingtime.schedule")
|
||||
private Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule =
|
||||
initDefaultWorkingTimeSchedule();
|
||||
// endregion
|
||||
|
||||
// region history configuration
|
||||
|
@ -446,6 +463,7 @@ public class TaskanaConfiguration {
|
|||
this.deleteHistoryOnTaskDeletionEnabled = tec.isDeleteHistoryOnTaskDeletionEnabled();
|
||||
this.germanPublicHolidaysEnabled = tec.isGermanPublicHolidaysEnabled();
|
||||
this.corpusChristiEnabled = tec.isCorpusChristiEnabled();
|
||||
this.workingTimeSchedule = tec.getWorkingTimeSchedule();
|
||||
this.jobBatchSize = tec.getJobBatchSize();
|
||||
this.maxNumberOfJobRetries = tec.getMaxNumberOfJobRetries();
|
||||
this.cleanupJobFirstRun = tec.getCleanupJobFirstRun();
|
||||
|
@ -726,5 +744,22 @@ public class TaskanaConfiguration {
|
|||
Collectors.toUnmodifiableMap(
|
||||
e -> e.getKey().toString(), e -> e.getValue().toString()));
|
||||
}
|
||||
|
||||
private static Map<DayOfWeek, Set<LocalTimeInterval>> initDefaultWorkingTimeSchedule() {
|
||||
Map<DayOfWeek, Set<LocalTimeInterval>> workingTime = new EnumMap<>(DayOfWeek.class);
|
||||
|
||||
// Default working time schedule is from Monday 00:00 - Friday 24:00, but CET (hence -1 hour)
|
||||
Set<LocalTimeInterval> standardWorkingSlots =
|
||||
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
||||
workingTime.put(
|
||||
DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.of(23, 0), LocalTime.MAX)));
|
||||
workingTime.put(DayOfWeek.MONDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.TUESDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.WEDNESDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.THURSDAY, standardWorkingSlots);
|
||||
workingTime.put(
|
||||
DayOfWeek.FRIDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.of(23, 0))));
|
||||
return workingTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import pro.taskana.classification.api.ClassificationService;
|
|||
import pro.taskana.common.api.exceptions.MismatchedRoleException;
|
||||
import pro.taskana.common.api.security.CurrentUserContext;
|
||||
import pro.taskana.common.internal.TaskanaEngineImpl;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.monitor.api.MonitorService;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
import pro.taskana.task.api.models.Task;
|
||||
|
@ -111,12 +112,13 @@ public interface TaskanaEngine {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the {@linkplain WorkingDaysToDaysConverter} of the TaskanaEngine. The {@linkplain
|
||||
* WorkingDaysToDaysConverter} is used to compute holidays.
|
||||
* Returns the {@linkplain WorkingTimeCalculator} of the TaskanaEngine. The {@linkplain
|
||||
* WorkingTimeCalculator} is used to add or subtract working time from Instants according to a
|
||||
* working time schedule or to calculate the working time between Instants.
|
||||
*
|
||||
* @return {@linkplain WorkingDaysToDaysConverter}
|
||||
* @return {@linkplain WorkingTimeCalculatorImpl}
|
||||
*/
|
||||
WorkingDaysToDaysConverter getWorkingDaysToDaysConverter();
|
||||
WorkingTimeCalculator getWorkingTimeCalculator();
|
||||
|
||||
/**
|
||||
* Checks if the {@linkplain pro.taskana.spi.history.api.TaskanaHistory TaskanaHistory} plugin is
|
||||
|
|
|
@ -34,7 +34,7 @@ import pro.taskana.common.api.ConfigurationService;
|
|||
import pro.taskana.common.api.JobService;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.AutocommitFailedException;
|
||||
import pro.taskana.common.api.exceptions.ConnectionNotSetException;
|
||||
import pro.taskana.common.api.exceptions.MismatchedRoleException;
|
||||
|
@ -47,6 +47,8 @@ import pro.taskana.common.internal.persistence.InstantTypeHandler;
|
|||
import pro.taskana.common.internal.persistence.MapTypeHandler;
|
||||
import pro.taskana.common.internal.persistence.StringTypeHandler;
|
||||
import pro.taskana.common.internal.security.CurrentUserContextImpl;
|
||||
import pro.taskana.common.internal.workingtime.HolidaySchedule;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.monitor.api.MonitorService;
|
||||
import pro.taskana.monitor.internal.MonitorMapper;
|
||||
import pro.taskana.monitor.internal.MonitorServiceImpl;
|
||||
|
@ -94,7 +96,9 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
private final BeforeRequestChangesManager beforeRequestChangesManager;
|
||||
private final AfterRequestChangesManager afterRequestChangesManager;
|
||||
private final InternalTaskanaEngineImpl internalTaskanaEngineImpl;
|
||||
private final WorkingDaysToDaysConverter workingDaysToDaysConverter;
|
||||
|
||||
private final WorkingTimeCalculator workingTimeCalculator;
|
||||
|
||||
private final HistoryEventManager historyEventManager;
|
||||
private final CurrentUserContext currentUserContext;
|
||||
protected ConnectionManagementMode mode;
|
||||
|
@ -113,11 +117,14 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
this.taskanaEngineConfiguration = taskanaEngineConfiguration;
|
||||
this.mode = connectionManagementMode;
|
||||
internalTaskanaEngineImpl = new InternalTaskanaEngineImpl();
|
||||
workingDaysToDaysConverter =
|
||||
new WorkingDaysToDaysConverter(
|
||||
HolidaySchedule holidaySchedule =
|
||||
new HolidaySchedule(
|
||||
taskanaEngineConfiguration.isGermanPublicHolidaysEnabled(),
|
||||
taskanaEngineConfiguration.isCorpusChristiEnabled(),
|
||||
taskanaEngineConfiguration.getCustomHolidays());
|
||||
workingTimeCalculator =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
holidaySchedule, taskanaEngineConfiguration.getWorkingTimeSchedule());
|
||||
currentUserContext =
|
||||
new CurrentUserContextImpl(TaskanaConfiguration.shouldUseLowerCaseForAccessIds());
|
||||
createTransactionFactory(taskanaEngineConfiguration.isUseManagedTransactions());
|
||||
|
@ -211,8 +218,8 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public WorkingDaysToDaysConverter getWorkingDaysToDaysConverter() {
|
||||
return workingDaysToDaysConverter;
|
||||
public WorkingTimeCalculator getWorkingTimeCalculator() {
|
||||
return workingTimeCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,14 +2,14 @@ package pro.taskana.monitor.internal.preprocessor;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.monitor.api.reports.header.TimeIntervalColumnHeader;
|
||||
import pro.taskana.monitor.api.reports.item.AgeQueryItem;
|
||||
import pro.taskana.monitor.api.reports.item.QueryItemPreprocessor;
|
||||
|
||||
/**
|
||||
* Uses {@linkplain WorkingDaysToDaysConverter} to convert an <I>s age to working days.
|
||||
* Uses {@linkplain WorkingDaysToDaysReportConverter} to convert an <I>s age to working days.
|
||||
*
|
||||
* @param <I> QueryItem which is being processed
|
||||
*/
|
||||
|
@ -20,11 +20,11 @@ public class DaysToWorkingDaysReportPreProcessor<I extends AgeQueryItem>
|
|||
|
||||
public DaysToWorkingDaysReportPreProcessor(
|
||||
List<? extends TimeIntervalColumnHeader> columnHeaders,
|
||||
WorkingDaysToDaysConverter converter,
|
||||
WorkingTimeCalculator workingTimeCalculator,
|
||||
boolean activate)
|
||||
throws InvalidArgumentException {
|
||||
if (activate) {
|
||||
instance = WorkingDaysToDaysReportConverter.initialize(columnHeaders, converter);
|
||||
instance = WorkingDaysToDaysReportConverter.initialize(columnHeaders, workingTimeCalculator);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,14 +11,14 @@ import java.util.stream.Collectors;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.monitor.api.reports.header.TimeIntervalColumnHeader;
|
||||
|
||||
/**
|
||||
* The DaysToWorkingDaysReportConverter provides a method to convert an age in days into an age in
|
||||
* working days. Before the method convertDaysToWorkingDays() can be used, the
|
||||
* WorkingDaysToDaysConverter has to be initialized. For a list of {@linkplain
|
||||
* WorkingDaysToDaysReportConverter has to be initialized. For a list of {@linkplain
|
||||
* TimeIntervalColumnHeader}s the converter creates a "table" with integer that represents the age
|
||||
* in days from the largest lower limit until the smallest upper limit of the
|
||||
* timeIntervalColumnHeaders. This table is valid for a whole day until the converter is initialized
|
||||
|
@ -29,21 +29,22 @@ public class WorkingDaysToDaysReportConverter {
|
|||
private static final Logger LOGGER =
|
||||
LoggerFactory.getLogger(WorkingDaysToDaysReportConverter.class);
|
||||
|
||||
private final WorkingDaysToDaysConverter daysToWorkingDaysConverter;
|
||||
private final WorkingTimeCalculator workingTimeCalculator;
|
||||
private final Map<Integer, Integer> cacheDaysToWorkingDays;
|
||||
|
||||
WorkingDaysToDaysReportConverter(
|
||||
List<? extends TimeIntervalColumnHeader> columnHeaders,
|
||||
WorkingDaysToDaysConverter daysToWorkingDaysConverter,
|
||||
WorkingTimeCalculator workingTimeCalculator,
|
||||
Instant referenceDate) {
|
||||
this.daysToWorkingDaysConverter = daysToWorkingDaysConverter;
|
||||
this.workingTimeCalculator = workingTimeCalculator;
|
||||
cacheDaysToWorkingDays = generateDaysToWorkingDays(columnHeaders, referenceDate);
|
||||
}
|
||||
|
||||
public static WorkingDaysToDaysReportConverter initialize(
|
||||
List<? extends TimeIntervalColumnHeader> columnHeaders, WorkingDaysToDaysConverter converter)
|
||||
List<? extends TimeIntervalColumnHeader> columnHeaders,
|
||||
WorkingTimeCalculator workingTimeCalculator)
|
||||
throws InvalidArgumentException {
|
||||
return initialize(columnHeaders, converter, Instant.now());
|
||||
return initialize(columnHeaders, workingTimeCalculator, Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,21 +54,22 @@ public class WorkingDaysToDaysReportConverter {
|
|||
*
|
||||
* @param columnHeaders a list of {@linkplain TimeIntervalColumnHeader}s that determines the size
|
||||
* of the table
|
||||
* @param converter the converter used by taskana to determine if a specific day is a working day.
|
||||
* @param workingTimeCalculator the workingTimeCalculator used by taskana to determine if a
|
||||
* specific day is a working day.
|
||||
* @param referenceDate a {@linkplain Instant} that represents the current day of the table
|
||||
* @return an instance of the WorkingDaysToDaysConverter
|
||||
* @throws InvalidArgumentException thrown if columnHeaders or referenceDate is null
|
||||
*/
|
||||
public static WorkingDaysToDaysReportConverter initialize(
|
||||
List<? extends TimeIntervalColumnHeader> columnHeaders,
|
||||
WorkingDaysToDaysConverter converter,
|
||||
WorkingTimeCalculator workingTimeCalculator,
|
||||
Instant referenceDate)
|
||||
throws InvalidArgumentException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialize WorkingDaysToDaysConverter with columnHeaders: {}", columnHeaders);
|
||||
}
|
||||
|
||||
if (converter == null) {
|
||||
if (workingTimeCalculator == null) {
|
||||
throw new InvalidArgumentException("WorkingDaysToDaysConverter can't be null");
|
||||
}
|
||||
if (columnHeaders == null) {
|
||||
|
@ -77,7 +79,8 @@ public class WorkingDaysToDaysReportConverter {
|
|||
throw new InvalidArgumentException("ReferenceDate can't be null");
|
||||
}
|
||||
|
||||
return new WorkingDaysToDaysReportConverter(columnHeaders, converter, referenceDate);
|
||||
return new WorkingDaysToDaysReportConverter(
|
||||
columnHeaders, workingTimeCalculator, referenceDate);
|
||||
}
|
||||
|
||||
public int convertDaysToWorkingDays(int amountOfDays) {
|
||||
|
@ -129,8 +132,7 @@ public class WorkingDaysToDaysReportConverter {
|
|||
int amountOfWorkdays = 0;
|
||||
while (Math.abs(amountOfWorkdays) < Math.abs(workdayLimit)) {
|
||||
amountOfDays += direction;
|
||||
if (daysToWorkingDaysConverter.isWorkingDay(
|
||||
referenceDate.plus(amountOfDays, ChronoUnit.DAYS))) {
|
||||
if (workingTimeCalculator.isWorkingDay(referenceDate.plus(amountOfDays, ChronoUnit.DAYS))) {
|
||||
amountOfWorkdays += direction;
|
||||
}
|
||||
daysToWorkingDaysMap.put(amountOfDays, amountOfWorkdays);
|
||||
|
@ -142,7 +144,7 @@ public class WorkingDaysToDaysReportConverter {
|
|||
return "DaysToWorkingDaysReportConverter [cacheDaysToWorkingDays="
|
||||
+ cacheDaysToWorkingDays
|
||||
+ ", daysToWorkingDaysConverter="
|
||||
+ daysToWorkingDaysConverter
|
||||
+ workingTimeCalculator
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class ClassificationCategoryReportBuilderImpl
|
|||
report.addItems(
|
||||
monitorQueryItems,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
this.columnHeaders, converter, this.inWorkingDays));
|
||||
this.columnHeaders, workingTimeCalculator, this.inWorkingDays));
|
||||
return report;
|
||||
} finally {
|
||||
this.taskanaEngine.returnConnection();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class ClassificationReportBuilderImpl
|
|||
report.addItems(
|
||||
monitorQueryItems,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
this.columnHeaders, converter, this.inWorkingDays));
|
||||
this.columnHeaders, workingTimeCalculator, this.inWorkingDays));
|
||||
Map<String, String> displayMap =
|
||||
classificationService
|
||||
.createClassificationQuery()
|
||||
|
@ -94,7 +94,7 @@ public class ClassificationReportBuilderImpl
|
|||
report.addItems(
|
||||
detailedMonitorQueryItems,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
this.columnHeaders, converter, this.inWorkingDays));
|
||||
this.columnHeaders, workingTimeCalculator, this.inWorkingDays));
|
||||
Stream<String> attachmentKeys =
|
||||
report.getRows().keySet().stream()
|
||||
.map(report::getRow)
|
||||
|
|
|
@ -50,7 +50,7 @@ public class TaskCustomFieldValueReportBuilderImpl
|
|||
report.addItems(
|
||||
monitorQueryItems,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
this.columnHeaders, converter, this.inWorkingDays));
|
||||
this.columnHeaders, workingTimeCalculator, this.inWorkingDays));
|
||||
return report;
|
||||
} finally {
|
||||
this.taskanaEngine.returnConnection();
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import pro.taskana.common.api.IntInterval;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.api.exceptions.MismatchedRoleException;
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
|
@ -48,7 +48,7 @@ abstract class TimeIntervalReportBuilderImpl<
|
|||
protected String[] domains;
|
||||
protected String[] classificationIds;
|
||||
protected String[] excludedClassificationIds;
|
||||
protected WorkingDaysToDaysConverter converter;
|
||||
protected WorkingTimeCalculator workingTimeCalculator;
|
||||
private String[] custom1In;
|
||||
private String[] custom1NotIn;
|
||||
private String[] custom1Like;
|
||||
|
@ -135,7 +135,7 @@ abstract class TimeIntervalReportBuilderImpl<
|
|||
this.taskanaEngine = taskanaEngine;
|
||||
this.monitorMapper = monitorMapper;
|
||||
this.columnHeaders = Collections.emptyList();
|
||||
converter = taskanaEngine.getEngine().getWorkingDaysToDaysConverter();
|
||||
workingTimeCalculator = taskanaEngine.getEngine().getWorkingTimeCalculator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -609,7 +609,7 @@ abstract class TimeIntervalReportBuilderImpl<
|
|||
private List<SelectedItem> convertWorkingDaysToDays(
|
||||
List<SelectedItem> selectedItems, List<H> columnHeaders) throws InvalidArgumentException {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(columnHeaders, converter);
|
||||
WorkingDaysToDaysReportConverter.initialize(columnHeaders, workingTimeCalculator);
|
||||
return selectedItems.stream()
|
||||
.map(
|
||||
s ->
|
||||
|
|
|
@ -76,7 +76,8 @@ public class TimestampReportBuilderImpl
|
|||
|
||||
report.addItems(
|
||||
items,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(columnHeaders, converter, inWorkingDays));
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
columnHeaders, workingTimeCalculator, inWorkingDays));
|
||||
return report;
|
||||
} finally {
|
||||
this.taskanaEngine.returnConnection();
|
||||
|
|
|
@ -5,7 +5,6 @@ import java.util.List;
|
|||
|
||||
import pro.taskana.common.api.IntInterval;
|
||||
import pro.taskana.common.api.TaskanaRole;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.api.exceptions.MismatchedRoleException;
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
|
@ -32,7 +31,6 @@ public class WorkbasketPriorityReportBuilderImpl implements WorkbasketPriorityRe
|
|||
protected String[] domains;
|
||||
protected String[] classificationIds;
|
||||
protected String[] excludedClassificationIds;
|
||||
protected WorkingDaysToDaysConverter converter;
|
||||
private WorkbasketType[] workbasketTypes;
|
||||
private String[] custom1In;
|
||||
private String[] custom1NotIn;
|
||||
|
|
|
@ -50,7 +50,7 @@ public class WorkbasketReportBuilderImpl
|
|||
report.addItems(
|
||||
monitorQueryItems,
|
||||
new DaysToWorkingDaysReportPreProcessor<>(
|
||||
this.columnHeaders, converter, this.inWorkingDays));
|
||||
this.columnHeaders, workingTimeCalculator, this.inWorkingDays));
|
||||
|
||||
Map<String, String> displayMap =
|
||||
taskanaEngine
|
||||
|
|
|
@ -2,6 +2,7 @@ package pro.taskana.spi.priority.api;
|
|||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.task.api.models.Task;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
||||
|
@ -11,6 +12,18 @@ import pro.taskana.task.api.models.TaskSummary;
|
|||
*/
|
||||
public interface PriorityServiceProvider {
|
||||
|
||||
/**
|
||||
* Provide the active {@linkplain TaskanaEngine} which is initialized for this TASKANA
|
||||
* installation.
|
||||
*
|
||||
* <p>This method is called during TASKANA startup and allows the service provider to store the
|
||||
* active {@linkplain TaskanaEngine} for later usage.
|
||||
*
|
||||
* @param taskanaEngine the active {@linkplain TaskanaEngine} which is initialized for this
|
||||
* installation
|
||||
*/
|
||||
default void initialize(TaskanaEngine taskanaEngine) {}
|
||||
|
||||
/**
|
||||
* Determine the {@linkplain Task#getPriority() priority} of a certain {@linkplain Task} during
|
||||
* execution of {@linkplain pro.taskana.task.api.TaskService#createTask(Task)} and {@linkplain
|
||||
|
|
|
@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import pro.taskana.classification.api.models.ClassificationSummary;
|
||||
import pro.taskana.common.api.BulkOperationResults;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.api.exceptions.TaskanaException;
|
||||
import pro.taskana.common.internal.InternalTaskanaEngine;
|
||||
|
@ -38,7 +38,7 @@ class ServiceLevelHandler {
|
|||
private final InternalTaskanaEngine taskanaEngine;
|
||||
private final TaskMapper taskMapper;
|
||||
private final AttachmentMapper attachmentMapper;
|
||||
private final WorkingDaysToDaysConverter converter;
|
||||
private final WorkingTimeCalculator workingTimeCalculator;
|
||||
private final TaskServiceImpl taskServiceImpl;
|
||||
|
||||
ServiceLevelHandler(
|
||||
|
@ -49,7 +49,7 @@ class ServiceLevelHandler {
|
|||
this.taskanaEngine = taskanaEngine;
|
||||
this.taskMapper = taskMapper;
|
||||
this.attachmentMapper = attachmentMapper;
|
||||
converter = taskanaEngine.getEngine().getWorkingDaysToDaysConverter();
|
||||
workingTimeCalculator = taskanaEngine.getEngine().getWorkingTimeCalculator();
|
||||
this.taskServiceImpl = taskServiceImpl;
|
||||
}
|
||||
|
||||
|
@ -108,12 +108,14 @@ class ServiceLevelHandler {
|
|||
return bulkLog;
|
||||
}
|
||||
|
||||
TaskImpl updatePrioPlannedDueOfTask(
|
||||
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, boolean forRefreshOnClassificationUpdate)
|
||||
// TODO: Is it worth splitting the logic of this method into two separate methods one for
|
||||
// creating new task the other for updating a task.
|
||||
TaskImpl updatePrioPlannedDueOfTask(TaskImpl newTaskImpl, TaskImpl oldTaskImpl)
|
||||
throws InvalidArgumentException {
|
||||
boolean onlyPriority = false;
|
||||
if (newTaskImpl.getClassificationSummary() == null
|
||||
|| newTaskImpl.getClassificationSummary().getServiceLevel() == null) {
|
||||
// TODO this should never be the case
|
||||
setPlannedDueOnMissingServiceLevel(newTaskImpl);
|
||||
onlyPriority = true;
|
||||
}
|
||||
|
@ -123,6 +125,7 @@ class ServiceLevelHandler {
|
|||
}
|
||||
|
||||
if (newTaskImpl.getPlanned() == null && newTaskImpl.getDue() == null) {
|
||||
// TODO bitte oldTaskImpl berücksichtigen
|
||||
newTaskImpl.setPlanned(Instant.now());
|
||||
}
|
||||
|
||||
|
@ -135,21 +138,17 @@ class ServiceLevelHandler {
|
|||
if (onlyPriority) {
|
||||
return newTaskImpl;
|
||||
}
|
||||
// classification update
|
||||
if (forRefreshOnClassificationUpdate) {
|
||||
newTaskImpl.setDue(
|
||||
getFollowingWorkingDays(newTaskImpl.getPlanned(), durationPrioHolder.getDuration()));
|
||||
return newTaskImpl;
|
||||
}
|
||||
|
||||
// creation of new task
|
||||
if (oldTaskImpl == null) {
|
||||
return updatePlannedDueOnCreationOfNewTask(newTaskImpl, durationPrioHolder);
|
||||
return updatePlannedDueOnCreationOfNewTask(newTaskImpl, durationPrioHolder.getDuration());
|
||||
} else {
|
||||
return updatePlannedDueOnTaskUpdate(newTaskImpl, oldTaskImpl, durationPrioHolder);
|
||||
return updatePlannedDueOnTaskUpdate(
|
||||
newTaskImpl, oldTaskImpl, durationPrioHolder.getDuration());
|
||||
}
|
||||
}
|
||||
|
||||
DurationPrioHolder determineTaskPrioDuration(TaskImpl newTaskImpl, boolean onlyPriority) {
|
||||
private DurationPrioHolder determineTaskPrioDuration(TaskImpl newTaskImpl, boolean onlyPriority) {
|
||||
Set<ClassificationSummary> classificationsInvolved =
|
||||
getClassificationsReferencedByATask(newTaskImpl);
|
||||
|
||||
|
@ -228,6 +227,7 @@ class ServiceLevelHandler {
|
|||
MinimalTaskSummary minimalTaskSummary,
|
||||
Map<String, Integer> classificationIdToPriorityMap,
|
||||
Map<String, Set<String>> taskIdToClassificationIdsMap) {
|
||||
// TODO this should allow negative Priorities just like #getFinalPrioDurationOfTask
|
||||
int actualPriority = 0;
|
||||
for (String classificationId :
|
||||
taskIdToClassificationIdsMap.get(minimalTaskSummary.getTaskId())) {
|
||||
|
@ -250,56 +250,71 @@ class ServiceLevelHandler {
|
|||
}
|
||||
|
||||
private TaskImpl updatePlannedDueOnTaskUpdate(
|
||||
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, DurationPrioHolder durationPrioHolder)
|
||||
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, Duration duration)
|
||||
throws InvalidArgumentException {
|
||||
// TODO pull this one out and in updatePlannedDueOnCreationOfNewTask, too.
|
||||
if (taskanaEngine.getEngine().getConfiguration().isAllowTimestampServiceLevelMismatch()
|
||||
&& newTaskImpl.getDue() != null
|
||||
&& newTaskImpl.getPlanned() != null) {
|
||||
|
||||
return newTaskImpl;
|
||||
}
|
||||
if (newTaskImpl.getPlanned() == null && newTaskImpl.getDue() == null) {
|
||||
newTaskImpl.setPlanned(oldTaskImpl.getPlanned());
|
||||
}
|
||||
|
||||
if (oldTaskImpl.getDue().equals(newTaskImpl.getDue())
|
||||
&& oldTaskImpl.getPlanned().equals(newTaskImpl.getPlanned())) {
|
||||
// case 1: no change of planned/due, but potentially change of an attachment or classification
|
||||
// -> recalculate due
|
||||
newTaskImpl.setDue(
|
||||
getFollowingWorkingDays(newTaskImpl.getPlanned(), durationPrioHolder.getDuration()));
|
||||
} else if (dueIsUnchangedOrNull(newTaskImpl, oldTaskImpl) && newTaskImpl.getPlanned() != null) {
|
||||
// case 2: due is null or only planned was changed -> normalize planned & recalculate due
|
||||
newTaskImpl.setPlanned(getFollowingWorkingDays(newTaskImpl.getPlanned(), Duration.ofDays(0)));
|
||||
newTaskImpl.setDue(
|
||||
getFollowingWorkingDays(newTaskImpl.getPlanned(), durationPrioHolder.getDuration()));
|
||||
boolean forcedDueRecalculation = newTaskImpl.getDue() == null;
|
||||
boolean forcedPlannedRecalculation = newTaskImpl.getPlanned() == null;
|
||||
if (forcedDueRecalculation) {
|
||||
recalcDueBasedPlanned(newTaskImpl, duration);
|
||||
} else if (forcedPlannedRecalculation) {
|
||||
recalcPlannedBasedOnDue(newTaskImpl, oldTaskImpl, duration);
|
||||
} else if (oldTaskImpl.getDue().equals(newTaskImpl.getDue())) {
|
||||
// We know due has not changed, but the following two options may happen
|
||||
// * no change of planned, but potentially change of an attachment or classification
|
||||
// * planned has changed
|
||||
// -> normalize planned and recalculate due
|
||||
recalcDueBasedPlanned(newTaskImpl, duration);
|
||||
} else {
|
||||
// case 3: due and (maybe) planned were changed -> validate SLA if planned changed
|
||||
Instant calcDue = getPrecedingWorkingDays(newTaskImpl.getDue(), Duration.ofDays(0));
|
||||
Instant calcPlanned = getPrecedingWorkingDays(calcDue, durationPrioHolder.getDuration());
|
||||
if (plannedHasChanged(newTaskImpl, oldTaskImpl)) {
|
||||
ensureServiceLevelIsNotViolated(newTaskImpl, durationPrioHolder.getDuration(), calcPlanned);
|
||||
}
|
||||
newTaskImpl.setPlanned(calcPlanned);
|
||||
newTaskImpl.setDue(calcDue);
|
||||
// Due has changed and (maybe) planned has changed
|
||||
// -> normalize due and recalculate planned
|
||||
recalcPlannedBasedOnDue(newTaskImpl, oldTaskImpl, duration);
|
||||
}
|
||||
return newTaskImpl;
|
||||
}
|
||||
|
||||
private boolean dueIsUnchangedOrNull(Task newTask, Task oldTask) {
|
||||
return newTask.getDue() == null || oldTask.getDue().equals(newTask.getDue());
|
||||
private void recalcPlannedBasedOnDue(
|
||||
TaskImpl newTaskImpl, TaskImpl oldTaskImpl, Duration duration)
|
||||
throws InvalidArgumentException {
|
||||
Instant calcDue = instantOrEndOfPreviousWorkSlot(newTaskImpl.getDue());
|
||||
Instant calcPlanned = subtractWorkingTime(calcDue, duration);
|
||||
if (plannedHasChanged(newTaskImpl, oldTaskImpl)) {
|
||||
ensureServiceLevelIsNotViolated(newTaskImpl, duration, calcPlanned);
|
||||
}
|
||||
newTaskImpl.setPlanned(calcPlanned);
|
||||
newTaskImpl.setDue(calcDue);
|
||||
}
|
||||
|
||||
private void recalcDueBasedPlanned(TaskImpl newTaskImpl, Duration duration) {
|
||||
newTaskImpl.setPlanned(instantOrStartOfNextWorkSlot(newTaskImpl.getPlanned()));
|
||||
newTaskImpl.setDue(addWorkingTime(newTaskImpl.getPlanned(), duration));
|
||||
}
|
||||
|
||||
private boolean plannedHasChanged(Task newTask, Task oldTask) {
|
||||
return newTask.getPlanned() != null && !oldTask.getPlanned().equals(newTask.getPlanned());
|
||||
}
|
||||
|
||||
private Instant getPrecedingWorkingDays(Instant instant, Duration days) {
|
||||
return converter.subtractWorkingDaysFromInstant(instant, days);
|
||||
private Instant instantOrEndOfPreviousWorkSlot(Instant instant) {
|
||||
return subtractWorkingTime(instant, Duration.ZERO);
|
||||
}
|
||||
|
||||
private Instant getFollowingWorkingDays(Instant instant, Duration days) {
|
||||
return converter.addWorkingDaysToInstant(instant, days);
|
||||
private Instant subtractWorkingTime(Instant instant, Duration workingTime) {
|
||||
return workingTimeCalculator.subtractWorkingTime(instant, workingTime);
|
||||
}
|
||||
|
||||
private Instant instantOrStartOfNextWorkSlot(Instant instant) {
|
||||
return addWorkingTime(instant, Duration.ZERO);
|
||||
}
|
||||
|
||||
private Instant addWorkingTime(Instant instant, Duration workingTime) {
|
||||
return workingTimeCalculator.addWorkingTime(instant, workingTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -325,11 +340,12 @@ class ServiceLevelHandler {
|
|||
*/
|
||||
private void ensureServiceLevelIsNotViolated(
|
||||
TaskImpl task, Duration duration, Instant calcPlanned) throws InvalidArgumentException {
|
||||
// TODO tests mit coverage. falls die Exception nie auftritt weg mit der Methode
|
||||
if (task.getPlanned() != null
|
||||
&& !task.getPlanned().equals(calcPlanned)
|
||||
// manual entered planned date is a different working day than computed value
|
||||
&& (converter.isWorkingDay(task.getPlanned())
|
||||
|| converter.hasWorkingDaysInBetween(task.getPlanned(), calcPlanned))) {
|
||||
&& (workingTimeCalculator.isWorkingDay(task.getPlanned())
|
||||
|| workingTimeCalculator.isWorkingTimeBetween(task.getPlanned(), calcPlanned))) {
|
||||
throw new InvalidArgumentException(
|
||||
String.format(
|
||||
"Cannot update a task with given planned %s "
|
||||
|
@ -338,8 +354,8 @@ class ServiceLevelHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private TaskImpl updatePlannedDueOnCreationOfNewTask(
|
||||
TaskImpl newTask, DurationPrioHolder durationPrioHolder) throws InvalidArgumentException {
|
||||
private TaskImpl updatePlannedDueOnCreationOfNewTask(TaskImpl newTask, Duration duration)
|
||||
throws InvalidArgumentException {
|
||||
if (taskanaEngine.getEngine().getConfiguration().isAllowTimestampServiceLevelMismatch()
|
||||
&& newTask.getDue() != null
|
||||
&& newTask.getPlanned() != null) {
|
||||
|
@ -347,16 +363,14 @@ class ServiceLevelHandler {
|
|||
}
|
||||
if (newTask.getDue() != null) {
|
||||
// due is specified: calculate back and check correctness
|
||||
Instant calcDue = getPrecedingWorkingDays(newTask.getDue(), Duration.ofDays(0));
|
||||
Instant calcPlanned = getPrecedingWorkingDays(calcDue, durationPrioHolder.getDuration());
|
||||
ensureServiceLevelIsNotViolated(newTask, durationPrioHolder.getDuration(), calcPlanned);
|
||||
Instant calcDue = instantOrEndOfPreviousWorkSlot(newTask.getDue());
|
||||
Instant calcPlanned = subtractWorkingTime(calcDue, duration);
|
||||
ensureServiceLevelIsNotViolated(newTask, duration, calcPlanned);
|
||||
newTask.setDue(calcDue);
|
||||
newTask.setPlanned(calcPlanned);
|
||||
} else {
|
||||
// task.due is null: calculate forward from planned
|
||||
newTask.setPlanned(getFollowingWorkingDays(newTask.getPlanned(), Duration.ofDays(0)));
|
||||
newTask.setDue(
|
||||
getFollowingWorkingDays(newTask.getPlanned(), durationPrioHolder.getDuration()));
|
||||
recalcDueBasedPlanned(newTask, duration);
|
||||
}
|
||||
return newTask;
|
||||
}
|
||||
|
@ -378,8 +392,7 @@ class ServiceLevelHandler {
|
|||
TaskImpl referenceTask = new TaskImpl();
|
||||
referenceTask.setPlanned(durationHolder.getPlanned());
|
||||
referenceTask.setModified(Instant.now());
|
||||
referenceTask.setDue(
|
||||
getFollowingWorkingDays(referenceTask.getPlanned(), durationHolder.getDuration()));
|
||||
referenceTask.setDue(addWorkingTime(referenceTask.getPlanned(), durationHolder.getDuration()));
|
||||
List<String> taskIdsToUpdate =
|
||||
taskDurationList.stream().map(TaskDuration::getTaskId).collect(Collectors.toList());
|
||||
Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
|
||||
|
@ -399,7 +412,7 @@ class ServiceLevelHandler {
|
|||
|
||||
taskIdsByDueDuration.forEach(
|
||||
(duration, taskIds) -> {
|
||||
referenceTask.setDue(getFollowingWorkingDays(planned, duration));
|
||||
referenceTask.setDue(addWorkingTime(planned, duration));
|
||||
Pair<List<MinimalTaskSummary>, BulkLog> existingAndAuthorizedTasks =
|
||||
taskServiceImpl.getMinimalTaskSummaries(taskIds);
|
||||
bulkLog.addAllErrors(existingAndAuthorizedTasks.getRight());
|
||||
|
@ -584,40 +597,35 @@ class ServiceLevelHandler {
|
|||
}
|
||||
|
||||
private boolean isPriorityAndDurationAlreadyCorrect(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
|
||||
if (oldTaskImpl != null) {
|
||||
final boolean isClassificationKeyChanged =
|
||||
newTaskImpl.getClassificationKey() != null
|
||||
&& (oldTaskImpl.getClassificationKey() == null
|
||||
|| !newTaskImpl
|
||||
.getClassificationKey()
|
||||
.equals(oldTaskImpl.getClassificationKey()));
|
||||
final boolean isManualPriorityChanged =
|
||||
newTaskImpl.getManualPriority() != oldTaskImpl.getManualPriority();
|
||||
final boolean isClassificationIdChanged =
|
||||
newTaskImpl.getClassificationId() != null
|
||||
&& (oldTaskImpl.getClassificationId() == null
|
||||
|| !newTaskImpl.getClassificationId().equals(oldTaskImpl.getClassificationId()));
|
||||
|
||||
return oldTaskImpl.getPlanned().equals(newTaskImpl.getPlanned())
|
||||
&& oldTaskImpl.getDue().equals(newTaskImpl.getDue())
|
||||
&& !isClassificationKeyChanged
|
||||
&& !isClassificationIdChanged
|
||||
&& !isManualPriorityChanged
|
||||
&& areAttachmentsUnchanged(newTaskImpl, oldTaskImpl);
|
||||
} else {
|
||||
if (oldTaskImpl == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO Do we need to compare Key and Id or could we simply compare ClassificationSummary only?
|
||||
final boolean isClassificationKeyChanged =
|
||||
Objects.equals(newTaskImpl.getClassificationKey(), oldTaskImpl.getClassificationKey());
|
||||
final boolean isClassificationIdChanged =
|
||||
Objects.equals(newTaskImpl.getClassificationId(), oldTaskImpl.getClassificationId());
|
||||
|
||||
final boolean isManualPriorityChanged =
|
||||
newTaskImpl.getManualPriority() != oldTaskImpl.getManualPriority();
|
||||
|
||||
return oldTaskImpl.getPlanned().equals(newTaskImpl.getPlanned())
|
||||
&& oldTaskImpl.getDue().equals(newTaskImpl.getDue())
|
||||
&& !isClassificationKeyChanged
|
||||
&& !isClassificationIdChanged
|
||||
&& !isManualPriorityChanged
|
||||
&& areAttachmentsUnchanged(newTaskImpl, oldTaskImpl);
|
||||
}
|
||||
|
||||
private boolean areAttachmentsUnchanged(TaskImpl newTaskImpl, TaskImpl oldTaskImpl) {
|
||||
List<String> oldAttachmentIds =
|
||||
Set<String> oldAttachmentIds =
|
||||
oldTaskImpl.getAttachments().stream()
|
||||
.map(AttachmentSummary::getId)
|
||||
.collect(Collectors.toList());
|
||||
List<String> newAttachmentIds =
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> newAttachmentIds =
|
||||
newTaskImpl.getAttachments().stream()
|
||||
.map(AttachmentSummary::getId)
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> oldClassificationIds =
|
||||
oldTaskImpl.getAttachments().stream()
|
||||
.map(Attachment::getClassificationSummary)
|
||||
|
|
|
@ -967,11 +967,9 @@ public class TaskServiceImpl implements TaskService {
|
|||
taskanaEngine
|
||||
.getEngine()
|
||||
.runAsAdmin(
|
||||
() -> {
|
||||
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
|
||||
tasks, serviceLevelChanged, priorityChanged);
|
||||
return null;
|
||||
});
|
||||
() ->
|
||||
serviceLevelHandler.refreshPriorityAndDueDatesOfTasks(
|
||||
tasks, serviceLevelChanged, priorityChanged));
|
||||
}
|
||||
} finally {
|
||||
taskanaEngine.returnConnection();
|
||||
|
@ -1738,7 +1736,7 @@ public class TaskServiceImpl implements TaskService {
|
|||
// This has to be called after the AttachmentHandler because the AttachmentHandler fetches
|
||||
// the Classifications of the Attachments.
|
||||
// This is necessary to guarantee that the following calculation is correct.
|
||||
serviceLevelHandler.updatePrioPlannedDueOfTask(task, null, false);
|
||||
serviceLevelHandler.updatePrioPlannedDueOfTask(task, null);
|
||||
}
|
||||
|
||||
private void setDefaultTaskReceivedDateFromAttachments(TaskImpl task) {
|
||||
|
@ -2048,7 +2046,7 @@ public class TaskServiceImpl implements TaskService {
|
|||
updateClassificationSummary(newTaskImpl, oldTaskImpl);
|
||||
|
||||
TaskImpl newTaskImpl1 =
|
||||
serviceLevelHandler.updatePrioPlannedDueOfTask(newTaskImpl, oldTaskImpl, false);
|
||||
serviceLevelHandler.updatePrioPlannedDueOfTask(newTaskImpl, oldTaskImpl);
|
||||
|
||||
// if no business process id is provided, use the id of the old task.
|
||||
if (newTaskImpl1.getBusinessProcessId() == null) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import pro.taskana.TaskanaConfiguration;
|
|||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.TaskanaEngine.ConnectionManagementMode;
|
||||
import pro.taskana.common.api.TimeInterval;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.internal.JobMapper;
|
||||
import pro.taskana.common.internal.TaskanaEngineImpl;
|
||||
import pro.taskana.common.test.config.DataSourceGenerator;
|
||||
|
@ -41,7 +41,7 @@ public abstract class AbstractAccTest {
|
|||
protected static TaskanaEngine taskanaEngine;
|
||||
|
||||
protected static TaskServiceImpl taskService;
|
||||
protected static WorkingDaysToDaysConverter converter;
|
||||
protected static WorkingTimeCalculator workingTimeCalculator;
|
||||
|
||||
@BeforeAll
|
||||
protected static void setupTest() throws Exception {
|
||||
|
@ -66,7 +66,7 @@ public abstract class AbstractAccTest {
|
|||
taskanaEngine =
|
||||
TaskanaEngine.buildTaskanaEngine(
|
||||
taskanaEngineConfiguration, ConnectionManagementMode.AUTOCOMMIT);
|
||||
converter = taskanaEngine.getWorkingDaysToDaysConverter();
|
||||
workingTimeCalculator = taskanaEngine.getWorkingTimeCalculator();
|
||||
taskService = (TaskServiceImpl) taskanaEngine.getTaskService();
|
||||
|
||||
sampleDataGenerator.clearDb();
|
||||
|
@ -140,10 +140,10 @@ public abstract class AbstractAccTest {
|
|||
}
|
||||
|
||||
protected Instant moveForwardToWorkingDay(Instant date) {
|
||||
return converter.addWorkingDaysToInstant(date, Duration.ZERO);
|
||||
return workingTimeCalculator.addWorkingTime(date, Duration.ZERO);
|
||||
}
|
||||
|
||||
protected Instant moveBackToWorkingDay(Instant date) {
|
||||
return converter.subtractWorkingDaysFromInstant(date, Duration.ZERO);
|
||||
return workingTimeCalculator.subtractWorkingTime(date, Duration.ZERO);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import pro.taskana.common.api.CustomHoliday;
|
|||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.test.config.DataSourceGenerator;
|
||||
|
||||
/** Test of configuration. */
|
||||
class TaskanaEngineConfigTest {
|
||||
|
||||
@Test
|
||||
|
@ -27,7 +26,7 @@ class TaskanaEngineConfigTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_SetCorpusChristiEnabled_When_PropertyIsSet() throws Exception {
|
||||
void should_SetCorpusChristiEnabled_When_PropertyIsSet() {
|
||||
DataSource ds = DataSourceGenerator.getDataSource();
|
||||
TaskanaConfiguration taskEngineConfiguration =
|
||||
new TaskanaConfiguration.Builder(ds, false, DataSourceGenerator.getSchemaName(), true)
|
||||
|
@ -38,20 +37,18 @@ class TaskanaEngineConfigTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnTheTwoCustomHolidays_When_TwoCustomHolidaysAreConfiguredInThePropertiesFile()
|
||||
throws Exception {
|
||||
void should_ReturnTheTwoCustomHolidays_When_TwoCustomHolidaysAreConfiguredInThePropertiesFile() {
|
||||
DataSource ds = DataSourceGenerator.getDataSource();
|
||||
TaskanaConfiguration taskEngineConfiguration =
|
||||
new TaskanaConfiguration.Builder(ds, false, DataSourceGenerator.getSchemaName(), true)
|
||||
.initTaskanaProperties("/custom_holiday_taskana.properties", "|")
|
||||
.build();
|
||||
assertThat(taskEngineConfiguration.getCustomHolidays()).contains(CustomHoliday.of(31, 7));
|
||||
assertThat(taskEngineConfiguration.getCustomHolidays()).contains(CustomHoliday.of(16, 12));
|
||||
assertThat(taskEngineConfiguration.getCustomHolidays())
|
||||
.contains(CustomHoliday.of(31, 7), CustomHoliday.of(16, 12));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnEmptyCustomHolidaysList_When_AllCustomHolidaysAreInWrongFormatInPropertiesFile()
|
||||
throws Exception {
|
||||
void should_ReturnEmptyList_When_AllCustomHolidaysAreInWrongFormatInPropertiesFile() {
|
||||
DataSource ds = DataSourceGenerator.getDataSource();
|
||||
TaskanaConfiguration taskEngineConfiguration =
|
||||
new TaskanaConfiguration.Builder(ds, false, DataSourceGenerator.getSchemaName(), true)
|
||||
|
|
|
@ -7,7 +7,6 @@ import acceptance.AbstractAccTest;
|
|||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -21,23 +20,22 @@ import pro.taskana.task.internal.jobs.helper.SqlConnectionRunner;
|
|||
class SqlConnectionRunnerAccTest extends AbstractAccTest {
|
||||
|
||||
@Test
|
||||
void should_executeSimpleQuery() throws Exception {
|
||||
void should_executeSimpleQuery() {
|
||||
// given
|
||||
SqlConnectionRunner runner = new SqlConnectionRunner(taskanaEngine);
|
||||
String taskId = "TKI:000000000000000000000000000000000050";
|
||||
|
||||
// when
|
||||
AtomicReference<ResultSet> resultSet = new AtomicReference<>();
|
||||
runner.runWithConnection(
|
||||
connection -> {
|
||||
ResultSet resultSet;
|
||||
PreparedStatement preparedStatement =
|
||||
connection.prepareStatement("select * from TASK where ID = ?");
|
||||
preparedStatement.setString(1, taskId);
|
||||
resultSet.set(preparedStatement.executeQuery());
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
// then
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
});
|
||||
|
||||
// then
|
||||
assertThat(resultSet.get().next()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -45,7 +45,7 @@ class UpdateObjectsUseUtcTimeStampsAccTest extends AbstractAccTest {
|
|||
task.setPlanned(now.plus(Duration.ofHours(17)));
|
||||
|
||||
// associated Classification has ServiceLevel 'P1D'
|
||||
task.setDue(converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1)));
|
||||
task.setDue(workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1)));
|
||||
|
||||
TaskImpl ti = (TaskImpl) task;
|
||||
ti.setCompleted(now.plus(Duration.ofHours(27)));
|
||||
|
|
|
@ -4,17 +4,22 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.spi.priority.api.PriorityServiceProvider;
|
||||
import pro.taskana.task.api.TaskCustomField;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
||||
public class TestPriorityServiceProvider implements PriorityServiceProvider {
|
||||
|
||||
private static final int MULTIPLIER = 10;
|
||||
|
||||
private final WorkingDaysToDaysConverter converter = new WorkingDaysToDaysConverter(true, true);
|
||||
private final WorkingTimeCalculator calculator = new WorkingTimeCalculator(converter);
|
||||
private WorkingTimeCalculator calculator;
|
||||
|
||||
@Override
|
||||
public void initialize(TaskanaEngine taskanaEngine) {
|
||||
calculator = taskanaEngine.getWorkingTimeCalculator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt calculatePriority(TaskSummary taskSummary) {
|
||||
|
@ -22,10 +27,7 @@ public class TestPriorityServiceProvider implements PriorityServiceProvider {
|
|||
long priority;
|
||||
try {
|
||||
priority =
|
||||
calculator
|
||||
.workingTimeBetweenTwoTimestamps(taskSummary.getCreated(), Instant.now())
|
||||
.toMinutes()
|
||||
+ 1;
|
||||
calculator.workingTimeBetween(taskSummary.getCreated(), Instant.now()).toMinutes() + 1;
|
||||
} catch (Exception e) {
|
||||
priority = Duration.between(taskSummary.getCreated(), Instant.now()).toMinutes();
|
||||
}
|
||||
|
|
|
@ -2,45 +2,70 @@ package acceptance.report;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import pro.taskana.common.api.CustomHoliday;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.LocalTimeInterval;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.internal.workingtime.HolidaySchedule;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.monitor.api.reports.header.TimeIntervalColumnHeader;
|
||||
import pro.taskana.monitor.internal.preprocessor.WorkingDaysToDaysReportConverter;
|
||||
|
||||
/** Test for the DaysToWorkingDaysReportConverter. */
|
||||
class WorkingDaysToDaysReportConverterTest {
|
||||
|
||||
private final WorkingDaysToDaysConverter converter;
|
||||
private final WorkingTimeCalculator workingTimeCalculator;
|
||||
|
||||
public WorkingDaysToDaysReportConverterTest() {
|
||||
Map<DayOfWeek, Set<LocalTimeInterval>> workingTime = new EnumMap<>(DayOfWeek.class);
|
||||
Set<LocalTimeInterval> standardWorkingSlots =
|
||||
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
||||
workingTime.put(DayOfWeek.MONDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.TUESDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.WEDNESDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.THURSDAY, standardWorkingSlots);
|
||||
workingTime.put(DayOfWeek.FRIDAY, standardWorkingSlots);
|
||||
|
||||
CustomHoliday dayOfReformation = CustomHoliday.of(31, 10);
|
||||
CustomHoliday allSaintsDays = CustomHoliday.of(1, 11);
|
||||
converter =
|
||||
new WorkingDaysToDaysConverter(true, false, List.of(dayOfReformation, allSaintsDays));
|
||||
workingTimeCalculator =
|
||||
new WorkingTimeCalculatorImpl(
|
||||
new HolidaySchedule(true, false, List.of(dayOfReformation, allSaintsDays)),
|
||||
workingTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_AssertNotEqual_When_InitializingDifferentDates() throws Exception {
|
||||
void should_AssertNotEqual_When_InitializingDifferentDates() {
|
||||
WorkingDaysToDaysReportConverter instance1 =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getShortListOfColumnHeaders(), converter, Instant.parse("2018-02-04T00:00:00.000Z"));
|
||||
getShortListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-04T00:00:00.000Z"));
|
||||
WorkingDaysToDaysReportConverter instance2 =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getShortListOfColumnHeaders(), converter, Instant.parse("2018-02-05T00:00:00.000Z"));
|
||||
getShortListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-05T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance1).isNotEqualTo(instance2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDays() throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDays() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
|
||||
int oneBelowLimit = -16;
|
||||
int oneAboveLimit = 16;
|
||||
|
@ -53,7 +78,7 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
assertThat(instance.convertDaysToWorkingDays(-2)).isEqualTo(-1);
|
||||
assertThat(instance.convertDaysToWorkingDays(-1)).isEqualTo(-1);
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(3);
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(3);
|
||||
|
@ -65,30 +90,34 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDaysUnchanged_When_ConvertingWorkingDaysOutOfNegativeLimit()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDaysUnchanged_When_ConvertingWorkingDaysOutOfNegativeLimit() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertWorkingDaysToDays(-999)).containsExactlyInAnyOrder(-999);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDaysUnchanged_When_ConvertingWorkingDaysOutOfPositiveLimit()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDaysUnchanged_When_ConvertingWorkingDaysOutOfPositiveLimit() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-06T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertWorkingDaysToDays(999)).containsExactlyInAnyOrder(999);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDays() throws Exception {
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDays() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-02-27T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-02-27T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertWorkingDaysToDays(-13)).containsExactlyInAnyOrder(-13);
|
||||
assertThat(instance.convertWorkingDaysToDays(-12)).containsExactlyInAnyOrder(-12);
|
||||
|
@ -119,10 +148,12 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDaysAtWeekend() throws Exception {
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDaysAtWeekend() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-03-10T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-03-10T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertWorkingDaysToDays(-13)).containsExactlyInAnyOrder(-13);
|
||||
assertThat(instance.convertWorkingDaysToDays(-12)).containsExactlyInAnyOrder(-12);
|
||||
|
@ -153,11 +184,12 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDaysOnEasterSunday()
|
||||
throws Exception {
|
||||
void should_ReturnAllMatchingDays_When_ConvertingWorkingDaysToDaysOnEasterSunday() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-04-01T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-04-01T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertWorkingDaysToDays(-13)).containsExactlyInAnyOrder(-13);
|
||||
assertThat(instance.convertWorkingDaysToDays(-12)).containsExactlyInAnyOrder(-12);
|
||||
|
@ -188,30 +220,32 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnEasterHolidays()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnEasterHolidays() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-03-28T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-03-28T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(6)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnWhitsunHolidays()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnWhitsunHolidays() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-05-16T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-05-16T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(2);
|
||||
|
@ -220,15 +254,17 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnLabourDay() throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnLabourDay() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-04-26T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-04-26T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(6)).isEqualTo(3);
|
||||
|
@ -236,13 +272,15 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnAscensionDay() throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnAscensionDay() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-05-07T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-05-07T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(3);
|
||||
|
@ -252,15 +290,16 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnDayOfGermanUnity()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnDayOfGermanUnity() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-10-01T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-10-01T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(3);
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isEqualTo(3);
|
||||
|
@ -269,16 +308,17 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnChristmasAndNewYearHolidays()
|
||||
throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnChristmasAndNewYearHolidays() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-12-20T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-12-20T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(6)).isEqualTo(2);
|
||||
|
@ -293,15 +333,17 @@ class WorkingDaysToDaysReportConverterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnCustomHoliday() throws Exception {
|
||||
void should_ReturnWorkingDays_When_ConvertingDaysToWorkingDaysOnCustomHoliday() {
|
||||
WorkingDaysToDaysReportConverter instance =
|
||||
WorkingDaysToDaysReportConverter.initialize(
|
||||
getLargeListOfColumnHeaders(), converter, Instant.parse("2018-10-26T00:00:00.000Z"));
|
||||
getLargeListOfColumnHeaders(),
|
||||
workingTimeCalculator,
|
||||
Instant.parse("2018-10-26T00:00:00.000Z"));
|
||||
|
||||
assertThat(instance.convertDaysToWorkingDays(0)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(1)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(2)).isZero();
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isEqualTo(1);
|
||||
assertThat(instance.convertDaysToWorkingDays(3)).isOne();
|
||||
assertThat(instance.convertDaysToWorkingDays(4)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(5)).isEqualTo(2);
|
||||
assertThat(instance.convertDaysToWorkingDays(6)).isEqualTo(2);
|
||||
|
|
|
@ -20,7 +20,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import pro.taskana.classification.api.ClassificationService;
|
||||
import pro.taskana.classification.api.models.Classification;
|
||||
import pro.taskana.common.api.BulkOperationResults;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||
import pro.taskana.common.api.exceptions.TaskanaException;
|
||||
import pro.taskana.common.test.security.JaasExtension;
|
||||
|
@ -35,11 +34,9 @@ import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionExcep
|
|||
class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
||||
|
||||
private final ClassificationService classificationService;
|
||||
private final WorkingDaysToDaysConverter converter;
|
||||
|
||||
ServiceLevelPriorityAccTest() {
|
||||
classificationService = taskanaEngine.getClassificationService();
|
||||
converter = taskanaEngine.getWorkingDaysToDaysConverter();
|
||||
}
|
||||
|
||||
/* CREATE TASK */
|
||||
|
@ -70,7 +67,7 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
assertThat(readTask.getDue()).isEqualTo(due);
|
||||
|
||||
Instant expectedPlanned =
|
||||
converter.subtractWorkingDaysFromInstant(due, Duration.ofDays(serviceLevelDays));
|
||||
workingTimeCalculator.subtractWorkingTime(due, Duration.ofDays(serviceLevelDays));
|
||||
assertThat(readTask.getPlanned()).isEqualTo(expectedPlanned);
|
||||
}
|
||||
|
||||
|
@ -98,7 +95,8 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
assertThat(readTask.getPlanned()).isEqualTo(planned);
|
||||
|
||||
Instant expectedDue =
|
||||
converter.addWorkingDaysToInstant(readTask.getPlanned(), Duration.ofDays(serviceLevelDays));
|
||||
workingTimeCalculator.addWorkingTime(
|
||||
readTask.getPlanned(), Duration.ofDays(serviceLevelDays));
|
||||
|
||||
assertThat(readTask.getDue()).isEqualTo(expectedDue);
|
||||
}
|
||||
|
@ -119,7 +117,7 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
|
||||
// due date according to service level
|
||||
Instant expectedDue =
|
||||
converter.addWorkingDaysToInstant(newTask.getPlanned(), Duration.ofDays(duration));
|
||||
workingTimeCalculator.addWorkingTime(newTask.getPlanned(), Duration.ofDays(duration));
|
||||
|
||||
newTask.setDue(expectedDue);
|
||||
ThrowingCallable call = () -> taskService.createTask(newTask);
|
||||
|
@ -365,10 +363,10 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
Instant dueBulk2 = taskService.getTask(tkId2).getDue();
|
||||
Instant dueBulk3 = taskService.getTask(tkId3).getDue();
|
||||
Instant dueBulk4 = taskService.getTask(tkId4).getDue();
|
||||
assertThat(dueBulk1).isEqualTo(getInstant("2020-05-14T07:00:00"));
|
||||
assertThat(dueBulk2).isEqualTo(getInstant("2020-05-22T07:00:00"));
|
||||
assertThat(dueBulk3).isEqualTo(getInstant("2020-05-14T07:00:00"));
|
||||
assertThat(dueBulk4).isEqualTo(getInstant("2020-05-22T07:00:00"));
|
||||
assertThat(dueBulk1).isEqualTo(getInstant("2020-05-14T00:00:00"));
|
||||
assertThat(dueBulk2).isEqualTo(getInstant("2020-05-21T00:00:00"));
|
||||
assertThat(dueBulk3).isEqualTo(getInstant("2020-05-14T00:00:00"));
|
||||
assertThat(dueBulk4).isEqualTo(getInstant("2020-05-21T00:00:00"));
|
||||
}
|
||||
|
||||
@WithAccessId(user = "admin")
|
||||
|
@ -399,7 +397,8 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
taskService.setPlannedPropertyOfTasks(planned, List.of(taskId));
|
||||
Task task = taskService.getTask(taskId);
|
||||
assertThat(results.containsErrors()).isFalse();
|
||||
Instant expectedDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expectedDue =
|
||||
workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expectedDue);
|
||||
}
|
||||
|
||||
|
@ -411,7 +410,8 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
// test update of planned date via updateTask()
|
||||
task.setPlanned(task.getPlanned().plus(Duration.ofDays(3)));
|
||||
task = taskService.updateTask(task);
|
||||
Instant expectedDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expectedDue =
|
||||
workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expectedDue);
|
||||
}
|
||||
|
||||
|
@ -432,14 +432,15 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
@Test
|
||||
void should_SetDue_When_OnlyPlannedWasChanged() throws Exception {
|
||||
String taskId = "TKI:000000000000000000000000000000000002";
|
||||
Instant planned = getInstant("2020-05-03T07:00:00");
|
||||
Instant planned = getInstant("2020-05-03T07:00:00"); // Sunday
|
||||
Instant expectedPlanned = getInstant("2020-05-04T00:00:00");
|
||||
Task task = taskService.getTask(taskId);
|
||||
task.setPlanned(planned);
|
||||
task = taskService.updateTask(task);
|
||||
String serviceLevel = task.getClassificationSummary().getServiceLevel();
|
||||
Instant expDue =
|
||||
converter.addWorkingDaysToInstant(task.getPlanned(), Duration.parse(serviceLevel));
|
||||
assertThat(task.getPlanned()).isEqualTo(planned);
|
||||
workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.parse(serviceLevel));
|
||||
assertThat(task.getPlanned()).isEqualTo(expectedPlanned);
|
||||
assertThat(task.getDue()).isEqualTo(expDue);
|
||||
}
|
||||
|
||||
|
@ -455,7 +456,7 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
|
||||
String serviceLevel = task.getClassificationSummary().getServiceLevel();
|
||||
Instant expPlanned =
|
||||
converter.subtractWorkingDaysFromInstant(task.getDue(), Duration.parse(serviceLevel));
|
||||
workingTimeCalculator.subtractWorkingTime(task.getDue(), Duration.parse(serviceLevel));
|
||||
assertThat(task.getPlanned()).isEqualTo(expPlanned);
|
||||
assertThat(task.getDue()).isEqualTo(due);
|
||||
}
|
||||
|
@ -469,53 +470,56 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
|
||||
task.setPlanned(null);
|
||||
task = taskService.updateTask(task);
|
||||
Instant expectedDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expectedDue =
|
||||
workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expectedDue);
|
||||
|
||||
task.setDue(null);
|
||||
task = taskService.updateTask(task);
|
||||
expectedDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
expectedDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expectedDue);
|
||||
|
||||
task.setPlanned(planned.plus(Duration.ofDays(13))); // Saturday
|
||||
task.setDue(null);
|
||||
task = taskService.updateTask(task);
|
||||
expectedDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
expectedDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expectedDue);
|
||||
|
||||
task.setDue(planned.plus(Duration.ofDays(13))); // Saturday
|
||||
task.setPlanned(null);
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-05-14T07:00:00"));
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-05-15T07:00:00"));
|
||||
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-05-15T00:00:00"));
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-05-16T00:00:00"));
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-2")
|
||||
@Test
|
||||
void should_UpdateTaskPlannedOrDue_When_PlannedOrDueAreWeekendDays() throws Exception {
|
||||
|
||||
Task task = taskService.getTask("TKI:000000000000000000000000000000000030"); // SL=P13D
|
||||
task.setPlanned(getInstant("2020-03-21T07:00:00")); // planned = saturday
|
||||
task.setPlanned(getInstant("2020-03-23T07:00:00")); // planned = saturday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-04-09T07:00:00"));
|
||||
|
||||
task.setDue(getInstant("2020-04-11T07:00:00")); // due = saturday
|
||||
task.setPlanned(null);
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00"));
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-24T00:00:00"));
|
||||
|
||||
task.setDue(getInstant("2020-04-12T07:00:00")); // due = sunday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00"));
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-24T00:00:00"));
|
||||
|
||||
task.setPlanned(getInstant("2020-03-21T07:00:00")); // planned = saturday
|
||||
task.setDue(getInstant("2020-04-09T07:00:00")); // thursday
|
||||
task.setDue(getInstant("2020-04-09T00:00:00")); // thursday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00"));
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T00:00:00"));
|
||||
|
||||
task.setPlanned(getInstant("2020-03-03T07:00:00")); // planned on tuesday
|
||||
task.setPlanned(getInstant("2020-03-04T00:00:00")); // planned on tuesday
|
||||
task.setDue(getInstant("2020-03-22T07:00:00")); // due = sunday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-20T07:00:00")); // friday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-21T00:00:00")); // friday, EOB
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
|
@ -532,51 +536,50 @@ class ServiceLevelPriorityAccTest extends AbstractAccTest {
|
|||
// planned changed, due did not change
|
||||
task.setPlanned(getInstant("2020-03-21T07:00:00")); // Saturday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
|
||||
// due changed, planned did not change
|
||||
task.setDue(getInstant("2020-04-12T07:00:00")); // Sunday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned())
|
||||
.isEqualTo(getInstant("2020-04-09T07:00:00")); // Thursday (skip Good Friday)
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-04-09T07:00:00"));
|
||||
Instant endOfHolyThursday = getInstant("2020-04-10T00:00:00");
|
||||
assertThat(task.getPlanned()).isEqualTo(endOfHolyThursday); // Thursday (skip Good Friday)
|
||||
assertThat(task.getDue()).isEqualTo(endOfHolyThursday);
|
||||
|
||||
// due changed, planned is null
|
||||
task.setDue(getInstant("2020-04-11T07:00:00")); // Saturday
|
||||
task.setPlanned(null);
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned())
|
||||
.isEqualTo(getInstant("2020-04-09T07:00:00")); // Thursday (skip Good Friday)
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-04-09T07:00:00"));
|
||||
assertThat(task.getPlanned()).isEqualTo(endOfHolyThursday); // Thursday (skip Good Friday)
|
||||
assertThat(task.getDue()).isEqualTo(endOfHolyThursday);
|
||||
|
||||
// planned changed, due is null
|
||||
task.setPlanned(getInstant("2020-03-22T07:00:00")); // Sunday
|
||||
task.setDue(null);
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
|
||||
// both changed, not null (due at weekend)
|
||||
task.setPlanned(getInstant("2020-03-20T07:00:00")); // Friday
|
||||
task.setPlanned(getInstant("2020-03-21T00:00:00")); // Friday
|
||||
task.setDue(getInstant("2020-03-22T07:00:00")); // Sunday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-20T07:00:00")); // Friday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-20T07:00:00")); // Friday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-21T00:00:00")); // Friday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-21T00:00:00")); // Friday
|
||||
|
||||
// both changed, not null (planned at weekend)
|
||||
task.setPlanned(getInstant("2020-03-22T07:00:00")); // Sunday
|
||||
task.setDue(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
task.setDue(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T07:00:00")); // Monday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-23T00:00:00")); // Monday
|
||||
|
||||
// both changed, not null (both at weekend) within SLA
|
||||
task.setPlanned(getInstant("2020-03-22T07:00:00")); // Sunday
|
||||
task.setDue(getInstant("2020-03-22T07:00:00")); // Sunday
|
||||
task = taskService.updateTask(task);
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-20T07:00:00")); // Friday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-20T07:00:00")); // Friday
|
||||
assertThat(task.getDue()).isEqualTo(getInstant("2020-03-21T00:00:00")); // Friday
|
||||
assertThat(task.getPlanned()).isEqualTo(getInstant("2020-03-21T00:00:00")); // Friday
|
||||
|
||||
// both changed, not null (planned > due)
|
||||
task.setPlanned(getInstant("2020-03-24T07:00:00")); // Tuesday
|
||||
|
|
|
@ -556,7 +556,8 @@ class CreateTaskAccTest extends AbstractAccTest {
|
|||
|
||||
assertThat(readTask.getPriority()).isEqualTo(99);
|
||||
|
||||
Instant expDue = converter.addWorkingDaysToInstant(readTask.getPlanned(), Duration.ofDays(1));
|
||||
Instant expDue =
|
||||
workingTimeCalculator.addWorkingTime(readTask.getPlanned(), Duration.ofDays(1));
|
||||
|
||||
assertThat(readTask.getDue()).isEqualTo(expDue);
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
|
|||
assertThat(task.getAttachments().get(0).getChannel()).isEqualTo(newChannel);
|
||||
assertThat(task.getPriority()).isEqualTo(99);
|
||||
|
||||
Instant expDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expDue);
|
||||
}
|
||||
|
||||
|
@ -321,7 +321,7 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
|
|||
assertThat(task.getAttachments()).hasSize(attachmentCount);
|
||||
assertThat(task.getAttachments().get(0).getChannel()).isEqualTo(newChannel);
|
||||
assertThat(task.getPriority()).isEqualTo(99);
|
||||
Instant expDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
|
||||
assertThat(task.getDue()).isEqualTo(expDue);
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
|
|||
task = taskService.getTask(task.getId());
|
||||
|
||||
assertThat(task.getPriority()).isEqualTo(101);
|
||||
Instant expDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(1));
|
||||
Instant expDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(1));
|
||||
assertThat(task.getDue()).isEqualTo(expDue);
|
||||
assertThat(task.getAttachments())
|
||||
.hasSize(2)
|
||||
|
@ -383,7 +383,7 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
|
|||
task = taskService.getTask(task.getId());
|
||||
assertThat(task.getPriority()).isEqualTo(99);
|
||||
|
||||
expDue = converter.addWorkingDaysToInstant(task.getPlanned(), Duration.ofDays(16));
|
||||
expDue = workingTimeCalculator.addWorkingTime(task.getPlanned(), Duration.ofDays(16));
|
||||
assertThat(task.getDue()).isEqualTo(expDue);
|
||||
assertThat(task.getAttachments())
|
||||
.hasSize(2)
|
||||
|
@ -513,7 +513,8 @@ class UpdateTaskAttachmentsAccTest extends AbstractAccTest {
|
|||
|
||||
assertThat(readTask.getPriority()).isEqualTo(99);
|
||||
|
||||
Instant expDue = converter.addWorkingDaysToInstant(readTask.getPlanned(), Duration.ofDays(1));
|
||||
Instant expDue =
|
||||
workingTimeCalculator.addWorkingTime(readTask.getPlanned(), Duration.ofDays(1));
|
||||
|
||||
assertThat(readTask.getDue()).isEqualTo(expDue);
|
||||
}
|
||||
|
|
|
@ -19,4 +19,8 @@ taskana.german.holidays.corpus-christi.enabled=false
|
|||
taskana.history.deletion.on.task.deletion.enabled=true
|
||||
taskana.validation.allowTimestampServiceLevelMismatch=false
|
||||
taskana.query.includeLongName=false
|
||||
|
||||
taskana.workingtime.schedule.MONDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.TUESDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
||||
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
||||
|
|
|
@ -22,7 +22,7 @@ import pro.taskana.common.api.ConfigurationService;
|
|||
import pro.taskana.common.api.JobService;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.TaskanaEngine.ConnectionManagementMode;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.security.CurrentUserContext;
|
||||
import pro.taskana.common.internal.ConfigurationMapper;
|
||||
import pro.taskana.common.internal.ConfigurationServiceImpl;
|
||||
|
@ -32,6 +32,7 @@ import pro.taskana.common.internal.TaskanaEngineImpl;
|
|||
import pro.taskana.common.internal.security.CurrentUserContextImpl;
|
||||
import pro.taskana.common.internal.util.ReflectionUtil;
|
||||
import pro.taskana.common.internal.util.SpiLoader;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.monitor.api.MonitorService;
|
||||
import pro.taskana.monitor.internal.MonitorServiceImpl;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
|
@ -106,8 +107,7 @@ public class TaskanaInitializationExtension implements TestInstancePostProcessor
|
|||
throw new JUnitException("Expected dataSource to be defined in store, but it's not.");
|
||||
}
|
||||
|
||||
return new TaskanaConfiguration.Builder(dataSource, false, schemaName)
|
||||
.initTaskanaProperties();
|
||||
return new TaskanaConfiguration.Builder(dataSource, false, schemaName).initTaskanaProperties();
|
||||
}
|
||||
|
||||
private static Map<Class<?>, Object> generateTaskanaEntityMap(TaskanaEngine taskanaEngine)
|
||||
|
@ -122,6 +122,7 @@ public class TaskanaInitializationExtension implements TestInstancePostProcessor
|
|||
CurrentUserContext currentUserContext = taskanaEngine.getCurrentUserContext();
|
||||
UserService userService = taskanaEngine.getUserService();
|
||||
SqlSession sqlSession = taskanaEngineProxy.getSqlSession();
|
||||
WorkingTimeCalculator workingTimeCalculator = taskanaEngine.getWorkingTimeCalculator();
|
||||
return Map.ofEntries(
|
||||
Map.entry(TaskanaConfiguration.class, taskanaEngine.getConfiguration()),
|
||||
Map.entry(TaskanaEngineImpl.class, taskanaEngine),
|
||||
|
@ -141,7 +142,8 @@ public class TaskanaInitializationExtension implements TestInstancePostProcessor
|
|||
Map.entry(JobServiceImpl.class, jobService),
|
||||
Map.entry(CurrentUserContext.class, currentUserContext),
|
||||
Map.entry(CurrentUserContextImpl.class, currentUserContext),
|
||||
Map.entry(WorkingDaysToDaysConverter.class, taskanaEngine.getWorkingDaysToDaysConverter()),
|
||||
Map.entry(WorkingTimeCalculator.class, workingTimeCalculator),
|
||||
Map.entry(WorkingTimeCalculatorImpl.class, workingTimeCalculator),
|
||||
Map.entry(ConfigurationMapper.class, sqlSession.getMapper(ConfigurationMapper.class)),
|
||||
Map.entry(UserService.class, userService),
|
||||
Map.entry(UserServiceImpl.class, userService));
|
||||
|
|
|
@ -10,7 +10,7 @@ import pro.taskana.classification.internal.ClassificationServiceImpl;
|
|||
import pro.taskana.common.api.ConfigurationService;
|
||||
import pro.taskana.common.api.JobService;
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.api.WorkingTimeCalculator;
|
||||
import pro.taskana.common.api.security.CurrentUserContext;
|
||||
import pro.taskana.common.internal.ConfigurationMapper;
|
||||
import pro.taskana.common.internal.ConfigurationServiceImpl;
|
||||
|
@ -18,6 +18,7 @@ import pro.taskana.common.internal.InternalTaskanaEngine;
|
|||
import pro.taskana.common.internal.JobServiceImpl;
|
||||
import pro.taskana.common.internal.TaskanaEngineImpl;
|
||||
import pro.taskana.common.internal.security.CurrentUserContextImpl;
|
||||
import pro.taskana.common.internal.workingtime.WorkingTimeCalculatorImpl;
|
||||
import pro.taskana.monitor.api.MonitorService;
|
||||
import pro.taskana.monitor.internal.MonitorServiceImpl;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
|
@ -48,7 +49,8 @@ class TaskanaDependencyInjectionExtensionTest {
|
|||
@TaskanaInject JobServiceImpl jobServiceImpl;
|
||||
@TaskanaInject ConfigurationService configurationService;
|
||||
@TaskanaInject ConfigurationServiceImpl configurationServiceImpl;
|
||||
@TaskanaInject WorkingDaysToDaysConverter workingDaysToDaysConverter;
|
||||
@TaskanaInject WorkingTimeCalculator workingTimeCalculator;
|
||||
@TaskanaInject WorkingTimeCalculatorImpl workingTimeCalculatorImpl;
|
||||
@TaskanaInject CurrentUserContext currentUserContext;
|
||||
@TaskanaInject CurrentUserContextImpl currentUserContextImpl;
|
||||
@TaskanaInject ConfigurationMapper configurationMapper;
|
||||
|
@ -194,9 +196,21 @@ class TaskanaDependencyInjectionExtensionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void should_InjectWorkingDaysToDaysConverter_When_FieldIsAnnotatedOrDeclaredAsParameter(
|
||||
WorkingDaysToDaysConverter workingDaysToDaysConverter) {
|
||||
assertThat(workingDaysToDaysConverter).isSameAs(this.workingDaysToDaysConverter).isNotNull();
|
||||
void should_InjectWorkingTimeCalculator_When_FieldIsAnnotatedOrDeclaredAsParameter(
|
||||
WorkingTimeCalculator workingTimeCalculator) {
|
||||
assertThat(workingTimeCalculator)
|
||||
.isSameAs(this.workingTimeCalculator)
|
||||
.isSameAs(this.workingTimeCalculatorImpl)
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_InjectWorkingTimeCalculatorImpl_When_FieldIsAnnotatedOrDeclaredAsParameter(
|
||||
WorkingTimeCalculatorImpl workingTimeCalculatorImpl) {
|
||||
assertThat(workingTimeCalculatorImpl)
|
||||
.isSameAs(this.workingTimeCalculatorImpl)
|
||||
.isSameAs(this.workingTimeCalculator)
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -5,7 +5,6 @@ import org.junit.jupiter.api.BeforeAll;
|
|||
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.common.api.TaskanaEngine.ConnectionManagementMode;
|
||||
import pro.taskana.common.api.WorkingDaysToDaysConverter;
|
||||
import pro.taskana.common.internal.configuration.DbSchemaCreator;
|
||||
import pro.taskana.common.test.config.DataSourceGenerator;
|
||||
import pro.taskana.sampledata.SampleDataGenerator;
|
||||
|
@ -16,7 +15,6 @@ public abstract class AbstractAccTest {
|
|||
|
||||
protected static TaskanaConfiguration taskanaEngineConfiguration;
|
||||
protected static TaskanaEngine taskanaEngine;
|
||||
protected static WorkingDaysToDaysConverter converter;
|
||||
|
||||
@BeforeAll
|
||||
protected static void setupTest() throws Exception {
|
||||
|
@ -45,7 +43,6 @@ public abstract class AbstractAccTest {
|
|||
taskanaEngine =
|
||||
TaskanaEngine.buildTaskanaEngine(
|
||||
taskanaEngineConfiguration, ConnectionManagementMode.AUTOCOMMIT);
|
||||
converter = taskanaEngine.getWorkingDaysToDaysConverter();
|
||||
}
|
||||
|
||||
protected ObjectReference createObjectReference(
|
||||
|
|
Loading…
Reference in New Issue