diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 2c8a530..5e84168 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,7 @@ - https://github.com/ObeoNetwork/pepper/issues/42[#42] Change behavior for start and end dates of a work package - https://github.com/ObeoNetwork/pepper/issues/52[#52] Change task creation in Gantt from creating a sibling task to a child task. - https://github.com/ObeoNetwork/pepper/issues/59[#59] Add concept of unavailability Period on Resource. +- https://github.com/ObeoNetwork/pepper/issues/89[#89] Make duration (AbstractTask and Workpackage) computed from non working days. For now, it corresponds to week-end and french public holidays 2026. === Bug fixes diff --git a/backend/pepper-domain-services/pom.xml b/backend/pepper-domain-services/pom.xml index 6d49ba3..35f204a 100644 --- a/backend/pepper-domain-services/pom.xml +++ b/backend/pepper-domain-services/pom.xml @@ -39,5 +39,10 @@ pepper-mm ${project.version} + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/backend/pepper-domain-services/src/main/java/pepper/domain/services/NonWorkingDaysService.java b/backend/pepper-domain-services/src/main/java/pepper/domain/services/NonWorkingDaysService.java new file mode 100644 index 0000000..b596bfb --- /dev/null +++ b/backend/pepper-domain-services/src/main/java/pepper/domain/services/NonWorkingDaysService.java @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +package pepper.domain.services; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * Service that manages the non working days. + * @author lfasani + */ +public class NonWorkingDaysService { + + /** + * National public holidays in metropolitan France for 2026. + */ + private static final List FRENCH_NON_WORKING_DAYS_2026 = List.of( + LocalDate.of(2026, 1, 1), + LocalDate.of(2026, 4, 6), + LocalDate.of(2026, 5, 1), + LocalDate.of(2026, 5, 8), + LocalDate.of(2026, 5, 14), + LocalDate.of(2026, 5, 25), + LocalDate.of(2026, 7, 14), + LocalDate.of(2026, 8, 15), + LocalDate.of(2026, 11, 1), + LocalDate.of(2026, 11, 11), + LocalDate.of(2026, 12, 25)); + + private static final List NON_WORKING_DAYS_IN_WEEK = List.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); + + /** + * Returns the duration bounded by {@code startTime} and {@code endTime}, including the working-time + * portions of both boundary days. Any portion that falls on a Saturday, Sunday, or configured fixed + * non-working day is excluded. + * + * @param startTime + * the start of the interval + * @param endTime + * the end of the interval + * @return the duration spent on working days rounded to the closest hour, or {@link Duration#ZERO} for a null or empty interval + */ + public Duration getDuration(Instant startTime, Instant endTime) { + if (startTime == null || endTime == null || !endTime.isAfter(startTime)) { + return Duration.ZERO; + } + + Duration duration = Duration.ZERO; + Instant currentTime = startTime; + while (currentTime.isBefore(endTime)) { + LocalDate currentDate = currentTime.atZone(ZoneOffset.UTC).toLocalDate(); + Instant nextDayStart = currentDate.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant(); + Instant intervalEnd = endTime.isBefore(nextDayStart) ? endTime : nextDayStart; + if (!this.isNonWorkingDay(currentDate)) { + duration = duration.plus(Duration.between(currentTime, intervalEnd)); + } + currentTime = intervalEnd; + } + return this.roundToNearestHalfDay(duration); + } + + /** + * Returns the duration of the working days from {@code startDate} (inclusive) to + * {@code endDate} (exclusive). Therefore, equal dates produce {@link Duration#ZERO}, while + * consecutive working dates produce a duration of one day. + * + * @param startDate + * the start boundary + * @param endDate + * the end boundary + * @return the duration of the working days in the interval, or {@link Duration#ZERO} for a null, + * empty, or reversed interval + */ + public Duration getDuration(LocalDate startDate, LocalDate endDate) { + if (startDate == null || endDate == null || !endDate.isAfter(startDate)) { + return Duration.ZERO; + } + + Duration duration = Duration.ZERO; + LocalDate currentDate = startDate; + while (currentDate.isBefore(endDate)) { + if (!this.isNonWorkingDay(currentDate)) { + duration = duration.plusDays(1); + } + currentDate = currentDate.plusDays(1); + } + return duration; + } + + /** + * Returns the end time reached after the specified number of working hours from {@code startTime}. + * Non-working days in week and configured fixed non-working days do not consume any duration. + * + * @param startTime + * the non-null start of the interval + * @param durationInHours + * the number of working hours to add + * @return the resulting end time, or {@code null} when {@code startTime} is null + */ + public Instant getEndTime(Instant startTime, int durationInHours) { + Duration remainingDuration = Duration.ofHours(durationInHours); + Instant currentEndTime = startTime; + while (!remainingDuration.isZero()) { + LocalDate currentDate = currentEndTime.atZone(ZoneOffset.UTC).toLocalDate(); + Instant nextDayStart = currentDate.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant(); + if (this.isNonWorkingDay(currentDate)) { + currentEndTime = nextDayStart; + } else { + Duration availableDuration = Duration.between(currentEndTime, nextDayStart); + Duration consumedDuration = remainingDuration.compareTo(availableDuration) < 0 + ? remainingDuration + : availableDuration; + currentEndTime = currentEndTime.plus(consumedDuration); + remainingDuration = remainingDuration.minus(consumedDuration); + } + } + return currentEndTime; + } + + /** + * Returns the supplied instant when it is on a working day. Otherwise, moves forward in + * half-day steps through the non-working period and returns the instant half a day into the + * next working day. + * + * @param instant + * the non-null instant to evaluate + * @return the supplied instant or the next valid end time + */ + public Instant getNextEndTime(Instant instant) { + if (instant == null) { + return null; + } + Instant nextEndTime = instant; + LocalDate date = instant.atZone(ZoneOffset.UTC).toLocalDate(); + if (this.isNonWorkingDay(date)) { + nextEndTime = instant.plus(6, ChronoUnit.HOURS).truncatedTo(ChronoUnit.HALF_DAYS); + while (this.isNonWorkingDay(nextEndTime.atZone(ZoneOffset.UTC).toLocalDate())) { + nextEndTime = nextEndTime.plus(1, ChronoUnit.HALF_DAYS); + } + nextEndTime = nextEndTime.plus(1, ChronoUnit.HALF_DAYS); + } + return nextEndTime; + } + + /** + * Returns the supplied instant when it is on a working day. Otherwise, moves backward in + * half-day steps through the non-working period and returns the instant half a day before that + * period. + * + * @param instant + * the non-null instant to evaluate + * @return the supplied instant or the previous valid start time + */ + public Instant getPreviousStartTime(Instant instant) { + if (instant == null) { + return null; + } + Instant previousStartTime = instant; + LocalDate date = instant.atZone(ZoneOffset.UTC).toLocalDate(); + if (this.isNonWorkingDay(date)) { + previousStartTime = instant.truncatedTo(ChronoUnit.HALF_DAYS); + while (this.isNonWorkingDay(previousStartTime.atZone(ZoneOffset.UTC).toLocalDate())) { + previousStartTime = previousStartTime.minus(1, ChronoUnit.HALF_DAYS); + } + } + return previousStartTime; + } + + /** + * Returns the supplied date when it is a working day. Otherwise, moves backward one day at a + * time through the non-working period and returns the preceding working date. + * + * @param startDate + * the non-null date to evaluate + * @return the supplied date or the previous valid start date + */ + public LocalDate getPreviousStartDate(LocalDate startDate) { + if (startDate == null) { + return null; + } + LocalDate previousStartDate = startDate; + while (this.isNonWorkingDay(previousStartDate)) { + previousStartDate = previousStartDate.minusDays(1); + } + return previousStartDate; + } + + /** + * Returns the supplied exclusive end date when the preceding, included date is a working day. + * Otherwise, moves the end boundary forward one day at a time until it follows a working day. + * The end date itself may be a non-working day because it is excluded from the interval. + * + * @param endDate + * the non-null exclusive end date to evaluate + * @return the supplied date or the next valid exclusive end date + */ + public LocalDate getNextEndDate(LocalDate endDate) { + if (endDate == null) { + return null; + } + LocalDate nextEndDate = endDate; + while (this.isNonWorkingDay(nextEndDate.minusDays(1))) { + nextEndDate = nextEndDate.plusDays(1); + } + return nextEndDate; + } + + /** + * Returns the start time reached after moving backward by the specified number of working hours + * from {@code endTime}. Non-working days in week and configured fixed non-working days do not + * consume any duration. Days are evaluated in UTC. + * + * @param endTime + * the non-null end of the interval + * @param durationInHours + * the number of working hours to subtract + * @return the resulting start time + */ + public Instant getStartTime(Instant endTime, int durationInHours) { + Duration remainingDuration = Duration.ofHours(durationInHours); + Instant currentStartTime = endTime; + + while (!remainingDuration.isZero()) { + LocalDate currentDate = currentStartTime.atZone(ZoneOffset.UTC).toLocalDate(); + Instant currentDayStart = currentDate.atStartOfDay(ZoneOffset.UTC).toInstant(); + + // Midnight is the end of the previous day when moving backward. + if (currentStartTime.equals(currentDayStart)) { + currentDate = currentDate.minusDays(1); + currentDayStart = currentDate.atStartOfDay(ZoneOffset.UTC).toInstant(); + } + + if (this.isNonWorkingDay(currentDate)) { + currentStartTime = currentDayStart; + } else { + Duration availableDuration = Duration.between(currentDayStart, currentStartTime); + Duration consumedDuration = remainingDuration.compareTo(availableDuration) < 0 + ? remainingDuration + : availableDuration; + currentStartTime = currentStartTime.minus(consumedDuration); + remainingDuration = remainingDuration.minus(consumedDuration); + } + } + return currentStartTime; + } + + /** + * Returns the start date reached after moving backward by the specified number of working days + * from {@code endDate}. The end date is excluded. Non-working days in week and configured fixed + * non-working days do not consume any duration. + * + * @param endDate + * the non-null end date + * @param durationInDays + * the number of working days to subtract + * @return the resulting start date + */ + public LocalDate getStartDate(LocalDate endDate, int durationInDays) { + int remainingDays = durationInDays; + LocalDate currentStartDate = endDate; + while (remainingDays > 0) { + currentStartDate = currentStartDate.minusDays(1); + if (!this.isNonWorkingDay(currentStartDate)) { + remainingDays--; + } + } + return currentStartDate; + } + + /** + * Returns the exclusive end date reached after moving forward by the specified number of working + * days from {@code startDate}. The start date is included. Non-working days in week and configured + * fixed non-working days do not consume any duration. + * + * @param startDate + * the non-null start date + * @param durationInDays + * the number of working days to add + * @return the resulting exclusive end date + */ + public LocalDate getEndDate(LocalDate startDate, int durationInDays) { + int remainingDays = durationInDays; + LocalDate currentEndDate = startDate; + while (remainingDays > 0) { + if (!this.isNonWorkingDay(currentEndDate)) { + remainingDays--; + } + currentEndDate = currentEndDate.plusDays(1); + } + return currentEndDate; + } + + private boolean isNonWorkingDay(LocalDate date) { + return NON_WORKING_DAYS_IN_WEEK.contains(date.getDayOfWeek()) || FRENCH_NON_WORKING_DAYS_2026.contains(date); + } + + public Duration roundToNearestHalfDay(Duration duration) { + return duration.isNegative() + ? duration.minusHours(6).truncatedTo(ChronoUnit.HALF_DAYS) + : duration.plusMinutes(6).truncatedTo(ChronoUnit.HALF_DAYS); + } +} diff --git a/backend/pepper-domain-services/src/main/java/pepper/domain/services/TaskComputationService.java b/backend/pepper-domain-services/src/main/java/pepper/domain/services/TaskComputationService.java index f0bf639..118caa5 100644 --- a/backend/pepper-domain-services/src/main/java/pepper/domain/services/TaskComputationService.java +++ b/backend/pepper-domain-services/src/main/java/pepper/domain/services/TaskComputationService.java @@ -12,8 +12,10 @@ *******************************************************************************/ package pepper.domain.services; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Optional; @@ -21,7 +23,9 @@ import org.springframework.stereotype.Service; import pepper.peppermm.AbstractTask; +import pepper.peppermm.DependencyRelatedObject; import pepper.peppermm.PepperFactory; +import pepper.peppermm.StartOrEnd; import pepper.peppermm.Task; import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; @@ -32,68 +36,76 @@ */ @Service public class TaskComputationService { + private final NonWorkingDaysService nonWorkingDaysService = new NonWorkingDaysService(); + private final ZoneId localZone = ZoneId.systemDefault(); + + /** + * Update the newStartTime and potentially duration or endTime according to the calculationOption. + * It also rounds newStartTime and shifts it sooner if included in a non-working day period. + */ public void updateStartTime(AbstractTask abstractTask, Instant newStartTime) { TaskTimeBoundariesConstraint calculationOption = abstractTask.getCalculationOption(); - if (TaskTimeBoundariesConstraint.END_DURATION.equals(calculationOption)) { - return; - } - abstractTask.setStartTime(newStartTime); - - Instant currentEndTime = abstractTask.getEndTime(); - int currentDuration = abstractTask.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END)) { - if (currentEndTime != null && newStartTime != null) { - int newDuration = (int) ChronoUnit.HOURS.between(newStartTime, currentEndTime); - if (ChronoUnit.MINUTES.between(newStartTime, currentEndTime) % 60 != 0) { - newDuration += 1; + if (!TaskTimeBoundariesConstraint.END_DURATION.equals(calculationOption) || this.hasDependency(abstractTask, StartOrEnd.START)) { + Instant roundedNewStartTime = this.roundToNearestHalfDay(newStartTime); + Instant previousStartTime = nonWorkingDaysService.getPreviousStartTime(roundedNewStartTime); + abstractTask.setStartTime(this.convertAccordingToTimeZone(previousStartTime)); + + Instant currentEndTime = this.roundToNearestHalfDay(abstractTask.getEndTime()); + int currentDuration = abstractTask.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END) || this.hasDependency(abstractTask, StartOrEnd.END)) { + if (currentEndTime != null && previousStartTime != null) { + long hourDuration = nonWorkingDaysService.getDuration(previousStartTime, currentEndTime).toHours(); + abstractTask.setDuration((int) hourDuration); } - abstractTask.setDuration(newDuration); + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousStartTime != null) { + Instant newEndTime = nonWorkingDaysService.getEndTime(previousStartTime, currentDuration).minus(1, ChronoUnit.MINUTES); + abstractTask.setEndTime(this.convertAccordingToTimeZone(newEndTime)); } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && newStartTime != null) { - Instant newEndTime = newStartTime.plus(currentDuration, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES); - abstractTask.setEndTime(newEndTime); } } + /** + * Update the endTime and potentially duration or startTime according to the calculationOption. + * It also rounds newEndTime and shifts it later if included in a non-working day period. + */ public void updateEndTime(AbstractTask abstractTask, Instant newEndTime) { TaskTimeBoundariesConstraint calculationOption = abstractTask.getCalculationOption(); - if (TaskTimeBoundariesConstraint.START_DURATION.equals(calculationOption)) { - return; - } - abstractTask.setEndTime(newEndTime); - - Instant currentStartTime = abstractTask.getStartTime(); - int currentDuration = abstractTask.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END)) { - if (newEndTime != null && currentStartTime != null) { - int newDuration = (int) ChronoUnit.HOURS.between(currentStartTime, newEndTime); - if (ChronoUnit.MINUTES.between(currentStartTime, newEndTime) % 60 != 0) { - newDuration += 1; + if (!TaskTimeBoundariesConstraint.START_DURATION.equals(calculationOption) || this.hasDependency(abstractTask, StartOrEnd.END)) { + Instant roundedNewEndTime = this.roundToNearestHalfDay(newEndTime); + Instant nextEndTime = nonWorkingDaysService.getNextEndTime(roundedNewEndTime); + abstractTask.setEndTime(this.convertAccordingToTimeZone(nextEndTime).minus(1, ChronoUnit.MINUTES)); + + Instant currentStartTime = this.roundToNearestHalfDay(abstractTask.getStartTime()); + int currentDuration = abstractTask.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END) || this.hasDependency(abstractTask, StartOrEnd.START)) { + if (nextEndTime != null && currentStartTime != null) { + long hourDuration = nonWorkingDaysService.getDuration(currentStartTime, nextEndTime).toHours(); + abstractTask.setDuration((int) hourDuration); } - abstractTask.setDuration(newDuration); + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextEndTime != null) { + Instant newStartTime = nonWorkingDaysService.getStartTime(nextEndTime, currentDuration); //.plus(1, ChronoUnit.MINUTES); + abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && newEndTime != null) { - Instant newStartTime = newEndTime.minus(currentDuration, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES); - abstractTask.setStartTime(newStartTime); } } public void updateDuration(AbstractTask abstractTask, int newDuration) { + int newDurationRouned = this.roundToNearestHalfDay(newDuration); TaskTimeBoundariesConstraint calculationOption = abstractTask.getCalculationOption(); if (TaskTimeBoundariesConstraint.START_END.equals(calculationOption)) { return; } - abstractTask.setDuration(newDuration); - - Instant currentStartTime = abstractTask.getStartTime(); - Instant currentEndTime = abstractTask.getEndTime(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION)) { - Instant newEndTime = currentStartTime.plus(newDuration, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES); - abstractTask.setEndTime(newEndTime); - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION)) { - Instant newStartTime = currentEndTime.minus(newDuration, ChronoUnit.HOURS).plus(1, ChronoUnit.MINUTES); - abstractTask.setStartTime(newStartTime); + abstractTask.setDuration(newDurationRouned); + + Instant currentStartTime = this.roundToNearestHalfDay(abstractTask.getStartTime()); + Instant currentEndTime = this.roundToNearestHalfDay(abstractTask.getEndTime()); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && currentStartTime != null) { + Instant newEndTime = nonWorkingDaysService.getEndTime(currentStartTime, newDurationRouned).minus(1, ChronoUnit.MINUTES); + abstractTask.setEndTime(this.convertAccordingToTimeZone(newEndTime)); + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && currentEndTime != null) { + Instant newStartTime = nonWorkingDaysService.getStartTime(currentEndTime, newDurationRouned); //.plus(1, ChronoUnit.MINUTES); + abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); } } @@ -107,11 +119,11 @@ public Task createNewTask(Workpackage workpackage, String name) { Task lastTask = optionalTask.get(); if (lastTask.getEndTime().equals(lastTask.getStartTime())) { // If the last task is a Milestone - updateStartTime(task, lastTask.getEndTime()); - updateEndTime(task, lastTask.getEndTime()); + this.updateStartTime(task, lastTask.getEndTime()); + this.updateEndTime(task, lastTask.getEndTime()); } else { - updateStartTime(task, lastTask.getEndTime().plus(1, ChronoUnit.MINUTES)); - updateEndTime(task, Instant.ofEpochSecond(2 * lastTask.getEndTime().getEpochSecond() - lastTask.getStartTime().getEpochSecond()).plus(1, ChronoUnit.MINUTES)); + this.updateStartTime(task, lastTask.getEndTime().plus(1, ChronoUnit.MINUTES)); + this.updateEndTime(task, Instant.ofEpochSecond(2 * lastTask.getEndTime().getEpochSecond() - lastTask.getStartTime().getEpochSecond()).plus(1, ChronoUnit.MINUTES)); } } else { if (workpackage.getEndDate() != null && workpackage.getStartDate() != null) { @@ -121,8 +133,8 @@ public Task createNewTask(Workpackage workpackage, String name) { String endTime = workpackage.getEndDate().toString() + "T23:59:00.00" + zone; Instant startInstant = Instant.parse(startTime); Instant endInstant = Instant.parse(endTime); - updateStartTime(task, startInstant); - updateEndTime(task, endInstant); + this.updateStartTime(task, startInstant); + this.updateEndTime(task, endInstant); } } return task; @@ -152,4 +164,35 @@ public Task createNewTask(AbstractTask abstractTask, String name) { } return task; } + + private int roundToNearestHalfDay(int nbHours) { + Duration inputDuration = Duration.ofHours(nbHours); + Duration duration = inputDuration.isNegative() + ? inputDuration.minusHours(6).truncatedTo(ChronoUnit.HALF_DAYS) + : inputDuration.plusMinutes(6).truncatedTo(ChronoUnit.HALF_DAYS); + + return Math.toIntExact(duration.toHours()); + } + + public Instant roundToNearestHalfDay(Instant instant) { + return Optional.ofNullable(instant) + .map(inst -> inst.plus(Duration.ofHours(6)) + .truncatedTo(ChronoUnit.HALF_DAYS)) + .orElse(null); + } + + private Instant convertAccordingToTimeZone(Instant instant) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneOffset offset = systemZone.getRules().getOffset(instant); + + return instant.atZone(systemZone).minusHours(offset.getTotalSeconds() / 3600).toInstant(); + } + + private boolean hasDependency(AbstractTask abstractTask, StartOrEnd boundaryKind) { + if (abstractTask instanceof DependencyRelatedObject dependencyRelatedObject) { + return dependencyRelatedObject.getDependencies().stream() + .anyMatch(dependencyLink -> boundaryKind.equals(dependencyLink.getTargetKind())); + } + return false; + } } diff --git a/backend/pepper-domain-services/src/main/java/pepper/domain/services/WorkpackageComputationService.java b/backend/pepper-domain-services/src/main/java/pepper/domain/services/WorkpackageComputationService.java index 3eb72d4..92aba22 100644 --- a/backend/pepper-domain-services/src/main/java/pepper/domain/services/WorkpackageComputationService.java +++ b/backend/pepper-domain-services/src/main/java/pepper/domain/services/WorkpackageComputationService.java @@ -20,53 +20,56 @@ import pepper.peppermm.PepperFactory; import pepper.peppermm.Project; +import pepper.peppermm.StartOrEnd; import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; /** * Domain service related to Workpackage entity. + * * @author lfasani */ @Service public class WorkpackageComputationService { + private final NonWorkingDaysService nonWorkingDaysService = new NonWorkingDaysService(); public void updateStartDate(Workpackage workpackage, LocalDate newStartDate) { + LocalDate previousNewStartDate = nonWorkingDaysService.getPreviousStartDate(newStartDate); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); - if (TaskTimeBoundariesConstraint.END_DURATION.equals(calculationOption)) { - return; - } - workpackage.setStartDate(newStartDate); + if (!TaskTimeBoundariesConstraint.END_DURATION.equals(calculationOption) || this.hasDependency(workpackage, StartOrEnd.START)) { + workpackage.setStartDate(previousNewStartDate); - LocalDate currentEndDate = workpackage.getEndDate(); - int currentDuration = workpackage.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END)) { - if (currentEndDate != null && newStartDate != null) { - int newDuration = (int) ChronoUnit.DAYS.between(newStartDate, currentEndDate) + 1; - workpackage.setDuration(newDuration); + LocalDate currentEndDate = workpackage.getEndDate(); + int currentDuration = workpackage.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END) || this.hasDependency(workpackage, StartOrEnd.END)) { + if (currentEndDate != null && previousNewStartDate != null) { + long newDuration = nonWorkingDaysService.getDuration(previousNewStartDate, currentEndDate).toDays(); + workpackage.setDuration((int) newDuration); + } + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousNewStartDate != null) { + LocalDate newEndDate = previousNewStartDate.plusDays(currentDuration - 1); + workpackage.setEndDate(newEndDate); } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && newStartDate != null) { - LocalDate newEndDate = newStartDate.plusDays(currentDuration - 1); - workpackage.setEndDate(newEndDate); } } public void updateEndDate(Workpackage workpackage, LocalDate newEndDate) { + LocalDate nextNewEndDate = nonWorkingDaysService.getNextEndDate(newEndDate); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); - if (TaskTimeBoundariesConstraint.START_DURATION.equals(calculationOption)) { - return; - } - workpackage.setEndDate(newEndDate); + if (!TaskTimeBoundariesConstraint.START_DURATION.equals(calculationOption) || this.hasDependency(workpackage, StartOrEnd.END)) { + workpackage.setEndDate(nextNewEndDate); - LocalDate currentStartDate = workpackage.getStartDate(); - int currentDuration = workpackage.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END)) { - if (newEndDate != null && currentStartDate != null) { - int newDuration = (int) ChronoUnit.DAYS.between(currentStartDate, newEndDate) + 1; - workpackage.setDuration(newDuration); + LocalDate currentStartDate = workpackage.getStartDate(); + int currentDuration = workpackage.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_END) || this.hasDependency(workpackage, StartOrEnd.START)) { + if (nextNewEndDate != null && currentStartDate != null) { + long newDuration = nonWorkingDaysService.getDuration(currentStartDate, nextNewEndDate).toDays(); + workpackage.setDuration((int) newDuration); + } + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextNewEndDate != null) { + LocalDate newStartDate = nextNewEndDate.minusDays(currentDuration - 1); + workpackage.setStartDate(newStartDate); } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && newEndDate != null) { - LocalDate newStartDate = newEndDate.minusDays(currentDuration - 1); - workpackage.setStartDate(newStartDate); } } @@ -119,4 +122,9 @@ public Workpackage createNewWorkpackage(Project project, String name) { } return workpackage; } + + private boolean hasDependency(Workpackage workpackage, StartOrEnd boundaryKind) { + return workpackage.getDependencies().stream() + .anyMatch(dependencyLink -> boundaryKind.equals(dependencyLink.getTargetKind())); + } } diff --git a/backend/pepper-domain-services/src/test/java/pepper/domain/services/NonWorkingDaysServiceTests.java b/backend/pepper-domain-services/src/test/java/pepper/domain/services/NonWorkingDaysServiceTests.java new file mode 100644 index 0000000..a171c5e --- /dev/null +++ b/backend/pepper-domain-services/src/test/java/pepper/domain/services/NonWorkingDaysServiceTests.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +package pepper.domain.services; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +/** + * Tests of {@link NonWorkingDaysService}. + * + * @author lfasani + */ +@SuppressWarnings("checkstyle:MultipleStringLiterals") +public class NonWorkingDaysServiceTests { + + @Test + public void getDurationIncludesPartialStartAndEndDays() { + var service = new NonWorkingDaysService(); + + Instant startTime = Instant.parse("2026-07-13T11:00:00Z"); + Instant endTime = Instant.parse("2026-07-15T12:00:00Z"); + + assertThat(service.getDuration(startTime, endTime)).isEqualTo(Duration.ofHours(24)); + + startTime = Instant.parse("2026-07-13T05:00:00Z"); + endTime = Instant.parse("2026-07-15T17:00:00Z"); + + assertThat(service.getDuration(startTime, endTime)).isEqualTo(Duration.ofHours(36)); + } + + @Test + public void getDurationBetweenInstantExcludesWeekendDays() { + var service = new NonWorkingDaysService(); + + // 10 and 11 are in a week-end and 14 is off + Instant startTime = Instant.parse("2026-07-10T12:00:00Z"); + Instant endTime = Instant.parse("2026-07-16T12:00:00Z"); + + assertThat(service.getDuration(startTime, endTime)).isEqualTo(Duration.ofDays(3)); + } + + @Test + public void getDurationBetweenEqualDatesIsZero() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 13); + + assertThat(service.getDuration(date, date)).isEqualTo(Duration.ZERO); + } + + @Test + public void getDurationBetweenConsecutiveWorkingDatesIsOneDay() { + var service = new NonWorkingDaysService(); + LocalDate startDate = LocalDate.of(2026, 7, 10); + + assertThat(service.getDuration(startDate, startDate.plusDays(1))).isEqualTo(Duration.ofDays(1)); + } + + @Test + public void getDurationBetweenDatesExcludesNonWorkingDays() { + var service = new NonWorkingDaysService(); + // 10 and 11 are in a week-end and 14 is off + LocalDate startDate = LocalDate.of(2026, 7, 10); + LocalDate endDate = LocalDate.of(2026, 7, 16); + + assertThat(service.getDuration(startDate, endDate)).isEqualTo(Duration.ofDays(3)); + } + + @Test + public void getEndTimeSkipsWeekendDays() { + var service = new NonWorkingDaysService(); + Instant startTime = Instant.parse("2026-07-31T13:00:00Z"); + + assertThat(service.getEndTime(startTime, 23)).isEqualTo(Instant.parse("2026-08-03T12:00:00Z")); + } + + @Test + public void getEndTimeSkipsNonWorkingDays() { + var service = new NonWorkingDaysService(); + Instant startTime = Instant.parse("2026-07-13T12:00:00Z"); + + assertThat(service.getEndTime(startTime, 24)).isEqualTo(Instant.parse("2026-07-15T12:00:00Z")); + } + + @Test + public void getNextEndTimeKeepsAnInstantOnAWorkingDay() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-07-31T13:00:00Z"); + + assertThat(service.getNextEndTime(instant)).isEqualTo(instant); + } + + @Test + public void getNextEndTimeMovesPastAWeekend() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-08-01T13:00:00Z"); + + assertThat(service.getNextEndTime(instant)).isEqualTo(Instant.parse("2026-08-03T12:00:00Z")); + } + + @Test + public void getNextEndTimeMovesPastANonWorkingDay() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-07-14T09:00:00Z"); + + assertThat(service.getNextEndTime(instant)).isEqualTo(Instant.parse("2026-07-15T12:00:00Z")); + } + + @Test + public void getPreviousStartTimeKeepsAnInstantOnAWorkingDay() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-07-31T13:00:00Z"); + + assertThat(service.getPreviousStartTime(instant)).isEqualTo(instant); + } + + @Test + public void getPreviousStartTimeMovesBeforeAWeekend() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-08-02T18:00:00Z"); + + assertThat(service.getPreviousStartTime(instant)).isEqualTo(Instant.parse("2026-07-31T12:00:00Z")); + } + + @Test + public void getPreviousStartTimeMovesBeforeANonWorkingDay() { + var service = new NonWorkingDaysService(); + Instant instant = Instant.parse("2026-07-14T09:00:00Z"); + + assertThat(service.getPreviousStartTime(instant)).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + } + + @Test + public void getPreviousStartDateKeepsAWorkingDate() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 31); + + assertThat(service.getPreviousStartDate(date)).isEqualTo(date); + } + + @Test + public void getPreviousStartDateMovesBeforeAWeekend() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 8, 2); + + assertThat(service.getPreviousStartDate(date)).isEqualTo(LocalDate.of(2026, 7, 31)); + } + + @Test + public void getPreviousStartDateMovesBeforeANonWorkingDay() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 14); + + assertThat(service.getPreviousStartDate(date)).isEqualTo(LocalDate.of(2026, 7, 13)); + } + + @Test + public void getNextEndDateKeepsAWorkingDate() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 31); + + assertThat(service.getNextEndDate(date)).isEqualTo(date); + } + + @Test + public void getNextEndDateAcceptsAnExcludedWeekendDate() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 8, 1); + + assertThat(service.getNextEndDate(date)).isEqualTo(date); + } + + @Test + public void getNextEndDateAcceptsAnExcludedNonWorkingDate() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 14); + + assertThat(service.getNextEndDate(date)).isEqualTo(date); + } + + @Test + public void getNextEndDateMovesABoundaryFollowingANonWorkingDay() { + var service = new NonWorkingDaysService(); + LocalDate date = LocalDate.of(2026, 7, 15); + + assertThat(service.getNextEndDate(date)).isEqualTo(LocalDate.of(2026, 7, 16)); + } + + @Test + public void getStartTimeSkipsWeekendDays() { + var service = new NonWorkingDaysService(); + Instant endTime = Instant.parse("2026-08-03T13:00:00Z"); + + assertThat(service.getStartTime(endTime, 25)).isEqualTo(Instant.parse("2026-07-31T12:00:00Z")); + } + + @Test + public void getStartTimeSkipsNonWorkingDays() { + var service = new NonWorkingDaysService(); + Instant endTime = Instant.parse("2026-07-15T12:00:00Z"); + + assertThat(service.getStartTime(endTime, 24)).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + } + + @Test + public void getStartDateSkipsWeekendDays() { + var service = new NonWorkingDaysService(); + LocalDate endDate = LocalDate.of(2026, 8, 3); + + assertThat(service.getStartDate(endDate, 1)).isEqualTo(LocalDate.of(2026, 7, 31)); + } + + @Test + public void getStartDateSkipsNonWorkingDays() { + var service = new NonWorkingDaysService(); + LocalDate endDate = LocalDate.of(2026, 7, 15); + + assertThat(service.getStartDate(endDate, 1)).isEqualTo(LocalDate.of(2026, 7, 13)); + } + + @Test + public void getEndDateSkipsWeekendDays() { + var service = new NonWorkingDaysService(); + LocalDate startDate = LocalDate.of(2026, 7, 31); + + assertThat(service.getEndDate(startDate, 2)).isEqualTo(LocalDate.of(2026, 8, 4)); + } + + @Test + public void getEndDateSkipsNonWorkingDays() { + var service = new NonWorkingDaysService(); + LocalDate startDate = LocalDate.of(2026, 7, 13); + + assertThat(service.getEndDate(startDate, 2)).isEqualTo(LocalDate.of(2026, 7, 16)); + } +} diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/PepperMMJavaService.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/PepperMMJavaService.java index 133e2cd..d3a0d51 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/PepperMMJavaService.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/PepperMMJavaService.java @@ -15,7 +15,6 @@ import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; @@ -47,9 +46,9 @@ import pepper.peppermm.TagFolder; import pepper.peppermm.Task; import pepper.peppermm.TaskTag; +import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; - /** * Java Service for the task related views. * @@ -75,7 +74,38 @@ public PepperMMJavaService(IFeedbackMessageService feedbackMessageService, TaskC this.workpackageComputationService = workpackageComputationService; } - @SuppressWarnings("checkstyle:NestedIfDepth") + private static Instant getTaskStartTime(Task task) { + if (task.isComputeStartEndDynamically()) { + return task.getSubTasks().stream() + .map(PepperMMJavaService::getTaskStartTime) + .min(Instant::compareTo) + .orElse(task.getStartTime()); + } + return task.getStartTime(); + } + + private static Instant getTaskEndTime(Task task) { + if (task.isComputeStartEndDynamically()) { + return task.getSubTasks().stream() + .map(PepperMMJavaService::getTaskEndTime) + .max(Instant::compareTo) + .orElse(task.getEndTime()); + } + return task.getEndTime(); + } + + private static Instant getlaterInstant(DependencyLink dep) { + Instant laterInstant = null; + Task source = (Task) dep.getSource(); + if (dep.getSourceKind() == StartOrEnd.END) { + laterInstant = getTaskEndTime(source).plus(dep.getDuration(), ChronoUnit.HOURS); + } else if (dep.getSourceKind() == StartOrEnd.START) { + laterInstant = getTaskStartTime(source).plus(dep.getDuration(), ChronoUnit.HOURS); + } + return laterInstant; + } + + @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MethodLength" }) public void editTask(EObject eObject, String name, String description, Instant startTime, Instant endTime, Integer progress, boolean keepDuration) { if (eObject instanceof Task task) { if (name != null) { @@ -85,49 +115,13 @@ public void editTask(EObject eObject, String name, String description, Instant s task.setDescription(description); } if (endTime != null && startTime != null) { - //set the new instants to xx:00 for the start time and xx:59 for the end time - ZonedDateTime zonedDateTimeStart = startTime.atZone(zone); - Instant newStartTime; - if (zonedDateTimeStart.getHour() < 6) { - newStartTime = zonedDateTimeStart.withHour(0) - .withMinute(0) - .toInstant(); - } else if (zonedDateTimeStart.getHour() <= 18) { - newStartTime = zonedDateTimeStart.withHour(12) - .withMinute(0) - .toInstant(); - } else { - newStartTime = zonedDateTimeStart.withHour(0) - .withMinute(0) - .toInstant() - .plus(1, ChronoUnit.DAYS); - } - - ZonedDateTime zonedDateTimeEnd = endTime.atZone(zone); - Instant newEndTime; - //if the task becomes a milestone - if (endTime.equals(startTime)) { - newEndTime = newStartTime; - } else if (zonedDateTimeEnd.getHour() < 6) { - newEndTime = zonedDateTimeEnd.withHour(23) - .withMinute(59) - .toInstant() - .minus(1, ChronoUnit.DAYS); - } - else if (zonedDateTimeEnd.getHour() <= 18) { - newEndTime = zonedDateTimeEnd.withHour(11) - .withMinute(59) - .toInstant(); - } else { - newEndTime = zonedDateTimeEnd.withHour(23) - .withMinute(59) - .toInstant(); - } - - long differenceEnd = task.getEndTime().getEpochSecond() - newEndTime.getEpochSecond(); - long differenceStart = task.getStartTime().getEpochSecond() - newStartTime.getEpochSecond(); - boolean taskShifted = differenceEnd == differenceStart; + Instant newStartTime = taskComputationService.roundToNearestHalfDay(startTime); + Instant newEndTime = taskComputationService.roundToNearestHalfDay(endTime); + long differenceStart = newStartTime.getEpochSecond() - taskComputationService.roundToNearestHalfDay(task.getStartTime()).getEpochSecond(); + long differenceEnd = newEndTime.getEpochSecond() - taskComputationService.roundToNearestHalfDay(task.getEndTime()).getEpochSecond(); + boolean taskShifted = differenceStart != 0 && differenceEnd != 0; List dependencies = task.getDependencies(); + // Nothing is done when moving a task constrained by dependencies if (dependencies.isEmpty() || !taskShifted) { boolean startTimeControlledByDependency = dependencies.stream() @@ -136,23 +130,57 @@ else if (zonedDateTimeEnd.getHour() <= 18) { boolean endTimeControlledByDependency = dependencies.stream() .anyMatch(dep -> dep.getTargetKind() == StartOrEnd.END); + TaskTimeBoundariesConstraint calculationOption = task.getCalculationOption(); + if (startTimeControlledByDependency && !endTimeControlledByDependency) { - this.setTaskDuration(task, newStartTime, newEndTime); - newEndTime = newEndTime.plus(differenceStart, ChronoUnit.SECONDS); - taskComputationService.updateEndTime(task, newEndTime); + if (differenceStart != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() - Math.round((float) differenceStart / 3600)); + } + } + if (differenceEnd != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() + Math.round((float) differenceEnd / 3600)); + } else { + taskComputationService.updateEndTime(task, newEndTime); + } + } } else if (!startTimeControlledByDependency && endTimeControlledByDependency) { - this.setTaskDuration(task, newStartTime, newEndTime); - newStartTime = newStartTime.plus(differenceEnd, ChronoUnit.SECONDS); - taskComputationService.updateStartTime(task, newStartTime); + if (differenceStart != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() - Math.round((float) differenceStart / 3600)); + } else { + taskComputationService.updateStartTime(task, newStartTime); + } + } + if (differenceEnd != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() + Math.round((float) differenceEnd / 3600)); + } + } } else if (!startTimeControlledByDependency && !endTimeControlledByDependency) { - if (!keepDuration) { - this.setTaskDuration(task, newStartTime, newEndTime); + if (taskShifted) { + taskComputationService.updateStartTime(task, newStartTime); + taskComputationService.updateEndTime(task, newEndTime); + } else { + if (differenceStart != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() - Math.round((float) differenceStart / 3600)); + } else { + taskComputationService.updateStartTime(task, newStartTime); + } + } + if (differenceEnd != 0) { + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION)) { + taskComputationService.updateDuration(task, task.getDuration() + Math.round((float) differenceEnd / 3600)); + } else { + taskComputationService.updateEndTime(task, newEndTime); + } + } } - taskComputationService.updateStartTime(task, newStartTime); - taskComputationService.updateEndTime(task, newEndTime); } if (!startTimeControlledByDependency || !endTimeControlledByDependency) { - followMoveDependency(task); + this.followMoveDependency(task); } } } @@ -168,9 +196,9 @@ private void setTaskDuration(Task task, Instant start, Instant end) { } public void createTask(EObject context) { - Task task = PepperFactory.eINSTANCE.createTask(); - task.setName(NEW_TASK); if (context instanceof AbstractTask abstractTask) { + Task task = PepperFactory.eINSTANCE.createTask(); + task.setName(NEW_TASK); // The new task follows the last sub-task. Optional optionalTask = abstractTask.getSubTasks().stream().reduce((first, second) -> second) .filter(filteredTask -> filteredTask.getEndTime() != null && filteredTask.getStartTime() != null); @@ -183,7 +211,8 @@ public void createTask(EObject context) { taskComputationService.updateEndTime(task, lastTask.getEndTime()); } else { taskComputationService.updateStartTime(task, lastTask.getEndTime().plus(1, ChronoUnit.MINUTES)); - taskComputationService.updateEndTime(task, Instant.ofEpochSecond(2 * lastTask.getEndTime().getEpochSecond() - lastTask.getStartTime().getEpochSecond()).plus(1, ChronoUnit.MINUTES)); + taskComputationService.updateEndTime(task, + Instant.ofEpochSecond(2 * lastTask.getEndTime().getEpochSecond() - lastTask.getStartTime().getEpochSecond()).plus(1, ChronoUnit.MINUTES)); } } else { if (abstractTask.getEndTime() != null && abstractTask.getStartTime() != null) { @@ -202,18 +231,17 @@ public void createTask(EObject context) { this.feedbackMessageService.addFeedbackMessage(new Message("Gantt can not display more than 4 levels of tasks. So the created task is not displayed.", MessageLevel.WARNING)); } } else if (context instanceof Workpackage workpackage) { - long epochSecondStartTime = Instant.now().getEpochSecond(); - taskComputationService.updateStartTime(task, Instant.ofEpochMilli(epochSecondStartTime)); - taskComputationService.updateEndTime(task, Instant.ofEpochMilli(epochSecondStartTime + 3600 * 4)); + Task newTask = taskComputationService.createNewTask(workpackage, NEW_TASK); - workpackage.getOwnedTasks().add(task); + workpackage.getOwnedTasks().add(newTask); } } - /** Delete a given {@link DependencyRelatedObject} and all {@link DependencyLink} related to it. - * Then update all dependency placement of its dependent objects. + /** + * Delete a given {@link DependencyRelatedObject} and all {@link DependencyLink} related to it. Then update all dependency placement of its dependent objects. * - * @param context the object to delete + * @param context + * the object to delete */ public void deleteDependencyRelatedObject(EObject context) { if (context instanceof DependencyRelatedObject source) { @@ -231,19 +259,19 @@ public void deleteDependencyRelatedObject(EObject context) { } if (source instanceof Task task) { for (Task subTask : task.getSubTasks()) { - targetDependencies.addAll(getAllDependencyTargetTask(new LinkedHashSet<>(), subTask)); + targetDependencies.addAll(this.getAllDependencyTargetTask(new LinkedHashSet<>(), subTask)); } } EcoreUtil.delete(source, true); for (DependencyLink sourceDependencyLink : sourceDependencies) { - followMoveDependency(sourceDependencyLink.getSource()); + this.followMoveDependency(sourceDependencyLink.getSource()); } for (DependencyRelatedObject targetDependency : targetDependencies) { List dependencyLinksOfTargetDependencies = targetDependency.getDependencies(); for (DependencyLink dependencyLinksOfTargetDependency : dependencyLinksOfTargetDependencies) { - followMoveDependency(dependencyLinksOfTargetDependency.getSource()); + this.followMoveDependency(dependencyLinksOfTargetDependency.getSource()); } } } @@ -262,18 +290,20 @@ private LinkedHashSet getAllDependencyTargetTask(LinkedHashSet targe } for (Task subTask : task.getSubTasks()) { - getAllDependencyTargetTask(targetTasks, subTask); + this.getAllDependencyTargetTask(targetTasks, subTask); } return targetTasks; } /** - * Deletes the {@link DependencyLink} between the specified source and target - * {@link DependencyRelatedObject}s. + * Deletes the {@link DependencyLink} between the specified source and target {@link DependencyRelatedObject}s. *

