diff --git a/backend/pepper-parent/pom.xml b/backend/pepper-parent/pom.xml
index e901846..d6046e5 100644
--- a/backend/pepper-parent/pom.xml
+++ b/backend/pepper-parent/pom.xml
@@ -63,12 +63,12 @@
pepper-mm
pepper-mm
- 2026.3.0
+ 2026.4.0
pepper-mm
pepper-edit
- 2026.3.0
+ 2026.4.0
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..5f8cadb 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
@@ -50,6 +50,20 @@ public final class MessageConstants {
public static final String PAGE_RISKS_TITLE = "PAGE_RISKS_TITLE";
+ public static final String RESOURCE_FORM_TITLE = "RESOURCE_FORM_TITLE";
+
+ public static final String PAGE_RESOURCE_AVAILABILITY = "PAGE_RESOURCE_AVAILABILITY";
+
+ public static final String PAGE_RESOURCE_AVAILABILITY_TITLE = "PAGE_RESOURCE_AVAILABILITY_TITLE";
+
+ public static final String UNAVAILABILITY_PERIOD_FORM_TITLE = "UNAVAILABILITY_PERIOD_FORM_TITLE";
+
+ public static final String CREATE_NEW_UNAVAILABILITY_PERIOD = "CREATE_NEW_UNAVAILABILITY_PERIOD";
+
+ public static final String CREATE_NEW_UNAVAILABILITY_PERIOD_HELP = "CREATE_NEW_UNAVAILABILITY_PERIOD_HELP";
+
+ public static final String DELETE_UNAVAILABILITY_PERIOD = "DELETE_UNAVAILABILITY_PERIOD";
+
public static final String INVALID_INPUT = "INVALID_INPUT";
public static final String CREATE_NEW_WORKPACKAGE = "CREATE_NEW_WORKPACKAGE";
diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/PepperMMEditingContextDescriptionProvider.java b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/PepperMMEditingContextDescriptionProvider.java
index 2470968..c0699ad 100644
--- a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/PepperMMEditingContextDescriptionProvider.java
+++ b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/PepperMMEditingContextDescriptionProvider.java
@@ -15,6 +15,8 @@
import pepper.starter.messages.IPepperMMMessageService;
import pepper.starter.messages.MessageConstants;
import pepper.peppermm.Project;
+import pepper.peppermm.Resource;
+import pepper.peppermm.UnavailabilityPeriod;
import java.util.List;
import java.util.Objects;
@@ -45,6 +47,10 @@ public class PepperMMEditingContextDescriptionProvider implements IEditingContex
public static final String PROJECT_FORM_ID = "projectFormDescription";
+ public static final String RESOURCE_FORM_ID = "resourceFormDescription";
+
+ public static final String UNAVAILABILITY_PERIOD_FORM_ID = "unavailabilityPeriodFormDescription";
+
private final ComposedAdapterFactory composedAdapterFactory;
private final ILabelService labelService;
@@ -84,6 +90,10 @@ public List getRepresentationDescriptions(IEditingCo
PageDescription workpackagesPageDescription = new WorkpackagesPageDescription(this.labelService, this.identityService, this.objectSearchService, this.cursorBasedNavigationServices, this.composedAdapterFactory, this.pepperMMMessageService, this.feedbackMessageService).getWorkpackagesPageDescription();
PageDescription workpackageArtefactPageDescription = new WorkpackageArtefactPageDescription(this.labelService, this.identityService, this.objectSearchService, this.cursorBasedNavigationServices, this.composedAdapterFactory, this.pepperMMMessageService, this.feedbackMessageService).getWorkpackageArtefactsPageDescription();
PageDescription risksPageDescription = new RisksPageDescription(this.labelService, this.identityService, this.objectSearchService, this.cursorBasedNavigationServices, this.composedAdapterFactory, this.pepperMMMessageService, this.feedbackMessageService).getRisksPageDescription();
+ PageDescription resourceAvailabilityPageDescription = new ResourceAvailabilityPageDescription(this.labelService, this.identityService, this.objectSearchService, this.cursorBasedNavigationServices, this.composedAdapterFactory,
+ this.pepperMMMessageService, this.feedbackMessageService).getPageDescription();
+ PageDescription unavailabilityPeriodPageDescription = new UnavailabilityPeriodPageDescription(this.labelService, this.identityService, this.objectSearchService, this.composedAdapterFactory, this.pepperMMMessageService,
+ this.feedbackMessageService).getPageDescription();
FormDescription projectFormDescription = FormDescription.newFormDescription(PROJECT_FORM_ID)
@@ -96,7 +106,25 @@ public List getRepresentationDescriptions(IEditingCo
.pageDescriptions(List.of(projectPageDescription, customerPageDescription, planningAndCostingPageDescription, workpackagesPageDescription, workpackageArtefactPageDescription, risksPageDescription))
.iconURLsProvider(vm -> List.of())
.build();
- return List.of(projectFormDescription);
+ FormDescription resourceFormDescription = FormDescription.newFormDescription(RESOURCE_FORM_ID)
+ .label(this.pepperMMMessageService.getMessage(MessageConstants.RESOURCE_FORM_TITLE))
+ .idProvider(new GetOrCreateRandomIdProvider())
+ .labelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.RESOURCE_FORM_TITLE))
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .canCreatePredicate(this::canCreateResourceForm)
+ .pageDescriptions(List.of(resourceAvailabilityPageDescription))
+ .iconURLsProvider(vm -> List.of())
+ .build();
+ FormDescription unavailabilityPeriodFormDescription = FormDescription.newFormDescription(UNAVAILABILITY_PERIOD_FORM_ID)
+ .label(this.pepperMMMessageService.getMessage(MessageConstants.UNAVAILABILITY_PERIOD_FORM_TITLE))
+ .idProvider(new GetOrCreateRandomIdProvider())
+ .labelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.UNAVAILABILITY_PERIOD_FORM_TITLE))
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .canCreatePredicate(this::canCreateUnavailabilityPeriodForm)
+ .pageDescriptions(List.of(unavailabilityPeriodPageDescription))
+ .iconURLsProvider(vm -> List.of())
+ .build();
+ return List.of(projectFormDescription, resourceFormDescription, unavailabilityPeriodFormDescription);
}
private String getTargetObjectId(VariableManager variableManager) {
@@ -110,4 +138,16 @@ private boolean canCreate(VariableManager variableManager) {
.filter(Project.class::isInstance)
.isPresent();
}
+
+ private boolean canCreateResourceForm(VariableManager variableManager) {
+ return variableManager.get(VariableManager.SELF, EObject.class)
+ .filter(Resource.class::isInstance)
+ .isPresent();
+ }
+
+ private boolean canCreateUnavailabilityPeriodForm(VariableManager variableManager) {
+ return variableManager.get(VariableManager.SELF, EObject.class)
+ .filter(UnavailabilityPeriod.class::isInstance)
+ .isPresent();
+ }
}
diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/ResourceAvailabilityPageDescription.java b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/ResourceAvailabilityPageDescription.java
new file mode 100644
index 0000000..0ac1d1f
--- /dev/null
+++ b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/ResourceAvailabilityPageDescription.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2024, 2026 CEA LIST.
+ * 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.descriptions;
+
+import pepper.peppermm.PepperFactory;
+import pepper.peppermm.Resource;
+import pepper.starter.messages.IPepperMMMessageService;
+import pepper.starter.messages.MessageConstants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
+import org.eclipse.sirius.components.core.api.IFeedbackMessageService;
+import org.eclipse.sirius.components.core.api.IIdentityService;
+import org.eclipse.sirius.components.core.api.ILabelService;
+import org.eclipse.sirius.components.core.api.IObjectSearchService;
+import org.eclipse.sirius.components.core.api.labels.StyledString;
+import org.eclipse.sirius.components.emf.tables.CursorBasedNavigationServices;
+import org.eclipse.sirius.components.forms.ButtonStyle;
+import org.eclipse.sirius.components.forms.WidgetIdProvider;
+import org.eclipse.sirius.components.forms.description.AbstractControlDescription;
+import org.eclipse.sirius.components.forms.description.ButtonDescription;
+import org.eclipse.sirius.components.forms.description.GroupDescription;
+import org.eclipse.sirius.components.forms.description.PageDescription;
+import org.eclipse.sirius.components.representations.Success;
+import org.eclipse.sirius.components.representations.VariableManager;
+import org.eclipse.sirius.components.tables.descriptions.LineDescription;
+import org.eclipse.sirius.components.tables.descriptions.PaginatedData;
+import org.eclipse.sirius.components.tables.descriptions.TableDescription;
+import org.eclipse.sirius.components.widget.table.TableWidgetDescription;
+
+/**
+ * This class is used to provide the resource availability page description.
+ *
+ * @author Obeo
+ */
+public class ResourceAvailabilityPageDescription {
+
+ public static final String UNAVAILABILITY_PERIODS_TABLE_ID = "unavailabilityPeriodsTableId";
+
+ private final ILabelService labelService;
+
+ private final IIdentityService identityService;
+
+ private final IObjectSearchService objectSearchService;
+
+ private final CursorBasedNavigationServices cursorBasedNavigationServices;
+
+ private final ComposedAdapterFactory composedAdapterFactory;
+
+ private final IPepperMMMessageService pepperMMMessageService;
+
+ private final IFeedbackMessageService feedbackMessageService;
+
+ public ResourceAvailabilityPageDescription(ILabelService labelService, IIdentityService identityService, IObjectSearchService objectSearchService, CursorBasedNavigationServices cursorBasedNavigationServices,
+ ComposedAdapterFactory composedAdapterFactory, IPepperMMMessageService pepperMMMessageService, IFeedbackMessageService feedbackMessageService) {
+ this.labelService = labelService;
+ this.identityService = identityService;
+ this.objectSearchService = objectSearchService;
+ this.cursorBasedNavigationServices = cursorBasedNavigationServices;
+ this.composedAdapterFactory = composedAdapterFactory;
+ this.pepperMMMessageService = pepperMMMessageService;
+ this.feedbackMessageService = feedbackMessageService;
+ }
+
+ PageDescription getPageDescription() {
+ List controlDescriptions = new ArrayList<>();
+
+ Function labelProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class)
+ .map(this.labelService::getStyledLabel)
+ .map(StyledString::toString)
+ .orElse(null);
+
+ LineDescription lineDescription = LineDescription.newLineDescription("Unavailability Periods - Line")
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .targetObjectKindProvider(this::getTargetObjectKind)
+ .semanticElementsProvider(this.getSemanticElementsProvider())
+ .headerLabelProvider(labelProvider)
+ .headerIconURLsProvider(vm -> List.of())
+ .headerIndexLabelProvider(vm -> "")
+ .initialHeightProvider(vm -> 60)
+ .isResizablePredicate(vm -> true)
+ .depthLevelProvider(vm -> 0)
+ .hasChildrenProvider(vm -> false)
+ .build();
+
+ WidgetDescriptionBuilderHelper widgetDescriptionBuilderHelper = new WidgetDescriptionBuilderHelper(this::getTargetObjectId, this.labelService, this.identityService, this.objectSearchService, this.composedAdapterFactory,
+ this.pepperMMMessageService, this.feedbackMessageService);
+ TableDescription tableDescription = TableDescription.newTableDescription(UNAVAILABILITY_PERIODS_TABLE_ID)
+ .label("")
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .targetObjectKindProvider(this::getTargetObjectKind)
+ .labelProvider(labelProvider)
+ .isStripeRowPredicate(vm -> true)
+ .lineDescription(lineDescription)
+ .columnDescriptions(List.of(widgetDescriptionBuilderHelper.buildFeaturesColumnDescription(PepperFactory.eINSTANCE.createUnavailabilityPeriod())))
+ .cellDescriptions(widgetDescriptionBuilderHelper.buildCellDescription())
+ .iconURLsProvider(vm -> List.of())
+ .enableSubRows(false)
+ .pageSizeOptionsProvider(vm -> List.of(10, 20))
+ .defaultPageSizeIndexProvider(vm -> 1)
+ .build();
+
+ TableWidgetDescription tableWidgetDescription = TableWidgetDescription.newTableWidgetDescription("unavailabilityPeriodsTableWidgetId")
+ .idProvider(new WidgetIdProvider())
+ .labelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.PAGE_RESOURCE_AVAILABILITY_TITLE))
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .diagnosticsProvider(variableManager -> List.of())
+ .kindProvider(object -> "")
+ .messageProvider(object -> "")
+ .tableDescription(tableDescription)
+ .build();
+
+ controlDescriptions.add(tableWidgetDescription);
+
+ GroupDescription group = GroupDescription.newGroupDescription("resourceAvailabilityGroupId")
+ .idProvider(variableManager -> "resourceAvailabilityGroupId")
+ .labelProvider(variableManager -> "")
+ .semanticElementsProvider(variableManager -> Collections.singletonList(variableManager.getVariables().get(VariableManager.SELF)))
+ .controlDescriptions(controlDescriptions)
+ .toolbarActionDescriptions(List.of(this.getCreateUnavailabilityPeriodButtonDescription()))
+ .build();
+
+ return PageDescription.newPageDescription("resourceAvailabilityPageId")
+ .idProvider(variableManager -> "resourceAvailabilityPageId")
+ .labelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.PAGE_RESOURCE_AVAILABILITY))
+ .semanticElementsProvider(variableManager -> Collections.singletonList(variableManager.getVariables().get(VariableManager.SELF)))
+ .groupDescriptions(List.of(group))
+ .canCreatePredicate(variableManager -> true)
+ .build();
+ }
+
+ private Function getSemanticElementsProvider() {
+ return variableManager -> variableManager.get(VariableManager.SELF, Resource.class)
+ .map(Resource::getUnavailabilityPeriods)
+ .map(periods -> this.cursorBasedNavigationServices.toPaginatedData(periods.stream().map(Object.class::cast).toList(),
+ variableManager.get("cursor", Object.class).orElse(null),
+ variableManager.get("direction", String.class).orElse(null),
+ variableManager.get("size", Integer.class).orElse(10)))
+ .orElseGet(() -> new PaginatedData(List.of(), false, false, 0));
+ }
+
+ private ButtonDescription getCreateUnavailabilityPeriodButtonDescription() {
+ return ButtonDescription.newButtonDescription("createUnavailabilityPeriod")
+ .idProvider(new WidgetIdProvider())
+ .targetObjectIdProvider(this::getTargetObjectId)
+ .labelProvider(variableManager -> "")
+ .iconURLProvider(variableManager -> List.of())
+ .isReadOnlyProvider(variableManager -> false)
+ .buttonLabelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.CREATE_NEW_UNAVAILABILITY_PERIOD))
+ .imageURLProvider(variableManager -> "icons/full/obj16/UnavailabilityPeriod.svg")
+ .pushButtonHandler(variableManager -> {
+ variableManager.get(VariableManager.SELF, Resource.class)
+ .ifPresent(resource -> resource.getUnavailabilityPeriods().add(PepperFactory.eINSTANCE.createUnavailabilityPeriod()));
+ return new Success();
+ })
+ .diagnosticsProvider(variableManager -> List.of())
+ .kindProvider(variableManager -> "")
+ .messageProvider(variableManager -> "")
+ .styleProvider(variableManager -> ButtonStyle.newButtonStyle()
+ .backgroundColor("#ffffff")
+ .foregroundColor("#261E58")
+ .build())
+ .helpTextProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.CREATE_NEW_UNAVAILABILITY_PERIOD_HELP))
+ .build();
+ }
+
+ private String getTargetObjectId(VariableManager variableManager) {
+ return variableManager.get(VariableManager.SELF, Object.class)
+ .map(this.identityService::getId)
+ .orElse(null);
+ }
+
+ private String getTargetObjectKind(VariableManager variableManager) {
+ return variableManager.get(VariableManager.SELF, Object.class)
+ .map(this.identityService::getId)
+ .orElse(null);
+ }
+}
diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodPageDescription.java b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodPageDescription.java
new file mode 100644
index 0000000..13735b7
--- /dev/null
+++ b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodPageDescription.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2024, 2026 CEA LIST.
+ * 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.descriptions;
+
+import pepper.peppermm.PepperPackage;
+import pepper.peppermm.UnavailabilityPeriod;
+import pepper.starter.messages.IPepperMMMessageService;
+import pepper.starter.messages.MessageConstants;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
+import org.eclipse.sirius.components.core.api.IFeedbackMessageService;
+import org.eclipse.sirius.components.core.api.IIdentityService;
+import org.eclipse.sirius.components.core.api.ILabelService;
+import org.eclipse.sirius.components.core.api.IObjectSearchService;
+import org.eclipse.sirius.components.emf.forms.EMFFormDescriptionProvider;
+import org.eclipse.sirius.components.emf.forms.EStructuralFeatureLabelProvider;
+import org.eclipse.sirius.components.forms.DateTimeStyle;
+import org.eclipse.sirius.components.forms.DateTimeType;
+import org.eclipse.sirius.components.forms.WidgetIdProvider;
+import org.eclipse.sirius.components.forms.description.DateTimeDescription;
+import org.eclipse.sirius.components.forms.description.GroupDescription;
+import org.eclipse.sirius.components.forms.description.PageDescription;
+import org.eclipse.sirius.components.forms.description.TextfieldDescription;
+import org.eclipse.sirius.components.representations.Failure;
+import org.eclipse.sirius.components.representations.IStatus;
+import org.eclipse.sirius.components.representations.Message;
+import org.eclipse.sirius.components.representations.MessageLevel;
+import org.eclipse.sirius.components.representations.Success;
+import org.eclipse.sirius.components.representations.VariableManager;
+
+/**
+ * This class is used to provide the page description for an unavailability period.
+ *
+ * @author Obeo
+ */
+public class UnavailabilityPeriodPageDescription {
+
+ private final ILabelService labelService;
+
+ private final IIdentityService identityService;
+
+ private final IObjectSearchService objectSearchService;
+
+ private final ComposedAdapterFactory composedAdapterFactory;
+
+ private final IPepperMMMessageService pepperMMMessageService;
+
+ private final IFeedbackMessageService feedbackMessageService;
+
+ private final Function semanticTargetIdProvider;
+
+ public UnavailabilityPeriodPageDescription(ILabelService labelService, IIdentityService identityService, IObjectSearchService objectSearchService, ComposedAdapterFactory composedAdapterFactory,
+ IPepperMMMessageService pepperMMMessageService, IFeedbackMessageService feedbackMessageService) {
+ this.labelService = labelService;
+ this.identityService = identityService;
+ this.objectSearchService = objectSearchService;
+ this.composedAdapterFactory = composedAdapterFactory;
+ this.pepperMMMessageService = pepperMMMessageService;
+ this.feedbackMessageService = feedbackMessageService;
+ this.semanticTargetIdProvider = variableManager -> variableManager.get(VariableManager.SELF, Object.class).map(this.identityService::getId).orElse(null);
+ }
+
+ PageDescription getPageDescription() {
+ WidgetDescriptionBuilderHelper widgetDescriptionBuilderHelper = new WidgetDescriptionBuilderHelper(this.semanticTargetIdProvider, this.labelService, this.identityService, this.objectSearchService, this.composedAdapterFactory,
+ this.pepperMMMessageService, this.feedbackMessageService);
+
+ GroupDescription group = GroupDescription.newGroupDescription("unavailabilityPeriodGroupId")
+ .idProvider(variableManager -> "unavailabilityPeriodGroupId")
+ .labelProvider(variableManager -> "")
+ .semanticElementsProvider(variableManager -> Collections.singletonList(variableManager.getVariables().get(VariableManager.SELF)))
+ .controlDescriptions(List.of(
+ this.buildDateTimeDescription(PepperPackage.eINSTANCE.getUnavailabilityPeriod_StartDate()),
+ this.buildDateTimeDescription(PepperPackage.eINSTANCE.getUnavailabilityPeriod_EndDate()),
+ this.buildDescriptionTextfieldDescription(widgetDescriptionBuilderHelper)))
+ .build();
+
+ return PageDescription.newPageDescription("unavailabilityPeriodPageId")
+ .idProvider(variableManager -> "unavailabilityPeriodPageId")
+ .labelProvider(variableManager -> this.pepperMMMessageService.getMessage(MessageConstants.PAGE_RESOURCE_AVAILABILITY))
+ .semanticElementsProvider(variableManager -> Collections.singletonList(variableManager.getVariables().get(VariableManager.SELF)))
+ .groupDescriptions(List.of(group))
+ .canCreatePredicate(variableManager -> true)
+ .build();
+ }
+
+ private DateTimeDescription buildDateTimeDescription(EAttribute feature) {
+ WidgetDescriptionBuilderHelper widgetDescriptionBuilderHelper = new WidgetDescriptionBuilderHelper(this.semanticTargetIdProvider, this.labelService, this.identityService, this.objectSearchService, this.composedAdapterFactory,
+ this.pepperMMMessageService, this.feedbackMessageService);
+
+ return DateTimeDescription.newDateTimeDescription(UUID.randomUUID().toString())
+ .idProvider(new WidgetIdProvider())
+ .type(DateTimeType.DATE)
+ .targetObjectIdProvider(this.semanticTargetIdProvider)
+ .labelProvider(this.getLabelProvider(feature))
+ .isReadOnlyProvider(variableManager -> false)
+ .stringValueProvider(this.getDateValueProvider(feature))
+ .newValueHandler(this.getDateNewValueHandler(feature))
+ .diagnosticsProvider(variableManager -> List.of())
+ .kindProvider(diagnostic -> "")
+ .messageProvider(diagnostic -> "")
+ .styleProvider(variableManager -> DateTimeStyle.newDateTimeStyle().widgetGridLayout(widgetDescriptionBuilderHelper.buildWidgetGridLayout()).build())
+ .build();
+ }
+
+ private TextfieldDescription buildDescriptionTextfieldDescription(WidgetDescriptionBuilderHelper widgetDescriptionBuilderHelper) {
+ EAttribute feature = PepperPackage.eINSTANCE.getUnavailabilityPeriod_Description();
+ return TextfieldDescription.newTextfieldDescription(UUID.randomUUID().toString())
+ .idProvider(new WidgetIdProvider())
+ .targetObjectIdProvider(this.semanticTargetIdProvider)
+ .labelProvider(this.getLabelProvider(feature))
+ .isReadOnlyProvider(variableManager -> false)
+ .valueProvider(variableManager -> variableManager.get(VariableManager.SELF, UnavailabilityPeriod.class)
+ .map(UnavailabilityPeriod::getDescription)
+ .orElse(""))
+ .newValueHandler((variableManager, newValue) -> {
+ variableManager.get(VariableManager.SELF, UnavailabilityPeriod.class)
+ .ifPresent(period -> period.setDescription(newValue));
+ return new Success();
+ })
+ .diagnosticsProvider(variableManager -> List.of())
+ .kindProvider(diagnostic -> "")
+ .messageProvider(diagnostic -> "")
+ .styleProvider(variableManager -> org.eclipse.sirius.components.forms.TextfieldStyle.newTextfieldStyle().widgetGridLayout(widgetDescriptionBuilderHelper.buildWidgetGridLayout()).build())
+ .build();
+ }
+
+ private Function getLabelProvider(EStructuralFeature feature) {
+ return new EStructuralFeatureLabelProvider(EMFFormDescriptionProvider.ESTRUCTURAL_FEATURE) {
+ @Override
+ public String apply(VariableManager variableManager) {
+ VariableManager childVM = variableManager.createChild();
+ childVM.put(EMFFormDescriptionProvider.ESTRUCTURAL_FEATURE, feature);
+ return super.apply(childVM);
+ }
+ };
+ }
+
+ private Function getDateValueProvider(EAttribute eAttribute) {
+ return variableManager -> variableManager.get(VariableManager.SELF, EObject.class)
+ .map(eObject -> eObject.eGet(eAttribute))
+ .filter(LocalDate.class::isInstance)
+ .map(LocalDate.class::cast)
+ .map(DateTimeFormatter.ISO_LOCAL_DATE::format)
+ .orElse("");
+ }
+
+ private BiFunction getDateNewValueHandler(EAttribute eAttribute) {
+ return (variableManager, newValue) -> variableManager.get(VariableManager.SELF, UnavailabilityPeriod.class)
+ .map(period -> this.setDate(period, eAttribute, newValue))
+ .orElseGet(() -> new Failure(""));
+ }
+
+ private IStatus setDate(UnavailabilityPeriod period, EAttribute eAttribute, String newValue) {
+ try {
+ LocalDate localDate = null;
+ if (newValue != null && !newValue.isBlank()) {
+ localDate = LocalDate.parse(newValue);
+ }
+ LocalDate startDate = eAttribute == PepperPackage.eINSTANCE.getUnavailabilityPeriod_StartDate() ? localDate : period.getStartDate();
+ LocalDate endDate = eAttribute == PepperPackage.eINSTANCE.getUnavailabilityPeriod_EndDate() ? localDate : period.getEndDate();
+ if (startDate != null && endDate != null && endDate.isBefore(startDate)) {
+ return new Failure(List.of(new Message(this.pepperMMMessageService.getMessage(MessageConstants.INVALID_VALUE, newValue), MessageLevel.ERROR)));
+ }
+ period.eSet(eAttribute, localDate);
+ return new Success();
+ } catch (DateTimeParseException exception) {
+ return new Failure(List.of(new Message(this.pepperMMMessageService.getMessage(MessageConstants.INVALID_VALUE, newValue), MessageLevel.ERROR)));
+ }
+ }
+}
diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuEntryExecutor.java b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuEntryExecutor.java
new file mode 100644
index 0000000..197db73
--- /dev/null
+++ b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuEntryExecutor.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024, 2026 CEA LIST.
+ * 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.descriptions;
+
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.sirius.components.collaborative.api.ChangeKind;
+import org.eclipse.sirius.components.collaborative.tables.api.IRowContextMenuEntryExecutor;
+import org.eclipse.sirius.components.core.api.IEditService;
+import org.eclipse.sirius.components.core.api.IEditingContext;
+import org.eclipse.sirius.components.core.api.IObjectSearchService;
+import org.eclipse.sirius.components.representations.IStatus;
+import org.eclipse.sirius.components.representations.Success;
+import org.eclipse.sirius.components.tables.Line;
+import org.eclipse.sirius.components.tables.Table;
+import org.eclipse.sirius.components.tables.descriptions.TableDescription;
+import org.springframework.stereotype.Service;
+
+/**
+ * Executes unavailability period row actions.
+ *
+ * @author Obeo
+ */
+@Service
+public class UnavailabilityPeriodRowContextMenuEntryExecutor implements IRowContextMenuEntryExecutor {
+
+ private final IEditService editService;
+
+ private final IObjectSearchService objectSearchService;
+
+ public UnavailabilityPeriodRowContextMenuEntryExecutor(IEditService editService, IObjectSearchService objectSearchService) {
+ this.editService = Objects.requireNonNull(editService);
+ this.objectSearchService = Objects.requireNonNull(objectSearchService);
+ }
+
+ @Override
+ public boolean canExecute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId) {
+ return UnavailabilityPeriodRowContextMenuProvider.DELETE_ID.equals(rowMenuContextEntryId)
+ && Objects.equals(tableDescription.getId(), ResourceAvailabilityPageDescription.UNAVAILABILITY_PERIODS_TABLE_ID);
+ }
+
+ @Override
+ public IStatus execute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId) {
+ this.objectSearchService.getObject(editingContext, row.getTargetObjectId()).ifPresent(this.editService::delete);
+ return new Success(ChangeKind.SEMANTIC_CHANGE, Map.of());
+ }
+}
diff --git a/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuProvider.java b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuProvider.java
new file mode 100644
index 0000000..e60de5c
--- /dev/null
+++ b/backend/pepper-starter/src/main/java/pepper/starter/services/descriptions/UnavailabilityPeriodRowContextMenuProvider.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2024, 2026 CEA LIST.
+ * 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.descriptions;
+
+import pepper.starter.messages.IPepperMMMessageService;
+import pepper.starter.messages.MessageConstants;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.sirius.components.collaborative.tables.api.IRowContextMenuEntryProvider;
+import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuEntry;
+import org.eclipse.sirius.components.core.api.IEditingContext;
+import org.eclipse.sirius.components.tables.Line;
+import org.eclipse.sirius.components.tables.Table;
+import org.eclipse.sirius.components.tables.descriptions.TableDescription;
+import org.springframework.stereotype.Service;
+
+/**
+ * Provides row actions for unavailability period tables.
+ *
+ * @author Obeo
+ */
+@Service
+public class UnavailabilityPeriodRowContextMenuProvider implements IRowContextMenuEntryProvider {
+
+ public static final String DELETE_ID = "pepper-unavailability-period-delete-row";
+
+ private final IPepperMMMessageService pepperMMMessageService;
+
+ public UnavailabilityPeriodRowContextMenuProvider(IPepperMMMessageService pepperMMMessageService) {
+ this.pepperMMMessageService = Objects.requireNonNull(pepperMMMessageService);
+ }
+
+ @Override
+ public boolean canHandle(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row) {
+ return Objects.equals(tableDescription.getId(), ResourceAvailabilityPageDescription.UNAVAILABILITY_PERIODS_TABLE_ID);
+ }
+
+ @Override
+ public List getRowContextMenuEntries(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row) {
+ return List.of(new RowContextMenuEntry(DELETE_ID, this.pepperMMMessageService.getMessage(MessageConstants.DELETE_UNAVAILABILITY_PERIOD), List.of()));
+ }
+}
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..fb1a71e 100644
--- a/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties
+++ b/backend/pepper-starter/src/main/resources/messages/pepper-starter.properties
@@ -26,6 +26,13 @@ PAGE_WORKPACKAGE_ARTEFACTS= WP Artefacts
PAGE_WORKPACKAGE_ARTEFACTS_TITLE= List of the workpackage Artefacts in all the workpackages of the project
PAGE_RISKS= Risks
PAGE_RISKS_TITLE= List of the Risks of the project
+RESOURCE_FORM_TITLE=Resource Form
+PAGE_RESOURCE_AVAILABILITY= Availability
+PAGE_RESOURCE_AVAILABILITY_TITLE= List of resource unavailability periods
+UNAVAILABILITY_PERIOD_FORM_TITLE=Unavailability Period Form
+CREATE_NEW_UNAVAILABILITY_PERIOD= Create New Unavailability Period
+CREATE_NEW_UNAVAILABILITY_PERIOD_HELP=Create a new unavailability period for the resource
+DELETE_UNAVAILABILITY_PERIOD=Delete period
CREATE_NEW_WORKPACKAGE= Create New Workpackage
CREATE_NEW_WORKPACKAGE_HELP=Create a new workpackage in the project
WORKPACKAGE_COLUMN_NAME=WP
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..dbf6c09 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,10 @@ 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
+RESOURCE_FORM_TITLE=Formulaire de ressource
+PAGE_RESOURCE_AVAILABILITY= Disponibilité
+PAGE_RESOURCE_AVAILABILITY_TITLE= Liste des périodes d'indisponibilité de la ressource
+UNAVAILABILITY_PERIOD_FORM_TITLE=Formulaire de période d'indisponibilité
+CREATE_NEW_UNAVAILABILITY_PERIOD= Créer une période d'indisponibilité
+CREATE_NEW_UNAVAILABILITY_PERIOD_HELP=Créer une nouvelle période d'indisponibilité pour la ressource
+DELETE_UNAVAILABILITY_PERIOD=Supprimer la période