From 7512fc062284be997afeb1639697ef09c4398050 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Wed, 26 Aug 2026 15:14:32 +0200 Subject: [PATCH 1/8] Fix rounding on AbstractTask.endTime --- .../pepper/domain/services/NonWorkingDaysService.java | 8 +++----- .../pepper/domain/services/TaskComputationService.java | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) 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 index 4b01521..5985679 100644 --- 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 @@ -145,9 +145,8 @@ public Instant getNextEndTime(Instant instant) { if (instant == null) { return null; } - Instant nextEndTime = instant; - LocalDate date = instant.atZone(ZoneOffset.UTC).toLocalDate(); - if (this.isNonWorkingDay(date)) { + Instant nextEndTime = instant.minus(1, ChronoUnit.MINUTES); + if (this.isNonWorkingDay(nextEndTime.atZone(ZoneOffset.UTC).toLocalDate())) { 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); @@ -171,8 +170,7 @@ public Instant getPreviousStartTime(Instant instant) { return null; } Instant previousStartTime = instant; - LocalDate date = instant.atZone(ZoneOffset.UTC).toLocalDate(); - if (this.isNonWorkingDay(date)) { + if (this.isNonWorkingDay(instant.atZone(ZoneOffset.UTC).toLocalDate())) { previousStartTime = instant.truncatedTo(ChronoUnit.HALF_DAYS); while (this.isNonWorkingDay(previousStartTime.atZone(ZoneOffset.UTC).toLocalDate())) { previousStartTime = previousStartTime.minus(1, 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 118caa5..3c6dadf 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 @@ -176,8 +176,7 @@ private int roundToNearestHalfDay(int nbHours) { public Instant roundToNearestHalfDay(Instant instant) { return Optional.ofNullable(instant) - .map(inst -> inst.plus(Duration.ofHours(6)) - .truncatedTo(ChronoUnit.HALF_DAYS)) + .map(inst -> inst.plus(Duration.ofHours(6)).truncatedTo(ChronoUnit.HALF_DAYS)) .orElse(null); } From 314dc56ee875da39b37a3e404c6d9a048c6281f1 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Thu, 27 Aug 2026 17:20:13 +0200 Subject: [PATCH 2/8] Fix behavior when moving a task --- .../services/NonWorkingDaysService.java | 4 +- .../services/TaskComputationService.java | 63 +++++----- .../WorkpackageComputationService.java | 59 ++++++---- .../services/TaskComputationServiceTests.java | 108 ++++++++++++++++++ .../representations/PepperMMJavaService.java | 99 +++++++--------- ...ot.autoconfigure.AutoConfiguration.imports | 1 + 6 files changed, 215 insertions(+), 119 deletions(-) create mode 100644 backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java 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 index 5985679..0dc29f3 100644 --- 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 @@ -145,8 +145,8 @@ public Instant getNextEndTime(Instant instant) { if (instant == null) { return null; } - Instant nextEndTime = instant.minus(1, ChronoUnit.MINUTES); - if (this.isNonWorkingDay(nextEndTime.atZone(ZoneOffset.UTC).toLocalDate())) { + Instant nextEndTime = instant; + if (this.isNonWorkingDay(nextEndTime.minus(1, ChronoUnit.MINUTES).atZone(ZoneOffset.UTC).toLocalDate())) { 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); 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 3c6dadf..9b7c0f2 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 @@ -32,6 +32,7 @@ /** * Domain service related to AbstractTask entities. + * * @author lfasani */ @Service @@ -41,51 +42,45 @@ public class TaskComputationService { 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. + * 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) || 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); - } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousStartTime != null) { - Instant newEndTime = nonWorkingDaysService.getEndTime(previousStartTime, currentDuration).minus(1, ChronoUnit.MINUTES); - abstractTask.setEndTime(this.convertAccordingToTimeZone(newEndTime)); + 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_DURATION) && previousStartTime != null) { + Instant newEndTime = nonWorkingDaysService.getEndTime(previousStartTime, currentDuration).minus(1, ChronoUnit.MINUTES); + abstractTask.setEndTime(this.convertAccordingToTimeZone(newEndTime)); + } else { + if (currentEndTime != null && previousStartTime != null) { + long hourDuration = nonWorkingDaysService.getDuration(previousStartTime, currentEndTime).toHours(); + abstractTask.setDuration((int) hourDuration); } } } /** - * 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. + * 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) || 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); - } - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextEndTime != null) { - Instant newStartTime = nonWorkingDaysService.getStartTime(nextEndTime, currentDuration); //.plus(1, ChronoUnit.MINUTES); - abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); + 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.END_DURATION) && nextEndTime != null) { + Instant newStartTime = nonWorkingDaysService.getStartTime(nextEndTime, currentDuration); //.plus(1, ChronoUnit.MINUTES); + abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); + } else { + if (nextEndTime != null && currentStartTime != null) { + long hourDuration = nonWorkingDaysService.getDuration(currentStartTime, nextEndTime).toHours(); + abstractTask.setDuration((int) hourDuration); } } } 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 92aba22..1b68b78 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 @@ -36,19 +36,32 @@ public class WorkpackageComputationService { public void updateStartDate(Workpackage workpackage, LocalDate newStartDate) { LocalDate previousNewStartDate = nonWorkingDaysService.getPreviousStartDate(newStartDate); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); - if (!TaskTimeBoundariesConstraint.END_DURATION.equals(calculationOption) || this.hasDependency(workpackage, StartOrEnd.START)) { - workpackage.setStartDate(previousNewStartDate); +// 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) || 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); +// } +// } + workpackage.setStartDate(previousNewStartDate); - 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); + LocalDate currentEndDate = workpackage.getEndDate(); + int currentDuration = workpackage.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousNewStartDate != null) { + LocalDate newEndDate = previousNewStartDate.plusDays(currentDuration - 1); + workpackage.setEndDate(newEndDate); + } else { + if (currentEndDate != null && previousNewStartDate != null) { + long newDuration = nonWorkingDaysService.getDuration(previousNewStartDate, currentEndDate).toDays(); + workpackage.setDuration((int) newDuration); } } } @@ -56,19 +69,17 @@ public void updateStartDate(Workpackage workpackage, LocalDate newStartDate) { public void updateEndDate(Workpackage workpackage, LocalDate newEndDate) { LocalDate nextNewEndDate = nonWorkingDaysService.getNextEndDate(newEndDate); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); - if (!TaskTimeBoundariesConstraint.START_DURATION.equals(calculationOption) || this.hasDependency(workpackage, StartOrEnd.END)) { - workpackage.setEndDate(nextNewEndDate); + workpackage.setEndDate(nextNewEndDate); - 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); + LocalDate currentStartDate = workpackage.getStartDate(); + int currentDuration = workpackage.getDuration(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextNewEndDate != null) { + LocalDate newStartDate = nextNewEndDate.minusDays(currentDuration - 1); + workpackage.setStartDate(newStartDate); + } else { + if (nextNewEndDate != null && currentStartDate != null) { + long newDuration = nonWorkingDaysService.getDuration(currentStartDate, nextNewEndDate).toDays(); + workpackage.setDuration((int) newDuration); } } } diff --git a/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java b/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java new file mode 100644 index 0000000..8cc82b4 --- /dev/null +++ b/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.junit.jupiter.api.Test; + +import pepper.peppermm.PepperFactory; +import pepper.peppermm.Task; +import pepper.peppermm.TaskTimeBoundariesConstraint; + +/** + * Tests of {@link TaskComputationService}. + * + * @author lfasani + */ +public class TaskComputationServiceTests { + + private static final Instant FRIDAY_2026_07_31_T00_00 = toInstant(2026, 7, 31, 0, 0); + private static final Instant FRIDAY_2026_07_31_T12_00 = toInstant(2026, 7, 31, 12, 0); + private static final Instant MONDAY_2026_08_03_T00_00 = toInstant(2026, 8, 03, 0, 0); + private static final Instant MONDAY_2026_08_03_T12_00 = toInstant(2026, 8, 03, 12, 0); + + private final TaskComputationService taskComputationService = new TaskComputationService(); + + private static Instant toInstant(int year, int month, int dayOfMonth, int hour, int minute) { + return LocalDateTime.of(year, month, dayOfMonth, hour, minute).atZone(ZoneId.systemDefault()).toInstant(); + } + + + @Test + public void updateStartTimeAcrossWeekendUpdatesDurationForStartEndConstraint() { + this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_END, 24); + } + + @Test + public void updateStartTimeAcrossWeekendPreservesDurationForEndDurationConstraint() { + this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.END_DURATION, 24); + } + + @Test + public void updateStartTimeAcrossWeekendUpdatesDurationForStartDurationConstraint() { + this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_DURATION, 12); + } + + private void updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint calculationOption, int expectedDuration) { + Task task1 = this.createTaskBeginningAfterWeekend(calculationOption); + + taskComputationService.updateStartTime(task1, FRIDAY_2026_07_31_T12_00); + assertThat(task1.getDuration()).isEqualTo(expectedDuration); + } + + private Task createTaskBeginningAfterWeekend(TaskTimeBoundariesConstraint calculationOption) { + Task task1 = PepperFactory.eINSTANCE.createTask(); + task1.setCalculationOption(calculationOption); + task1.setDuration(12); + taskComputationService.updateStartTime(task1, MONDAY_2026_08_03_T00_00); + taskComputationService.updateEndTime(task1, MONDAY_2026_08_03_T12_00); + return task1; + } + + @Test + public void updateEndTimeAcrossWeekendUpdatesDurationForStartEndConstraint() { + this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_END, 36); + } + + @Test + public void updateEndTimeAcrossWeekendPreservesDurationForEndDurationConstraint() { + this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.END_DURATION, 12); + } + + @Test + public void updateEndTimeAcrossWeekendUpdatesDurationForStartDurationConstraint() { + this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_DURATION, 36); + } + + private void updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint calculationOption, int expectedDuration) { + Task task1 = this.createTaskEndingBeforeWeekend(calculationOption); + + taskComputationService.updateEndTime(task1, MONDAY_2026_08_03_T12_00); + assertThat(task1.getDuration()).isEqualTo(expectedDuration); + } + + private Task createTaskEndingBeforeWeekend(TaskTimeBoundariesConstraint calculationOption) { + Task task1 = PepperFactory.eINSTANCE.createTask(); + task1.setCalculationOption(calculationOption); + task1.setDuration(12); + taskComputationService.updateStartTime(task1, FRIDAY_2026_07_31_T00_00); + taskComputationService.updateEndTime(task1, FRIDAY_2026_07_31_T12_00); + return task1; + } +} 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 1d6ecb9..e46af61 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 @@ -105,7 +105,7 @@ private static Instant getlaterInstant(DependencyLink dep) { return laterInstant; } - @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MethodLength" }) + @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MethodLength", "checkstyle:MissingSwitchDefault" }) 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) { @@ -130,58 +130,32 @@ public void editTask(EObject eObject, String name, String description, Instant s boolean endTimeControlledByDependency = dependencies.stream() .anyMatch(dep -> dep.getTargetKind() == StartOrEnd.END); - TaskTimeBoundariesConstraint calculationOption = task.getCalculationOption(); - if (startTimeControlledByDependency && !endTimeControlledByDependency) { - 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) { - 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 (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 { + if (taskShifted) { + if (dependencies.isEmpty()) { + TaskTimeBoundariesConstraint calculationOption = task.getCalculationOption(); + switch (calculationOption) { + case START_DURATION -> taskComputationService.updateStartTime(task, newStartTime); + case END_DURATION -> taskComputationService.updateEndTime(task, newEndTime); + case START_END -> { 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); } } + this.followMoveDependency(task); + } + } else { + if (differenceStart != 0 && !startTimeControlledByDependency) { + taskComputationService.updateStartTime(task, newStartTime); + this.followMoveDependency(task); + } + + if (differenceEnd != 0 && !endTimeControlledByDependency) { + taskComputationService.updateEndTime(task, newEndTime); + this.followMoveDependency(task); } } - if (!startTimeControlledByDependency || !endTimeControlledByDependency) { - this.followMoveDependency(task); - } + } } if (progress != null) { @@ -932,7 +906,7 @@ public void deleteWorkpackage(EObject context) { } } - @SuppressWarnings("checkstyle:NestedIfDepth") + @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MissingSwitchDefault" }) public void editWorkpackage(EObject eObject, String name, String description, LocalDate startDate, LocalDate endDate, Integer progress, boolean keepDuration) { if (eObject instanceof Workpackage workpackage) { if (name != null) { @@ -952,21 +926,28 @@ public void editWorkpackage(EObject eObject, String name, String description, Lo boolean endDateControlledByDependency = dependencies.stream() .anyMatch(dep -> dep.getTargetKind() == StartOrEnd.END); - if (dependencies.isEmpty() || differenceEnd != differenceStart) { - if (startDateControlledByDependency && !endDateControlledByDependency) { - this.workpackageSetDuration(workpackage, startDate, endDate); - workpackageComputationService.updateEndDate(workpackage, endDate.plusDays(differenceStart)); - } else if (endDateControlledByDependency && !startDateControlledByDependency) { - this.workpackageSetDuration(workpackage, startDate, endDate); - workpackageComputationService.updateStartDate(workpackage, startDate.plusDays(differenceEnd)); - } else if (!startDateControlledByDependency && !endDateControlledByDependency) { - if (!keepDuration) { - this.workpackageSetDuration(workpackage, startDate, endDate); + + if (differenceStart != 0 && differenceEnd != 0) { + if (dependencies.isEmpty()) { + TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); + switch (calculationOption) { + case START_DURATION -> workpackageComputationService.updateStartDate(workpackage, startDate); + case END_DURATION -> workpackageComputationService.updateEndDate(workpackage, endDate); + case START_END -> { + workpackageComputationService.updateStartDate(workpackage, startDate); + workpackageComputationService.updateEndDate(workpackage, endDate); + } } + this.followMoveDependency(workpackage); + } + } else { + if (differenceStart != 0 && !startDateControlledByDependency) { workpackageComputationService.updateStartDate(workpackage, startDate); - workpackageComputationService.updateEndDate(workpackage, endDate); + this.followMoveDependency(workpackage); } - if (!startDateControlledByDependency || !endDateControlledByDependency) { + + if (differenceEnd != 0 && !endDateControlledByDependency) { + workpackageComputationService.updateEndDate(workpackage, endDate); this.followMoveDependency(workpackage); } } diff --git a/backend/pepper-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/backend/pepper-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index bbae07f..7acd281 100644 --- a/backend/pepper-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/backend/pepper-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ pepper.starter.configuration.PepperStarterConfiguration +pepper.domain.services.configuration.PepperDomainServicesConfiguration From 84c197ef04c74d901617502c63a2b1b035087e39 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Wed, 26 Aug 2026 10:06:34 +0200 Subject: [PATCH 3/8] Add tooltips for task details view --- .../pepper/starter/messages/MessageConstants.java | 7 +++++++ .../details/AbstractTaskPropertiesConfigurer.java | 11 ++++++++++- .../details/WorkpackagePropertiesConfigurer.java | 13 +++++++++++-- .../resources/messages/pepper-starter.properties | 6 ++++++ .../resources/messages/pepper-starter_fr.properties | 6 ++++++ 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java b/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java index 88c5d2d..93f5ce6 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java @@ -82,6 +82,13 @@ public final class MessageConstants { public static final String OS_HELP = "OS_HELP"; + public static final String RESOURCES = "RESOURCES"; + + public static final String HELP_DURATION = "HELP_DURATION"; + public static final String HELP_DATE = "HELP_DATE"; + public static final String HELP_ROUNDED_TO_HALF_DAY = "HELP_ROUNDED_TO_HALF_DAY"; + public static final String HELP_COMPUTATION_OPTION = "HELP_COMPUTATION_OPTION"; + private MessageConstants() { // Prevent instantiation } diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java index 8bb7a75..fa8ec61 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java @@ -75,6 +75,8 @@ import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Team; import pepper.peppermm.provider.PepperItemProviderAdapterFactory; +import pepper.starter.messages.MessageConstants; +import pepper.starter.messages.PepperMessageService; import pepper.starter.services.representations.PepperMMJavaService; /** @@ -103,14 +105,17 @@ public class AbstractTaskPropertiesConfigurer implements IPropertiesDescriptionR private final PepperMMJavaService service; + private final PepperMessageService pepperMessageService; + public AbstractTaskPropertiesConfigurer(IIdentityService identityService, PropertiesConfigurerService propertiesConfigurerService, IPropertiesWidgetCreationService propertiesWidgetCreationService, - ILabelService labelService, TaskComputationService taskComputationService, WorkpackageComputationService workpackageComputationService) { + ILabelService labelService, TaskComputationService taskComputationService, WorkpackageComputationService workpackageComputationService, PepperMessageService pepperMessageService) { this.identityService = Objects.requireNonNull(identityService); this.propertiesConfigurerService = Objects.requireNonNull(propertiesConfigurerService); this.propertiesWidgetCreationService = Objects.requireNonNull(propertiesWidgetCreationService); this.labelService = labelService; this.taskComputationService = Objects.requireNonNull(taskComputationService); this.workpackageComputationService = Objects.requireNonNull(workpackageComputationService); + this.pepperMessageService = pepperMessageService; this.service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), this.taskComputationService, this.workpackageComputationService); } @@ -283,6 +288,7 @@ private RadioDescription getCalculationOptionWidget() { .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__CALCULATION_OPTION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_COMPUTATION_OPTION)) .build(); } @@ -328,6 +334,7 @@ private TextfieldDescription getDurationWidget() { .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__DURATION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DURATION) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } @@ -463,6 +470,7 @@ private DateTimeDescription getStartTimeWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE_TIME) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } @@ -511,6 +519,7 @@ private DateTimeDescription getEndTimeWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE_TIME) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java index 56ab1fe..e7f73b1 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java @@ -69,6 +69,8 @@ import pepper.peppermm.Team; import pepper.peppermm.Workpackage; import pepper.peppermm.provider.PepperItemProviderAdapterFactory; +import pepper.starter.messages.IPepperMessageService; +import pepper.starter.messages.MessageConstants; import pepper.starter.services.representations.PepperMMJavaService; /** @@ -95,13 +97,16 @@ public class WorkpackagePropertiesConfigurer implements IPropertiesDescriptionRe private final WorkpackageComputationService workpackageComputationService; + private final IPepperMessageService pepperMessageService; + public WorkpackagePropertiesConfigurer(IIdentityService identityService, PropertiesConfigurerService propertiesConfigurerService, IPropertiesWidgetCreationService propertiesWidgetCreationService, ILabelService labelService, - WorkpackageComputationService workpackageComputationService) { + WorkpackageComputationService workpackageComputationService, IPepperMessageService pepperMMMessageService) { this.identityService = identityService; this.propertiesConfigurerService = Objects.requireNonNull(propertiesConfigurerService); this.propertiesWidgetCreationService = Objects.requireNonNull(propertiesWidgetCreationService); this.labelService = labelService; this.workpackageComputationService = workpackageComputationService; + this.pepperMessageService = pepperMMMessageService; } @Override @@ -118,7 +123,7 @@ public void addPropertiesDescriptions(IPropertiesDescriptionRegistry registry) { GroupDescription groupDescriptionGeneral = this.propertiesWidgetCreationService.createSimpleGroupDescription(controlsGeneral); GroupDescription groupDescriptionRessources = GroupDescription.newGroupDescription("group2") .idProvider(variableManager -> "group2") - .labelProvider(variableManager -> "Ressources") + .labelProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.RESOURCES)) .semanticElementsProvider(this.propertiesConfigurerService.getSemanticElementsProvider()) .controlDescriptions(controlsRessources) .build(); @@ -270,6 +275,7 @@ else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_DU .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.WORKPACKAGE__CALCULATION_OPTION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_COMPUTATION_OPTION)) .build(); } @@ -315,6 +321,7 @@ private TextfieldDescription getDurationWidget() { .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.WORKPACKAGE__DURATION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DURATION)) .build(); } @@ -422,6 +429,7 @@ private DateTimeDescription getStartDateWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE)) .build(); } @@ -474,6 +482,7 @@ private DateTimeDescription getEndDateWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE)) .build(); } diff --git a/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties b/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties index b80a2e9..8566144 100644 --- a/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties +++ b/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties @@ -41,3 +41,9 @@ FUNDING_HELP=Funding=Global cost * (Funding Rate / 100) MANPOWER_HELP=Labour costed in the in-house estimate EOTP_HELP=To be completed by the manager/financial assistant OS_HELP=Statistical order\nTo be completed by the manager/financial assistant + +RESOURCES=Resources +HELP_DURATION=The duration reflects the actual time worked.\nIt excludes days not worked (recurring days or holidays), which explains why it may be shorter than the period between the start and end dates +HELP_DATE=When the date depends on the duration, the duration is calculated excluding non-working days (recurring days or holidays), which can make the interval between the start and end dates longer than the duration itself. +HELP_ROUNDED_TO_HALF_DAY=It is rounded to half-day. +HELP_COMPUTATION_OPTION=If a date (start or end) has a dependency on another task, that dependency takes precedence when calculating the date. diff --git a/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties b/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties index 3c3018b..c889a7d 100644 --- a/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties +++ b/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties @@ -41,3 +41,9 @@ FUNDING_HELP=Financement=Co MANPOWER_HELP=Main d'oeuvre chiffrée dans le devis interne EOTP_HELP=A compléter par le gestionnaire/assistant financier OS_HELP=Ordre Statistique\nA compléter par le gestionnaire/assistant financier + +RESOURCES=Ressources +HELP_DURATION=La durée reflète le temps de travail effectif.\nSon calcul exclut les jours non travaillés (récurrents ou fériés), ce qui explique qu?elle puisse être plus courte que la période entre les dates de début et de fin. +HELP_DATE=Lorsque la date dépend de la durée, celle-ci est calculée sans les jours non travaillés (récurrents ou fériés), ce qui peut rendre l'intervalle entre le début et la fin plus long que la durée elle-même. +HELP_ROUNDED_TO_HALF_DAY=Elle est arrondie à la demi journée. +HELP_COMPUTATION_OPTION=Si une date(début ou fin) possède un lien de dépendance vers une autre tâche, ce lien est prioritaire pour le calcul de la date. From 071d2f1fd44a86fe38dd1827b2f083d17ad2a0ac Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Wed, 26 Aug 2026 15:43:18 +0200 Subject: [PATCH 4/8] [60] Add AbstractTask.effort Issue: https://github.com/ObeoNetwork/pepper/issues/60 --- .../provider/AbstractTaskItemProvider.java | 24 ++++++++ .../AssignableObjectItemProvider.java | 26 ++++----- .../provider/NamedElementItemProvider.java | 28 ++++----- .../provider/WorkpackageItemProvider.java | 2 +- .../src/main/resources/plugin.properties | 1 + .../src/main/resources/plugin_fr.properties | 1 + .../java/pepper/peppermm/AbstractTask.java | 23 ++++++++ .../java/pepper/peppermm/PepperPackage.java | 57 ++++++++++++++++++- .../java/pepper/peppermm/Workpackage.java | 23 ++++---- .../peppermm/impl/AbstractTaskImpl.java | 56 ++++++++++++++++++ .../peppermm/impl/AssignableObjectImpl.java | 16 +++--- .../peppermm/impl/NamedElementImpl.java | 53 ++++++++--------- .../peppermm/impl/PepperPackageImpl.java | 14 ++++- .../pepper/peppermm/impl/WorkpackageImpl.java | 19 ++++--- .../src/main/resources/model/pepper.ecore | 3 +- .../src/main/resources/model/pepper.genmodel | 1 + 16 files changed, 261 insertions(+), 86 deletions(-) diff --git a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AbstractTaskItemProvider.java b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AbstractTaskItemProvider.java index de685cb..8f021c8 100644 --- a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AbstractTaskItemProvider.java +++ b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AbstractTaskItemProvider.java @@ -71,6 +71,7 @@ public List getPropertyDescriptors(Object object) { addTagsPropertyDescriptor(object); addCalculationOptionPropertyDescriptor(object); addDurationPropertyDescriptor(object); + addEffortPropertyDescriptor(object); } return itemPropertyDescriptors; } @@ -224,6 +225,28 @@ protected void addDurationPropertyDescriptor(Object object) { } /** + * This adds a property descriptor for the Effort feature. + * + * + * @generated + */ + protected void addEffortPropertyDescriptor(Object object) { + itemPropertyDescriptors.add + (createItemPropertyDescriptor + (((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(), + getResourceLocator(), + getString("_UI_AbstractTask_effort_feature"), + getString("_UI_PropertyDescriptor_description", "_UI_AbstractTask_effort_feature", "_UI_AbstractTask_type"), + PepperPackage.Literals.ABSTRACT_TASK__EFFORT, + true, + false, + false, + ItemPropertyDescriptor.INTEGRAL_VALUE_IMAGE, + null, + null)); + } + + /** * This specifies how to implement {@link #getChildren} and is used to deduce an appropriate feature for an * {@link org.eclipse.emf.edit.command.AddCommand}, {@link org.eclipse.emf.edit.command.RemoveCommand} or * {@link org.eclipse.emf.edit.command.MoveCommand} in {@link #createCommand}. @@ -292,6 +315,7 @@ public void notifyChanged(Notification notification) { case PepperPackage.ABSTRACT_TASK__COMPUTE_START_END_DYNAMICALLY: case PepperPackage.ABSTRACT_TASK__CALCULATION_OPTION: case PepperPackage.ABSTRACT_TASK__DURATION: + case PepperPackage.ABSTRACT_TASK__EFFORT: fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true)); return; case PepperPackage.ABSTRACT_TASK__SUB_TASKS: diff --git a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AssignableObjectItemProvider.java b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AssignableObjectItemProvider.java index e58c346..9ab7243 100644 --- a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AssignableObjectItemProvider.java +++ b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/AssignableObjectItemProvider.java @@ -51,8 +51,8 @@ public List getPropertyDescriptors(Object object) { if (itemPropertyDescriptors == null) { super.getPropertyDescriptors(object); - this.addAssignedPersonsPropertyDescriptor(object); - this.addAssignedTeamsPropertyDescriptor(object); + addAssignedPersonsPropertyDescriptor(object); + addAssignedTeamsPropertyDescriptor(object); } return itemPropertyDescriptors; } @@ -65,11 +65,11 @@ public List getPropertyDescriptors(Object object) { */ protected void addAssignedPersonsPropertyDescriptor(Object object) { itemPropertyDescriptors.add - (this.createItemPropertyDescriptor + (createItemPropertyDescriptor (((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(), - this.getResourceLocator(), - this.getString("_UI_AssignableObject_assignedPersons_feature"), - this.getString("_UI_PropertyDescriptor_description", "_UI_AssignableObject_assignedPersons_feature", "_UI_AssignableObject_type"), + getResourceLocator(), + getString("_UI_AssignableObject_assignedPersons_feature"), + getString("_UI_PropertyDescriptor_description", "_UI_AssignableObject_assignedPersons_feature", "_UI_AssignableObject_type"), PepperPackage.Literals.ASSIGNABLE_OBJECT__ASSIGNED_PERSONS, true, false, @@ -87,11 +87,11 @@ protected void addAssignedPersonsPropertyDescriptor(Object object) { */ protected void addAssignedTeamsPropertyDescriptor(Object object) { itemPropertyDescriptors.add - (this.createItemPropertyDescriptor + (createItemPropertyDescriptor (((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(), - this.getResourceLocator(), - this.getString("_UI_AssignableObject_assignedTeams_feature"), - this.getString("_UI_PropertyDescriptor_description", "_UI_AssignableObject_assignedTeams_feature", "_UI_AssignableObject_type"), + getResourceLocator(), + getString("_UI_AssignableObject_assignedTeams_feature"), + getString("_UI_PropertyDescriptor_description", "_UI_AssignableObject_assignedTeams_feature", "_UI_AssignableObject_type"), PepperPackage.Literals.ASSIGNABLE_OBJECT__ASSIGNED_TEAMS, true, false, @@ -121,8 +121,8 @@ protected boolean shouldComposeCreationImage() { public String getText(Object object) { String label = ((AssignableObject)object).getName(); return label == null || label.length() == 0 ? - this.getString("_UI_AssignableObject_type") : - this.getString("_UI_AssignableObject_type") + " " + label; + getString("_UI_AssignableObject_type") : + getString("_UI_AssignableObject_type") + " " + label; } @@ -135,7 +135,7 @@ public String getText(Object object) { */ @Override public void notifyChanged(Notification notification) { - this.updateChildren(notification); + updateChildren(notification); super.notifyChanged(notification); } diff --git a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/NamedElementItemProvider.java b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/NamedElementItemProvider.java index 6ba2e5f..42bedef 100644 --- a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/NamedElementItemProvider.java +++ b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/NamedElementItemProvider.java @@ -67,8 +67,8 @@ public List getPropertyDescriptors(Object object) { if (itemPropertyDescriptors == null) { super.getPropertyDescriptors(object); - this.addNamePropertyDescriptor(object); - this.addDescriptionPropertyDescriptor(object); + addNamePropertyDescriptor(object); + addDescriptionPropertyDescriptor(object); } return itemPropertyDescriptors; } @@ -81,11 +81,11 @@ public List getPropertyDescriptors(Object object) { */ protected void addNamePropertyDescriptor(Object object) { itemPropertyDescriptors.add - (this.createItemPropertyDescriptor + (createItemPropertyDescriptor (((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(), - this.getResourceLocator(), - this.getString("_UI_NamedElement_name_feature"), - this.getString("_UI_PropertyDescriptor_description", "_UI_NamedElement_name_feature", "_UI_NamedElement_type"), + getResourceLocator(), + getString("_UI_NamedElement_name_feature"), + getString("_UI_PropertyDescriptor_description", "_UI_NamedElement_name_feature", "_UI_NamedElement_type"), PepperPackage.Literals.NAMED_ELEMENT__NAME, true, false, @@ -103,11 +103,11 @@ protected void addNamePropertyDescriptor(Object object) { */ protected void addDescriptionPropertyDescriptor(Object object) { itemPropertyDescriptors.add - (this.createItemPropertyDescriptor + (createItemPropertyDescriptor (((ComposeableAdapterFactory)adapterFactory).getRootAdapterFactory(), - this.getResourceLocator(), - this.getString("_UI_NamedElement_description_feature"), - this.getString("_UI_PropertyDescriptor_description", "_UI_NamedElement_description_feature", "_UI_NamedElement_type"), + getResourceLocator(), + getString("_UI_NamedElement_description_feature"), + getString("_UI_PropertyDescriptor_description", "_UI_NamedElement_description_feature", "_UI_NamedElement_type"), PepperPackage.Literals.NAMED_ELEMENT__DESCRIPTION, true, false, @@ -137,8 +137,8 @@ protected boolean shouldComposeCreationImage() { public String getText(Object object) { String label = ((NamedElement)object).getName(); return label == null || label.length() == 0 ? - this.getString("_UI_NamedElement_type") : - this.getString("_UI_NamedElement_type") + " " + label; + getString("_UI_NamedElement_type") : + getString("_UI_NamedElement_type") + " " + label; } @@ -151,12 +151,12 @@ public String getText(Object object) { */ @Override public void notifyChanged(Notification notification) { - this.updateChildren(notification); + updateChildren(notification); switch (notification.getFeatureID(NamedElement.class)) { case PepperPackage.NAMED_ELEMENT__NAME: case PepperPackage.NAMED_ELEMENT__DESCRIPTION: - this.fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true)); + fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true)); return; } super.notifyChanged(notification); diff --git a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/WorkpackageItemProvider.java b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/WorkpackageItemProvider.java index 632201c..97e702a 100644 --- a/backend/pepper-edit/src/main/java/pepper/peppermm/provider/WorkpackageItemProvider.java +++ b/backend/pepper-edit/src/main/java/pepper/peppermm/provider/WorkpackageItemProvider.java @@ -156,7 +156,7 @@ protected void addEffortPropertyDescriptor(Object object) { true, false, false, - ItemPropertyDescriptor.GENERIC_VALUE_IMAGE, + ItemPropertyDescriptor.INTEGRAL_VALUE_IMAGE, null, null)); } diff --git a/backend/pepper-edit/src/main/resources/plugin.properties b/backend/pepper-edit/src/main/resources/plugin.properties index 641e283..fed2cc1 100644 --- a/backend/pepper-edit/src/main/resources/plugin.properties +++ b/backend/pepper-edit/src/main/resources/plugin.properties @@ -183,3 +183,4 @@ _UI_NamedElement_description_feature=Description _UI_AssignableObject_type=Assignable Object _UI_AssignableObject_assignedTeams_feature=Assigned Teams _UI_AssignableObject_assignedPersons_feature=Assigned Persons +_UI_AbstractTask_effort_feature=Effort diff --git a/backend/pepper-edit/src/main/resources/plugin_fr.properties b/backend/pepper-edit/src/main/resources/plugin_fr.properties index e38762d..d7dad98 100644 --- a/backend/pepper-edit/src/main/resources/plugin_fr.properties +++ b/backend/pepper-edit/src/main/resources/plugin_fr.properties @@ -182,3 +182,4 @@ _UI_NamedElement_description_feature=Description _UI_AssignableObject_type=Assignable Object _UI_AssignableObject_assignedTeams_feature=Equipes Assign\u00E9es _UI_AssignableObject_assignedPersons_feature=Personnes Assignées +_UI_AbstractTask_effort_feature=Effort diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/AbstractTask.java b/backend/pepper-mm/src/main/java/pepper/peppermm/AbstractTask.java index e3c06ba..bed3460 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/AbstractTask.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/AbstractTask.java @@ -33,6 +33,7 @@ *
  • {@link pepper.peppermm.AbstractTask#getSubTasks Sub Tasks}
  • *
  • {@link pepper.peppermm.AbstractTask#getCalculationOption Calculation Option}
  • *
  • {@link pepper.peppermm.AbstractTask#getDuration Duration}
  • + *
  • {@link pepper.peppermm.AbstractTask#getEffort Effort}
  • * * * @see pepper.peppermm.PepperPackage#getAbstractTask() @@ -195,4 +196,26 @@ public interface AbstractTask extends AssignableObject { */ void setDuration(int value); + /** + * Returns the value of the 'Effort' attribute. + * + * + * @return the value of the 'Effort' attribute. + * @see #setEffort(int) + * @see pepper.peppermm.PepperPackage#getAbstractTask_Effort() + * @model + * @generated + */ + int getEffort(); + + /** + * Sets the value of the '{@link pepper.peppermm.AbstractTask#getEffort Effort}' attribute. + * + * + * @param value the new value of the 'Effort' attribute. + * @see #getEffort() + * @generated + */ + void setEffort(int value); + } // AbstractTask diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/PepperPackage.java b/backend/pepper-mm/src/main/java/pepper/peppermm/PepperPackage.java index 55e7372..85afc55 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/PepperPackage.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/PepperPackage.java @@ -624,13 +624,22 @@ public interface PepperPackage extends EPackage { int ABSTRACT_TASK__DURATION = ASSIGNABLE_OBJECT_FEATURE_COUNT + 7; /** + * The feature id for the 'Effort' attribute. + * + * + * @generated + * @ordered + */ + int ABSTRACT_TASK__EFFORT = ASSIGNABLE_OBJECT_FEATURE_COUNT + 8; + + /** * The number of structural features of the 'Abstract Task' class. * * @generated * @ordered */ - int ABSTRACT_TASK_FEATURE_COUNT = ASSIGNABLE_OBJECT_FEATURE_COUNT + 8; + int ABSTRACT_TASK_FEATURE_COUNT = ASSIGNABLE_OBJECT_FEATURE_COUNT + 9; /** * The meta object id for the '{@link pepper.peppermm.impl.TagFolderImpl Tag Folder}' class. + * + * @generated + * @ordered + */ + int TASK__EFFORT = ABSTRACT_TASK__EFFORT; + + /** * The feature id for the 'Dependencies' reference list. * @@ -951,6 +969,15 @@ public interface PepperPackage extends EPackage { int OBJECTIVE__DURATION = ABSTRACT_TASK__DURATION; /** + * The feature id for the 'Effort' attribute. + * + * + * @generated + * @ordered + */ + int OBJECTIVE__EFFORT = ABSTRACT_TASK__EFFORT; + + /** * The feature id for the 'Owned Key Results' containment reference list. * * @@ -1080,6 +1107,15 @@ public interface PepperPackage extends EPackage { int KEY_RESULT__DURATION = ABSTRACT_TASK__DURATION; /** + * The feature id for the 'Effort' attribute. + * + * + * @generated + * @ordered + */ + int KEY_RESULT__EFFORT = ABSTRACT_TASK__EFFORT; + + /** * The number of structural features of the 'Key Result' class. * @@ -2248,6 +2284,17 @@ public interface PepperPackage extends EPackage { EAttribute getAbstractTask_Duration(); /** + * Returns the meta object for the attribute '{@link pepper.peppermm.AbstractTask#getEffort Effort}'. + * + * + * @return the meta object for the attribute 'Effort'. + * @see pepper.peppermm.AbstractTask#getEffort() + * @see #getAbstractTask() + * @generated + */ + EAttribute getAbstractTask_Effort(); + + /** * Returns the meta object for class '{@link pepper.peppermm.TagFolder Tag Folder}'. * @@ -3465,6 +3512,14 @@ interface Literals { EAttribute ABSTRACT_TASK__DURATION = eINSTANCE.getAbstractTask_Duration(); /** + * The meta object literal for the 'Effort' attribute feature. + * + * + * @generated + */ + EAttribute ABSTRACT_TASK__EFFORT = eINSTANCE.getAbstractTask_Effort(); + + /** * The meta object literal for the '{@link pepper.peppermm.impl.TagFolderImpl Tag Folder}' class. * diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/Workpackage.java b/backend/pepper-mm/src/main/java/pepper/peppermm/Workpackage.java index 71c2a2c..5a233b3 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/Workpackage.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/Workpackage.java @@ -110,25 +110,24 @@ public interface Workpackage extends AssignableObject, DependencyRelatedObject { * Returns the value of the 'Effort' attribute. * * @return the value of the 'Effort' attribute. - * @see #setEffort(Integer) + * @see #setEffort(int) * @see pepper.peppermm.PepperPackage#getWorkpackage_Effort() * @model * @generated */ - Integer getEffort(); + int getEffort(); /** - * Sets the value of the '{@link Workpackage#getEffort Effort}' attribute. - * - * @param value - * the new value of the 'Effort' attribute. - * @see #getEffort() - * @generated - */ - void setEffort(Integer value); + * Sets the value of the '{@link pepper.peppermm.Workpackage#getEffort Effort}' attribute. + * + * + * @param value the new value of the 'Effort' attribute. + * @see #getEffort() + * @generated + */ + void setEffort(int value); - /** + /** * Returns the value of the 'Outputs' containment reference list. * The list contents are of type {@link pepper.peppermm.WorkpackageArtefact}. * diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AbstractTaskImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AbstractTaskImpl.java index 29a42da..5b86f09 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AbstractTaskImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AbstractTaskImpl.java @@ -49,6 +49,7 @@ *
  • {@link pepper.peppermm.impl.AbstractTaskImpl#getSubTasks Sub Tasks}
  • *
  • {@link pepper.peppermm.impl.AbstractTaskImpl#getCalculationOption Calculation Option}
  • *
  • {@link pepper.peppermm.impl.AbstractTaskImpl#getDuration Duration}
  • + *
  • {@link pepper.peppermm.impl.AbstractTaskImpl#getEffort Effort}
  • * * * @generated @@ -193,6 +194,26 @@ public abstract class AbstractTaskImpl extends AssignableObjectImpl implements A protected int duration = DURATION_EDEFAULT; /** + * The default value of the '{@link #getEffort() Effort}' attribute. + * + * + * @see #getEffort() + * @generated + * @ordered + */ + protected static final int EFFORT_EDEFAULT = 0; + + /** + * The cached value of the '{@link #getEffort() Effort}' attribute. + * + * + * @see #getEffort() + * @generated + * @ordered + */ + protected int effort = EFFORT_EDEFAULT; + + /** * * @generated */ @@ -366,6 +387,29 @@ public void setDuration(int newDuration) { } /** + * + * + * @generated + */ + @Override + public int getEffort() { + return effort; + } + + /** + * + * + * @generated + */ + @Override + public void setEffort(int newEffort) { + int oldEffort = effort; + effort = newEffort; + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.ABSTRACT_TASK__EFFORT, oldEffort, effort)); + } + + /** * * @generated */ @@ -401,6 +445,8 @@ public Object eGet(int featureID, boolean resolve, boolean coreType) { return getCalculationOption(); case PepperPackage.ABSTRACT_TASK__DURATION: return getDuration(); + case PepperPackage.ABSTRACT_TASK__EFFORT: + return getEffort(); } return super.eGet(featureID, resolve, coreType); } @@ -439,6 +485,9 @@ public void eSet(int featureID, Object newValue) { case PepperPackage.ABSTRACT_TASK__DURATION: setDuration((Integer)newValue); return; + case PepperPackage.ABSTRACT_TASK__EFFORT: + setEffort((Integer)newValue); + return; } super.eSet(featureID, newValue); } @@ -474,6 +523,9 @@ public void eUnset(int featureID) { case PepperPackage.ABSTRACT_TASK__DURATION: setDuration(DURATION_EDEFAULT); return; + case PepperPackage.ABSTRACT_TASK__EFFORT: + setEffort(EFFORT_EDEFAULT); + return; } super.eUnset(featureID); } @@ -501,6 +553,8 @@ public boolean eIsSet(int featureID) { return calculationOption != CALCULATION_OPTION_EDEFAULT; case PepperPackage.ABSTRACT_TASK__DURATION: return duration != DURATION_EDEFAULT; + case PepperPackage.ABSTRACT_TASK__EFFORT: + return effort != EFFORT_EDEFAULT; } return super.eIsSet(featureID); } @@ -526,6 +580,8 @@ public String toString() { result.append(calculationOption); result.append(", duration: "); result.append(duration); + result.append(", effort: "); + result.append(effort); result.append(')'); return result.toString(); } diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AssignableObjectImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AssignableObjectImpl.java index fce267b..ca3e575 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AssignableObjectImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/AssignableObjectImpl.java @@ -112,9 +112,9 @@ public EList getAssignedTeams() { public Object eGet(int featureID, boolean resolve, boolean coreType) { switch (featureID) { case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_PERSONS: - return this.getAssignedPersons(); + return getAssignedPersons(); case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_TEAMS: - return this.getAssignedTeams(); + return getAssignedTeams(); } return super.eGet(featureID, resolve, coreType); } @@ -129,12 +129,12 @@ public Object eGet(int featureID, boolean resolve, boolean coreType) { public void eSet(int featureID, Object newValue) { switch (featureID) { case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_PERSONS: - this.getAssignedPersons().clear(); - this.getAssignedPersons().addAll((Collection)newValue); + getAssignedPersons().clear(); + getAssignedPersons().addAll((Collection)newValue); return; case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_TEAMS: - this.getAssignedTeams().clear(); - this.getAssignedTeams().addAll((Collection)newValue); + getAssignedTeams().clear(); + getAssignedTeams().addAll((Collection)newValue); return; } super.eSet(featureID, newValue); @@ -149,10 +149,10 @@ public void eSet(int featureID, Object newValue) { public void eUnset(int featureID) { switch (featureID) { case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_PERSONS: - this.getAssignedPersons().clear(); + getAssignedPersons().clear(); return; case PepperPackage.ASSIGNABLE_OBJECT__ASSIGNED_TEAMS: - this.getAssignedTeams().clear(); + getAssignedTeams().clear(); return; } super.eUnset(featureID); diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/NamedElementImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/NamedElementImpl.java index 100be7c..feb37fd 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/NamedElementImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/NamedElementImpl.java @@ -48,24 +48,24 @@ public abstract class NamedElementImpl extends MinimalEObjectImpl.Container impl protected static final String NAME_EDEFAULT = null; /** - * The default value of the '{@link #getDescription() Description}' attribute. + * The cached value of the '{@link #getName() Name}' attribute. * * - * @see #getDescription() + * @see #getName() * @generated * @ordered */ - protected static final String DESCRIPTION_EDEFAULT = null; + protected String name = NAME_EDEFAULT; /** - * The cached value of the '{@link #getName() Name}' attribute. + * The default value of the '{@link #getDescription() Description}' attribute. * * - * @see #getName() + * @see #getDescription() * @generated * @ordered */ - protected String name = NAME_EDEFAULT; + protected static final String DESCRIPTION_EDEFAULT = null; /** * The cached value of the '{@link #getDescription() Description}' attribute. @@ -115,8 +115,8 @@ public String getName() { public void setName(String newName) { String oldName = name; name = newName; - if (this.eNotificationRequired()) - this.eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.NAMED_ELEMENT__NAME, oldName, name)); + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.NAMED_ELEMENT__NAME, oldName, name)); } /** @@ -138,8 +138,8 @@ public String getDescription() { public void setDescription(String newDescription) { String oldDescription = description; description = newDescription; - if (this.eNotificationRequired()) - this.eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.NAMED_ELEMENT__DESCRIPTION, oldDescription, description)); + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.NAMED_ELEMENT__DESCRIPTION, oldDescription, description)); } /** @@ -151,9 +151,9 @@ public void setDescription(String newDescription) { public Object eGet(int featureID, boolean resolve, boolean coreType) { switch (featureID) { case PepperPackage.NAMED_ELEMENT__NAME: - return this.getName(); + return getName(); case PepperPackage.NAMED_ELEMENT__DESCRIPTION: - return this.getDescription(); + return getDescription(); } return super.eGet(featureID, resolve, coreType); } @@ -167,10 +167,10 @@ public Object eGet(int featureID, boolean resolve, boolean coreType) { public void eSet(int featureID, Object newValue) { switch (featureID) { case PepperPackage.NAMED_ELEMENT__NAME: - this.setName((String)newValue); + setName((String)newValue); return; case PepperPackage.NAMED_ELEMENT__DESCRIPTION: - this.setDescription((String)newValue); + setDescription((String)newValue); return; } super.eSet(featureID, newValue); @@ -185,10 +185,10 @@ public void eSet(int featureID, Object newValue) { public void eUnset(int featureID) { switch (featureID) { case PepperPackage.NAMED_ELEMENT__NAME: - this.setName(NAME_EDEFAULT); + setName(NAME_EDEFAULT); return; case PepperPackage.NAMED_ELEMENT__DESCRIPTION: - this.setDescription(DESCRIPTION_EDEFAULT); + setDescription(DESCRIPTION_EDEFAULT); return; } super.eUnset(featureID); @@ -203,9 +203,9 @@ public void eUnset(int featureID) { public boolean eIsSet(int featureID) { switch (featureID) { case PepperPackage.NAMED_ELEMENT__NAME: - return !Objects.equals(NAME_EDEFAULT, name); + return NAME_EDEFAULT == null ? name != null : !NAME_EDEFAULT.equals(name); case PepperPackage.NAMED_ELEMENT__DESCRIPTION: - return !Objects.equals(DESCRIPTION_EDEFAULT, description); + return DESCRIPTION_EDEFAULT == null ? description != null : !DESCRIPTION_EDEFAULT.equals(description); } return super.eIsSet(featureID); } @@ -217,14 +217,15 @@ public boolean eIsSet(int featureID) { */ @Override public String toString() { - if (this.eIsProxy()) return super.toString(); - - String result = super.toString() + " (name: " - + name - + ", description: " - + description - + ')'; - return result; + if (eIsProxy()) return super.toString(); + + StringBuilder result = new StringBuilder(super.toString()); + result.append(" (name: "); + result.append(name); + result.append(", description: "); + result.append(description); + result.append(')'); + return result.toString(); } } //NamedElementImpl diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java index 372da79..8df70cd 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java @@ -606,6 +606,16 @@ public EAttribute getAbstractTask_Duration() { } /** + * + * + * @generated + */ + @Override + public EAttribute getAbstractTask_Effort() { + return (EAttribute)abstractTaskEClass.getEStructuralFeatures().get(8); + } + + /** * * @generated */ @@ -1488,6 +1498,7 @@ public void createPackageContents() { createEReference(abstractTaskEClass, ABSTRACT_TASK__SUB_TASKS); createEAttribute(abstractTaskEClass, ABSTRACT_TASK__CALCULATION_OPTION); createEAttribute(abstractTaskEClass, ABSTRACT_TASK__DURATION); + createEAttribute(abstractTaskEClass, ABSTRACT_TASK__EFFORT); tagFolderEClass = createEClass(TAG_FOLDER); createEAttribute(tagFolderEClass, TAG_FOLDER__NAME); @@ -1682,6 +1693,7 @@ public void initializePackageContents() { initEReference(getAbstractTask_SubTasks(), this.getTask(), null, "subTasks", null, 0, -1, AbstractTask.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEAttribute(getAbstractTask_CalculationOption(), this.getTaskTimeBoundariesConstraint(), "calculationOption", "START_END", 0, 1, AbstractTask.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEAttribute(getAbstractTask_Duration(), ecorePackage.getEInt(), "duration", null, 0, 1, AbstractTask.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + initEAttribute(getAbstractTask_Effort(), ecorePackage.getEInt(), "effort", null, 0, 1, AbstractTask.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEClass(tagFolderEClass, TagFolder.class, "TagFolder", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS); initEAttribute(getTagFolder_Name(), ecorePackage.getEString(), "name", null, 0, 1, TagFolder.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); @@ -1735,7 +1747,7 @@ public void initializePackageContents() { initEAttribute(getWorkpackage_StartDate(), this.getDate(), "startDate", null, 0, 1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEAttribute(getWorkpackage_EndDate(), this.getDate(), "endDate", null, 0, 1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEReference(getWorkpackage_Leader(), this.getPerson(), null, "leader", null, 0, 1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_COMPOSITE, IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); - initEAttribute(getWorkpackage_Effort(), ecorePackage.getEIntegerObject(), "effort", null, 0, 1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + initEAttribute(getWorkpackage_Effort(), ecorePackage.getEInt(), "effort", null, 0, 1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEReference(getWorkpackage_Outputs(), this.getWorkpackageArtefact(), null, "outputs", null, 0, -1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEReference(getWorkpackage_OwnedTasks(), this.getTask(), null, "ownedTasks", null, 0, -1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEReference(getWorkpackage_OwnedObjectives(), this.getObjective(), null, "ownedObjectives", null, 0, -1, Workpackage.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/WorkpackageImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/WorkpackageImpl.java index f7d79b3..368cfcc 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/WorkpackageImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/WorkpackageImpl.java @@ -130,7 +130,7 @@ public class WorkpackageImpl extends AssignableObjectImpl implements Workpackage * @generated * @ordered */ - protected static final Integer EFFORT_EDEFAULT = null; + protected static final int EFFORT_EDEFAULT = 0; /** * The cached value of the '{@link #getEffort() Effort}' attribute. @@ -140,7 +140,7 @@ public class WorkpackageImpl extends AssignableObjectImpl implements Workpackage * @generated * @ordered */ - protected Integer effort = EFFORT_EDEFAULT; + protected int effort = EFFORT_EDEFAULT; /** * The cached value of the '{@link #getOutputs() Outputs}' containment reference list. @@ -412,23 +412,24 @@ public void setLeader(Person newLeader) { * @generated */ @Override - public Integer getEffort() { + public int getEffort() { return effort; } /** - * + * + * * @generated */ - @Override - public void setEffort(Integer newEffort) { - Integer oldEffort = effort; + @Override + public void setEffort(int newEffort) { + int oldEffort = effort; effort = newEffort; if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, PepperPackage.WORKPACKAGE__EFFORT, oldEffort, effort)); } - /** + /** * * @generated */ @@ -690,7 +691,7 @@ public boolean eIsSet(int featureID) { case PepperPackage.WORKPACKAGE__LEADER: return leader != null; case PepperPackage.WORKPACKAGE__EFFORT: - return EFFORT_EDEFAULT == null ? effort != null : !EFFORT_EDEFAULT.equals(effort); + return effort != EFFORT_EDEFAULT; case PepperPackage.WORKPACKAGE__OUTPUTS: return outputs != null && !outputs.isEmpty(); case PepperPackage.WORKPACKAGE__OWNED_TASKS: diff --git a/backend/pepper-mm/src/main/resources/model/pepper.ecore b/backend/pepper-mm/src/main/resources/model/pepper.ecore index b80d84f..500463b 100644 --- a/backend/pepper-mm/src/main/resources/model/pepper.ecore +++ b/backend/pepper-mm/src/main/resources/model/pepper.ecore @@ -54,6 +54,7 @@ + @@ -120,7 +121,7 @@ - + + From 27bcf52a1ea5dd1697eff38a23e2c21e760ad2bb Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Thu, 27 Aug 2026 17:29:35 +0200 Subject: [PATCH 5/8] Remove unused code --- .../services/NonWorkingDaysService.java | 46 ------------------- .../services/NonWorkingDaysServiceTests.java | 32 ------------- 2 files changed, 78 deletions(-) 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 index 0dc29f3..5afe200 100644 --- 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 @@ -256,52 +256,6 @@ public Instant getStartTime(Instant endTime, int durationInHours) { 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); } 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 index 3f070df..e66322e 100644 --- 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 @@ -216,36 +216,4 @@ public void getStartTimeSkipsNonWorkingDays() { 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)); - } } From 3ec0ca31b06f336a65c8107de47bfeca7f6e92e2 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Fri, 28 Aug 2026 11:37:54 +0200 Subject: [PATCH 6/8] [60] Make calculation option based on effort instead of duration Duration is now always calculated from start and end Duration widget is added for AbstractTask and Workspace and is readonly. Issue: https://github.com/ObeoNetwork/pepper/issues/60 --- CHANGELOG.adoc | 1 + backend/pepper-domain-services/README.md | 30 +- .../services/NonWorkingDaysService.java | 295 +++++++++++++----- .../services/TaskComputationService.java | 53 ++-- .../WorkpackageComputationService.java | 62 ++-- .../services/NonWorkingDaysServiceTests.java | 264 ++++++++++++++-- .../services/TaskComputationServiceTests.java | 38 +-- .../src/main/resources/plugin.properties | 10 +- .../src/main/resources/plugin_fr.properties | 52 +-- .../TaskTimeBoundariesConstraint.java | 56 ++-- .../peppermm/impl/PepperPackageImpl.java | 4 +- .../src/main/resources/model/pepper.ecore | 4 +- .../src/main/resources/model/pepper.genmodel | 4 +- .../starter/messages/MessageConstants.java | 1 + .../representations/PepperMMJavaService.java | 62 ++-- .../deck/ViewDeckDescriptionBuilder.java | 2 +- .../AbstractTaskPropertiesConfigurer.java | 57 +++- .../DependencyLinkPropertiesConfigurer.java | 6 +- .../WorkpackagePropertiesConfigurer.java | 57 +++- .../messages/pepper-starter.properties | 5 +- .../messages/pepper-starter_fr.properties | 3 +- .../view/PepperMMJavaServiceTests.java | 16 +- 22 files changed, 747 insertions(+), 335 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index e0d6a56..76e4a35 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,7 @@ - https://github.com/ObeoNetwork/pepper/issues/90[#90] Display the duration of AbstractTask in days, rounding to the nearest half-day. - 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. - https://github.com/ObeoNetwork/pepper/issues/94[#94] Rename the dependency link duration to dependency link delay and display the delay in days +- https://github.com/ObeoNetwork/pepper/issues/60[#60] Take Resource (including Unavailability period) into account for effort commutation === Bug fixes diff --git a/backend/pepper-domain-services/README.md b/backend/pepper-domain-services/README.md index e6cc578..33f94ef 100644 --- a/backend/pepper-domain-services/README.md +++ b/backend/pepper-domain-services/README.md @@ -9,14 +9,30 @@ A task is constrained by two `TimeConstraint` values among: - `START` - `END` -- `DURATION` +- `EFFORT` If a constrained boundary (`START` or `END`) is also constrained by a dependency, the dependency constraint is considered stronger than the task constraint. -In that case, the duration is no longer considered constraining. +In that case, the effort is no longer considered constraining. A boundary constrained by a dependency can not be changed directly. -For example: A START-DURATION task has its end date constrained by a dependency. If the start date is moved, the end date remains unchanged and the duration is updated accordingly. +For example: A START-EFFORT task has its end date constrained by a dependency. If the start date is moved, the end date remains unchanged and the effort is updated accordingly. + +## Task bounds computation + +An AbstractTask has its bounds defined as Instant. +When modifying the task, either from Gantt, details view or by the algorithm, the AbstractTask bounds are rounded to the closest half-day. +Non-working days (in week and configured fixed non-working days) do not consume any effort. + +The workpackage has its bounds defined as LocalDate +Both workpackage startDate and endDate are included. + +### Task with assigned persons + +If a task has assigned persons, then the calculation of time constraints will consider the unavailability periods of Person. +An unavailability period does not consume any effort. +On the contrary if multiple persons are available on a task, the effort is more consumed. +If no person is assigned, one working day consume an effort of one day. ## Gantt interactions @@ -24,12 +40,12 @@ For example: A START-DURATION task has its end date constrained by a dependency. - If the task is constrained by dependencies nothing is done. - Otherwise, the constraining boundary or boundaries are updated according to the task calculation option: - -- `START-END`: both boundaries are updated. The duration may change if the number of included non-working days changes. - -- `START-DURATION` and `END-DURATION`: the constraining boundary is updated. The constrained boundary may move by more than the drag delta if the number of included non-working days changes, because the duration is preserved. + -- `START-END`: both boundaries are updated. The effort may change if the number of included non-working days changes. + -- `START-EFFORT` and `END-EFFORT`: the constraining boundary is updated. The constrained boundary may move by more than the drag delta if the number of included non-working days changes, because the effort is preserved. ### Changing one task boundary - If the boundary is constraining, this boundary is updated and the other constraint is preserved. -- If the opposite boundary is constrained by a dependency, the moved boundary is updated and the duration is updated as well. -- Otherwise, the change is interpreted as a user intent to update the duration by the move delta. +- If the opposite boundary is constrained by a dependency, the moved boundary is updated and the effort is updated as well. +- Otherwise, the change is interpreted as a user intent to update the effort by the move delta. -- [FUTURE ENHANCEMENT] A global option could forbid changing a non-constraining boundary directly. In that mode, moving such a task boundary would not be allowed. 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 index 5afe200..11e937a 100644 --- 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 @@ -21,12 +21,14 @@ import java.time.temporal.ChronoUnit; import java.util.List; +import pepper.peppermm.Person; + /** * Service that manages the non working days. + * * @author lfasani */ public class NonWorkingDaysService { - /** * National public holidays in metropolitan France for 2026. */ @@ -46,14 +48,44 @@ public class NonWorkingDaysService { 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 + * Returns the effort 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. + * When assignedPersons is provided, days that correspond of unavailability period of all the persons are also excluded. + * + * @param startTime + * the start of the interval + * @param endTime + * the end of the interval + * @return the effort spent on working days rounded to the closest hour, or {@link Duration#ZERO} for a null or empty interval + */ + public Duration getEffort(Instant startTime, Instant endTime, List assignedPersons) { + if (startTime == null || endTime == null || !endTime.isAfter(startTime)) { + return Duration.ZERO; + } + + Duration effort = 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; + int nbWorkingPersons = this.getNbWorkingPersons(currentDate, assignedPersons); + if (nbWorkingPersons > 0) { + effort = effort.plus(Duration.ofHours(Duration.between(currentTime, intervalEnd).toHours() * nbWorkingPersons)); + } + currentTime = intervalEnd; + } + return this.roundToNearestHalfDay(effort); + } + + /** + * 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 + * the start of the interval * @param endTime - * the end of the interval + * 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) { @@ -67,7 +99,7 @@ public Duration getDuration(Instant startTime, Instant 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)) { + if (this.isWorkingDay(currentDate, List.of())) { duration = duration.plus(Duration.between(currentTime, intervalEnd)); } currentTime = intervalEnd; @@ -76,17 +108,40 @@ public Duration getDuration(Instant startTime, Instant endTime) { } /** - * Returns the duration of the working days from {@code startDate} to {@code endDate}, with both - * boundary dates included. Therefore, equal working dates produce a duration of one day. + * Returns the effort of the working days from {@code startDate} to {@code endDate}, with both boundary dates included. Therefore, equal working dates produce a effort of one day. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param startDate - * the start boundary + * 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 - * or reversed interval + * the end boundary + * @return the effort of the working days in the interval, or {@link Duration#ZERO} for a null or reversed interval */ - public Duration getDuration(LocalDate startDate, LocalDate endDate) { + public Duration getEffort(LocalDate startDate, LocalDate endDate, List persons) { + if (startDate == null || endDate == null || endDate.isBefore(startDate)) { + return Duration.ZERO; + } + + Duration effort = Duration.ZERO; + LocalDate currentDate = startDate; + while (!currentDate.isAfter(endDate)) { + int nbWorkingPersons = this.getNbWorkingPersons(currentDate, persons); + effort = effort.plusDays(nbWorkingPersons); + currentDate = currentDate.plusDays(1); + } + return effort; + } + + /** + * Returns the duration of the working days from {@code startDate} to {@code endDate}, with both boundary dates included. Therefore, equal 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 or reversed interval + */ + public Duration getDuration(LocalDate startDate, LocalDate endDate, List persons) { if (startDate == null || endDate == null || endDate.isBefore(startDate)) { return Duration.ZERO; } @@ -94,7 +149,7 @@ public Duration getDuration(LocalDate startDate, LocalDate endDate) { Duration duration = Duration.ZERO; LocalDate currentDate = startDate; while (!currentDate.isAfter(endDate)) { - if (!this.isNonWorkingDay(currentDate)) { + if (this.isWorkingDay(currentDate, persons)) { duration = duration.plusDays(1); } currentDate = currentDate.plusDays(1); @@ -103,52 +158,58 @@ public Duration getDuration(LocalDate startDate, LocalDate endDate) { } /** - * 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. + * 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 effort. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param startTime - * the non-null start of the interval - * @param durationInHours - * the number of working hours to add + * the non-null start of the interval + * @param effortInHours + * 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); + public Instant getNextEndTime(Instant startTime, int effortInHours, List persons) { + if (startTime == null) { + return null; + } + + Duration remainingDuration = Duration.ofHours(effortInHours); 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; + Instant nextHalfDayStart = currentEndTime.truncatedTo(ChronoUnit.HALF_DAYS).plus(1, ChronoUnit.HALF_DAYS); + int nbWorkingPersons = this.getNbWorkingPersons(currentDate, persons); + if (nbWorkingPersons == 0) { + currentEndTime = nextHalfDayStart; } else { - Duration availableDuration = Duration.between(currentEndTime, nextDayStart); + Duration availableDuration = Duration.ofHours(Duration.between(currentEndTime, nextHalfDayStart).toHours() * nbWorkingPersons); Duration consumedDuration = remainingDuration.compareTo(availableDuration) < 0 ? remainingDuration : availableDuration; - currentEndTime = currentEndTime.plus(consumedDuration); remainingDuration = remainingDuration.minus(consumedDuration); + currentEndTime = nextHalfDayStart; } } 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. + * 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. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param instant - * the non-null instant to evaluate + * the non-null instant to evaluate + * @param persons * @return the supplied instant or the next valid end time */ - public Instant getNextEndTime(Instant instant) { + public Instant getNextEndTime(Instant instant, List persons) { if (instant == null) { return null; } Instant nextEndTime = instant; - if (this.isNonWorkingDay(nextEndTime.minus(1, ChronoUnit.MINUTES).atZone(ZoneOffset.UTC).toLocalDate())) { + if (!this.isWorkingDay(nextEndTime.minus(1, ChronoUnit.MINUTES).atZone(ZoneOffset.UTC).toLocalDate(), persons)) { nextEndTime = instant.plus(6, ChronoUnit.HOURS).truncatedTo(ChronoUnit.HALF_DAYS); - while (this.isNonWorkingDay(nextEndTime.atZone(ZoneOffset.UTC).toLocalDate())) { + while (!this.isWorkingDay(nextEndTime.atZone(ZoneOffset.UTC).toLocalDate(), persons)) { nextEndTime = nextEndTime.plus(1, ChronoUnit.HALF_DAYS); } nextEndTime = nextEndTime.plus(1, ChronoUnit.HALF_DAYS); @@ -157,22 +218,21 @@ public Instant getNextEndTime(Instant instant) { } /** - * 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. + * 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. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param instant - * the non-null instant to evaluate + * the non-null instant to evaluate * @return the supplied instant or the previous valid start time */ - public Instant getPreviousStartTime(Instant instant) { + public Instant getPreviousStartTime(Instant instant, List persons) { if (instant == null) { return null; } Instant previousStartTime = instant; - if (this.isNonWorkingDay(instant.atZone(ZoneOffset.UTC).toLocalDate())) { + if (!this.isWorkingDay(instant.atZone(ZoneOffset.UTC).toLocalDate(), persons)) { previousStartTime = instant.truncatedTo(ChronoUnit.HALF_DAYS); - while (this.isNonWorkingDay(previousStartTime.atZone(ZoneOffset.UTC).toLocalDate())) { + while (!this.isWorkingDay(previousStartTime.atZone(ZoneOffset.UTC).toLocalDate(), persons)) { previousStartTime = previousStartTime.minus(1, ChronoUnit.HALF_DAYS); } } @@ -180,84 +240,155 @@ public Instant getPreviousStartTime(Instant instant) { } /** - * 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. + * 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 effort. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. + * + * @param endTime + * the non-null end of the interval + * @param effortInHours + * the number of working hours to subtract + * @return the resulting start time + */ + public Instant getPreviousStartTime(Instant endTime, int effortInHours, List persons) { + if (endTime == null) { + return null; + } + + Duration remainingDuration = Duration.ofHours(effortInHours); + Instant currentStartTime = endTime; + + while (!remainingDuration.isZero()) { + Instant previousHalfDayStart = currentStartTime.minusNanos(1).truncatedTo(ChronoUnit.HALF_DAYS); + LocalDate currentDate = previousHalfDayStart.atZone(ZoneOffset.UTC).toLocalDate(); + int nbWorkingPersons = this.getNbWorkingPersons(currentDate, persons); + if (nbWorkingPersons == 0) { + currentStartTime = previousHalfDayStart; + } else { + Duration availableDuration = Duration.ofHours(Duration.between(previousHalfDayStart, currentStartTime).toHours() * nbWorkingPersons); + Duration consumedDuration = remainingDuration.compareTo(availableDuration) < 0 + ? remainingDuration + : availableDuration; + remainingDuration = remainingDuration.minus(consumedDuration); + currentStartTime = previousHalfDayStart; + } + } + return currentStartTime; + } + + /** + * 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. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param startDate - * the non-null date to evaluate + * the non-null date to evaluate * @return the supplied date or the previous valid start date */ - public LocalDate getPreviousStartDate(LocalDate startDate) { + public LocalDate getPreviousStartDate(LocalDate startDate, List persons) { if (startDate == null) { return null; } LocalDate previousStartDate = startDate; - while (this.isNonWorkingDay(previousStartDate)) { + while (!this.isWorkingDay(previousStartDate, persons)) { previousStartDate = previousStartDate.minusDays(1); } return previousStartDate; } /** - * Returns the supplied end date when it is a working day. Otherwise, moves forward one day at a - * time through the non-working period and returns the next working date. The end date is included. + * Returns the inclusive start date reached after moving backward by the specified number of working days from {@code startDate}. Non-working days in week and configured fixed non-working days do + * not consume any effort. When persons is provided, each available person contributes one day of effort per calendar day. + * + * @param startDate + * the non-null date from which to move backward + * @param effortInDays + * the number of working days to subtract + * @param persons + * the assigned persons + * @return the resulting start date, or {@code null} when {@code startDate} is null + */ + public LocalDate getPreviousStartDate(LocalDate startDate, int effortInDays, List persons) { + if (startDate == null) { + return null; + } + + int remainingEffort = effortInDays; + LocalDate currentStartDate = startDate; + while (remainingEffort > 0) { + remainingEffort -= this.getNbWorkingPersons(currentStartDate, persons); + if (remainingEffort > 0) { + currentStartDate = currentStartDate.minusDays(1); + } + } + return currentStartDate; + } + + /** + * Returns the supplied end date when it is a working day. Otherwise, moves forward one day at a time through the non-working period and returns the next working date. The end date is included. + * When persons is provided, days that correspond of unavailability period of all the persons are also excluded. * * @param endDate - * the end date to evaluate + * the end date to evaluate * @return the supplied date or the next valid inclusive end date */ - public LocalDate getNextEndDate(LocalDate endDate) { + public LocalDate getNextEndDate(LocalDate endDate, List persons) { if (endDate == null) { return null; } LocalDate nextEndDate = endDate; - while (this.isNonWorkingDay(nextEndDate)) { + while (!this.isWorkingDay(nextEndDate, persons)) { 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. + * Returns the inclusive end date reached after moving forward by the specified number of working days from {@code startDate}. Non-working days in week and configured fixed non-working days do not + * consume any effort. When persons is provided, each available person contributes one day of effort per calendar day. * - * @param endTime - * the non-null end of the interval - * @param durationInHours - * the number of working hours to subtract - * @return the resulting start time + * @param startDate + * the non-null date from which to move forward + * @param effortInDays + * the number of working days to add + * @param persons + * the assigned persons + * @return the resulting end date, or {@code null} when {@code startDate} is null */ - 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(); + public LocalDate getNextEndDate(LocalDate startDate, int effortInDays, List persons) { + if (startDate == null) { + return null; + } - // 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(); + int remainingEffort = effortInDays; + LocalDate currentEndDate = startDate; + while (remainingEffort > 0) { + remainingEffort -= this.getNbWorkingPersons(currentEndDate, persons); + if (remainingEffort > 0) { + currentEndDate = currentEndDate.plusDays(1); } + } + return currentEndDate; + } - if (this.isNonWorkingDay(currentDate)) { - currentStartTime = currentDayStart; + private boolean isWorkingDay(LocalDate date, List persons) { + return this.getNbWorkingPersons(date, persons) > 0; + } + + private int getNbWorkingPersons(LocalDate date, List assignedPersons) { + long nbWorkingDays = 0; + boolean isNonWorkingDay = NON_WORKING_DAYS_IN_WEEK.contains(date.getDayOfWeek()) || FRENCH_NON_WORKING_DAYS_2026.contains(date); + if (!isNonWorkingDay) { + if (assignedPersons == null || assignedPersons.isEmpty()) { + nbWorkingDays = 1; } else { - Duration availableDuration = Duration.between(currentDayStart, currentStartTime); - Duration consumedDuration = remainingDuration.compareTo(availableDuration) < 0 - ? remainingDuration - : availableDuration; - currentStartTime = currentStartTime.minus(consumedDuration); - remainingDuration = remainingDuration.minus(consumedDuration); + nbWorkingDays = assignedPersons.stream() + .filter(person -> person.getUnavailabilityPeriods().stream() + .noneMatch(unavailabilityPeriod -> !date.isBefore(unavailabilityPeriod.getStartDate()) && !date.isAfter(unavailabilityPeriod.getEndDate()))) + .count(); } } - return currentStartTime; - } - private boolean isNonWorkingDay(LocalDate date) { - return NON_WORKING_DAYS_IN_WEEK.contains(date.getDayOfWeek()) || FRENCH_NON_WORKING_DAYS_2026.contains(date); + return Math.toIntExact(nbWorkingDays); } public Duration roundToNearestHalfDay(Duration duration) { 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 9b7c0f2..a7b8358 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 @@ -42,64 +42,75 @@ public class TaskComputationService { 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. + * Update the newStartTime and potentially effort 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(); Instant roundedNewStartTime = this.roundToNearestHalfDay(newStartTime); - Instant previousStartTime = nonWorkingDaysService.getPreviousStartTime(roundedNewStartTime); + Instant previousStartTime = nonWorkingDaysService.getPreviousStartTime(roundedNewStartTime, abstractTask.getAssignedPersons()); abstractTask.setStartTime(this.convertAccordingToTimeZone(previousStartTime)); Instant currentEndTime = this.roundToNearestHalfDay(abstractTask.getEndTime()); - int currentDuration = abstractTask.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousStartTime != null) { - Instant newEndTime = nonWorkingDaysService.getEndTime(previousStartTime, currentDuration).minus(1, ChronoUnit.MINUTES); + int currentEffort = abstractTask.getEffort(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_EFFORT) && previousStartTime != null) { + Instant newEndTime = nonWorkingDaysService.getNextEndTime(previousStartTime, currentEffort, abstractTask.getAssignedPersons()).minus(1, ChronoUnit.MINUTES); abstractTask.setEndTime(this.convertAccordingToTimeZone(newEndTime)); } else { if (currentEndTime != null && previousStartTime != null) { - long hourDuration = nonWorkingDaysService.getDuration(previousStartTime, currentEndTime).toHours(); - abstractTask.setDuration((int) hourDuration); + long hourEffort = nonWorkingDaysService.getEffort(previousStartTime, currentEndTime, abstractTask.getAssignedPersons()).toHours(); + abstractTask.setEffort((int) hourEffort); } } + + this.updateDuration(abstractTask); } /** - * 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. + * Update the endTime and potentially effort 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(); Instant roundedNewEndTime = this.roundToNearestHalfDay(newEndTime); - Instant nextEndTime = nonWorkingDaysService.getNextEndTime(roundedNewEndTime); + Instant nextEndTime = nonWorkingDaysService.getNextEndTime(roundedNewEndTime, abstractTask.getAssignedPersons()); abstractTask.setEndTime(this.convertAccordingToTimeZone(nextEndTime).minus(1, ChronoUnit.MINUTES)); Instant currentStartTime = this.roundToNearestHalfDay(abstractTask.getStartTime()); - int currentDuration = abstractTask.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextEndTime != null) { - Instant newStartTime = nonWorkingDaysService.getStartTime(nextEndTime, currentDuration); //.plus(1, ChronoUnit.MINUTES); + int currentEffort = abstractTask.getEffort(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_EFFORT) && nextEndTime != null) { + Instant newStartTime = nonWorkingDaysService.getPreviousStartTime(nextEndTime, currentEffort, abstractTask.getAssignedPersons()); //.plus(1, ChronoUnit.MINUTES); abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); } else { if (nextEndTime != null && currentStartTime != null) { - long hourDuration = nonWorkingDaysService.getDuration(currentStartTime, nextEndTime).toHours(); - abstractTask.setDuration((int) hourDuration); + long hourEffort = nonWorkingDaysService.getEffort(currentStartTime, nextEndTime, abstractTask.getAssignedPersons()).toHours(); + abstractTask.setEffort((int) hourEffort); } } + + this.updateDuration(abstractTask); + } + + private void updateDuration(AbstractTask abstractTask) { + if (abstractTask.getStartTime() != null && abstractTask.getEndTime() != null) { + long hourDuration = nonWorkingDaysService.getDuration(this.roundToNearestHalfDay(abstractTask.getStartTime()), this.roundToNearestHalfDay(abstractTask.getEndTime())).toHours(); + abstractTask.setDuration((int) hourDuration); + } } - public void updateDuration(AbstractTask abstractTask, int newDuration) { - int newDurationRouned = this.roundToNearestHalfDay(newDuration); + public void updateEffort(AbstractTask abstractTask, int newEffort) { + int newEffortRouned = this.roundToNearestHalfDay(newEffort); TaskTimeBoundariesConstraint calculationOption = abstractTask.getCalculationOption(); if (TaskTimeBoundariesConstraint.START_END.equals(calculationOption)) { return; } - abstractTask.setDuration(newDurationRouned); + abstractTask.setEffort(newEffortRouned); 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); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_EFFORT) && currentStartTime != null) { + Instant newEndTime = nonWorkingDaysService.getNextEndTime(currentStartTime, newEffortRouned, abstractTask.getAssignedPersons()).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); + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_EFFORT) && currentEndTime != null) { + Instant newStartTime = nonWorkingDaysService.getPreviousStartTime(currentEndTime, newEffortRouned, abstractTask.getAssignedPersons()); //.plus(1, ChronoUnit.MINUTES); abstractTask.setStartTime(this.convertAccordingToTimeZone(newStartTime)); } } 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 1b68b78..a800c04 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 @@ -34,70 +34,66 @@ public class WorkpackageComputationService { private final NonWorkingDaysService nonWorkingDaysService = new NonWorkingDaysService(); public void updateStartDate(Workpackage workpackage, LocalDate newStartDate) { - LocalDate previousNewStartDate = nonWorkingDaysService.getPreviousStartDate(newStartDate); + LocalDate previousNewStartDate = nonWorkingDaysService.getPreviousStartDate(newStartDate, workpackage.getAssignedPersons()); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); -// 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) || 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); -// } -// } workpackage.setStartDate(previousNewStartDate); LocalDate currentEndDate = workpackage.getEndDate(); - int currentDuration = workpackage.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION) && previousNewStartDate != null) { - LocalDate newEndDate = previousNewStartDate.plusDays(currentDuration - 1); + int currentEffort = workpackage.getEffort(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_EFFORT) && previousNewStartDate != null) { + LocalDate newEndDate = nonWorkingDaysService.getNextEndDate(previousNewStartDate, currentEffort, workpackage.getAssignedPersons()); workpackage.setEndDate(newEndDate); } else { if (currentEndDate != null && previousNewStartDate != null) { - long newDuration = nonWorkingDaysService.getDuration(previousNewStartDate, currentEndDate).toDays(); - workpackage.setDuration((int) newDuration); + long newEffort = nonWorkingDaysService.getEffort(previousNewStartDate, currentEndDate, workpackage.getAssignedPersons()).toDays(); + workpackage.setEffort((int) newEffort); } } + + this.updateDuration(workpackage); + } + + private void updateDuration(Workpackage workpackage) { + if (workpackage.getStartDate() != null && workpackage.getEndDate() != null) { + long hourDuration = nonWorkingDaysService.getDuration(workpackage.getStartDate(), workpackage.getEndDate(), workpackage.getAssignedPersons()).toHours(); + workpackage.setDuration((int) hourDuration); + } } public void updateEndDate(Workpackage workpackage, LocalDate newEndDate) { - LocalDate nextNewEndDate = nonWorkingDaysService.getNextEndDate(newEndDate); + LocalDate nextNewEndDate = nonWorkingDaysService.getNextEndDate(newEndDate, workpackage.getAssignedPersons()); TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); workpackage.setEndDate(nextNewEndDate); LocalDate currentStartDate = workpackage.getStartDate(); - int currentDuration = workpackage.getDuration(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION) && nextNewEndDate != null) { - LocalDate newStartDate = nextNewEndDate.minusDays(currentDuration - 1); + int currentEffort = workpackage.getEffort(); + if (calculationOption.equals(TaskTimeBoundariesConstraint.END_EFFORT) && nextNewEndDate != null) { + LocalDate newStartDate = nonWorkingDaysService.getPreviousStartDate(nextNewEndDate, currentEffort, workpackage.getAssignedPersons()); workpackage.setStartDate(newStartDate); } else { if (nextNewEndDate != null && currentStartDate != null) { - long newDuration = nonWorkingDaysService.getDuration(currentStartDate, nextNewEndDate).toDays(); - workpackage.setDuration((int) newDuration); + long newEffort = nonWorkingDaysService.getEffort(currentStartDate, nextNewEndDate, workpackage.getAssignedPersons()).toDays(); + workpackage.setEffort((int) newEffort); } } + + this.updateDuration(workpackage); } - public void updateDuration(Workpackage workpackage, int newDuration) { + public void updateEffort(Workpackage workpackage, int newEffort) { TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); if (TaskTimeBoundariesConstraint.START_END.equals(calculationOption)) { return; } - workpackage.setDuration(newDuration); + workpackage.setEffort(newEffort); LocalDate currentStartDate = workpackage.getStartDate(); LocalDate currentEndDate = workpackage.getEndDate(); - if (calculationOption.equals(TaskTimeBoundariesConstraint.START_DURATION)) { - LocalDate newEndDate = currentStartDate.plusDays(newDuration - 1); + if (calculationOption.equals(TaskTimeBoundariesConstraint.START_EFFORT)) { + LocalDate newEndDate = nonWorkingDaysService.getNextEndDate(currentStartDate, newEffort, workpackage.getAssignedPersons()); workpackage.setEndDate(newEndDate); - } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_DURATION)) { - LocalDate newStartDate = currentEndDate.minusDays(newDuration - 1); + } else if (calculationOption.equals(TaskTimeBoundariesConstraint.END_EFFORT)) { + LocalDate newStartDate = nonWorkingDaysService.getPreviousStartDate(currentEndDate, newEffort, workpackage.getAssignedPersons()); workpackage.setStartDate(newStartDate); } } 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 index e66322e..73ac8cb 100644 --- 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 @@ -18,9 +18,14 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; +import java.util.List; import org.junit.jupiter.api.Test; +import pepper.peppermm.PepperFactory; +import pepper.peppermm.Person; +import pepper.peppermm.UnavailabilityPeriod; + /** * Tests of {@link NonWorkingDaysService}. * @@ -29,72 +34,84 @@ @SuppressWarnings("checkstyle:MultipleStringLiterals") public class NonWorkingDaysServiceTests { + private static final LocalDate MONDAY_2026_07_06 = LocalDate.of(2026, 7, 6); + private static final LocalDate TUESDAY_2026_07_07 = LocalDate.of(2026, 7, 7); + private static final LocalDate WEDNESDAY_2026_07_08 = LocalDate.of(2026, 7, 8); + private static final LocalDate FRIDAY_2026_07_10 = LocalDate.of(2026, 7, 10); + + private static final Instant MONDAY_2026_07_06_12_00 = Instant.parse("2026-07-06T12:00:00Z"); + private static final Instant TUESDAY_2026_07_07_12_00 = Instant.parse("2026-07-07T12:00:00Z"); + private static final Instant TUESDAY_2026_07_07_00_00 = Instant.parse("2026-07-07T00:00:00Z"); + private static final Instant WEDNESDAY_2026_07_08_00_00 = Instant.parse("2026-07-08T00:00:00Z"); + private static final Instant WEDNESDAY_2026_07_08_12_00 = Instant.parse("2026-07-08T12:00:00Z"); + private static final Instant THURSDAY_2026_07_09_00_00 = Instant.parse("2026-07-09T00:00:00Z"); + @Test - public void getDurationIncludesPartialStartAndEndDays() { + public void getEffortIncludesPartialStartAndEndDays() { 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)); + assertThat(service.getEffort(startTime, endTime, List.of())).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)); + assertThat(service.getEffort(startTime, endTime, List.of())).isEqualTo(Duration.ofHours(36)); } @Test - public void getDurationBetweenInstantExcludesWeekendDays() { + public void getEffortBetweenInstantExcludesWeekendDays() { 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)); + assertThat(service.getEffort(startTime, endTime, List.of())).isEqualTo(Duration.ofDays(3)); } @Test - public void getDurationBetweenEqualWorkingDatesIsOneDay() { + public void getEffortBetweenEqualWorkingDatesIsOneDay() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 13); - assertThat(service.getDuration(date, date)).isEqualTo(Duration.ofDays(1)); + assertThat(service.getEffort(date, date, List.of())).isEqualTo(Duration.ofDays(1)); } @Test - public void getDurationBetweenConsecutiveWorkingDatesIsOneDay() { + public void getEffortBetweenConsecutiveWorkingDatesIsOneDay() { var service = new NonWorkingDaysService(); - LocalDate startDate = LocalDate.of(2026, 7, 10); + LocalDate startDate = FRIDAY_2026_07_10; - assertThat(service.getDuration(startDate, startDate.plusDays(1))).isEqualTo(Duration.ofDays(1)); + assertThat(service.getEffort(startDate, startDate.plusDays(1), List.of())).isEqualTo(Duration.ofDays(1)); } @Test - public void getDurationBetweenDatesExcludesNonWorkingDays() { + public void getEffortBetweenDatesExcludesNonWorkingDays() { var service = new NonWorkingDaysService(); // 10 and 11 are in a week-end and 14 is off - LocalDate startDate = LocalDate.of(2026, 7, 10); + LocalDate startDate = FRIDAY_2026_07_10; LocalDate endDate = LocalDate.of(2026, 7, 16); - assertThat(service.getDuration(startDate, endDate)).isEqualTo(Duration.ofDays(4)); + assertThat(service.getEffort(startDate, endDate, List.of())).isEqualTo(Duration.ofDays(4)); } @Test - public void getEndTimeSkipsWeekendDays() { + public void getNextEndTimeSkipsWeekendDays() { 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")); + assertThat(service.getNextEndTime(startTime, 23, List.of())).isEqualTo(Instant.parse("2026-08-03T12:00:00Z")); } @Test - public void getEndTimeSkipsNonWorkingDays() { + public void getNextEndTimeSkipsNonWorkingDays() { 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")); + assertThat(service.getNextEndTime(startTime, 24, List.of())).isEqualTo(Instant.parse("2026-07-15T12:00:00Z")); } @Test @@ -102,7 +119,7 @@ public void getNextEndTimeKeepsAnInstantOnAWorkingDay() { var service = new NonWorkingDaysService(); Instant instant = Instant.parse("2026-07-31T13:00:00Z"); - assertThat(service.getNextEndTime(instant)).isEqualTo(instant); + assertThat(service.getNextEndTime(instant, List.of())).isEqualTo(instant); } @Test @@ -110,7 +127,7 @@ 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")); + assertThat(service.getNextEndTime(instant, List.of())).isEqualTo(Instant.parse("2026-08-03T12:00:00Z")); } @Test @@ -118,7 +135,7 @@ 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")); + assertThat(service.getNextEndTime(instant, List.of())).isEqualTo(Instant.parse("2026-07-15T12:00:00Z")); } @Test @@ -126,7 +143,7 @@ public void getPreviousStartTimeKeepsAnInstantOnAWorkingDay() { var service = new NonWorkingDaysService(); Instant instant = Instant.parse("2026-07-31T13:00:00Z"); - assertThat(service.getPreviousStartTime(instant)).isEqualTo(instant); + assertThat(service.getPreviousStartTime(instant, List.of())).isEqualTo(instant); } @Test @@ -134,7 +151,7 @@ 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")); + assertThat(service.getPreviousStartTime(instant, List.of())).isEqualTo(Instant.parse("2026-07-31T12:00:00Z")); } @Test @@ -142,7 +159,7 @@ 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")); + assertThat(service.getPreviousStartTime(instant, List.of())).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); } @Test @@ -150,7 +167,7 @@ public void getPreviousStartDateKeepsAWorkingDate() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 31); - assertThat(service.getPreviousStartDate(date)).isEqualTo(date); + assertThat(service.getPreviousStartDate(date, List.of())).isEqualTo(date); } @Test @@ -158,7 +175,7 @@ public void getPreviousStartDateMovesBeforeAWeekend() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 8, 2); - assertThat(service.getPreviousStartDate(date)).isEqualTo(LocalDate.of(2026, 7, 31)); + assertThat(service.getPreviousStartDate(date, List.of())).isEqualTo(LocalDate.of(2026, 7, 31)); } @Test @@ -166,7 +183,7 @@ public void getPreviousStartDateMovesBeforeANonWorkingDay() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 14); - assertThat(service.getPreviousStartDate(date)).isEqualTo(LocalDate.of(2026, 7, 13)); + assertThat(service.getPreviousStartDate(date, List.of())).isEqualTo(LocalDate.of(2026, 7, 13)); } @Test @@ -174,7 +191,7 @@ public void getNextEndDateKeepsAWorkingDate() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 31); - assertThat(service.getNextEndDate(date)).isEqualTo(date); + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(date); } @Test @@ -182,7 +199,7 @@ public void getNextEndDateMovesPastAWeekend() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 8, 1); - assertThat(service.getNextEndDate(date)).isEqualTo(LocalDate.of(2026, 8, 3)); + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(LocalDate.of(2026, 8, 3)); } @Test @@ -190,7 +207,7 @@ public void getNextEndDateMovesPastANonWorkingDate() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 14); - assertThat(service.getNextEndDate(date)).isEqualTo(LocalDate.of(2026, 7, 15)); + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(LocalDate.of(2026, 7, 15)); } @Test @@ -198,22 +215,201 @@ public void getNextEndDateKeepsAWorkingDateFollowingANonWorkingDay() { var service = new NonWorkingDaysService(); LocalDate date = LocalDate.of(2026, 7, 15); - assertThat(service.getNextEndDate(date)).isEqualTo(date); + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(date); } @Test - public void getStartTimeSkipsWeekendDays() { + public void getPreviousStartTimeSkipsWeekendDays() { 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")); + assertThat(service.getPreviousStartTime(endTime, 25, List.of())).isEqualTo(Instant.parse("2026-07-31T12:00:00Z")); } @Test - public void getStartTimeSkipsNonWorkingDays() { + public void getPreviousStartTimeSkipsNonWorkingDays() { var service = new NonWorkingDaysService(); Instant endTime = Instant.parse("2026-07-15T12:00:00Z"); + assertThat(service.getPreviousStartTime(endTime, 24, List.of())).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + +// endTime = Instant.parse("2026-07-15T09:59:00Z"); +// assertThat(service.getStartTime(endTime, 24)).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + + endTime = Instant.parse("2026-07-16T00:00:00Z"); + assertThat(service.getPreviousStartTime(endTime, 36, List.of())).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + +// endTime = Instant.parse("2026-07-15T21:59:00Z"); +// assertThat(service.getStartTime(endTime, 36)).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + } + + @Test + public void getEffortBetweenInstantWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + // only non working days + Instant startTime = MONDAY_2026_07_06_12_00; + Instant endTime = Instant.parse("2026-07-10T12:00:00Z"); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getEffort(startTime, endTime, List.of())).isEqualTo(Duration.ofHours(4 * 24)); + assertThat(service.getEffort(startTime, endTime, List.of(person1))).isEqualTo(Duration.ofHours(60)); + assertThat(service.getEffort(startTime, endTime, List.of(person1, person2))).isEqualTo(Duration.ofHours(60 + 4 * 24)); + } + + private Person getPerson1() { + Person person1 = PepperFactory.eINSTANCE.createPerson(); + UnavailabilityPeriod unavailabilityPeriod = PepperFactory.eINSTANCE.createUnavailabilityPeriod(); + unavailabilityPeriod.setStartDate(TUESDAY_2026_07_07); + unavailabilityPeriod.setEndDate(TUESDAY_2026_07_07); + person1.getUnavailabilityPeriods().add(unavailabilityPeriod); + UnavailabilityPeriod unavailabilityPeriod2 = PepperFactory.eINSTANCE.createUnavailabilityPeriod(); + unavailabilityPeriod2.setStartDate(FRIDAY_2026_07_10); + unavailabilityPeriod2.setEndDate(FRIDAY_2026_07_10); + person1.getUnavailabilityPeriods().add(unavailabilityPeriod2); + return person1; + } + + @Test + public void getEffortBetweenDateWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + // only non working days + LocalDate startDate = MONDAY_2026_07_06; + LocalDate endDate = FRIDAY_2026_07_10; + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getEffort(startDate, endDate, List.of())).isEqualTo(Duration.ofDays(5)); + assertThat(service.getEffort(startDate, endDate, List.of(person1))).isEqualTo(Duration.ofDays(3)); + assertThat(service.getEffort(startDate, endDate, List.of(person1, person2))).isEqualTo(Duration.ofDays(8)); + } + + @Test + public void getNextEndDateMovesWithAssignedPersons() { + var service = new NonWorkingDaysService(); + LocalDate date = TUESDAY_2026_07_07; + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(TUESDAY_2026_07_07); + assertThat(service.getNextEndDate(date, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getNextEndDate(date, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07); + } + + @Test + public void getPreviousStartDateMovesWithAssignedPersons() { + var service = new NonWorkingDaysService(); + LocalDate date = TUESDAY_2026_07_07; + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getPreviousStartDate(date, List.of())).isEqualTo(TUESDAY_2026_07_07); + assertThat(service.getPreviousStartDate(date, List.of(person1))).isEqualTo(MONDAY_2026_07_06); + assertThat(service.getPreviousStartDate(date, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07); + } + + @Test + public void getPreviousStartDateWithEffortWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 1, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 2, List.of(person1))).isEqualTo(MONDAY_2026_07_06); + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 1, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 2, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 3, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07); + assertThat(service.getPreviousStartDate(WEDNESDAY_2026_07_08, 4, List.of(person1, person2))).isEqualTo(MONDAY_2026_07_06); + } + + @Test + public void getNextEndDateWithEffortWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 1, List.of(person1))).isEqualTo(MONDAY_2026_07_06); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 2, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 1, List.of(person1, person2))).isEqualTo(MONDAY_2026_07_06); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 2, List.of(person1, person2))).isEqualTo(MONDAY_2026_07_06); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 3, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 4, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getNextEndDate(MONDAY_2026_07_06, 5, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08); + } + + @Test + public void getNextTimeMovesWithAssignedPersons() { + var service = new NonWorkingDaysService(); + LocalDate date = TUESDAY_2026_07_07; + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getNextEndDate(date, List.of())).isEqualTo(TUESDAY_2026_07_07); + assertThat(service.getNextEndDate(date, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08); + assertThat(service.getNextEndDate(date, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07); + } + + @Test + public void getNextEndTimeWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, List.of(person1))).isEqualTo(MONDAY_2026_07_06_12_00); + assertThat(service.getNextEndTime(TUESDAY_2026_07_07_12_00, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08_12_00); + assertThat(service.getNextEndTime(TUESDAY_2026_07_07_12_00, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_12_00); + } + + @Test + public void getNextEndTimeWithEffortWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 24, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08_12_00); + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 36, List.of(person1))).isEqualTo(THURSDAY_2026_07_09_00_00); + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 36, List.of(person1))).isEqualTo(THURSDAY_2026_07_09_00_00); + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 12, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_00_00); + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 24, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_00_00); + assertThat(service.getNextEndTime(MONDAY_2026_07_06_12_00, 36, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_12_00); + } + + @Test + public void getPreviousStartTimeWithAssignedPersons() { + var service = new NonWorkingDaysService(); + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); + + assertThat(service.getPreviousStartTime(WEDNESDAY_2026_07_08_00_00, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08_00_00); + assertThat(service.getPreviousStartTime(WEDNESDAY_2026_07_08_00_00, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08_00_00); + assertThat(service.getPreviousStartTime(TUESDAY_2026_07_07_12_00, List.of(person1))).isEqualTo(MONDAY_2026_07_06_12_00); + assertThat(service.getPreviousStartTime(TUESDAY_2026_07_07_12_00, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_12_00); + } + + @Test + public void getPreviousStartTimeWithEffortWithAssignedPersons() { + var service = new NonWorkingDaysService(); + Instant startTime = WEDNESDAY_2026_07_08_12_00; + + Person person1 = this.getPerson1(); + Person person2 = PepperFactory.eINSTANCE.createPerson(); - assertThat(service.getStartTime(endTime, 24)).isEqualTo(Instant.parse("2026-07-13T12:00:00Z")); + assertThat(service.getPreviousStartTime(startTime, 12, List.of(person1))).isEqualTo(WEDNESDAY_2026_07_08_00_00); + assertThat(service.getPreviousStartTime(startTime, 24, List.of(person1))).isEqualTo(MONDAY_2026_07_06_12_00); + assertThat(service.getPreviousStartTime(startTime, 12, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08_00_00); + assertThat(service.getPreviousStartTime(startTime, 24, List.of(person1, person2))).isEqualTo(WEDNESDAY_2026_07_08_00_00); + assertThat(service.getPreviousStartTime(startTime, 36, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_12_00); + assertThat(service.getPreviousStartTime(startTime, 48, List.of(person1, person2))).isEqualTo(TUESDAY_2026_07_07_00_00); } } diff --git a/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java b/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java index 8cc82b4..3bc565e 100644 --- a/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java +++ b/backend/pepper-domain-services/src/test/java/pepper/domain/services/TaskComputationServiceTests.java @@ -45,62 +45,64 @@ private static Instant toInstant(int year, int month, int dayOfMonth, int hour, @Test - public void updateStartTimeAcrossWeekendUpdatesDurationForStartEndConstraint() { - this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_END, 24); + public void updateStartTimeAcrossWeekendUpdatesEffortForStartEndConstraint() { + this.updateStartTimeBeforeWeekendAndAssertEffort(TaskTimeBoundariesConstraint.START_END, 24); } @Test - public void updateStartTimeAcrossWeekendPreservesDurationForEndDurationConstraint() { - this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.END_DURATION, 24); + public void updateStartTimeAcrossWeekendPreservesEffortForEndEffortConstraint() { + this.updateStartTimeBeforeWeekendAndAssertEffort(TaskTimeBoundariesConstraint.END_EFFORT, 24); } @Test - public void updateStartTimeAcrossWeekendUpdatesDurationForStartDurationConstraint() { - this.updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_DURATION, 12); + public void updateStartTimeAcrossWeekendUpdatesEffortForStartEffortConstraint() { + this.updateStartTimeBeforeWeekendAndAssertEffort(TaskTimeBoundariesConstraint.START_EFFORT, 12); } - private void updateStartTimeBeforeWeekendAndAssertDuration(TaskTimeBoundariesConstraint calculationOption, int expectedDuration) { + private void updateStartTimeBeforeWeekendAndAssertEffort(TaskTimeBoundariesConstraint calculationOption, int expectedEffort) { Task task1 = this.createTaskBeginningAfterWeekend(calculationOption); taskComputationService.updateStartTime(task1, FRIDAY_2026_07_31_T12_00); - assertThat(task1.getDuration()).isEqualTo(expectedDuration); + assertThat(task1.getEffort()).isEqualTo(expectedEffort); + assertThat(task1.getDuration()).isEqualTo(expectedEffort); } private Task createTaskBeginningAfterWeekend(TaskTimeBoundariesConstraint calculationOption) { Task task1 = PepperFactory.eINSTANCE.createTask(); task1.setCalculationOption(calculationOption); - task1.setDuration(12); + task1.setEffort(12); taskComputationService.updateStartTime(task1, MONDAY_2026_08_03_T00_00); taskComputationService.updateEndTime(task1, MONDAY_2026_08_03_T12_00); return task1; } @Test - public void updateEndTimeAcrossWeekendUpdatesDurationForStartEndConstraint() { - this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_END, 36); + public void updateEndTimeAcrossWeekendUpdatesEffortForStartEndConstraint() { + this.updateEndTimePastWeekendAndAssertEffort(TaskTimeBoundariesConstraint.START_END, 36); } @Test - public void updateEndTimeAcrossWeekendPreservesDurationForEndDurationConstraint() { - this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.END_DURATION, 12); + public void updateEndTimeAcrossWeekendPreservesEffortForEndEffortConstraint() { + this.updateEndTimePastWeekendAndAssertEffort(TaskTimeBoundariesConstraint.END_EFFORT, 12); } @Test - public void updateEndTimeAcrossWeekendUpdatesDurationForStartDurationConstraint() { - this.updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint.START_DURATION, 36); + public void updateEndTimeAcrossWeekendUpdatesEffortForStartEffortConstraint() { + this.updateEndTimePastWeekendAndAssertEffort(TaskTimeBoundariesConstraint.START_EFFORT, 36); } - private void updateEndTimePastWeekendAndAssertDuration(TaskTimeBoundariesConstraint calculationOption, int expectedDuration) { + private void updateEndTimePastWeekendAndAssertEffort(TaskTimeBoundariesConstraint calculationOption, int expectedEffort) { Task task1 = this.createTaskEndingBeforeWeekend(calculationOption); taskComputationService.updateEndTime(task1, MONDAY_2026_08_03_T12_00); - assertThat(task1.getDuration()).isEqualTo(expectedDuration); + assertThat(task1.getEffort()).isEqualTo(expectedEffort); + assertThat(task1.getDuration()).isEqualTo(expectedEffort); } private Task createTaskEndingBeforeWeekend(TaskTimeBoundariesConstraint calculationOption) { Task task1 = PepperFactory.eINSTANCE.createTask(); task1.setCalculationOption(calculationOption); - task1.setDuration(12); + task1.setEffort(12); taskComputationService.updateStartTime(task1, FRIDAY_2026_07_31_T00_00); taskComputationService.updateEndTime(task1, FRIDAY_2026_07_31_T12_00); return task1; diff --git a/backend/pepper-edit/src/main/resources/plugin.properties b/backend/pepper-edit/src/main/resources/plugin.properties index fed2cc1..657ce14 100644 --- a/backend/pepper-edit/src/main/resources/plugin.properties +++ b/backend/pepper-edit/src/main/resources/plugin.properties @@ -66,6 +66,7 @@ _UI_AbstractTask_tags_feature = Tags _UI_AbstractTask_subTasks_feature = Sub Tasks _UI_AbstractTask_calculationOption_feature = Calculation Option _UI_AbstractTask_duration_feature = Duration (Days) +_UI_AbstractTask_effort_feature=Effort (Days) _UI_TagFolder_name_feature = Name _UI_TagFolder_ownedTags_feature = Owned Tags _UI_TagFolder_subFolders_feature = Sub Folders @@ -137,8 +138,8 @@ _UI_DependencyLink_delay_feature=Delay (Days) _UI_DependencyLink_delayDayUnit = Days _UI_DependencyRelatedObject_dependencies_feature = Dependencies _UI_TaskTimeBoundariesConstraint_StartEnd_feature = Start - End -_UI_TaskTimeBoundariesConstraint_EndDuration_feature = End - Duration -_UI_TaskTimeBoundariesConstraint_StartDuration_feature = Start - Duration +_UI_TaskTimeBoundariesConstraint_EndEffort_feature = End - Effort +_UI_TaskTimeBoundariesConstraint_StartEffort_feature = Start - Effort _UI_StartOrEnd_Start_feature = Start _UI_StartOrEnd_End_feature = End _UI_Unknown_feature = Unspecified @@ -170,8 +171,8 @@ _UI_WorkpackageArtefactNature_Other_literal = OTHER _UI_StartOrEnd_Start_literal = START _UI_StartOrEnd_End_literal = END _UI_TaskTimeBoundariesConstraint_StartEnd_literal = START_END -_UI_TaskTimeBoundariesConstraint_EndDuration_literal = END_DURATION -_UI_TaskTimeBoundariesConstraint_StartDuration_literal = START_DURATION +_UI_TaskTimeBoundariesConstraint_EndEffort_literal=END_EFFORT +_UI_TaskTimeBoundariesConstraint_StartEffort_literal=START_EFFORT _UI_UnavailabilityPeriod_endDate_feature=End Date _UI_Resource_unavailabilityPeriods_feature=Unavailability Periods _UI_UnavailabilityPeriod_startDate_feature=Start Date @@ -183,4 +184,3 @@ _UI_NamedElement_description_feature=Description _UI_AssignableObject_type=Assignable Object _UI_AssignableObject_assignedTeams_feature=Assigned Teams _UI_AssignableObject_assignedPersons_feature=Assigned Persons -_UI_AbstractTask_effort_feature=Effort diff --git a/backend/pepper-edit/src/main/resources/plugin_fr.properties b/backend/pepper-edit/src/main/resources/plugin_fr.properties index d7dad98..c6e4e68 100644 --- a/backend/pepper-edit/src/main/resources/plugin_fr.properties +++ b/backend/pepper-edit/src/main/resources/plugin_fr.properties @@ -65,6 +65,7 @@ _UI_AbstractTask_tags_feature = Tags _UI_AbstractTask_subTasks_feature = Sous t\u00E2ches _UI_AbstractTask_calculationOption_feature = Option de calcul _UI_AbstractTask_duration_feature = Dur\u00E9e (Jours) +_UI_AbstractTask_effort_feature=Effort (Jours) _UI_TagFolder_name_feature = Nom _UI_TagFolder_ownedTags_feature = Tags _UI_TagFolder_subFolders_feature = Sous r\u00E9pertoires @@ -73,12 +74,12 @@ _UI_TaskTag_suffix_feature = Suffix _UI_Objective_ownedKeyResults_feature = R\u00E9sultats cl\u00E9 _UI_Project_ownedWorkpackages_feature = Workpackages _UI_Project_ownedObjectives_feature = Objectifs -_UI_Project_ownedTagFolders_feature = Répertoire de tags +_UI_Project_ownedTagFolders_feature = R\u00E9pertoire de tags _UI_Project_ownedRisks_feature = Risques -_UI_Project_reference_feature = Référence -_UI_Project_leadingUnit_feature = Unité porteuse -_UI_Project_participantUnits_feature = Unités associées -_UI_Project_plannifiedClientCopilMeetings_feature = Des COPIL avec le client sont planifiés +_UI_Project_reference_feature = R\u00E9f\u00E9rence +_UI_Project_leadingUnit_feature = Unit\u00E9 porteuse +_UI_Project_participantUnits_feature = Unit\u00E9s associ\u00E9es +_UI_Project_plannifiedClientCopilMeetings_feature = Des COPIL avec le client sont planifi\u00E9s _UI_Project_mainProgramBrick_feature = Brique programe principale _UI_Project_state_feature = Statut _UI_Project_clients_feature = Clients @@ -87,12 +88,12 @@ _UI_Project_isTransverse_feature = Project transverse _UI_Project_leader_feature = Chef de projet _UI_Project_members_feature = Membres du projet _UI_Project_isSensitive_feature = Ce projet est sensible -_UI_Project_contractualStartDate_feature = Date de début contractuelle -_UI_Project_duration_feature = Durée contractuelle(semaines) +_UI_Project_contractualStartDate_feature = Date de d\u00E9but contractuelle +_UI_Project_duration_feature = Dur\u00E9e contractuelle(semaines) _UI_Project_contractualEndDate_feature = Date de fin contractuelle -_UI_Project_effectiveStartDate_feature = Date de début effective +_UI_Project_effectiveStartDate_feature = Date de d\u00E9but effective _UI_Project_effectiveEndDate_feature = Date de fin effective -_UI_Project_contractTermExtension_feature = Extention de durée contractuelle(semaines) +_UI_Project_contractTermExtension_feature = Extention de dur\u00E9e contractuelle(semaines) _UI_Project_globalCost_feature = Coût global(k\u20AC) _UI_Project_fundingRate_feature = Taux de financement(%) _UI_Project_funding_feature = Financement(k\u20AC) @@ -110,8 +111,8 @@ _UI_Workpackage_ownedTasks_feature = Taches _UI_Workpackage_ownedObjectives_feature = Objectifs _UI_Workpackage_progress_feature = Progr\u00E8s _UI_Workpackage_calculationOption_feature = Option de calcul -_UI_Workpackage_dependencyDelay_feature = Délai (Jours) -_UI_Workpackage_duration_feature = Durée (Jours) +_UI_Workpackage_dependencyDelay_feature = D\u00E9lai (Jours) +_UI_Workpackage_duration_feature = Dur\u00E9e (Jours) _UI_WorkpackageArtefact_nature_feature = Nature _UI_WorkpackageArtefact_plannedDeadline_feature = Deadline plannifi\u00E9e _UI_WorkpackageArtefact_effectiveDeadLine_feature = Deadline effective @@ -125,20 +126,20 @@ _UI_Risk_description_feature = Description _UI_Risk_criticity_feature = Criticit\u00E9 _UI_Risk_action_feature = Action _UI_Risk_responsible_feature = Responsable -_UI_Risk_operationDate_feature = Date d'opération +_UI_Risk_operationDate_feature = Date d'op\u00E9ration _UI_Risk_state_feature = Etat _UI_Risk_workpackages_feature = Workpackages _UI_DependencyLink_targetKind_feature = Type cible _UI_DependencyLink_sourceKind_feature = Type source _UI_DependencyLink_source_feature = Source _UI_DependencyLink_target_feature = Cible -_UI_DependencyLink_delay_feature=Délai (Jours) +_UI_DependencyLink_delay_feature=D\u00E9lai (Jours) _UI_DependencyLink_delayDayUnit = Jours -_UI_DependencyRelatedObject_dependencies_feature = Dépendances -_UI_TaskTimeBoundariesConstraint_StartEnd_feature = Début - Fin -_UI_TaskTimeBoundariesConstraint_EndDuration_feature = Fin - Durée -_UI_TaskTimeBoundariesConstraint_StartDuration_feature = Début - Durée -_UI_StartOrEnd_Start_feature = Début +_UI_DependencyRelatedObject_dependencies_feature = D\u00E9pendances +_UI_TaskTimeBoundariesConstraint_StartEnd_feature = D\u00E9but - Fin +_UI_TaskTimeBoundariesConstraint_EndEffort_feature = Fin - Effort +_UI_TaskTimeBoundariesConstraint_StartEffort_feature = D\u00E9but - Effort +_UI_StartOrEnd_Start_feature = D\u00E9but _UI_StartOrEnd_End_feature = Fin _UI_Unknown_feature = Unspecified @@ -169,17 +170,16 @@ _UI_ProjectState_Completed_literal = COMPLETED _UI_StartOrEnd_Start_literal = START _UI_StartOrEnd_End_literal = END _UI_TaskTimeBoundariesConstraint_StartEnd_literal = START_END -_UI_TaskTimeBoundariesConstraint_EndDuration_literal = END_DURATION -_UI_TaskTimeBoundariesConstraint_StartDuration_literal = START_DURATION +_UI_TaskTimeBoundariesConstraint_EndEffort_literal=END_EFFORT +_UI_TaskTimeBoundariesConstraint_StartEffort_literal=START_EFFORT _UI_UnavailabilityPeriod_endDate_feature=Date de fin -_UI_Resource_unavailabilityPeriods_feature=Périodes d'indisponibilité -_UI_UnavailabilityPeriod_startDate_feature=Date de début +_UI_Resource_unavailabilityPeriods_feature=P\u00E9riodes d'indisponibilit\u00E9 +_UI_UnavailabilityPeriod_startDate_feature=Date de d\u00E9but _UI_UnavailabilityPeriod_description_feature=Description -_UI_UnavailabilityPeriod_type=Période d'indisponibilité -_UI_NamedElement_type=Element nommé +_UI_UnavailabilityPeriod_type=P\u00E9riode d'indisponibilit\u00E9 +_UI_NamedElement_type=Element nomm\u00E9 _UI_NamedElement_name_feature=Nom _UI_NamedElement_description_feature=Description _UI_AssignableObject_type=Assignable Object _UI_AssignableObject_assignedTeams_feature=Equipes Assign\u00E9es -_UI_AssignableObject_assignedPersons_feature=Personnes Assignées -_UI_AbstractTask_effort_feature=Effort +_UI_AssignableObject_assignedPersons_feature=Personnes Assign\u00E9es diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/TaskTimeBoundariesConstraint.java b/backend/pepper-mm/src/main/java/pepper/peppermm/TaskTimeBoundariesConstraint.java index 9c0e10e..bbe1723 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/TaskTimeBoundariesConstraint.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/TaskTimeBoundariesConstraint.java @@ -36,22 +36,22 @@ public enum TaskTimeBoundariesConstraint implements Enumerator { START_END(0, "StartEnd", "START_END"), /** - * The 'End Duration' literal object. - * - * @see #END_DURATION_VALUE + * The 'End Effort' literal object. + * + * + * @see #END_EFFORT_VALUE * @generated * @ordered */ - END_DURATION(1, "EndDuration", "END_DURATION"), - - /** - * The 'Start Duration' literal object. - * - * @see #START_DURATION_VALUE + END_EFFORT(1, "EndEffort", "END_EFFORT"), /** + * The 'Start Effort' literal object. + * + * + * @see #START_EFFORT_VALUE * @generated * @ordered */ - START_DURATION(2, "StartDuration", "START_DURATION"); + START_EFFORT(2, "StartEffort", "START_EFFORT"); /** * The 'Start End' literal value. @@ -64,26 +64,28 @@ public enum TaskTimeBoundariesConstraint implements Enumerator { public static final int START_END_VALUE = 0; /** - * The 'End Duration' literal value. - * - * @see #END_DURATION - * @model name="EndDuration" literal="END_DURATION" + * The 'End Effort' literal value. + * + * + * @see #END_EFFORT + * @model name="EndEffort" literal="END_EFFORT" * @generated * @ordered */ - public static final int END_DURATION_VALUE = 1; - - /** - * The 'Start Duration' literal value. - * - * @see #START_DURATION - * @model name="StartDuration" literal="START_DURATION" + public static final int END_EFFORT_VALUE = 1; + + /** + * The 'Start Effort' literal value. + * + * + * @see #START_EFFORT + * @model name="StartEffort" literal="START_EFFORT" * @generated * @ordered */ - public static final int START_DURATION_VALUE = 2; + public static final int START_EFFORT_VALUE = 2; - /** + /** * An array of all the 'Task Time Boundaries Constraint' enumerators. * @@ -91,8 +93,8 @@ public enum TaskTimeBoundariesConstraint implements Enumerator { */ private static final TaskTimeBoundariesConstraint[] VALUES_ARRAY = new TaskTimeBoundariesConstraint[] { START_END, - END_DURATION, - START_DURATION, + END_EFFORT, + START_EFFORT, }; /** @@ -153,8 +155,8 @@ public static TaskTimeBoundariesConstraint getByName(String name) { public static TaskTimeBoundariesConstraint get(int value) { switch (value) { case START_END_VALUE: return START_END; - case END_DURATION_VALUE: return END_DURATION; - case START_DURATION_VALUE: return START_DURATION; + case END_EFFORT_VALUE: return END_EFFORT; + case START_EFFORT_VALUE: return START_EFFORT; } return null; } diff --git a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java index 8df70cd..4fccca5 100644 --- a/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java +++ b/backend/pepper-mm/src/main/java/pepper/peppermm/impl/PepperPackageImpl.java @@ -1828,8 +1828,8 @@ public void initializePackageContents() { initEEnum(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.class, "TaskTimeBoundariesConstraint"); addEEnumLiteral(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.START_END); - addEEnumLiteral(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.END_DURATION); - addEEnumLiteral(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.START_DURATION); + addEEnumLiteral(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.END_EFFORT); + addEEnumLiteral(taskTimeBoundariesConstraintEEnum, TaskTimeBoundariesConstraint.START_EFFORT); // Initialize data types initEDataType(dateEDataType, LocalDate.class, "Date", IS_SERIALIZABLE, !IS_GENERATED_INSTANCE_CLASS); diff --git a/backend/pepper-mm/src/main/resources/model/pepper.ecore b/backend/pepper-mm/src/main/resources/model/pepper.ecore index 500463b..836f01d 100644 --- a/backend/pepper-mm/src/main/resources/model/pepper.ecore +++ b/backend/pepper-mm/src/main/resources/model/pepper.ecore @@ -206,8 +206,8 @@ - - + + diff --git a/backend/pepper-mm/src/main/resources/model/pepper.genmodel b/backend/pepper-mm/src/main/resources/model/pepper.genmodel index 6897e43..1234b9a 100644 --- a/backend/pepper-mm/src/main/resources/model/pepper.genmodel +++ b/backend/pepper-mm/src/main/resources/model/pepper.genmodel @@ -50,8 +50,8 @@ - - + + diff --git a/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java b/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java index 93f5ce6..9c1ebee 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/messages/MessageConstants.java @@ -85,6 +85,7 @@ public final class MessageConstants { public static final String RESOURCES = "RESOURCES"; public static final String HELP_DURATION = "HELP_DURATION"; + public static final String HELP_EFFORT = "HELP_EFFORT"; public static final String HELP_DATE = "HELP_DATE"; public static final String HELP_ROUNDED_TO_HALF_DAY = "HELP_ROUNDED_TO_HALF_DAY"; public static final String HELP_COMPUTATION_OPTION = "HELP_COMPUTATION_OPTION"; 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 e46af61..8494ec0 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 @@ -106,7 +106,7 @@ private static Instant getlaterInstant(DependencyLink dep) { } @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MethodLength", "checkstyle:MissingSwitchDefault" }) - public void editTask(EObject eObject, String name, String description, Instant startTime, Instant endTime, Integer progress, boolean keepDuration) { + public void editTask(EObject eObject, String name, String description, Instant startTime, Instant endTime, Integer progress, boolean keepEffort) { if (eObject instanceof Task task) { if (name != null) { task.setName(name); @@ -135,8 +135,8 @@ public void editTask(EObject eObject, String name, String description, Instant s if (dependencies.isEmpty()) { TaskTimeBoundariesConstraint calculationOption = task.getCalculationOption(); switch (calculationOption) { - case START_DURATION -> taskComputationService.updateStartTime(task, newStartTime); - case END_DURATION -> taskComputationService.updateEndTime(task, newEndTime); + case START_EFFORT -> taskComputationService.updateStartTime(task, newStartTime); + case END_EFFORT -> taskComputationService.updateEndTime(task, newEndTime); case START_END -> { taskComputationService.updateStartTime(task, newStartTime); taskComputationService.updateEndTime(task, newEndTime); @@ -164,9 +164,9 @@ public void editTask(EObject eObject, String name, String description, Instant s } } - private void setTaskDuration(Task task, Instant start, Instant end) { - int duration = (int) ChronoUnit.HOURS.between(start, end) + 1; //+1 because between(00:00, 00:59) = 0. We want 1. - taskComputationService.updateDuration(task, duration); + private void setTaskEffort(Task task, Instant start, Instant end) { + int effort = (int) ChronoUnit.HOURS.between(start, end) + 1; //+1 because between(00:00, 00:59) = 0. We want 1. + taskComputationService.updateEffort(task, effort); } public void createTask(EObject context) { @@ -488,7 +488,7 @@ private void followWorkpackageMoveDependency(List targetWorkpackage } if (winnerEnd != null && winnerStart != null) { if (workpackage.getStartDate().isAfter(workpackage.getEndDate())) { - workpackageComputationService.updateDuration(workpackage, 1); + workpackageComputationService.updateEffort(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)); @@ -557,7 +557,7 @@ private void followTaskMoveDependency(List targetTasks, Task sourceTask) { if (winnerEnd != null && winnerStart != null) { if (task.getEndTime().isBefore(task.getStartTime())) { Instant newEndTime = task.getStartTime().plus(12, ChronoUnit.HOURS); - this.setTaskDuration(task, task.getStartTime(), newEndTime); + this.setTaskEffort(task, task.getStartTime(), newEndTime); taskComputationService.updateEndTime(task, newEndTime.minus(1, ChronoUnit.MINUTES)); this.feedbackMessageService.addFeedbackMessage(new Message("Task dependencies overlap.", MessageLevel.ERROR)); } @@ -611,7 +611,7 @@ public void followMoveDependenciesParent(Task task) { /** * 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 effort 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 @@ -666,7 +666,7 @@ private void setTaskNewEndDate(Task task, DependencyLink dep) { newTaskEnd = newTaskEnd.plus(1, ChronoUnit.MINUTES); } } - this.setTaskDuration(task, task.getStartTime(), newTaskEnd); + this.setTaskEffort(task, task.getStartTime(), newTaskEnd); taskComputationService.updateEndTime(task, newTaskEnd); } @@ -686,7 +686,7 @@ private void setTaskNewStartDate(Task task, DependencyLink dep) { } else if (sourceStartOrEnd == StartOrEnd.START) { newTaskStart = sourceStart.plus(delay, ChronoUnit.HOURS); } - this.setTaskDuration(task, task.getStartTime(), newTaskStart); + this.setTaskEffort(task, task.getStartTime(), newTaskStart); taskComputationService.updateStartTime(task, newTaskStart); } @@ -704,7 +704,7 @@ private void setWorkpackageNewDates(Workpackage workpackage, DependencyLink depe LocalDate sourceEnd = bestSourceworkpackage.getEndDate(); LocalDate oldWorkpackageStart = workpackage.getStartDate(); LocalDate oldWorkpackageEnd = workpackage.getEndDate(); - long duration = ChronoUnit.DAYS.between(oldWorkpackageStart, oldWorkpackageEnd); + long effort = ChronoUnit.DAYS.between(oldWorkpackageStart, oldWorkpackageEnd); StartOrEnd sourceStartOrEnd = dependencyLink.getSourceKind(); StartOrEnd targetStartOrEnd = dependencyLink.getTargetKind(); int delay = dependencyLink.getDelay(); @@ -716,19 +716,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); + LocalDate newWorkpackageEnd = newWorkpackageStart.plusDays(effort); workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.START) { LocalDate newWorkpackageStart = sourceStart.plusDays(delay); - LocalDate newWorkpackageEnd = newWorkpackageStart.plusDays(duration); + LocalDate newWorkpackageEnd = newWorkpackageStart.plusDays(effort); workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } else if (sourceStartOrEnd == StartOrEnd.END && targetStartOrEnd == StartOrEnd.END) { LocalDate newWorkpackageEnd = sourceEnd.plusDays(delay); - LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(duration); + LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(effort); workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); } else if (sourceStartOrEnd == StartOrEnd.START && targetStartOrEnd == StartOrEnd.END) { LocalDate newWorkpackageEnd = sourceStart.plusDays(delay); - LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(duration); + LocalDate newWorkpackageStart = newWorkpackageEnd.minusDays(effort); workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); } } @@ -752,7 +752,7 @@ private void setWorkpackageNewEndDate(Workpackage workpackage, DependencyLink de } else if (sourceStartOrEnd == StartOrEnd.START) { newWorkpackageEnd = sourceStart.plusDays(delay); } - workpackageComputationService.updateDuration(workpackage, (int) ChronoUnit.DAYS.between(workpackage.getStartDate(), newWorkpackageEnd)); + workpackageComputationService.updateEffort(workpackage, (int) ChronoUnit.DAYS.between(workpackage.getStartDate(), newWorkpackageEnd)); workpackageComputationService.updateEndDate(workpackage, newWorkpackageEnd); } @@ -772,7 +772,7 @@ private void setWorkpackageNewStartDate(Workpackage workpackage, DependencyLink } else if (sourceStartOrEnd == StartOrEnd.START) { newWorkpackageStart = sourceStart.plusDays(delay); } - workpackageComputationService.updateDuration(workpackage, (int) ChronoUnit.DAYS.between(newWorkpackageStart, workpackage.getEndDate())); + workpackageComputationService.updateEffort(workpackage, (int) ChronoUnit.DAYS.between(newWorkpackageStart, workpackage.getEndDate())); workpackageComputationService.updateStartDate(workpackage, newWorkpackageStart); } @@ -787,8 +787,8 @@ private LocalDate getlaterLocalDate(DependencyLink dep) { return laterLocalDate; } - public void editDependencyLinkDuration(DependencyLink depLink, int newDuration) { - depLink.setDelay(newDuration); + public void editDependencyLinkDelay(DependencyLink depLink, int newDelay) { + depLink.setDelay(newDelay); this.followMoveDependency(depLink.getSource()); } @@ -804,11 +804,11 @@ public List getTasksWithTag(TaskTag tag, Workpackage workpackage) { .toList(); } - public String computeTaskDurationDays(Task task) { + public String computeTaskEffortDays(Task task) { String value = ""; - int duration = task.getDuration(); - int dd = duration / 24; - int hh = duration % 24; + int effort = task.getEffort(); + int dd = effort / 24; + int hh = effort % 24; value = String.format("%02dd%02dh", dd, hh); return value; } @@ -880,7 +880,7 @@ 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. + // The new task follows the context task and has the same effort than the context task. if (workpackage.getEndDate() != null && workpackage.getStartDate() != null) { workpackageComputationService.updateStartDate(newWorkpackage, workpackage.getEndDate()); workpackageComputationService.updateEndDate(newWorkpackage, workpackage.getEndDate().plusDays(workpackage.getEndDate().toEpochDay() - workpackage.getStartDate().toEpochDay())); @@ -907,7 +907,7 @@ public void deleteWorkpackage(EObject context) { } @SuppressWarnings({ "checkstyle:NestedIfDepth", "checkstyle:MissingSwitchDefault" }) - public void editWorkpackage(EObject eObject, String name, String description, LocalDate startDate, LocalDate endDate, Integer progress, boolean keepDuration) { + public void editWorkpackage(EObject eObject, String name, String description, LocalDate startDate, LocalDate endDate, Integer progress, boolean keepEffort) { if (eObject instanceof Workpackage workpackage) { if (name != null) { workpackage.setName(name); @@ -931,8 +931,8 @@ public void editWorkpackage(EObject eObject, String name, String description, Lo if (dependencies.isEmpty()) { TaskTimeBoundariesConstraint calculationOption = workpackage.getCalculationOption(); switch (calculationOption) { - case START_DURATION -> workpackageComputationService.updateStartDate(workpackage, startDate); - case END_DURATION -> workpackageComputationService.updateEndDate(workpackage, endDate); + case START_EFFORT -> workpackageComputationService.updateStartDate(workpackage, startDate); + case END_EFFORT -> workpackageComputationService.updateEndDate(workpackage, endDate); case START_END -> { workpackageComputationService.updateStartDate(workpackage, startDate); workpackageComputationService.updateEndDate(workpackage, endDate); @@ -958,9 +958,9 @@ public void editWorkpackage(EObject eObject, String name, String description, Lo } } - private void workpackageSetDuration(Workpackage workpackage, LocalDate start, LocalDate end) { - int duration = (int) ChronoUnit.DAYS.between(start, end) + 1; //+1 because between(00:00, 00:59) = 0. We want 1. - workpackageComputationService.updateDuration(workpackage, duration); + private void workpackageSetEffort(Workpackage workpackage, LocalDate start, LocalDate end) { + int effort = (int) ChronoUnit.DAYS.between(start, end) + 1; //+1 because between(00:00, 00:59) = 0. We want 1. + workpackageComputationService.updateEffort(workpackage, effort); } public void moveWorkpackageInProject(Workpackage sourceWorkpackage, Project project, int indexInTarget) { diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/ViewDeckDescriptionBuilder.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/ViewDeckDescriptionBuilder.java index a8c4a66..999befe 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/ViewDeckDescriptionBuilder.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/ViewDeckDescriptionBuilder.java @@ -137,7 +137,7 @@ private CardDescription createCardDescription(View view, String tagPrefix) { .name("Card Description") .semanticCandidatesExpression("aql:self.getTasksWithTag(deckTarget)") .titleExpression(AQL_SELF_NAME) - .labelExpression("aql:self.computeTaskDurationDays()") + .labelExpression("aql:self.computeTaskEffortDays()") .descriptionExpression(AQL_SELF_DESCRIPTION) .editTool(editCardTool) .deleteTool(deleteCardTool) diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java index fa8ec61..a7bb052 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java @@ -178,6 +178,9 @@ private List getGeneralControlDescription() { var endTime = this.getEndTimeWidget(); controls.add(endTime); + var effort = this.getEffortWidget(); + controls.add(effort); + var duration = this.getDurationWidget(); controls.add(duration); @@ -262,10 +265,10 @@ private RadioDescription getCalculationOptionWidget() { TaskTimeBoundariesConstraint taskTimeBoundariesConstraint = taskTimeBoundariesConstraintOpt.get(); if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_END)) { label = abstractTaskAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartEnd_feature"); - } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_DURATION)) { - label = abstractTaskAdapter.getString("_UI_TaskTimeBoundariesConstraint_EndDuration_feature"); - } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_DURATION)) { - label = abstractTaskAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartDuration_feature"); + } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_EFFORT)) { + label = abstractTaskAdapter.getString("_UI_TaskTimeBoundariesConstraint_EndEffort_feature"); + } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_EFFORT)) { + label = abstractTaskAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartEffort_feature"); } } return label; @@ -278,7 +281,7 @@ private RadioDescription getCalculationOptionWidget() { .labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_calculationOption_feature")) .isReadOnlyProvider(variableManager -> false) .optionSelectedProvider(optionSelectedProvider) - .optionsProvider(variableManager -> Arrays.asList(TaskTimeBoundariesConstraint.START_DURATION, TaskTimeBoundariesConstraint.END_DURATION, TaskTimeBoundariesConstraint.START_END)) + .optionsProvider(variableManager -> Arrays.asList(TaskTimeBoundariesConstraint.START_EFFORT, TaskTimeBoundariesConstraint.END_EFFORT, TaskTimeBoundariesConstraint.START_END)) .optionIdProvider(variableManager -> variableManager.get(SelectComponent.CANDIDATE_VARIABLE, TaskTimeBoundariesConstraint.class) .map(TaskTimeBoundariesConstraint::getValue) .map(String::valueOf) @@ -292,10 +295,10 @@ private RadioDescription getCalculationOptionWidget() { .build(); } - private TextfieldDescription getDurationWidget() { + private TextfieldDescription getEffortWidget() { Function valueProvider = variableManager -> variableManager.get(VariableManager.SELF, AbstractTask.class) .map(abstractTask -> { - double nbOfDays = abstractTask.getDuration() / 24.0; + double nbOfDays = abstractTask.getEffort() / 24.0; return String.format("%.1f", nbOfDays); }) .map(String::valueOf) @@ -304,12 +307,12 @@ private TextfieldDescription getDurationWidget() { var taskOpt = variableManager.get(VariableManager.SELF, AbstractTask.class); if (taskOpt.isPresent()) { if (newValue == null || newValue.isBlank()) { - taskComputationService.updateDuration(taskOpt.get(), 0); + taskComputationService.updateEffort(taskOpt.get(), 0); } else { try { int valueInHours = this.roundToNearestHalfDayInHours(newValue); var task = taskOpt.get(); - taskComputationService.updateDuration(task, valueInHours); + taskComputationService.updateEffort(task, valueInHours); service.editTask(task, task.getName(), task.getDescription(), task.getStartTime(), task.getEndTime(), task.getProgress(), true); } catch (NumberFormatException e) { // Ignore @@ -321,16 +324,40 @@ private TextfieldDescription getDurationWidget() { } }; - String id = "abstractTask.duration"; + String id = "abstractTask.effort"; return TextfieldDescription.newTextfieldDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, AbstractTask.class) .map(task -> task.getCalculationOption() == TaskTimeBoundariesConstraint.START_END || this.isDateOptionForced(task)) .orElse(true)) .idProvider(variableManager -> id) .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) - .labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_duration_feature")) + .labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_effort_feature")) .valueProvider(valueProvider) .newValueHandler(newValueHandler) + .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__EFFORT)) + .kindProvider(this.propertiesConfigurerService.getKindProvider()) + .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_EFFORT) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) + .build(); + } + + private TextfieldDescription getDurationWidget() { + Function valueProvider = variableManager -> variableManager.get(VariableManager.SELF, AbstractTask.class) + .map(abstractTask -> { + double nbOfDays = abstractTask.getDuration() / 24.0; + return String.format("%.1f", nbOfDays); + }) + .map(String::valueOf) + .orElse("0"); + + String id = "abstractTask.duration"; + return TextfieldDescription.newTextfieldDescription(id) + .isReadOnlyProvider(vm -> true) + .idProvider(variableManager -> id) + .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) + .labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_duration_feature")) + .valueProvider(valueProvider) + .newValueHandler((variableManager, newValue)-> new Failure("")) .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__DURATION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) @@ -459,7 +486,7 @@ private DateTimeDescription getStartTimeWidget() { String id = "abstractTask.startTime"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, AbstractTask.class) - .map(task -> task.getCalculationOption() == TaskTimeBoundariesConstraint.END_DURATION || this.isDateOptionForced(task) || this.isPointed(task, StartOrEnd.START)) + .map(task -> task.getCalculationOption() == TaskTimeBoundariesConstraint.END_EFFORT || this.isDateOptionForced(task) || this.isPointed(task, StartOrEnd.START)) .orElse(true)) .idProvider(variableManager -> id) .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) @@ -508,7 +535,7 @@ private DateTimeDescription getEndTimeWidget() { String id = "abstractTask.endTime"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, AbstractTask.class) - .map(task -> task.getCalculationOption() == TaskTimeBoundariesConstraint.START_DURATION || this.isDateOptionForced(task) || this.isPointed(task, StartOrEnd.END)) + .map(task -> task.getCalculationOption() == TaskTimeBoundariesConstraint.START_EFFORT || this.isDateOptionForced(task) || this.isPointed(task, StartOrEnd.END)) .orElse(true)) .idProvider(variableManager -> id) .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) @@ -524,8 +551,8 @@ private DateTimeDescription getEndTimeWidget() { } private Boolean isDateOptionForced(AbstractTask task) { - return (task.getCalculationOption() == TaskTimeBoundariesConstraint.END_DURATION && this.isPointed(task, StartOrEnd.START)) - || (task.getCalculationOption() == TaskTimeBoundariesConstraint.START_DURATION && this.isPointed(task, StartOrEnd.END)); + return (task.getCalculationOption() == TaskTimeBoundariesConstraint.END_EFFORT && this.isPointed(task, StartOrEnd.START)) + || (task.getCalculationOption() == TaskTimeBoundariesConstraint.START_EFFORT && this.isPointed(task, StartOrEnd.END)); } private Boolean isPointed(AbstractTask task, StartOrEnd startOrEnd) { diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/DependencyLinkPropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/DependencyLinkPropertiesConfigurer.java index 389e256..062ae63 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/DependencyLinkPropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/DependencyLinkPropertiesConfigurer.java @@ -196,7 +196,7 @@ private RadioDescription getSourceTargetKindWidget(boolean isSource) { StartOrEnd newStartOrEnd = StartOrEnd.get(integer); depLink.setTargetKind(newStartOrEnd); } - service.editDependencyLinkDuration(depLink, depLink.getDelay()); + service.editDependencyLinkDelay(depLink, depLink.getDelay()); return new Success(); } else { return new Failure(""); @@ -323,10 +323,10 @@ private TextfieldDescription getDurationWidget() { try { if (dependencyLink.eContainer() instanceof Workpackage) { int integer = Integer.parseInt(newValue); - service.editDependencyLinkDuration(dependencyLink, integer); + service.editDependencyLinkDelay(dependencyLink, integer); } else { int valueInHours = this.roundToNearestHalfDayInHours(newValue); - service.editDependencyLinkDuration(dependencyLink, valueInHours); + service.editDependencyLinkDelay(dependencyLink, valueInHours); } } catch (NumberFormatException e) { // Ignore diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java index e7f73b1..e57d0fc 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java @@ -167,6 +167,9 @@ private List getGeneralControlDescription() { var endDate = this.getEndDateWidget(); controls.add(endDate); + var effort = this.getEffortWidget(); + controls.add(effort); + var duration = this.getDurationWidget(); controls.add(duration); @@ -249,10 +252,10 @@ private RadioDescription getCalculationOptionWidget() { if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_END)) { label = workpackageAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartEnd_feature"); } - else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_DURATION)) { - label = workpackageAdapter.getString("_UI_TaskTimeBoundariesConstraint_EndDuration_feature"); - } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_DURATION)) { - label = workpackageAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartDuration_feature"); + else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_EFFORT)) { + label = workpackageAdapter.getString("_UI_TaskTimeBoundariesConstraint_EndEffort_feature"); + } else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.START_EFFORT)) { + label = workpackageAdapter.getString("_UI_TaskTimeBoundariesConstraint_StartEffort_feature"); } } return label; @@ -265,7 +268,7 @@ else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_DU .labelProvider(variableManager -> workpackageAdapter.getString("_UI_Workpackage_calculationOption_feature")) .isReadOnlyProvider(variableManager -> false) .optionSelectedProvider(optionSelectedProvider) - .optionsProvider(variableManager -> Arrays.asList(TaskTimeBoundariesConstraint.START_DURATION, TaskTimeBoundariesConstraint.END_DURATION, TaskTimeBoundariesConstraint.START_END)) + .optionsProvider(variableManager -> Arrays.asList(TaskTimeBoundariesConstraint.START_EFFORT, TaskTimeBoundariesConstraint.END_EFFORT, TaskTimeBoundariesConstraint.START_END)) .optionIdProvider(variableManager -> variableManager.get(SelectComponent.CANDIDATE_VARIABLE, TaskTimeBoundariesConstraint.class) .map(TaskTimeBoundariesConstraint::getValue) .map(String::valueOf) @@ -279,21 +282,21 @@ else if (taskTimeBoundariesConstraint.equals(TaskTimeBoundariesConstraint.END_DU .build(); } - private TextfieldDescription getDurationWidget() { + private TextfieldDescription getEffortWidget() { Function valueProvider = variableManager -> variableManager.get(VariableManager.SELF, Workpackage.class) - .map(Workpackage::getDuration) + .map(Workpackage::getEffort) .map(String::valueOf) .orElse("0"); BiFunction newValueHandler = (variableManager, newValue) -> { var workpackageOpt = variableManager.get(VariableManager.SELF, Workpackage.class); if (workpackageOpt.isPresent()) { if (newValue == null || newValue.isBlank()) { - workpackageComputationService.updateDuration(workpackageOpt.get(), 0); + workpackageComputationService.updateEffort(workpackageOpt.get(), 0); } else { try { int integer = Integer.parseInt(newValue); var workpackage = workpackageOpt.get(); - workpackageComputationService.updateDuration(workpackage, integer); + workpackageComputationService.updateEffort(workpackage, integer); service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), workpackage.getStartDate(), workpackage.getEndDate(), workpackage.getProgress(), true); } catch (NumberFormatException e) { // Ignore @@ -305,7 +308,7 @@ private TextfieldDescription getDurationWidget() { } }; - String id = "workpackage.duration"; + String id = "workpackage.effort"; return TextfieldDescription.newTextfieldDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, Workpackage.class) .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_END @@ -315,9 +318,33 @@ private TextfieldDescription getDurationWidget() { .orElse(true)) .idProvider(variableManager -> id) .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) - .labelProvider(variableManager -> workpackageAdapter.getString("_UI_Workpackage_duration_feature")) + .labelProvider(variableManager -> workpackageAdapter.getString("_UI_Workpackage_effort_feature")) .valueProvider(valueProvider) .newValueHandler(newValueHandler) + .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.WORKPACKAGE__EFFORT)) + .kindProvider(this.propertiesConfigurerService.getKindProvider()) + .messageProvider(this.propertiesConfigurerService.getMessageProvider()) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_EFFORT)) + .build(); + } + + private TextfieldDescription getDurationWidget() { + Function valueProvider = variableManager -> variableManager.get(VariableManager.SELF, Workpackage.class) + .map(abstractTask -> { + double nbOfDays = abstractTask.getDuration() / 24.0; + return String.format("%.1f", nbOfDays); + }) + .map(String::valueOf) + .orElse("0"); + + String id = "workpackage.duration"; + return TextfieldDescription.newTextfieldDescription(id) + .isReadOnlyProvider(vm -> true) + .idProvider(variableManager -> id) + .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) + .labelProvider(variableManager -> workpackageAdapter.getString("_UI_Workpackage_duration_feature")) + .valueProvider(valueProvider) + .newValueHandler((variableManager, newValue) -> new Failure("")) .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.WORKPACKAGE__DURATION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) @@ -414,7 +441,7 @@ private DateTimeDescription getStartDateWidget() { String id = "workpackage.startTime"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, Workpackage.class) - .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.END_DURATION + .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.END_EFFORT || this.isNonConstrainingDateCalculatedByDependency(workpackage) || this.isDateCalculatedByDependency(workpackage, StartOrEnd.START) || !workpackage.getOwnedTasks().isEmpty() @@ -467,7 +494,7 @@ private DateTimeDescription getEndDateWidget() { String id = "workpackage.endTime"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, Workpackage.class) - .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_DURATION + .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_EFFORT || this.isNonConstrainingDateCalculatedByDependency(workpackage) || this.isDateCalculatedByDependency(workpackage, StartOrEnd.END) || !workpackage.getOwnedTasks().isEmpty() @@ -487,8 +514,8 @@ private DateTimeDescription getEndDateWidget() { } private Boolean isNonConstrainingDateCalculatedByDependency(Workpackage workpackage) { - return (workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.END_DURATION && this.isDateCalculatedByDependency(workpackage, StartOrEnd.START)) - || (workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_DURATION && this.isDateCalculatedByDependency(workpackage, StartOrEnd.END)); + return (workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.END_EFFORT && this.isDateCalculatedByDependency(workpackage, StartOrEnd.START)) + || (workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_EFFORT && this.isDateCalculatedByDependency(workpackage, StartOrEnd.END)); } private Boolean isDateCalculatedByDependency(Workpackage workpackage, StartOrEnd startOrEnd) { diff --git a/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties b/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties index 8566144..0e8736b 100644 --- a/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties +++ b/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties @@ -43,7 +43,8 @@ EOTP_HELP=To be completed by the manager/financial assistant OS_HELP=Statistical order\nTo be completed by the manager/financial assistant RESOURCES=Resources -HELP_DURATION=The duration reflects the actual time worked.\nIt excludes days not worked (recurring days or holidays), which explains why it may be shorter than the period between the start and end dates -HELP_DATE=When the date depends on the duration, the duration is calculated excluding non-working days (recurring days or holidays), which can make the interval between the start and end dates longer than the duration itself. +HELP_DURATION=Duration reflects the actual time worked.\nIt excludes days not worked (recurring days or holidays), which explains why it may be shorter than the period between the start and end dates +HELP_EFFORT=Effort reflects the actual work time spent by the assigned individuals.\nIts calculation excludes non-working days (recurring or holidays), as with duration, and also takes into account the individuals assigned to the task and their availability periods. +HELP_DATE=When the date depends on the effort, the effort is calculated excluding non-working days (recurring days or holidays), which can make the interval between the start and end dates longer than the duration itself. HELP_ROUNDED_TO_HALF_DAY=It is rounded to half-day. HELP_COMPUTATION_OPTION=If a date (start or end) has a dependency on another task, that dependency takes precedence when calculating the date. diff --git a/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties b/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties index c889a7d..f88230b 100644 --- a/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties +++ b/backend/pepper-starter/src/main/resources/messages/pepper-starter_fr.properties @@ -44,6 +44,7 @@ OS_HELP=Ordre Statistique\nA compl RESOURCES=Ressources HELP_DURATION=La durée reflète le temps de travail effectif.\nSon calcul exclut les jours non travaillés (récurrents ou fériés), ce qui explique qu?elle puisse être plus courte que la période entre les dates de début et de fin. -HELP_DATE=Lorsque la date dépend de la durée, celle-ci est calculée sans les jours non travaillés (récurrents ou fériés), ce qui peut rendre l'intervalle entre le début et la fin plus long que la durée elle-même. +HELP_EFFORT=L'effort reflète le temps de travail effectif des personnes assignées.\nSon calcul exclut les jours non travaillés(récurrents ou fériés), comme pour la durée, et prend de plus en compte les personnes assignées à la tâche avec leurs périodes de disponibilité. +HELP_DATE=Lorsque la date dépend de l'effort, celui-ci est calculé sans les jours non travaillés(récurrents ou fériés), ce qui peut rendre l'intervalle entre le début et la fin plus long que la durée elle-même. HELP_ROUNDED_TO_HALF_DAY=Elle est arrondie à la demi journée. HELP_COMPUTATION_OPTION=Si une date(début ou fin) possède un lien de dépendance vers une autre tâche, ce lien est prioritaire pour le calcul de la date. 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 4e87a68..7008a62 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 @@ -110,14 +110,14 @@ public void editTaskWithDependency() { taskComputationService.updateEndTime(task1, Instant.parse(MONDAY_2026_01_05_T23_59_00)); Task task2 = PepperFactory.eINSTANCE.createTask(); - task2.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + task2.setCalculationOption(TaskTimeBoundariesConstraint.START_EFFORT); taskComputationService.updateStartTime(task2, Instant.parse(MONDAY_2026_01_05_T00_00_00)); - taskComputationService.updateDuration(task2, 24); + taskComputationService.updateEffort(task2, 24); Task task3 = PepperFactory.eINSTANCE.createTask(); - task3.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + task3.setCalculationOption(TaskTimeBoundariesConstraint.START_EFFORT); taskComputationService.updateStartTime(task3, Instant.parse(MONDAY_2026_01_05_T00_00_00)); - taskComputationService.updateDuration(task3, 24); + taskComputationService.updateEffort(task3, 24); workpackage.getOwnedTasks().add(task3); workpackage.getOwnedTasks().add(task2); @@ -201,9 +201,9 @@ public void editSubTaskOfDynamicTaskWithDependency() { @Test public void createDependencyLink() { Task task = PepperFactory.eINSTANCE.createTask(); - task.setCalculationOption(TaskTimeBoundariesConstraint.START_DURATION); + task.setCalculationOption(TaskTimeBoundariesConstraint.START_EFFORT); taskComputationService.updateStartTime(task, Instant.parse(MONDAY_2026_01_05_T00_00_00)); - taskComputationService.updateDuration(task, 24); + taskComputationService.updateEffort(task, 24); Task taskDependency = PepperFactory.eINSTANCE.createTask(); taskComputationService.updateStartTime(taskDependency, Instant.parse(MONDAY_2026_01_05_T00_00_00)); @@ -281,12 +281,12 @@ public void deleteDependencyLink() { } @Test - public void computeTaskDurationDays() { + public void computeTaskEffortDays() { Task task = PepperFactory.eINSTANCE.createTask(); taskComputationService.updateStartTime(task, Instant.now()); taskComputationService.updateEndTime(task, Instant.now().plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.DAYS)); var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); - var result = service.computeTaskDurationDays(task); + var result = service.computeTaskEffortDays(task); assertThat(result).isNotNull(); assertThat(result).isEqualTo("01d00h"); } From 6c6f889cd9b317e4f476c1fab7abe4759b0fa804 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Tue, 1 Sep 2026 19:00:52 +0200 Subject: [PATCH 7/8] From details view, reset only the time constraint when it is null --- .../AbstractTaskPropertiesConfigurer.java | 119 +++++++++--------- .../WorkpackagePropertiesConfigurer.java | 105 ++++++++-------- 2 files changed, 111 insertions(+), 113 deletions(-) diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java index a7bb052..85b77fe 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/AbstractTaskPropertiesConfigurer.java @@ -295,6 +295,7 @@ private RadioDescription getCalculationOptionWidget() { .build(); } + @SuppressWarnings("checkstyle:MultipleStringLiterals") private TextfieldDescription getEffortWidget() { Function valueProvider = variableManager -> variableManager.get(VariableManager.SELF, AbstractTask.class) .map(abstractTask -> { @@ -304,24 +305,25 @@ private TextfieldDescription getEffortWidget() { .map(String::valueOf) .orElse("0"); BiFunction newValueHandler = (variableManager, newValue) -> { - var taskOpt = variableManager.get(VariableManager.SELF, AbstractTask.class); - if (taskOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - taskComputationService.updateEffort(taskOpt.get(), 0); - } else { - try { - int valueInHours = this.roundToNearestHalfDayInHours(newValue); - var task = taskOpt.get(); - taskComputationService.updateEffort(task, valueInHours); - service.editTask(task, task.getName(), task.getDescription(), task.getStartTime(), task.getEndTime(), task.getProgress(), true); - } catch (NumberFormatException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, AbstractTask.class) + .map(abstractTask -> { + if (newValue == null || newValue.isBlank()) { + taskComputationService.updateEffort(abstractTask, 0); + } else { + try { + int valueInHours = this.roundToNearestHalfDayInHours(newValue); + if (valueInHours >= 0) { + taskComputationService.updateEffort(abstractTask, valueInHours); + service.editTask(abstractTask, abstractTask.getName(), abstractTask.getDescription(), abstractTask.getStartTime(), abstractTask.getEndTime(), abstractTask.getProgress(), + true); + } + } catch (NumberFormatException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; String id = "abstractTask.effort"; @@ -337,7 +339,8 @@ private TextfieldDescription getEffortWidget() { .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__EFFORT)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) - .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_EFFORT) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_EFFORT) + System.lineSeparator() + this.pepperMessageService.getMessage( + MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } @@ -357,11 +360,12 @@ private TextfieldDescription getDurationWidget() { .targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider()) .labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_duration_feature")) .valueProvider(valueProvider) - .newValueHandler((variableManager, newValue)-> new Failure("")) + .newValueHandler((variableManager, newValue) -> new Failure("")) .diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__DURATION)) .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) - .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DURATION) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DURATION) + System.lineSeparator() + this.pepperMessageService.getMessage( + MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } @@ -382,7 +386,7 @@ private ReferenceWidgetDescription getDependenciesWidget() { String targetKind = link.getTargetKind().toString(); int delay = link.getDelay(); double nbOfDays = delay / 24.0; - String delayStr = String.format("%.1f", nbOfDays); + String delayStr = String.format("%.1f", nbOfDays); String delayString = name + ": " + sourceKind + " -> " + targetKind; if (delay != 0) { @@ -464,24 +468,21 @@ private DateTimeDescription getStartTimeWidget() { }) .orElse(""); BiFunction newValueHandler = (variableManager, newValue) -> { - var taskOpt = variableManager.get(VariableManager.SELF, AbstractTask.class); - if (taskOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - taskComputationService.updateStartTime(taskOpt.get(), null); - } else { - try { - Instant instant = Instant.parse(newValue); - var task = taskOpt.get(); - service.editTask(task, task.getName(), task.getDescription(), instant, task.getEndTime(), task.getProgress(), true); - - } catch (DateTimeParseException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, AbstractTask.class) + .map(abstractTask -> { + if (newValue == null || newValue.isBlank()) { + abstractTask.setStartTime(null); + } else { + try { + Instant instant = Instant.parse(newValue); + service.editTask(abstractTask, abstractTask.getName(), abstractTask.getDescription(), instant, abstractTask.getEndTime(), abstractTask.getProgress(), true); + } catch (DateTimeParseException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; String id = "abstractTask.startTime"; return DateTimeDescription.newDateTimeDescription(id) @@ -497,7 +498,8 @@ private DateTimeDescription getStartTimeWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE_TIME) - .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage( + MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } @@ -514,23 +516,21 @@ private DateTimeDescription getEndTimeWidget() { }) .orElse(""); BiFunction newValueHandler = (variableManager, newValue) -> { - var taskOpt = variableManager.get(VariableManager.SELF, AbstractTask.class); - if (taskOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - taskComputationService.updateEndTime(taskOpt.get(), null); - } else { - try { - Instant instant = Instant.parse(newValue); - var task = taskOpt.get(); - service.editTask(task, task.getName(), task.getDescription(), task.getStartTime(), instant, task.getProgress(), true); - } catch (DateTimeParseException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, AbstractTask.class) + .map(abstractTask -> { + if (newValue == null || newValue.isBlank()) { + abstractTask.setEndTime(null); + } else { + try { + Instant instant = Instant.parse(newValue); + service.editTask(abstractTask, abstractTask.getName(), abstractTask.getDescription(), abstractTask.getStartTime(), instant, abstractTask.getProgress(), true); + } catch (DateTimeParseException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; String id = "abstractTask.endTime"; return DateTimeDescription.newDateTimeDescription(id) @@ -546,7 +546,8 @@ private DateTimeDescription getEndTimeWidget() { .kindProvider(this.propertiesConfigurerService.getKindProvider()) .messageProvider(this.propertiesConfigurerService.getMessageProvider()) .type(DateTimeType.DATE_TIME) - .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage(MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) + .helpTextProvider(variableManager -> this.pepperMessageService.getMessage(MessageConstants.HELP_DATE) + System.lineSeparator() + this.pepperMessageService.getMessage( + MessageConstants.HELP_ROUNDED_TO_HALF_DAY)) .build(); } diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java index e57d0fc..2fab299 100644 --- a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/details/WorkpackagePropertiesConfigurer.java @@ -288,24 +288,25 @@ private TextfieldDescription getEffortWidget() { .map(String::valueOf) .orElse("0"); BiFunction newValueHandler = (variableManager, newValue) -> { - var workpackageOpt = variableManager.get(VariableManager.SELF, Workpackage.class); - if (workpackageOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - workpackageComputationService.updateEffort(workpackageOpt.get(), 0); - } else { - try { - int integer = Integer.parseInt(newValue); - var workpackage = workpackageOpt.get(); - workpackageComputationService.updateEffort(workpackage, integer); - service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), workpackage.getStartDate(), workpackage.getEndDate(), workpackage.getProgress(), true); - } catch (NumberFormatException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, Workpackage.class) + .map(workpackage -> { + if (newValue == null || newValue.isBlank()) { + workpackageComputationService.updateEffort(workpackage, 0); + } else { + try { + int integer = Integer.parseInt(newValue); + if (integer >= 0) { + workpackageComputationService.updateEffort(workpackage, integer); + service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), workpackage.getStartDate(), workpackage.getEndDate(), + workpackage.getProgress(), true); + } + } catch (NumberFormatException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; String id = "workpackage.effort"; @@ -420,25 +421,23 @@ private DateTimeDescription getStartDateWidget() { }) .orElse(""); BiFunction newValueHandler = (variableManager, newValue) -> { - var workpackageOpt = variableManager.get(VariableManager.SELF, Workpackage.class); - if (workpackageOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - workpackageComputationService.updateStartDate(workpackageOpt.get(), null); - } else { - try { - LocalDate localDate = LocalDate.parse(newValue); - var workpackage = workpackageOpt.get(); - service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), localDate, workpackage.getEndDate(), workpackage.getProgress(), true); - } catch (DateTimeParseException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, Workpackage.class) + .map(workpackage -> { + if (newValue == null || newValue.isBlank()) { + workpackage.setStartDate(null); + } else { + try { + LocalDate localDate = LocalDate.parse(newValue); + service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), localDate, workpackage.getEndDate(), workpackage.getProgress(), true); + } catch (DateTimeParseException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; - String id = "workpackage.startTime"; + String id = "workpackage.startDate"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, Workpackage.class) .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.END_EFFORT @@ -473,25 +472,23 @@ private DateTimeDescription getEndDateWidget() { }) .orElse(""); BiFunction newValueHandler = (variableManager, newValue) -> { - var workpackageOpt = variableManager.get(VariableManager.SELF, Workpackage.class); - if (workpackageOpt.isPresent()) { - if (newValue == null || newValue.isBlank()) { - workpackageComputationService.updateEndDate(workpackageOpt.get(), null); - } else { - try { - LocalDate localDate = LocalDate.parse(newValue); - var workpackage = workpackageOpt.get(); - service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), workpackage.getStartDate(), localDate, workpackage.getProgress(), true); - } catch (DateTimeParseException e) { - // Ignore - } - } - return new Success(); - } else { - return new Failure(""); - } + return variableManager.get(VariableManager.SELF, Workpackage.class) + .map(workpackage -> { + if (newValue == null || newValue.isBlank()) { + workpackage.setEndDate(null); + } else { + try { + LocalDate localDate = LocalDate.parse(newValue); + service.editWorkpackage(workpackage, workpackage.getName(), workpackage.getDescription(), workpackage.getStartDate(), localDate, workpackage.getProgress(), true); + } catch (DateTimeParseException e) { + // Ignore + } + } + return (IStatus) new Success(); + }) + .orElse(new Failure("")); }; - String id = "workpackage.endTime"; + String id = "workpackage.endDate"; return DateTimeDescription.newDateTimeDescription(id) .isReadOnlyProvider(vm -> vm.get(VariableManager.SELF, Workpackage.class) .map(workpackage -> workpackage.getCalculationOption() == TaskTimeBoundariesConstraint.START_EFFORT From ac0dd47ebfb761521e5267050fb7a25edc1fbfc4 Mon Sep 17 00:00:00 2001 From: Laurent Fasani Date: Wed, 2 Sep 2026 16:56:24 +0200 Subject: [PATCH 8/8] [cleanup] Split PepperMMJavaService --- .../representations/PepperMMJavaService.java | 178 ------------- .../deck/PepperDeckJavaService.java | 237 ++++++++++++++++++ .../view/PepperMMJavaServiceTests.java | 7 +- 3 files changed, 241 insertions(+), 181 deletions(-) create mode 100644 backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/PepperDeckJavaService.java 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 8494ec0..2f34ef1 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 @@ -22,7 +22,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.StreamSupport; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; @@ -38,14 +37,10 @@ import pepper.peppermm.AbstractTask; import pepper.peppermm.DependencyLink; import pepper.peppermm.DependencyRelatedObject; -import pepper.peppermm.KeyResult; -import pepper.peppermm.Objective; import pepper.peppermm.PepperFactory; import pepper.peppermm.Project; import pepper.peppermm.StartOrEnd; -import pepper.peppermm.TagFolder; import pepper.peppermm.Task; -import pepper.peppermm.TaskTag; import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; @@ -792,58 +787,6 @@ public void editDependencyLinkDelay(DependencyLink depLink, int newDelay) { this.followMoveDependency(depLink.getSource()); } - public List getTasksWithTag(TaskTag tag, Workpackage workpackage) { - return Optional.of(workpackage).stream() - .flatMap(wkP -> { - Iterable content = () -> wkP.eAllContents(); - return StreamSupport.stream(content.spliterator(), false); - }) - .filter(Task.class::isInstance) - .map(Task.class::cast) - .filter(task -> task.getTags().contains(tag)) - .toList(); - } - - public String computeTaskEffortDays(Task task) { - String value = ""; - int effort = task.getEffort(); - int dd = effort / 24; - int hh = effort % 24; - value = String.format("%02dd%02dh", dd, hh); - return value; - } - - public void createCard(EObject context) { - Task task = PepperFactory.eINSTANCE.createTask(); - task.setName(NEW_TASK); - task.setDescription("new description"); - if (context instanceof TaskTag tag) { - task.getTags().add(tag); - - EObject parent = context.eContainer(); - if (parent instanceof TagFolder tagFolder) { - EObject parent2 = tagFolder.eContainer(); - if (parent2 instanceof Project project) { - var workpackages = project.getOwnedWorkpackages(); - if (!workpackages.isEmpty()) { - workpackages.get(0).getOwnedTasks().add(task); - } - } - } - } - } - - public void editCard(EObject eObject, String title, String description, String label) { - if (eObject instanceof AbstractTask task) { - if (title != null) { - task.setName(title); - } - if (description != null) { - task.setDescription(description); - } - } - } - public void moveTaskIntoTarget(Task sourceTask, EObject target, int indexInTarget) { if (target instanceof Task targetTask) { // check that the target is not a child of the dropped task @@ -958,11 +901,6 @@ public void editWorkpackage(EObject eObject, String name, String description, Lo } } - private void workpackageSetEffort(Workpackage workpackage, LocalDate start, LocalDate end) { - int effort = (int) ChronoUnit.DAYS.between(start, end) + 1; //+1 because between(00:00, 00:59) = 0. We want 1. - workpackageComputationService.updateEffort(workpackage, effort); - } - public void moveWorkpackageInProject(Workpackage sourceWorkpackage, Project project, int indexInTarget) { EList ownedWorkpackages = project.getOwnedWorkpackages(); if (ownedWorkpackages.contains(sourceWorkpackage)) { @@ -977,16 +915,6 @@ public void moveWorkpackageInProject(Workpackage sourceWorkpackage, Project proj } } - public void moveKeyResultIntoObjective(KeyResult sourceKeyResult, Objective targetObjective, int indexInTarget) { - EList ownedKeyResults = targetObjective.getOwnedKeyResults(); - if (sourceKeyResult.eContainer().equals(targetObjective)) { - ownedKeyResults.move(indexInTarget, sourceKeyResult); - } else { - ownedKeyResults.add(sourceKeyResult); - ownedKeyResults.move(indexInTarget, sourceKeyResult); - } - } - private void moveTaskInSubTasks(Task sourceTask, int indexInTarget, Task targetTask) { List subTasks = targetTask.getSubTasks(); if (subTasks.contains(sourceTask)) { @@ -1012,110 +940,4 @@ private void moveTaskInSubTasks(Task sourceTask, int indexInTarget, Task targetT } } } - - public Task moveTaskInTag(Task moveTask, int index, TaskTag targetTag) { - 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(); - - List tasksBeforeTheDroppedTaskWithSameParent = allTaskInTheLane.subList(0, index).stream().filter(task -> task.eContainer().equals(moveTask.eContainer())).toList(); - Optional lastTaskBeforeTheDroppedTaskWithSameParent = Optional.empty(); - if (!tasksBeforeTheDroppedTaskWithSameParent.isEmpty()) { - lastTaskBeforeTheDroppedTaskWithSameParent = Optional.of(tasksBeforeTheDroppedTaskWithSameParent.get(tasksBeforeTheDroppedTaskWithSameParent.size() - 1)); - } - - if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent() || firstTaskAfterTheDroppedTaskWithSameParent.isPresent()) { - EObject eContainer = moveTask.eContainer(); - if (eContainer instanceof Workpackage workpackage) { - int indexInParent = 0; - if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent()) { - indexInParent = workpackage.getOwnedTasks().indexOf(lastTaskBeforeTheDroppedTaskWithSameParent.get()) + 1; - } else { - indexInParent = workpackage.getOwnedTasks().indexOf(firstTaskAfterTheDroppedTaskWithSameParent.get()); - } - workpackage.getOwnedTasks().move(indexInParent, moveTask); - } else if (eContainer instanceof AbstractTask parentTask) { - int indexInParent = 0; - if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent()) { - indexInParent = parentTask.getSubTasks().indexOf(lastTaskBeforeTheDroppedTaskWithSameParent.get()) + 1; - } else { - indexInParent = parentTask.getSubTasks().indexOf(firstTaskAfterTheDroppedTaskWithSameParent.get()); - } - parentTask.getSubTasks().move(indexInParent, moveTask); - } - } - } - return moveTask; - } - - Optional getParent(EObject eObject, Class clazz) { - Optional objectOpt = Optional.empty(); - EObject parent = eObject.eContainer(); - while (parent != null) { - if (clazz.isInstance(parent)) { - objectOpt = Optional.of(clazz.cast(parent)); - break; - } - parent = parent.eContainer(); - } - - return objectOpt; - } - - public void moveObjectiveAtIndex(Objective objective, int index) { - if (objective.eContainer() instanceof Project project) { - project.getOwnedObjectives().move(index, objective); - } - } - - public void moveTagAtIndex(TaskTag movedTag, int index) { - EObject eContainer = movedTag.eContainer(); - if (eContainer instanceof TagFolder tagFolder) { - String prefix = movedTag.getPrefix(); - List tagList = tagFolder.getOwnedTags().stream().filter(tag -> tag.getPrefix().equals(prefix)).toList(); - - int newIndex = this.computeIndexOfTagToMove(movedTag, index, tagList, tagFolder); - // We move the current tag before the tagToReplace in the project ownTags list. - int oldIndex = tagFolder.getOwnedTags().indexOf(movedTag); - // If the moved tag was located before the new location, the index after having remove the tag is - // decremented. - if (oldIndex < newIndex) { - newIndex--; - } - tagFolder.getOwnedTags().move(newIndex, movedTag); - - } - } - - /** - * 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. - * @param index - * the new index in the project tag list. - * @param tagList - * 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. - */ - private int computeIndexOfTagToMove(TaskTag tag, int index, List tagList, TagFolder tagFolder) { - int newIndex; - List unmovedLaneTags = tagList.stream().filter(currentTag -> currentTag != tag).toList(); - if (index < unmovedLaneTags.size()) { - // We retrieve the tag that will be located after the moved one. - TaskTag tagToMoveAround = unmovedLaneTags.get(index); - newIndex = tagFolder.getOwnedTags().indexOf(tagToMoveAround); - } else { - // We need to locate the tag after the last one in the deck representation - TaskTag lastTag = unmovedLaneTags.get(unmovedLaneTags.size() - 1); - newIndex = tagFolder.getOwnedTags().indexOf(lastTag) + 1; - } - return newIndex; - } } diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/PepperDeckJavaService.java b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/PepperDeckJavaService.java new file mode 100644 index 0000000..1009ec3 --- /dev/null +++ b/backend/pepper-starter/src/main/java/pepper/starter/services/representations/deck/PepperDeckJavaService.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * 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.starter.services.representations.deck; + +import java.util.List; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; + +import pepper.peppermm.AbstractTask; +import pepper.peppermm.KeyResult; +import pepper.peppermm.Objective; +import pepper.peppermm.PepperFactory; +import pepper.peppermm.Project; +import pepper.peppermm.TagFolder; +import pepper.peppermm.Task; +import pepper.peppermm.TaskTag; +import pepper.peppermm.Workpackage; + +/** + * Java Service for the task related views. + * + * @author lfasani + */ +public class PepperDeckJavaService { + + private static final String NEW_TASK = "New Task"; + + public PepperDeckJavaService() { + } + + public List getTasksWithTag(TaskTag tag, Workpackage workpackage) { + return Optional.of(workpackage).stream() + .flatMap(wkP -> { + Iterable content = () -> wkP.eAllContents(); + return StreamSupport.stream(content.spliterator(), false); + }) + .filter(Task.class::isInstance) + .map(Task.class::cast) + .filter(task -> task.getTags().contains(tag)) + .toList(); + } + + public String computeTaskEffortDays(Task task) { + String value = ""; + int effort = task.getEffort(); + int dd = effort / 24; + int hh = effort % 24; + value = String.format("%02dd%02dh", dd, hh); + return value; + } + + public void createCard(EObject context) { + Task task = PepperFactory.eINSTANCE.createTask(); + task.setName(NEW_TASK); + task.setDescription("new description"); + if (context instanceof TaskTag tag) { + task.getTags().add(tag); + + EObject parent = context.eContainer(); + if (parent instanceof TagFolder tagFolder) { + EObject parent2 = tagFolder.eContainer(); + if (parent2 instanceof Project project) { + var workpackages = project.getOwnedWorkpackages(); + if (!workpackages.isEmpty()) { + workpackages.get(0).getOwnedTasks().add(task); + } + } + } + } + } + + public void editCard(EObject eObject, String title, String description, String label) { + if (eObject instanceof AbstractTask task) { + if (title != null) { + task.setName(title); + } + if (description != null) { + task.setDescription(description); + } + } + } + + public void moveKeyResultIntoObjective(KeyResult sourceKeyResult, Objective targetObjective, int indexInTarget) { + EList ownedKeyResults = targetObjective.getOwnedKeyResults(); + if (sourceKeyResult.eContainer().equals(targetObjective)) { + ownedKeyResults.move(indexInTarget, sourceKeyResult); + } else { + ownedKeyResults.add(sourceKeyResult); + ownedKeyResults.move(indexInTarget, sourceKeyResult); + } + } + + private void moveTaskInSubTasks(Task sourceTask, int indexInTarget, Task targetTask) { + List subTasks = targetTask.getSubTasks(); + if (subTasks.contains(sourceTask)) { + if (indexInTarget >= 0 && indexInTarget <= subTasks.size()) { + int indexOfSource = subTasks.indexOf(sourceTask); + if (indexOfSource < indexInTarget) { + targetTask.getSubTasks().move(indexInTarget - 1, sourceTask); + } else { + targetTask.getSubTasks().move(indexInTarget, sourceTask); + } + } else { + targetTask.getSubTasks().move(subTasks.size() - 1, sourceTask); + } + } else { + boolean targetHadNoChild = subTasks.isEmpty(); + if (targetHadNoChild) { + targetTask.setComputeStartEndDynamically(true); + } + if (indexInTarget >= 0 && indexInTarget <= targetTask.getSubTasks().size()) { + targetTask.getSubTasks().add(indexInTarget, sourceTask); + } else { + targetTask.getSubTasks().add(sourceTask); + } + } + } + + public Task moveTaskInTag(Task moveTask, int index, TaskTag targetTag) { + 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(); + + List tasksBeforeTheDroppedTaskWithSameParent = allTaskInTheLane.subList(0, index).stream().filter(task -> task.eContainer().equals(moveTask.eContainer())).toList(); + Optional lastTaskBeforeTheDroppedTaskWithSameParent = Optional.empty(); + if (!tasksBeforeTheDroppedTaskWithSameParent.isEmpty()) { + lastTaskBeforeTheDroppedTaskWithSameParent = Optional.of(tasksBeforeTheDroppedTaskWithSameParent.get(tasksBeforeTheDroppedTaskWithSameParent.size() - 1)); + } + + if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent() || firstTaskAfterTheDroppedTaskWithSameParent.isPresent()) { + EObject eContainer = moveTask.eContainer(); + if (eContainer instanceof Workpackage workpackage) { + int indexInParent = 0; + if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent()) { + indexInParent = workpackage.getOwnedTasks().indexOf(lastTaskBeforeTheDroppedTaskWithSameParent.get()) + 1; + } else { + indexInParent = workpackage.getOwnedTasks().indexOf(firstTaskAfterTheDroppedTaskWithSameParent.get()); + } + workpackage.getOwnedTasks().move(indexInParent, moveTask); + } else if (eContainer instanceof AbstractTask parentTask) { + int indexInParent = 0; + if (lastTaskBeforeTheDroppedTaskWithSameParent.isPresent()) { + indexInParent = parentTask.getSubTasks().indexOf(lastTaskBeforeTheDroppedTaskWithSameParent.get()) + 1; + } else { + indexInParent = parentTask.getSubTasks().indexOf(firstTaskAfterTheDroppedTaskWithSameParent.get()); + } + parentTask.getSubTasks().move(indexInParent, moveTask); + } + } + } + return moveTask; + } + + Optional getParent(EObject eObject, Class clazz) { + Optional objectOpt = Optional.empty(); + EObject parent = eObject.eContainer(); + while (parent != null) { + if (clazz.isInstance(parent)) { + objectOpt = Optional.of(clazz.cast(parent)); + break; + } + parent = parent.eContainer(); + } + + return objectOpt; + } + + public void moveObjectiveAtIndex(Objective objective, int index) { + if (objective.eContainer() instanceof Project project) { + project.getOwnedObjectives().move(index, objective); + } + } + + public void moveTagAtIndex(TaskTag movedTag, int index) { + EObject eContainer = movedTag.eContainer(); + if (eContainer instanceof TagFolder tagFolder) { + String prefix = movedTag.getPrefix(); + List tagList = tagFolder.getOwnedTags().stream().filter(tag -> tag.getPrefix().equals(prefix)).toList(); + + int newIndex = this.computeIndexOfTagToMove(movedTag, index, tagList, tagFolder); + // We move the current tag before the tagToReplace in the project ownTags list. + int oldIndex = tagFolder.getOwnedTags().indexOf(movedTag); + // If the moved tag was located before the new location, the index after having remove the tag is + // decremented. + if (oldIndex < newIndex) { + newIndex--; + } + tagFolder.getOwnedTags().move(newIndex, movedTag); + + } + } + + /** + * 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. + * @param index + * the new index in the project tag list. + * @param tagList + * 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. + */ + private int computeIndexOfTagToMove(TaskTag tag, int index, List tagList, TagFolder tagFolder) { + int newIndex; + List unmovedLaneTags = tagList.stream().filter(currentTag -> currentTag != tag).toList(); + if (index < unmovedLaneTags.size()) { + // We retrieve the tag that will be located after the moved one. + TaskTag tagToMoveAround = unmovedLaneTags.get(index); + newIndex = tagFolder.getOwnedTags().indexOf(tagToMoveAround); + } else { + // We need to locate the tag after the last one in the deck representation + TaskTag lastTag = unmovedLaneTags.get(unmovedLaneTags.size() - 1); + newIndex = tagFolder.getOwnedTags().indexOf(lastTag) + 1; + } + return newIndex; + } +} 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 7008a62..06b19a4 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 @@ -43,6 +43,7 @@ import pepper.peppermm.TaskTimeBoundariesConstraint; import pepper.peppermm.Workpackage; import pepper.starter.services.representations.PepperMMJavaService; +import pepper.starter.services.representations.deck.PepperDeckJavaService; /** * Test used to validate the service for the task related views. @@ -285,7 +286,7 @@ public void computeTaskEffortDays() { Task task = PepperFactory.eINSTANCE.createTask(); taskComputationService.updateStartTime(task, Instant.now()); taskComputationService.updateEndTime(task, Instant.now().plus(1, ChronoUnit.HOURS).plus(1, ChronoUnit.DAYS)); - var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); + var service = new PepperDeckJavaService(); var result = service.computeTaskEffortDays(task); assertThat(result).isNotNull(); assertThat(result).isEqualTo("01d00h"); @@ -294,7 +295,7 @@ public void computeTaskEffortDays() { @Test public void editCard() { AbstractTask card = PepperFactory.eINSTANCE.createTask(); - var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); + var service = new PepperDeckJavaService(); service.editCard(card, NEW_NAME, NEW_DESCRIPTION, null); assertThat(card.getName()).isEqualTo(NEW_NAME); assertThat(card.getDescription()).isEqualTo(NEW_DESCRIPTION); @@ -309,7 +310,7 @@ public void createCard() { project.getOwnedWorkpackages().add(projectWorkpackage); project.getOwnedTagFolders().add(tagFolder); project.getOwnedTagFolders().get(0).getOwnedTags().add(tag); - var service = new PepperMMJavaService(new IFeedbackMessageService.NoOp(), new TaskComputationService(), new WorkpackageComputationService()); + var service = new PepperDeckJavaService(); service.createCard(tag); assertThat(project.getOwnedWorkpackages().get(0).getOwnedTasks()).hasSize(1); assertThat(project.getOwnedWorkpackages().get(0).getOwnedTasks().get(0).getName()).isEqualTo("New Task");