* Then the target object's placement is then updated according to its remaining dependencies. - * @param target the dependency target - * @param source the dependency source + * + * @param target + * the dependency target + * @param source + * the dependency source */ public void deleteDependencyLink(EObject target, EObject source) { if (target instanceof DependencyRelatedObject targetObject) { @@ -282,13 +312,12 @@ public void deleteDependencyLink(EObject target, EObject source) { } for (DependencyLink targetDependencyLink : targetObject.getDependencies()) { - followMoveDependency(targetDependencyLink.getSource()); + this.followMoveDependency(targetDependencyLink.getSource()); } } } - - public void createDependencyLink(EObject target, EObject source, org.eclipse.sirius.components.gantt.StartOrEnd sourceStartOrEnd, org.eclipse.sirius.components.gantt.StartOrEnd targetStartOrEnd) { + public void createDependencyLink(EObject source, EObject target, org.eclipse.sirius.components.gantt.StartOrEnd sourceStartOrEnd, org.eclipse.sirius.components.gantt.StartOrEnd targetStartOrEnd) { DependencyLink dependencyLink = PepperFactory.eINSTANCE.createDependencyLink(); if (sourceStartOrEnd.equals(org.eclipse.sirius.components.gantt.StartOrEnd.END)) { dependencyLink.setSourceKind(StartOrEnd.END); @@ -320,8 +349,10 @@ public void createDependencyLink(EObject target, EObject source, org.eclipse.sir /** * Checks if the source task already depends on one of the target task's ancestor tasks. * - * @param sourceObject the {@link DependencyRelatedObject} source - * @param parent the parent {@link Task} of the target + * @param sourceObject + * the {@link DependencyRelatedObject} source + * @param parent + * the parent {@link Task} of the target * @return {@code true} if the source already depends on the specified parent or one of its ancestors; {@code false} otherwise */ private boolean isParentCycle(DependencyRelatedObject sourceObject, Task parent) { @@ -332,7 +363,7 @@ private boolean isParentCycle(DependencyRelatedObject sourceObject, Task parent) } boolean isParentCycle = false; if (parent.eContainer() instanceof Task grandParent) { - isParentCycle = isParentCycle(sourceObject, grandParent); + isParentCycle = this.isParentCycle(sourceObject, grandParent); } return isParentCycle; } @@ -343,7 +374,7 @@ private boolean isCycle(DependencyRelatedObject sourceObject, DependencyRelatedO if (dep.getSource().equals(targetObject)) { isCycle = true; } else if (!isCycle) { - isCycle = isCycle(dep.getSource(), targetObject); + isCycle = this.isCycle(dep.getSource(), targetObject); } } return isCycle; @@ -352,8 +383,10 @@ private boolean isCycle(DependencyRelatedObject sourceObject, DependencyRelatedO /** * Validates a dependency creation request. * - * @param sourceObject the dependency source - * @param targetObject the dependency target + * @param sourceObject + * the dependency source + * @param targetObject + * the dependency target * @return {@code true} if the dependency is invalid because it would create a cycle or duplicate a dependency; {@code false} otherwise */ private boolean isDuplicateOrCycle(DependencyRelatedObject sourceObject, DependencyRelatedObject targetObject) { @@ -361,16 +394,17 @@ private boolean isDuplicateOrCycle(DependencyRelatedObject sourceObject, Depende boolean isParentChildDependency = sourceObject.equals(targetObject.eContainer()) || targetObject.equals(sourceObject.eContainer()); boolean isParentCycle = false; if (targetObject.eContainer() instanceof Task parent) { - isParentCycle = isParentCycle(sourceObject, parent); + isParentCycle = this.isParentCycle(sourceObject, parent); } - boolean isCycle = isCycle(sourceObject, targetObject); + boolean isCycle = this.isCycle(sourceObject, targetObject); if (isParentChildDependency) { this.feedbackMessageService.addFeedbackMessage(new Message("Creating a dependency between a parent task and one of its children is not possible.", MessageLevel.ERROR)); } if (isParentCycle) { - this.feedbackMessageService.addFeedbackMessage(new Message("Creating a dependency when the source task already depends on one of the target task's parent tasks is not possible", MessageLevel.ERROR)); + this.feedbackMessageService.addFeedbackMessage( + new Message("Creating a dependency when the source task already depends on one of the target task's parent tasks is not possible", MessageLevel.ERROR)); } if (isCycle) { this.feedbackMessageService.addFeedbackMessage(new Message("Creating a cyclic dependency is not possible.", MessageLevel.ERROR)); @@ -395,10 +429,10 @@ private boolean isDuplicateOrCycle(DependencyRelatedObject sourceObject, Depende } /** - * Finds all {@link DependencyRelatedObject} instances that depend on the given - * {@link DependencyRelatedObject} and update them according to their dependency relationships. + * Finds all {@link DependencyRelatedObject} instances that depend on the given {@link DependencyRelatedObject} and update them according to their dependency relationships. * - * @param sourceObject the object that has been moved + * @param sourceObject + * the object that has been moved */ public void followMoveDependency(DependencyRelatedObject sourceObject) { List targetTasks = new ArrayList<>(); @@ -418,7 +452,7 @@ public void followMoveDependency(DependencyRelatedObject sourceObject) { } if (sourceObject instanceof Task sourceTask) { this.followTaskMoveDependency(targetTasks, sourceTask); - followMoveDependenciesParent(sourceTask); + this.followMoveDependenciesParent(sourceTask); } if (sourceObject instanceof Workpackage sourceWorkpackage) { this.followWorkpackageMoveDependency(targetWorkpackages, sourceWorkpackage); @@ -428,8 +462,10 @@ public void followMoveDependency(DependencyRelatedObject sourceObject) { /** * Updates the dates of all {@link Workpackage} that depend on the given source {@link Workpackage}. * - * @param targetWorkpackages the dependent workpackages - * @param sourceWorkpackage the workpackage that has been moved + * @param targetWorkpackages + * the dependent workpackages + * @param sourceWorkpackage + * the workpackage that has been moved */ private void followWorkpackageMoveDependency(List targetWorkpackages, Workpackage sourceWorkpackage) { List dependencies = new ArrayList<>(); @@ -441,14 +477,14 @@ private void followWorkpackageMoveDependency(List targetWorkpackage LocalDate laterEnd = null; for (DependencyLink dep : workpackage.getDependencies()) { if (dep.getTargetKind().equals(StartOrEnd.END)) { - LocalDate newLocalDate = getlaterLocalDate(dep); + LocalDate newLocalDate = this.getlaterLocalDate(dep); if (laterEnd == null || laterEnd.isBefore(newLocalDate)) { laterEnd = newLocalDate; winnerEnd = dep; } } if (dep.getTargetKind().equals(StartOrEnd.START)) { - LocalDate newLocalDate = getlaterLocalDate(dep); + LocalDate newLocalDate = this.getlaterLocalDate(dep); if (laterStart == null || laterStart.isBefore(newLocalDate)) { laterStart = newLocalDate; winnerStart = dep; @@ -458,20 +494,19 @@ private void followWorkpackageMoveDependency(List targetWorkpackage for (DependencyLink dep : workpackage.getDependencies()) { if ((dep.equals(winnerStart) && winnerEnd == null) || (dep.equals(winnerEnd) && winnerStart == null)) { Workpackage bestSourceWorkpackage = (Workpackage) dep.getSource(); - setWorkpackageNewDates(workpackage, dep); + this.setWorkpackageNewDates(workpackage, dep); if (bestSourceWorkpackage == sourceWorkpackage) { dependencies.add(workpackage); } } else if (dep.equals(winnerEnd)) { Workpackage bestSourceWorkpackage = (Workpackage) dep.getSource(); - setWorkpackageNewEndDate(workpackage, dep); + this.setWorkpackageNewEndDate(workpackage, dep); if (bestSourceWorkpackage == sourceWorkpackage) { dependencies.add(workpackage); } - } - else if (dep.equals(winnerStart)) { + } else if (dep.equals(winnerStart)) { Workpackage bestSourceWorkpackage = (Workpackage) dep.getSource(); - setWorkpackageNewStartDate(workpackage, dep); + this.setWorkpackageNewStartDate(workpackage, dep); if (bestSourceWorkpackage == sourceWorkpackage) { dependencies.add(workpackage); } @@ -481,7 +516,8 @@ else if (dep.equals(winnerStart)) { if (workpackage.getStartDate().isAfter(workpackage.getEndDate())) { workpackageComputationService.updateDuration(workpackage, 1); workpackageComputationService.updateEndDate(workpackage, workpackage.getStartDate().plusDays(1)); - this.feedbackMessageService.addFeedbackMessage(new Message("Task dependencies overlap : End date has been changed to avoid to have end date before start date.", MessageLevel.WARNING)); + this.feedbackMessageService.addFeedbackMessage( + new Message("Task dependencies overlap : End date has been changed to avoid to have end date before start date.", MessageLevel.WARNING)); } } } @@ -493,8 +529,10 @@ else if (dep.equals(winnerStart)) { /** * Updates the dates of all {@link Task} that depend on the given source {@link Task}. * - * @param targetTasks the dependent workpackages - * @param sourceTask the workpackage that has been moved + * @param targetTasks + * the dependent workpackages + * @param sourceTask + * the workpackage that has been moved */ private void followTaskMoveDependency(List targetTasks, Task sourceTask) { List dependencies = new ArrayList<>(); @@ -524,21 +562,19 @@ private void followTaskMoveDependency(List targetTasks, Task sourceTask) { //if the task is only pointed to one extremity if ((dep.equals(winnerStart) && winnerEnd == null) || (dep.equals(winnerEnd) && winnerStart == null)) { Task bestSourceTask = (Task) dep.getSource(); - setTaskNewDates(task, dep); + this.setTaskNewDates(task, dep); if (bestSourceTask == sourceTask) { dependencies.add(task); } - } - else if (dep.equals(winnerEnd)) { + } else if (dep.equals(winnerEnd)) { Task bestSourceTask = (Task) dep.getSource(); - setTaskNewEndDate(task, dep); + this.setTaskNewEndDate(task, dep); if (bestSourceTask == sourceTask) { dependencies.add(task); } - } - else if (dep.equals(winnerStart)) { + } else if (dep.equals(winnerStart)) { Task bestSourceTask = (Task) dep.getSource(); - setTaskNewStartDate(task, dep); + this.setTaskNewStartDate(task, dep); if (bestSourceTask == sourceTask) { dependencies.add(task); } @@ -547,7 +583,7 @@ else if (dep.equals(winnerStart)) { if (winnerEnd != null && winnerStart != null) { if (task.getEndTime().isBefore(task.getStartTime())) { Instant newEndTime = task.getStartTime().plus(12, ChronoUnit.HOURS); - setTaskDuration(task, task.getStartTime(), newEndTime); + this.setTaskDuration(task, task.getStartTime(), newEndTime); taskComputationService.updateEndTime(task, newEndTime.minus(1, ChronoUnit.MINUTES)); this.feedbackMessageService.addFeedbackMessage(new Message("Task dependencies overlap.", MessageLevel.ERROR)); } @@ -563,75 +599,50 @@ private boolean isMilestone(Task task) { } private int startAdjustmentMinutes(Task sourceTask) { - if (isMilestone(sourceTask)) { + if (this.isMilestone(sourceTask)) { return 0; - } - else { + } else { return 1; } } private int endAdjustmentMinutes(Task sourceTask, Task targetTask) { int adjustment = 0; - if (isMilestone(sourceTask)) { + if (this.isMilestone(sourceTask)) { adjustment--; } - if (isMilestone(targetTask)) { + if (this.isMilestone(targetTask)) { adjustment++; } return adjustment; } - private static Instant getTaskStartTime(Task task) { - if (task.isComputeStartEndDynamically()) { - return task.getSubTasks().stream() - .map(PepperMMJavaService::getTaskStartTime) - .min(Instant::compareTo) - .orElse(task.getStartTime()); - } - return task.getStartTime(); - } - - private static Instant getTaskEndTime(Task task) { - if (task.isComputeStartEndDynamically()) { - return task.getSubTasks().stream() - .map(PepperMMJavaService::getTaskEndTime) - .max(Instant::compareTo) - .orElse(task.getEndTime()); - } - return task.getEndTime(); - } - /** - * Propagates dependency updates through the hierarchy of dynamically computed - * parent {@link Task}s. + * Propagates dependency updates through the hierarchy of dynamically computed parent {@link Task}s. *

- * The parent task hierarchy is traversed recursively and - * {@link #followMoveDependency(DependencyRelatedObject)} is invoked on each - * parent task. + * The parent task hierarchy is traversed recursively and {@link #followMoveDependency(DependencyRelatedObject)} is invoked on each parent task. * - * @param task the task from which dependency updates are propagated + * @param task + * the task from which dependency updates are propagated */ public void followMoveDependenciesParent(Task task) { if (task.eContainer() instanceof Task parentTask) { if (parentTask.isComputeStartEndDynamically()) { - followMoveDependency(parentTask); - followMoveDependenciesParent(parentTask); + this.followMoveDependency(parentTask); + this.followMoveDependenciesParent(parentTask); } } } /** - * Recalculates and updates the start and end dates of the specified target {@link Task} - * according to the given {@link DependencyLink}. + * Recalculates and updates the start and end dates of the specified target {@link Task} according to the given {@link DependencyLink}. *

- * The task duration is preserved during the calculation. Only the start and - * end instants are shifted to satisfy the dependency constraints. + * The task duration is preserved during the calculation. Only the start and end instants are shifted to satisfy the dependency constraints. * - * @param task the target {@link Task} whose start and end dates must be updated - * according to the dependency - * @param dep the {@link DependencyLink} defining the relationship between the source - * and the target tasks, including the dependency type and delay + * @param task + * the target {@link Task} whose start and end dates must be updated according to the dependency + * @param dep + * the {@link DependencyLink} defining the relationship between the source and the target tasks, including the dependency type and delay */ private void setTaskNewDates(Task task, DependencyLink dep) { Task bestSourceTask = (Task) dep.getSource(); @@ -644,29 +655,21 @@ private void setTaskNewDates(Task task, DependencyLink dep) { StartOrEnd targetStartOrEnd = dep.getTargetKind(); if (sourceStartOrEnd == StartOrEnd.END && targetStartOrEnd == StartOrEnd.START) { Instant newTaskStart = sourceEnd.plus(delay, ChronoUnit.HOURS) - .plus(startAdjustmentMinutes(bestSourceTask), ChronoUnit.MINUTES); - Instant newTaskEnd = Instant.ofEpochSecond(newTaskStart.getEpochSecond() + oldTaskEnd.getEpochSecond() - oldTaskStart.getEpochSecond()); - taskComputationService.updateEndTime(task, newTaskEnd); + .plus(this.startAdjustmentMinutes(bestSourceTask), ChronoUnit.MINUTES); taskComputationService.updateStartTime(task, newTaskStart); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.START) { Instant newTaskStart = sourceStart.plus(delay, ChronoUnit.HOURS); - Instant newTaskEnd = Instant.ofEpochSecond(newTaskStart.getEpochSecond() + oldTaskEnd.getEpochSecond() - oldTaskStart.getEpochSecond()); - taskComputationService.updateEndTime(task, newTaskEnd); taskComputationService.updateStartTime(task, newTaskStart); } else if (sourceStartOrEnd == StartOrEnd.END && targetStartOrEnd == StartOrEnd.END) { Instant newTaskEnd = sourceEnd.plus(delay, ChronoUnit.HOURS) - .plus(endAdjustmentMinutes(bestSourceTask, task), ChronoUnit.MINUTES); - Instant newTaskStart = Instant.ofEpochSecond(newTaskEnd.getEpochSecond() - (oldTaskEnd.getEpochSecond() - oldTaskStart.getEpochSecond())); + .plus(this.endAdjustmentMinutes(bestSourceTask, task), ChronoUnit.MINUTES); taskComputationService.updateEndTime(task, newTaskEnd); - taskComputationService.updateStartTime(task, newTaskStart); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.END) { Instant newTaskEnd = sourceStart.plus(delay, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES); - if (isMilestone(task)) { + if (this.isMilestone(task)) { newTaskEnd = newTaskEnd.plus(1, ChronoUnit.MINUTES); } - Instant newTaskStart = Instant.ofEpochSecond(newTaskEnd.getEpochSecond() - (oldTaskEnd.getEpochSecond() - oldTaskStart.getEpochSecond())); taskComputationService.updateEndTime(task, newTaskEnd); - taskComputationService.updateStartTime(task, newTaskStart); } } @@ -682,14 +685,14 @@ private void setTaskNewEndDate(Task task, DependencyLink dep) { Instant newTaskEnd = task.getEndTime(); if (sourceStartOrEnd == StartOrEnd.END) { newTaskEnd = sourceEnd.plus(delay, ChronoUnit.HOURS) - .plus(endAdjustmentMinutes(bestSourceTask, task), ChronoUnit.MINUTES); + .plus(this.endAdjustmentMinutes(bestSourceTask, task), ChronoUnit.MINUTES); } else if (sourceStartOrEnd == StartOrEnd.START) { newTaskEnd = sourceStart.plus(delay, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES); - if (isMilestone(task)) { + if (this.isMilestone(task)) { newTaskEnd = newTaskEnd.plus(1, ChronoUnit.MINUTES); } } - setTaskDuration(task, task.getStartTime(), newTaskEnd); + this.setTaskDuration(task, task.getStartTime(), newTaskEnd); taskComputationService.updateEndTime(task, newTaskEnd); } @@ -705,11 +708,11 @@ private void setTaskNewStartDate(Task task, DependencyLink dep) { Instant newTaskStart = task.getStartTime(); if (sourceStartOrEnd == StartOrEnd.END) { newTaskStart = sourceEnd.plus(delay, ChronoUnit.HOURS) - .plus(startAdjustmentMinutes(bestSourceTask), ChronoUnit.MINUTES); + .plus(this.startAdjustmentMinutes(bestSourceTask), ChronoUnit.MINUTES); } else if (sourceStartOrEnd == StartOrEnd.START) { newTaskStart = sourceStart.plus(delay, ChronoUnit.HOURS); } - setTaskDuration(task, task.getStartTime(), newTaskStart); + this.setTaskDuration(task, task.getStartTime(), newTaskStart); taskComputationService.updateStartTime(task, newTaskStart); } @@ -740,23 +743,19 @@ private void setWorkpackageNewDates(Workpackage workpackage, DependencyLink depe if (sourceStartOrEnd == StartOrEnd.END && targetStartOrEnd == StartOrEnd.START) { LocalDate newWorkpackageStart = sourceEnd.plusDays(delay); LocalDate newWorkpackageEnd = newWorkpackageStart.plusDays(duration); - workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.START) { LocalDate newWorkpackageStart = sourceStart.plusDays(delay); LocalDate newWorkpackageEnd = newWorkpackageStart.plusDays(duration); - workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } else if (sourceStartOrEnd == StartOrEnd.END && targetStartOrEnd == StartOrEnd.END) { LocalDate newWorkpackageEnd = sourceEnd.plusDays(delay); LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(duration); workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); - workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.END) { LocalDate newWorkpackageEnd = sourceStart.plusDays(delay); LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(duration); workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); - workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } } @@ -803,17 +802,6 @@ private void setWorkpackageNewStartDate(Workpackage workpackage, DependencyLink workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } - private static Instant getlaterInstant(DependencyLink dep) { - Instant laterInstant = null; - Task source = (Task) dep.getSource(); - if (dep.getSourceKind() == StartOrEnd.END) { - laterInstant = getTaskEndTime(source).plus(dep.getDuration(), ChronoUnit.HOURS); - } else if (dep.getSourceKind() == StartOrEnd.START) { - laterInstant = getTaskStartTime(source).plus(dep.getDuration(), ChronoUnit.HOURS); - } - return laterInstant; - } - private LocalDate getlaterLocalDate(DependencyLink dep) { LocalDate laterLocalDate = null; Workpackage source = (Workpackage) dep.getSource(); @@ -825,10 +813,9 @@ private LocalDate getlaterLocalDate(DependencyLink dep) { return laterLocalDate; } - public void editDependencyLinkDuration(DependencyLink depLink, int newDuration) { depLink.setDuration(newDuration); - followMoveDependency(depLink.getSource()); + this.followMoveDependency(depLink.getSource()); } public List getTasksWithTag(TaskTag tag, Workpackage workpackage) { @@ -915,15 +902,14 @@ public void moveTaskIntoTarget(Task sourceTask, EObject target, int indexInTarge } } - public void createWorkpackage(EObject context) { Workpackage newWorkpackage = PepperFactory.eINSTANCE.createWorkpackage(); newWorkpackage.setName("New Workpackage"); if (context instanceof Workpackage workpackage) { // The new task follows the context task and has the same duration than the context task. if (workpackage.getEndDate() != null && workpackage.getStartDate() != null) { - workpackageComputationService.updateStartDate(newWorkpackage, workpackage.getEndDate().plusDays(1)); - workpackageComputationService.updateEndDate(newWorkpackage, workpackage.getEndDate().plusDays(workpackage.getEndDate().toEpochDay() - workpackage.getStartDate().toEpochDay() + 1)); + workpackageComputationService.updateStartDate(newWorkpackage, workpackage.getEndDate()); + workpackageComputationService.updateEndDate(newWorkpackage, workpackage.getEndDate().plusDays(workpackage.getEndDate().toEpochDay() - workpackage.getStartDate().toEpochDay())); } EObject parent = context.eContainer(); @@ -981,7 +967,7 @@ public void editWorkpackage(EObject eObject, String name, String description, Lo workpackageComputationService.updateEndDate(workpackage, endDate); } if (!startDateControlledByDependency || !endDateControlledByDependency) { - followMoveDependency(workpackage); + this.followMoveDependency(workpackage); } } } @@ -1047,12 +1033,13 @@ private void moveTaskInSubTasks(Task sourceTask, int indexInTarget, Task targetT } public Task moveTaskInTag(Task moveTask, int index, TaskTag targetTag) { - Optional workPackageOpt = getParent(moveTask, Workpackage.class); + Optional workPackageOpt = this.getParent(moveTask, Workpackage.class); if (workPackageOpt.isPresent()) { // We retrieve all tasks with the same tag (in the same lane). List allTaskInTheLane = this.getTasksWithTag(targetTag, workPackageOpt.get()); - Optional firstTaskAfterTheDroppedTaskWithSameParent = allTaskInTheLane.subList(index, allTaskInTheLane.size()).stream().filter(task -> task.eContainer().equals(moveTask.eContainer())).findFirst(); + Optional firstTaskAfterTheDroppedTaskWithSameParent = allTaskInTheLane.subList(index, allTaskInTheLane.size()).stream() + .filter(task -> task.eContainer().equals(moveTask.eContainer())).findFirst(); List tasksBeforeTheDroppedTaskWithSameParent = allTaskInTheLane.subList(0, index).stream().filter(task -> task.eContainer().equals(moveTask.eContainer())).toList(); Optional lastTaskBeforeTheDroppedTaskWithSameParent = Optional.empty(); @@ -1124,8 +1111,7 @@ public void moveTagAtIndex(TaskTag movedTag, int index) { } /** - * When a lane is moved, we change the underlying tag ordering. We need to compute the new index in the project tag - * list. + * When a lane is moved, we change the underlying tag ordering. We need to compute the new index in the project tag list. * * @param tag * the tag to move. @@ -1135,8 +1121,7 @@ public void moveTagAtIndex(TaskTag movedTag, int index) { * the current deck representation tag list (might be a sub set of the project tag list). * @param tagFolder * the TagFolder owning the tags. - * @return the index on which the tag should be moved in the project tag list to match the new index in the deck - * representation. + * @return the index on which the tag should be moved in the project tag list to match the new index in the deck representation. */ private int computeIndexOfTagToMove(TaskTag tag, int index, List tagList, TagFolder tagFolder) { int newIndex; diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/gantt/ViewGanttDescriptionBuilder.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/gantt/ViewGanttDescriptionBuilder.java index 762c995..6e0b445 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/gantt/ViewGanttDescriptionBuilder.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/gantt/ViewGanttDescriptionBuilder.java @@ -127,7 +127,7 @@ private CreateTaskDependencyTool createTaskDependencyTool() { return new GanttBuilders().newCreateTaskDependencyTool() .name("Create Task Dependency") .body(new ChangeContextBuilder() - .expression("aql:targetObject.createDependencyLink(sourceObject, sourceStartOrEnd, targetStartOrEnd)") + .expression("aql:sourceObject.createDependencyLink(targetObject, sourceStartOrEnd, targetStartOrEnd)") .build()) .build(); } diff --git a/backend/pepper-starter/src/test/java/pepper/starter/configuration/view/PepperMMJavaServiceTests.java b/backend/pepper-starter/src/test/java/pepper/starter/configuration/view/PepperMMJavaServiceTests.java index 5d8c005..586d54d 100644 --- a/backend/pepper-starter/src/test/java/pepper/starter/configuration/view/PepperMMJavaServiceTests.java +++ b/backend/pepper-starter/src/test/java/pepper/starter/configuration/view/PepperMMJavaServiceTests.java @@ -40,6 +40,7 @@ import pepper.peppermm.TagFolder; import pepper.peppermm.Task; import pepper.peppermm.TaskTag; +import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; import pepper.starter.services.representations.PepperMMJavaService; @@ -58,10 +59,17 @@ public class PepperMMJavaServiceTests { .atZone(ZoneId.systemDefault()); private static final String ZONE = ZONED_DATE_TIME.getOffset().toString(); - private static final String DATE2024_01_01_T00_00_00 = "2024-01-01T00:00:00" + ZONE; - private static final String DATE2024_01_01_T23_59_00 = "2024-01-01T23:59:00" + ZONE; - private static final String DATE2024_01_02_T00_00_00 = "2024-01-02T00:00:00" + ZONE; - private static final String DATE2024_01_02_T23_59_00 = "2024-01-02T23:59:00" + ZONE; + private static final String MONDAY_2026_01_05_T00_00_00 = "2026-01-05T00:00:00" + ZONE; + private static final String MONDAY_2026_01_05_T23_59_00 = "2026-01-05T23:59:00" + ZONE; + private static final String TUESDAY_2026_01_06_T00_00_00 = "2026-01-06T00:00:00" + ZONE; + private static final String TUESDAY_2026_01_06_T23_59_00 = "2026-01-06T23:59:00" + ZONE; + + + private static final LocalDate MONDAY_20260105 = LocalDate.ofYearDay(2026, 5); + private static final LocalDate TUESDAY_20260106 = LocalDate.ofYearDay(2026, 6); + private static final LocalDate WEDNESDAY_20260107 = LocalDate.ofYearDay(2026, 7); + private static final LocalDate THURSDAY_20260108 = LocalDate.ofYearDay(2026, 8); + private static final LocalDate FRIDAY_20260109 = LocalDate.ofYearDay(2026, 9); private final Workpackage workpackage = PepperFactory.eINSTANCE.createWorkpackage(); @@ -87,77 +95,84 @@ public void editTask() { taskComputationService.updateEndTime(task, Instant.now()); workpackage.getOwnedTasks().add(task); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - service.editTask(task, NEW_NAME, NEW_DESCRIPTION, Instant.parse(DATE2024_01_01_T00_00_00), Instant.parse(DATE2024_01_01_T23_59_00), 10, false); + service.editTask(task, NEW_NAME, NEW_DESCRIPTION, Instant.parse(MONDAY_2026_01_05_T00_00_00), Instant.parse(MONDAY_2026_01_05_T23_59_00), 10, false); assertThat(task.getName()).isEqualTo(NEW_NAME); assertThat(task.getDescription()).isEqualTo(NEW_DESCRIPTION); - assertThat(task.getStartTime()).isEqualTo(Instant.parse(DATE2024_01_01_T00_00_00)); - assertThat(task.getEndTime()).isEqualTo(Instant.parse(DATE2024_01_01_T23_59_00)); + assertThat(task.getStartTime()).isEqualTo(Instant.parse(MONDAY_2026_01_05_T00_00_00)); + assertThat(task.getEndTime()).isEqualTo(Instant.parse(MONDAY_2026_01_05_T23_59_00)); assertThat(task.getProgress()).isEqualTo(10); } @Test public void editTaskWithDependency() { - Task task = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task, Instant.parse(DATE2024_01_01_T23_59_00)); + Task task1 = PepperFactory.eINSTANCE.createTask(); + taskComputationService.updateStartTime(task1, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); - Task taskDependency = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(taskDependency, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(taskDependency, Instant.parse(DATE2024_01_01_T23_59_00)); - Task masterTask = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(masterTask, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(masterTask, Instant.parse(DATE2024_01_01_T23_59_00)); + Task task2 = PepperFactory.eINSTANCE.createTask(); + task2.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + taskComputationService.updateStartTime(task2, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateDuration(task2, 24); - workpackage.getOwnedTasks().add(task); - workpackage.getOwnedTasks().add(taskDependency); - workpackage.getOwnedTasks().add(masterTask); + Task task3 = PepperFactory.eINSTANCE.createTask(); + task3.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + taskComputationService.updateStartTime(task3, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateDuration(task3, 24); + + workpackage.getOwnedTasks().add(task3); + workpackage.getOwnedTasks().add(task2); + workpackage.getOwnedTasks().add(task1); DependencyLink dependencyLinkOfTask = PepperFactory.eINSTANCE.createDependencyLink(); dependencyLinkOfTask.setDuration(0); dependencyLinkOfTask.setTargetKind(pepper.peppermm.StartOrEnd.START); dependencyLinkOfTask.setSourceKind(pepper.peppermm.StartOrEnd.END); - dependencyLinkOfTask.setSource(taskDependency); - task.getDependencies().add(dependencyLinkOfTask); + dependencyLinkOfTask.setSource(task2); + task3.getDependencies().add(dependencyLinkOfTask); DependencyLink dependencyLinkOfTaskDependency = PepperFactory.eINSTANCE.createDependencyLink(); dependencyLinkOfTaskDependency.setDuration(0); dependencyLinkOfTaskDependency.setTargetKind(pepper.peppermm.StartOrEnd.START); dependencyLinkOfTaskDependency.setSourceKind(pepper.peppermm.StartOrEnd.END); - dependencyLinkOfTaskDependency.setSource(masterTask); - taskDependency.getDependencies().add(dependencyLinkOfTaskDependency); + dependencyLinkOfTaskDependency.setSource(task1); + task2.getDependencies().add(dependencyLinkOfTaskDependency); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - service.editTask(taskDependency, null, null, Instant.parse(DATE2024_01_02_T00_00_00), Instant.parse(DATE2024_01_02_T00_00_00).plus(1, ChronoUnit.DAYS), null, false); - assertThat(taskDependency.getStartTime()).isEqualTo(Instant.parse(DATE2024_01_01_T00_00_00)); - assertThat(taskDependency.getEndTime()).isEqualTo(Instant.parse(DATE2024_01_01_T23_59_00)); - - service.editTask(masterTask, null, null, Instant.parse(DATE2024_01_02_T00_00_00), Instant.parse(DATE2024_01_02_T00_00_00).plus(1, ChronoUnit.DAYS), null, false); - assertThat(masterTask.getStartTime()).isEqualTo(DATE2024_01_02_T00_00_00); - assertThat(masterTask.getEndTime()).isEqualTo(DATE2024_01_02_T23_59_00); - assertThat(taskDependency.getStartTime()).isEqualTo(masterTask.getEndTime().plus(1, ChronoUnit.MINUTES)); - assertThat(taskDependency.getEndTime()).isEqualTo(masterTask.getEndTime().plus(1, ChronoUnit.DAYS)); + service.editTask(task2, null, null, Instant.parse(TUESDAY_2026_01_06_T00_00_00), Instant.parse(TUESDAY_2026_01_06_T00_00_00).plus(1, ChronoUnit.DAYS), null, false); + assertThat(task2.getStartTime()).isEqualTo(Instant.parse(MONDAY_2026_01_05_T00_00_00)); + assertThat(task2.getEndTime()).isEqualTo(Instant.parse(MONDAY_2026_01_05_T23_59_00)); + + service.editTask(task1, null, null, Instant.parse(TUESDAY_2026_01_06_T00_00_00), Instant.parse(TUESDAY_2026_01_06_T00_00_00).plus(1, ChronoUnit.DAYS), null, false); + assertThat(task1.getStartTime()).isEqualTo(TUESDAY_2026_01_06_T00_00_00); + assertThat(task1.getEndTime()).isEqualTo(TUESDAY_2026_01_06_T23_59_00); + assertThat(task2.getStartTime()).isEqualTo(task1.getEndTime().plus(1, ChronoUnit.MINUTES)); + assertThat(task2.getEndTime()).isEqualTo(task1.getEndTime().plus(1, ChronoUnit.DAYS)); // Verify transitive dependency propagation - assertThat(task.getStartTime()).isEqualTo(taskDependency.getEndTime().plus(1, ChronoUnit.MINUTES)); - assertThat(task.getEndTime()).isEqualTo(taskDependency.getEndTime().plus(1, ChronoUnit.DAYS)); + assertThat(task3.getStartTime()).isEqualTo(task2.getEndTime().plus(1, ChronoUnit.MINUTES)); + assertThat(task3.getEndTime()).isEqualTo(task2.getEndTime().plus(1, ChronoUnit.DAYS)); } @Test public void editSubTaskOfDynamicTaskWithDependency() { Task task1 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task1, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task1, Instant.parse(DATE2024_01_01_T23_59_00)); + task1.setCalculationOption(TaskTimeBoundariesConstraint.START_END); + taskComputationService.updateStartTime(task1, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task2 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task2, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task2, Instant.parse(DATE2024_01_01_T23_59_00)); + task2.setCalculationOption(TaskTimeBoundariesConstraint.START_END); + taskComputationService.updateStartTime(task2, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task2, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task3 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task3, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task3, Instant.parse(DATE2024_01_01_T23_59_00)); + task3.setCalculationOption(TaskTimeBoundariesConstraint.START_END); + taskComputationService.updateStartTime(task3, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task3, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task31 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task31, Instant.parse(DATE2024_01_02_T00_00_00)); - taskComputationService.updateEndTime(task31, Instant.parse(DATE2024_01_02_T23_59_00)); + task31.setCalculationOption(TaskTimeBoundariesConstraint.START_END); + taskComputationService.updateStartTime(task31, Instant.parse(TUESDAY_2026_01_06_T00_00_00)); + taskComputationService.updateEndTime(task31, Instant.parse(TUESDAY_2026_01_06_T23_59_00)); workpackage.getOwnedTasks().add(task1); workpackage.getOwnedTasks().add(task2); @@ -177,7 +192,7 @@ public void editSubTaskOfDynamicTaskWithDependency() { service.editTask(task31, null, null, task31.getStartTime(), task31.getEndTime().plus(1, ChronoUnit.DAYS), null, true); assertThat(task3.getSubTasks().size()).isEqualTo(1); - assertThat(task31.getEndTime()).isEqualTo(Instant.parse(DATE2024_01_02_T23_59_00).plus(1, ChronoUnit.DAYS)); + assertThat(task31.getEndTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T23_59_00).plus(1, ChronoUnit.DAYS)); assertThat(task1.getStartTime()).isEqualTo(task31.getEndTime().plus(1, ChronoUnit.MINUTES)); } @@ -186,40 +201,41 @@ public void editSubTaskOfDynamicTaskWithDependency() { @Test public void createDependencyLink() { Task task = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task, Instant.parse(DATE2024_01_01_T23_59_00)); + task.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + taskComputationService.updateStartTime(task, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateDuration(task, 24); Task taskDependency = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(taskDependency, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(taskDependency, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(taskDependency, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(taskDependency, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task masterTask = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(masterTask, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(masterTask, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(masterTask, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(masterTask, Instant.parse(MONDAY_2026_01_05_T23_59_00)); workpackage.getOwnedTasks().add(task); workpackage.getOwnedTasks().add(taskDependency); workpackage.getOwnedTasks().add(masterTask); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - service.createDependencyLink(task, taskDependency, StartOrEnd.END, StartOrEnd.START); + service.createDependencyLink(taskDependency, task, StartOrEnd.END, StartOrEnd.START); assertThat(task.getDependencies().size()).isEqualTo(1); assertThat(task.getDependencies().get(0).getSource()).isEqualTo(taskDependency); - assertThat(task.getStartTime()).isEqualTo(Instant.parse(DATE2024_01_02_T00_00_00)); - assertThat(task.getEndTime()).isEqualTo(Instant.parse(DATE2024_01_02_T23_59_00)); + assertThat(task.getStartTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T00_00_00)); + assertThat(task.getEndTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T23_59_00)); - service.createDependencyLink(taskDependency, masterTask, StartOrEnd.END, StartOrEnd.END); + service.createDependencyLink(masterTask, taskDependency, StartOrEnd.END, StartOrEnd.END); assertThat(taskDependency.getDependencies().size()).isEqualTo(1); assertThat(taskDependency.getDependencies().get(0).getSource()).isEqualTo(masterTask); assertThat(taskDependency.getStartTime()).isEqualTo(masterTask.getStartTime()); assertThat(taskDependency.getEndTime()).isEqualTo(masterTask.getEndTime()); // Verify transitive dependency propagation assertThat(task.getStartTime()).isEqualTo(taskDependency.getEndTime().plus(1, ChronoUnit.MINUTES)); - assertThat(task.getEndTime()).isEqualTo(Instant.parse(DATE2024_01_02_T23_59_00)); + assertThat(task.getEndTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T23_59_00)); // Verify that cyclic dependencies are impossible assertThat(masterTask.getDependencies()).isEmpty(); - service.createDependencyLink(masterTask, task, StartOrEnd.END, StartOrEnd.START); + service.createDependencyLink(task, masterTask, StartOrEnd.END, StartOrEnd.START); assertThat(masterTask.getDependencies()).isEmpty(); } @@ -228,16 +244,16 @@ public void createDependencyLink() { public void deleteDependencyLink() { Task task1 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task1, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task1, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task1, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task2 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task2, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task2, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task2, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task2, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task3 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task3, Instant.parse(DATE2024_01_02_T00_00_00)); - taskComputationService.updateEndTime(task3, Instant.parse(DATE2024_01_02_T23_59_00)); + taskComputationService.updateStartTime(task3, Instant.parse(TUESDAY_2026_01_06_T00_00_00)); + taskComputationService.updateEndTime(task3, Instant.parse(TUESDAY_2026_01_06_T23_59_00)); workpackage.getOwnedTasks().add(task1); workpackage.getOwnedTasks().add(task2); @@ -272,7 +288,7 @@ public void computeTaskDurationDays() { var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); var result = service.computeTaskDurationDays(task); assertThat(result).isNotNull(); - assertThat(result).isEqualTo("01d01h"); + assertThat(result).isEqualTo("01d00h"); } @Test @@ -304,12 +320,12 @@ public void createCard() { @Test public void createTask() { Task task11 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task11, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task11, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task11, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task11, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task1 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task1, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task1, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task1, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); task1.getSubTasks().add(task11); workpackage.getOwnedTasks().add(task1); @@ -320,8 +336,8 @@ public void createTask() { service.createTask(task1); assertThat(task1.getSubTasks()).hasSize(2); - assertThat(task1.getSubTasks().get(1).getStartTime()).isEqualTo(Instant.parse(DATE2024_01_02_T00_00_00)); - assertThat(task1.getSubTasks().get(1).getEndTime()).isEqualTo(Instant.parse(DATE2024_01_02_T23_59_00)); + assertThat(task1.getSubTasks().get(1).getStartTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T00_00_00)); + assertThat(task1.getSubTasks().get(1).getEndTime()).isEqualTo(Instant.parse(TUESDAY_2026_01_06_T23_59_00)); service.createTask(task11); assertThat(task11.getSubTasks()).hasSize(1); @@ -332,29 +348,29 @@ public void createTask() { @Test public void deleteTask() { Task task11 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task11, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task11, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task11, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task11, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task1 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task1, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task1, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task1, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); task1.getSubTasks().add(task11); Task task2 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task2, Instant.parse(DATE2024_01_01_T00_00_00)); - taskComputationService.updateEndTime(task2, Instant.parse(DATE2024_01_01_T23_59_00)); + taskComputationService.updateStartTime(task2, Instant.parse(MONDAY_2026_01_05_T00_00_00)); + taskComputationService.updateEndTime(task2, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task3 = PepperFactory.eINSTANCE.createTask(); - taskComputationService.updateStartTime(task3, Instant.parse(DATE2024_01_02_T00_00_00)); - taskComputationService.updateEndTime(task3, Instant.parse(DATE2024_01_02_T23_59_00)); + taskComputationService.updateStartTime(task3, Instant.parse(TUESDAY_2026_01_06_T00_00_00)); + taskComputationService.updateEndTime(task3, Instant.parse(TUESDAY_2026_01_06_T23_59_00)); workpackage.getOwnedTasks().add(task1); workpackage.getOwnedTasks().add(task2); workpackage.getOwnedTasks().add(task3); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - service.createDependencyLink(task2, task3, StartOrEnd.END, StartOrEnd.START); - service.createDependencyLink(task2, task11, StartOrEnd.END, StartOrEnd.START); + service.createDependencyLink(task3, task2, StartOrEnd.END, StartOrEnd.START); + service.createDependencyLink(task11, task2, StartOrEnd.END, StartOrEnd.START); service.deleteDependencyRelatedObject(task1); assertThat(workpackage.getOwnedTasks()).hasSize(2); @@ -366,28 +382,28 @@ public void deleteTask() { public void createWorkpackage() { Project project = PepperFactory.eINSTANCE.createProject(); Workpackage projectWorkpackage = PepperFactory.eINSTANCE.createWorkpackage(); - workpackageComputationService.updateStartDate(projectWorkpackage, LocalDate.ofYearDay(2026, 1)); - workpackageComputationService.updateEndDate(projectWorkpackage, LocalDate.ofYearDay(2026, 3)); + workpackageComputationService.updateStartDate(projectWorkpackage, MONDAY_20260105); + workpackageComputationService.updateEndDate(projectWorkpackage, WEDNESDAY_20260107); project.getOwnedWorkpackages().add(projectWorkpackage); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); service.createWorkpackage(projectWorkpackage); assertThat(project.getOwnedWorkpackages()).hasSize(2); - assertThat(project.getOwnedWorkpackages().get(1).getStartDate()).isEqualTo(LocalDate.ofYearDay(2026, 4)); - assertThat(project.getOwnedWorkpackages().get(1).getEndDate()).isEqualTo(LocalDate.ofYearDay(2026, 6)); + assertThat(project.getOwnedWorkpackages().get(1).getStartDate()).isEqualTo(WEDNESDAY_20260107); + assertThat(project.getOwnedWorkpackages().get(1).getEndDate()).isEqualTo(FRIDAY_20260109); } @Test public void editWorkpackage() { - workpackageComputationService.updateStartDate(workpackage, LocalDate.ofYearDay(2026, 5)); - workpackageComputationService.updateEndDate(workpackage, LocalDate.ofYearDay(2026, 8)); + workpackageComputationService.updateStartDate(workpackage, MONDAY_20260105); + workpackageComputationService.updateEndDate(workpackage, TUESDAY_20260106); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - service.editWorkpackage(workpackage, NEW_NAME, NEW_DESCRIPTION, LocalDate.ofYearDay(2026, 1), LocalDate.ofYearDay(2026, 3), 10, false); + service.editWorkpackage(workpackage, NEW_NAME, NEW_DESCRIPTION, WEDNESDAY_20260107, FRIDAY_20260109, 10, false); assertThat(workpackage.getName()).isEqualTo(NEW_NAME); assertThat(workpackage.getDescription()).isEqualTo(NEW_DESCRIPTION); - assertThat(workpackage.getStartDate()).isEqualTo(LocalDate.ofYearDay(2026, 1)); - assertThat(workpackage.getEndDate()).isEqualTo(LocalDate.ofYearDay(2026, 3)); + assertThat(workpackage.getStartDate()).isEqualTo(WEDNESDAY_20260107); + assertThat(workpackage.getEndDate()).isEqualTo(FRIDAY_20260109); assertThat(workpackage.getProgress()).isEqualTo(10); } diff --git a/backend/releng/pepper-resources/checkstyle/CheckstyleConfiguration.xml b/backend/releng/pepper-resources/checkstyle/CheckstyleConfiguration.xml index ae5d25b..86bf22f 100644 --- a/backend/releng/pepper-resources/checkstyle/CheckstyleConfiguration.xml +++ b/backend/releng/pepper-resources/checkstyle/CheckstyleConfiguration.xml @@ -170,11 +170,6 @@ - - - - -