From 0a24e1455b5fe6daebde791657a89ada7c541dbe Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Thu, 30 Apr 2026 17:03:06 +0200 Subject: [PATCH 01/11] draft merge some modifications into a new composite Signed-off-by: Mathieu DEHARBE --- .../server/CompositeController.java | 12 +++++++++++ .../service/NetworkModificationService.java | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/org/gridsuite/modification/server/CompositeController.java b/src/main/java/org/gridsuite/modification/server/CompositeController.java index 89fd56789..903a5769d 100644 --- a/src/main/java/org/gridsuite/modification/server/CompositeController.java +++ b/src/main/java/org/gridsuite/modification/server/CompositeController.java @@ -78,6 +78,18 @@ public ResponseEntity moveSubModification( return ResponseEntity.ok().build(); } + @PostMapping(value = "/groups/{groupUuid}/composite-modification", consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Merge some network modifications into a new composite modification") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")}) + public CompletableFuture> mergeNetworkModificationsIntoNewComposite( + @Parameter(description = "group UUID where the modifications are located") @PathVariable("groupUuid") UUID targetGroupUuid, + @RequestBody Pair, List> modificationApplicationContexts) { + return networkModificationService.mergeNetworkModificationsIntoNewComposite( + targetGroupUuid, + modificationApplicationContexts + ).thenApply(ResponseEntity.ok()::body); + } + @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Create a network composite modification") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")}) diff --git a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java index 2437ba88d..2097c458b 100644 --- a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java @@ -21,6 +21,7 @@ import org.gridsuite.filter.AbstractFilter; import org.gridsuite.modification.ModificationType; import org.gridsuite.modification.NetworkModificationException; +import org.gridsuite.modification.dto.CompositeModificationInfos; import org.gridsuite.modification.dto.EquipmentModificationInfos; import org.gridsuite.modification.dto.GenerationDispatchInfos; import org.gridsuite.modification.dto.ModificationInfos; @@ -474,6 +475,25 @@ public CompletableFuture insertCompositeModification new NetworkModificationsResult(ids, result)); } + @Transactional + public CompletableFuture mergeNetworkModificationsIntoNewComposite( + UUID targetGroupUuid, + @NonNull Pair, List> modificationApplicationContexts) { + // TODO : get the target (groupUuid or composite Uuid) + + // TODO : create the composite + + CompositeModificationInfos composite = new CompositeModificationInfos(); + + // TODO : assign modifications (remove previous assignment) + + // apply the composite (and implicitely those contained) : + return applyModifications(targetGroupUuid, List.of(composite), modificationApplicationContexts.getSecond()) + .thenApply(results -> + new NetworkModificationsResult(List.of(composite.getUuid()), results) + ); + } + @Transactional public UUID createNetworkCompositeModification(@NonNull List modificationUuids) { return networkModificationRepository.createNetworkCompositeModification(modificationUuids); From 166a8bc787020d5ff64d248f3cd0639bc5f2200f Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 5 May 2026 10:17:29 +0200 Subject: [PATCH 02/11] mergeNetworkModificationsIntoNewComposite Signed-off-by: Mathieu DEHARBE --- .../repositories/ModificationRepository.java | 5 +++ .../NetworkModificationRepository.java | 44 +++++++++++++++++++ .../service/NetworkModificationService.java | 13 +++--- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java index 24397faa3..7d710d65d 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java @@ -65,6 +65,11 @@ public interface ModificationRepository extends JpaRepository findModificationIdsByCompositeModificationId(UUID uuid); + // return the uuid of the composite containing the modifcation sent as parameter + // TODO : à changer après fusion de ma fiche sur les ordre de modifications de réseau + @Query(value = "SELECT cast(id AS UUID) FROM composite_modification_sub_modifications WHERE modification_id = :uuid ORDER BY modifications_order", nativeQuery = true) + UUID findCompositeIdByContainedModificationId(UUID uuid); + @Query(value = "SELECT cast(modification_id AS VARCHAR) FROM composite_modification_sub_modifications WHERE id IN (?1) ORDER BY modifications_order", nativeQuery = true) List findModificationIdsByCompositeModificationIdIn(List uuids); diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index 931c39379..cb7fb6d0a 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -916,6 +916,50 @@ public List insertCompositeModifications( return newEntities.stream().map(ModificationEntity::toModificationInfos).toList(); } + @Transactional + public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( + List mergedModificationsUuids) { + // get the target (groupUuid or composite Uuid of the first merged modification + its index in this target) + UUID firstModifUuid = mergedModificationsUuids.getFirst(); + ModificationInfos firstModification = getModificationInfo(firstModifUuid); + ModificationEntity firstModificationEntity = getModificationEntity(firstModification.getUuid()); + int targetIndex = firstModificationEntity.getModificationsOrder(); + ModificationGroupEntity targetGroup = firstModificationEntity.getGroup(); + CompositeModificationEntity targetComposite = null; + if (targetGroup == null) { + // the first modification is inside a composite + UUID targetCompositeUuid = modificationRepository.findCompositeIdByContainedModificationId(firstModifUuid); + targetComposite = compositeModificationRepository.getReferenceById(targetCompositeUuid); + } + + // get all the modifications to be merged, remove previous assignment + List mergedModifications = mergedModificationsUuids.stream() + .map(modificationRepository::getReferenceById) + .collect(Collectors.toList()); + // TODO :remove previous assignments (group/composites..) + mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null)); // TODO : group should be reordered ?? + + // create the composite + CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder().modificationsInfos(List.of()).build(); + CompositeModificationEntity newCompositeEntity = (CompositeModificationEntity) ModificationEntity.fromDTO(newCompositeInfos); + newCompositeEntity.setModificationsOrder(targetIndex); + + // assign modifications + newCompositeEntity.setModifications(mergedModifications); + if ( targetGroup != null) { + // TODO : réordonancement pour être au même point que la fusionnée num 1 + newCompositeEntity.setGroup(targetGroup); + } + if (targetComposite != null) { + List modifications = targetComposite.getModifications(); + modifications.add(newCompositeEntity); // TODO : réordonancement pour être au même point que la fusionnée num 1 + targetComposite.setModifications(modifications); + } + + return modificationRepository.save(newCompositeEntity); + } + + @Transactional // TODO use modificationsOrder like modifications in a group : remove the OrderColumn annotation in CompositeModificationEntity public void moveSubModification(@NonNull UUID groupUuid, UUID sourceCompositeUuid, UUID targetCompositeUuid, diff --git a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java index 2097c458b..cca16166f 100644 --- a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java @@ -479,18 +479,15 @@ public CompletableFuture insertCompositeModification public CompletableFuture mergeNetworkModificationsIntoNewComposite( UUID targetGroupUuid, @NonNull Pair, List> modificationApplicationContexts) { - // TODO : get the target (groupUuid or composite Uuid) + List mergedModificationsUuids = modificationApplicationContexts.getFirst(); - // TODO : create the composite - - CompositeModificationInfos composite = new CompositeModificationInfos(); - - // TODO : assign modifications (remove previous assignment) + CompositeModificationInfos newComposite = + networkModificationRepository.mergeNetworkModificationsIntoNewComposite(mergedModificationsUuids).toModificationInfos(); // apply the composite (and implicitely those contained) : - return applyModifications(targetGroupUuid, List.of(composite), modificationApplicationContexts.getSecond()) + return applyModifications(targetGroupUuid, List.of(newComposite), modificationApplicationContexts.getSecond()) .thenApply(results -> - new NetworkModificationsResult(List.of(composite.getUuid()), results) + new NetworkModificationsResult(List.of(newComposite.getUuid()), results) ); } From 5e7311fe44485d7fdb25556221222dfae1689cd0 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Wed, 13 May 2026 17:12:32 +0200 Subject: [PATCH 03/11] corrections post merge Signed-off-by: Mathieu DEHARBE --- .../server/repositories/ModificationRepository.java | 1 - .../server/repositories/NetworkModificationRepository.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java index 20b940f23..7c409fa77 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java @@ -76,7 +76,6 @@ SELECT CAST(sm.modification_id AS VARCHAR) @Query(value = "SELECT cast(id AS UUID) FROM composite_modification_sub_modifications WHERE modification_id = :uuid ORDER BY modifications_order", nativeQuery = true) UUID findCompositeIdByContainedModificationId(UUID uuid); - @Query(value = "SELECT cast(modification_id AS VARCHAR) FROM composite_modification_sub_modifications WHERE id IN (?1) ORDER BY modifications_order", nativeQuery = true) @Query(value = """ SELECT CAST(sm.modification_id AS VARCHAR) FROM composite_modification_sub_modifications sm diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index 4a324ab34..32565fd01 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -939,7 +939,7 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( // assign modifications newCompositeEntity.setModifications(mergedModifications); - if ( targetGroup != null) { + if (targetGroup != null) { // TODO : réordonancement pour être au même point que la fusionnée num 1 newCompositeEntity.setGroup(targetGroup); } @@ -952,7 +952,6 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( return modificationRepository.save(newCompositeEntity); } - @Transactional public void moveSubModification(@NonNull UUID groupUuid, UUID sourceCompositeUuid, UUID targetCompositeUuid, @NonNull UUID modificationUuid, UUID beforeUuid) { From 72a768a676e694a4ea96c870bf78a39d9ba855db Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 May 2026 12:11:29 +0200 Subject: [PATCH 04/11] remove auto application + send UUID + adept to refacto Signed-off-by: Mathieu DEHARBE --- .../modification/server/CompositeController.java | 12 +++++++----- .../server/repositories/ModificationRepository.java | 11 ++++++++--- .../repositories/NetworkModificationRepository.java | 12 ++++++++---- .../server/service/NetworkModificationService.java | 8 ++------ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/CompositeController.java b/src/main/java/org/gridsuite/modification/server/CompositeController.java index 903a5769d..f1969b621 100644 --- a/src/main/java/org/gridsuite/modification/server/CompositeController.java +++ b/src/main/java/org/gridsuite/modification/server/CompositeController.java @@ -81,13 +81,15 @@ public ResponseEntity moveSubModification( @PostMapping(value = "/groups/{groupUuid}/composite-modification", consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Merge some network modifications into a new composite modification") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")}) - public CompletableFuture> mergeNetworkModificationsIntoNewComposite( + public ResponseEntity mergeNetworkModificationsIntoNewComposite( @Parameter(description = "group UUID where the modifications are located") @PathVariable("groupUuid") UUID targetGroupUuid, @RequestBody Pair, List> modificationApplicationContexts) { - return networkModificationService.mergeNetworkModificationsIntoNewComposite( - targetGroupUuid, - modificationApplicationContexts - ).thenApply(ResponseEntity.ok()::body); + + return ResponseEntity.ok().body( + networkModificationService.mergeNetworkModificationsIntoNewComposite( + targetGroupUuid, + modificationApplicationContexts) + ); } @PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java index 7c409fa77..3458f3346 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/ModificationRepository.java @@ -71,9 +71,14 @@ SELECT CAST(sm.modification_id AS VARCHAR) """, nativeQuery = true) List findModificationIdsByCompositeModificationId(UUID uuid); - // return the uuid of the composite containing the modifcation sent as parameter - // TODO : à changer après fusion de ma fiche sur les ordre de modifications de réseau - @Query(value = "SELECT cast(id AS UUID) FROM composite_modification_sub_modifications WHERE modification_id = :uuid ORDER BY modifications_order", nativeQuery = true) + // return the uuid of the composite containing the modification sent as parameter + @Query(value = """ + SELECT CAST(sm.id AS VARCHAR) + FROM composite_modification_sub_modifications sm + INNER JOIN modification m ON sm.modification_id = m.id + WHERE sm.modification_id = :uuid + ORDER BY m.modifications_order + """, nativeQuery = true) UUID findCompositeIdByContainedModificationId(UUID uuid); @Query(value = """ diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index 32565fd01..b0517ae48 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -933,19 +933,23 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null)); // TODO : group should be reordered ?? // create the composite - CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder().modificationsInfos(List.of()).build(); + CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder() + .modificationsInfos(List.of()) + .name("New composite modification") + .build(); CompositeModificationEntity newCompositeEntity = (CompositeModificationEntity) ModificationEntity.fromDTO(newCompositeInfos); newCompositeEntity.setModificationsOrder(targetIndex); // assign modifications newCompositeEntity.setModifications(mergedModifications); if (targetGroup != null) { - // TODO : réordonancement pour être au même point que la fusionnée num 1 - newCompositeEntity.setGroup(targetGroup); + List modifications = targetGroup.getModifications(); + modifications.add(targetIndex, newCompositeEntity); + targetGroup.setModifications(modifications); } if (targetComposite != null) { List modifications = targetComposite.getModifications(); - modifications.add(newCompositeEntity); // TODO : réordonancement pour être au même point que la fusionnée num 1 + modifications.add(targetIndex, newCompositeEntity); targetComposite.setModifications(modifications); } diff --git a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java index fb1fb2c8f..b9dcc8252 100644 --- a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java @@ -476,7 +476,7 @@ public CompletableFuture insertCompositeModification } @Transactional - public CompletableFuture mergeNetworkModificationsIntoNewComposite( + public UUID mergeNetworkModificationsIntoNewComposite( UUID targetGroupUuid, @NonNull Pair, List> modificationApplicationContexts) { List mergedModificationsUuids = modificationApplicationContexts.getFirst(); @@ -484,11 +484,7 @@ public CompletableFuture mergeNetworkModificationsIn CompositeModificationInfos newComposite = networkModificationRepository.mergeNetworkModificationsIntoNewComposite(mergedModificationsUuids).toModificationInfos(); - // apply the composite (and implicitely those contained) : - return applyModifications(targetGroupUuid, List.of(newComposite), modificationApplicationContexts.getSecond()) - .thenApply(results -> - new NetworkModificationsResult(List.of(newComposite.getUuid()), results) - ); + return newComposite.getUuid(); } @Transactional From 49a522aca1d12373eeb127586b7f8997395b3d19 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 May 2026 14:41:27 +0200 Subject: [PATCH 05/11] frees merges modifications from its groups Signed-off-by: Mathieu DEHARBE --- .../NetworkModificationRepository.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index b0517ae48..85fd0f3a8 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -914,8 +914,7 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( List mergedModificationsUuids) { // get the target (groupUuid or composite Uuid of the first merged modification + its index in this target) UUID firstModifUuid = mergedModificationsUuids.getFirst(); - ModificationInfos firstModification = getModificationInfo(firstModifUuid); - ModificationEntity firstModificationEntity = getModificationEntity(firstModification.getUuid()); + ModificationEntity firstModificationEntity = getModificationEntity(firstModifUuid); int targetIndex = firstModificationEntity.getModificationsOrder(); ModificationGroupEntity targetGroup = firstModificationEntity.getGroup(); CompositeModificationEntity targetComposite = null; @@ -927,10 +926,21 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( // get all the modifications to be merged, remove previous assignment List mergedModifications = mergedModificationsUuids.stream() - .map(modificationRepository::getReferenceById) - .collect(Collectors.toList()); - // TODO :remove previous assignments (group/composites..) - mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null)); // TODO : group should be reordered ?? + .map(modificationRepository::findById).filter(Optional::isPresent).map(Optional::get).toList(); + // remove previous assignments of the merged modifications + // 1. cleans and reorders the origin group if there is one : + ModificationGroupEntity originGroup = mergedModifications.stream() + .map(ModificationEntity::getGroup) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + if (originGroup != null) { + List originGroupModifications = originGroup.getModifications(); + originGroupModifications.removeIf(mod -> mergedModificationsUuids.contains(mod.getId())); + originGroup.setModifications(originGroupModifications); + mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null)); + } + // TODO : 2. from composites // create the composite CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder() @@ -946,8 +956,7 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( List modifications = targetGroup.getModifications(); modifications.add(targetIndex, newCompositeEntity); targetGroup.setModifications(modifications); - } - if (targetComposite != null) { + } else { List modifications = targetComposite.getModifications(); modifications.add(targetIndex, newCompositeEntity); targetComposite.setModifications(modifications); From 028a31c4e970f79b2377bf825b794b75f09502c9 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 May 2026 16:22:05 +0200 Subject: [PATCH 06/11] cleans the composites whose submodifications are merged into a new one Signed-off-by: Mathieu DEHARBE --- .../NetworkModificationRepository.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java index 85fd0f3a8..0a37f2c0c 100644 --- a/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java +++ b/src/main/java/org/gridsuite/modification/server/repositories/NetworkModificationRepository.java @@ -921,7 +921,7 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( if (targetGroup == null) { // the first modification is inside a composite UUID targetCompositeUuid = modificationRepository.findCompositeIdByContainedModificationId(firstModifUuid); - targetComposite = compositeModificationRepository.getReferenceById(targetCompositeUuid); + targetComposite = compositeModificationRepository.findById(targetCompositeUuid).orElse(null); } // get all the modifications to be merged, remove previous assignment @@ -940,9 +940,22 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( originGroup.setModifications(originGroupModifications); mergedModifications.forEach(modificationEntity -> modificationEntity.setGroup(null)); } - // TODO : 2. from composites + // 2. cleans the composites whose submodifications are merged into a new one + for (ModificationEntity mergedModification : mergedModifications.stream().filter(mod -> mod.getGroup() == null).toList()) { + UUID compositeUuid = modificationRepository.findCompositeIdByContainedModificationId(mergedModification.getId()); + if (compositeUuid != null) { + CompositeModificationEntity previousOwner = compositeModificationRepository.findById(compositeUuid).orElse(null); + if (previousOwner != null) { + List modificationsLeft = previousOwner.getModifications() + .stream() + .filter(mod -> !mergedModificationsUuids.contains(mod.getId())) + .toList(); + previousOwner.setModifications(modificationsLeft); + } + } + } - // create the composite + // create the new composite CompositeModificationInfos newCompositeInfos = CompositeModificationInfos.builder() .modificationsInfos(List.of()) .name("New composite modification") @@ -952,14 +965,14 @@ public CompositeModificationEntity mergeNetworkModificationsIntoNewComposite( // assign modifications newCompositeEntity.setModifications(mergedModifications); + // put the new composite in the target group or composite if (targetGroup != null) { List modifications = targetGroup.getModifications(); modifications.add(targetIndex, newCompositeEntity); targetGroup.setModifications(modifications); - } else { + } else if (targetComposite != null) { List modifications = targetComposite.getModifications(); modifications.add(targetIndex, newCompositeEntity); - targetComposite.setModifications(modifications); } return modificationRepository.save(newCompositeEntity); From db56d65c9469107d6f1153e51abe4e9111a4f133 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Fri, 15 May 2026 17:50:12 +0200 Subject: [PATCH 07/11] simplify endpoint Signed-off-by: Mathieu DEHARBE --- .../modification/server/CompositeController.java | 8 +++----- .../server/service/NetworkModificationService.java | 5 +---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/gridsuite/modification/server/CompositeController.java b/src/main/java/org/gridsuite/modification/server/CompositeController.java index f1969b621..a500637bb 100644 --- a/src/main/java/org/gridsuite/modification/server/CompositeController.java +++ b/src/main/java/org/gridsuite/modification/server/CompositeController.java @@ -78,17 +78,15 @@ public ResponseEntity moveSubModification( return ResponseEntity.ok().build(); } - @PostMapping(value = "/groups/{groupUuid}/composite-modification", consumes = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/composite-modification", consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Merge some network modifications into a new composite modification") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")}) public ResponseEntity mergeNetworkModificationsIntoNewComposite( - @Parameter(description = "group UUID where the modifications are located") @PathVariable("groupUuid") UUID targetGroupUuid, - @RequestBody Pair, List> modificationApplicationContexts) { + @RequestBody List mergedModificationsUuids) { return ResponseEntity.ok().body( networkModificationService.mergeNetworkModificationsIntoNewComposite( - targetGroupUuid, - modificationApplicationContexts) + mergedModificationsUuids) ); } diff --git a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java index b9dcc8252..9e1573791 100644 --- a/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java +++ b/src/main/java/org/gridsuite/modification/server/service/NetworkModificationService.java @@ -477,10 +477,7 @@ public CompletableFuture insertCompositeModification @Transactional public UUID mergeNetworkModificationsIntoNewComposite( - UUID targetGroupUuid, - @NonNull Pair, List> modificationApplicationContexts) { - List mergedModificationsUuids = modificationApplicationContexts.getFirst(); - + @NonNull List mergedModificationsUuids) { CompositeModificationInfos newComposite = networkModificationRepository.mergeNetworkModificationsIntoNewComposite(mergedModificationsUuids).toModificationInfos(); From 63f60b8794e2fed0897658dc84119107373923e6 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Mon, 18 May 2026 16:48:16 +0200 Subject: [PATCH 08/11] testMergeNetworkModificationsIntoNewComposite Signed-off-by: Mathieu DEHARBE --- .../server/CompositeControllerTest.java | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java index 64c693773..81e8a0eff 100644 --- a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java +++ b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java @@ -38,11 +38,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import static org.gridsuite.modification.ModificationType.COMPOSITE_MODIFICATION; import static org.gridsuite.modification.server.utils.NetworkCreation.VARIANT_ID; @@ -276,7 +272,9 @@ void testDuplicateCompositeModification() throws Exception { Map returnedMap = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); assertEquals(1, returnedMap.size()); - Map.Entry returnedIds = returnedMap.entrySet().stream().findFirst().get(); + Optional> first = returnedMap.entrySet().stream().findFirst(); + assertTrue(first.isPresent()); + Map.Entry returnedIds = first.get(); UUID returnedSourceId = returnedIds.getKey(); UUID returnedNewId = returnedIds.getValue(); assertNotEquals(returnedSourceId, returnedNewId); @@ -484,6 +482,60 @@ void testMoveSubModificationFromCompositeToRoot() throws Exception { assertNull(remainingEntity.getGroup()); } + @Test + void testMergeNetworkModificationsIntoNewComposite() throws Exception { + // Create 3 root-level modifications in the group + List rootMods = createSomeSwitchModifications(TEST_GROUP_ID, 3); + List rootModUuids = rootMods.stream().map(ModificationInfos::getUuid).toList(); + + assertEquals(3, modificationRepository.getModifications(TEST_GROUP_ID, true, true).size()); + + // Merge the first 2 root-level modifications into a new composite + List mergedModificationUuids = rootModUuids.subList(0, 2); + MvcResult mvcResult = mockMvc.perform(post(URI_COMPOSITE_NETWORK_MODIF_BASE + "/composite-modification") + .content(mapper.writeValueAsString(mergedModificationUuids)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + + UUID compositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); + assertNotNull(compositeUuid); + + // The root group should now contain the new composite and the remaining non-merged modification + List rootModificationsAfterMerge = modificationRepository.getModifications(TEST_GROUP_ID, true, true); + assertEquals(2, rootModificationsAfterMerge.size()); + assertEquals(compositeUuid, rootModificationsAfterMerge.getFirst().getUuid()); + assertEquals(COMPOSITE_MODIFICATION, rootModificationsAfterMerge.getFirst().getType()); + assertEquals(rootModUuids.get(2), rootModificationsAfterMerge.get(1).getUuid()); + + // The new composite should contain the merged modifications in the same order + Map> compositeContentMap = mapper.readValue( + mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", compositeUuid)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + new TypeReference<>() { }); + List compositeContent = compositeContentMap.get(compositeUuid); + + assertEquals(2, compositeContent.size()); + assertEquals(rootModUuids.get(0), compositeContent.get(0).getUuid()); + assertEquals(rootModUuids.get(1), compositeContent.get(1).getUuid()); + + // The new composite must belong to TEST_GROUP_ID at root level + ModificationEntity compositeEntity = modificationRepository.getModificationEntity(compositeUuid); + assertNotNull(compositeEntity.getGroup()); + assertEquals(TEST_GROUP_ID, compositeEntity.getGroup().getId()); + + // The merged modifications must no longer belong directly to the group + ModificationEntity firstMergedEntity = modificationRepository.getModificationEntity(rootModUuids.get(0)); + ModificationEntity secondMergedEntity = modificationRepository.getModificationEntity(rootModUuids.get(1)); + assertNull(firstMergedEntity.getGroup()); + assertNull(secondMergedEntity.getGroup()); + + // The non-merged modification must still belong to TEST_GROUP_ID + ModificationEntity remainingEntity = modificationRepository.getModificationEntity(rootModUuids.get(2)); + assertNotNull(remainingEntity.getGroup()); + assertEquals(TEST_GROUP_ID, remainingEntity.getGroup().getId()); + } + + @Test void testExpandToLeafUuidsNestedComposites() throws Exception { // Build nested structure: outerComposite → [innerComposite → [leaf1, leaf2], leaf3] From 261f807c4ad4a4a3c8182c38b93bda644abc0040 Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Mon, 18 May 2026 17:03:55 +0200 Subject: [PATCH 09/11] remove useless line Signed-off-by: Mathieu DEHARBE --- .../gridsuite/modification/server/CompositeControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java index 81e8a0eff..55104f720 100644 --- a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java +++ b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java @@ -535,7 +535,6 @@ void testMergeNetworkModificationsIntoNewComposite() throws Exception { assertEquals(TEST_GROUP_ID, remainingEntity.getGroup().getId()); } - @Test void testExpandToLeafUuidsNestedComposites() throws Exception { // Build nested structure: outerComposite → [innerComposite → [leaf1, leaf2], leaf3] From b9b7e057679bd10de1895eea7fa496ee2ca3460e Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Mon, 18 May 2026 17:59:26 +0200 Subject: [PATCH 10/11] test handles more cases Signed-off-by: Mathieu DEHARBE --- .../server/CompositeControllerTest.java | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java index 55104f720..a97f6726f 100644 --- a/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java +++ b/src/test/java/org/gridsuite/modification/server/CompositeControllerTest.java @@ -20,7 +20,9 @@ import org.gridsuite.modification.dto.ModificationInfos; import org.gridsuite.modification.server.dto.NetworkModificationResult; import org.gridsuite.modification.server.dto.NetworkModificationsResult; +import org.gridsuite.modification.server.entities.CompositeModificationEntity; import org.gridsuite.modification.server.entities.ModificationEntity; +import org.gridsuite.modification.server.repositories.CompositeModificationRepository; import org.gridsuite.modification.server.repositories.NetworkModificationRepository; import org.gridsuite.modification.server.service.ReportService; import org.gridsuite.modification.server.utils.NetworkCreation; @@ -72,6 +74,9 @@ class CompositeControllerTest { @Autowired private NetworkModificationRepository modificationRepository; + @Autowired + private CompositeModificationRepository compositeRepository; + @MockitoBean private ReportService reportService; @@ -490,38 +495,38 @@ void testMergeNetworkModificationsIntoNewComposite() throws Exception { assertEquals(3, modificationRepository.getModifications(TEST_GROUP_ID, true, true).size()); - // Merge the first 2 root-level modifications into a new composite + // ---- 1. Merge the first 2 root-level modifications into a new composite List mergedModificationUuids = rootModUuids.subList(0, 2); MvcResult mvcResult = mockMvc.perform(post(URI_COMPOSITE_NETWORK_MODIF_BASE + "/composite-modification") .content(mapper.writeValueAsString(mergedModificationUuids)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn(); - UUID compositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); - assertNotNull(compositeUuid); + UUID firstCompositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); + assertNotNull(firstCompositeUuid); // The root group should now contain the new composite and the remaining non-merged modification List rootModificationsAfterMerge = modificationRepository.getModifications(TEST_GROUP_ID, true, true); assertEquals(2, rootModificationsAfterMerge.size()); - assertEquals(compositeUuid, rootModificationsAfterMerge.getFirst().getUuid()); + assertEquals(firstCompositeUuid, rootModificationsAfterMerge.getFirst().getUuid()); assertEquals(COMPOSITE_MODIFICATION, rootModificationsAfterMerge.getFirst().getType()); assertEquals(rootModUuids.get(2), rootModificationsAfterMerge.get(1).getUuid()); // The new composite should contain the merged modifications in the same order Map> compositeContentMap = mapper.readValue( - mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", compositeUuid)) + mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), new TypeReference<>() { }); - List compositeContent = compositeContentMap.get(compositeUuid); + List compositeContent = compositeContentMap.get(firstCompositeUuid); assertEquals(2, compositeContent.size()); assertEquals(rootModUuids.get(0), compositeContent.get(0).getUuid()); assertEquals(rootModUuids.get(1), compositeContent.get(1).getUuid()); // The new composite must belong to TEST_GROUP_ID at root level - ModificationEntity compositeEntity = modificationRepository.getModificationEntity(compositeUuid); - assertNotNull(compositeEntity.getGroup()); - assertEquals(TEST_GROUP_ID, compositeEntity.getGroup().getId()); + CompositeModificationEntity firstComposite = compositeRepository.findById(firstCompositeUuid).orElseThrow(); + assertNotNull(firstComposite.getGroup()); + assertEquals(TEST_GROUP_ID, firstComposite.getGroup().getId()); // The merged modifications must no longer belong directly to the group ModificationEntity firstMergedEntity = modificationRepository.getModificationEntity(rootModUuids.get(0)); @@ -533,6 +538,43 @@ void testMergeNetworkModificationsIntoNewComposite() throws Exception { ModificationEntity remainingEntity = modificationRepository.getModificationEntity(rootModUuids.get(2)); assertNotNull(remainingEntity.getGroup()); assertEquals(TEST_GROUP_ID, remainingEntity.getGroup().getId()); + + // ---- 2. now merges a modification which is inside a composite with something that is outside : + mergedModificationUuids = List.of(compositeContent.get(0).getUuid(), rootModUuids.get(2)); + mvcResult = mockMvc.perform(post(URI_COMPOSITE_NETWORK_MODIF_BASE + "/composite-modification") + .content(mapper.writeValueAsString(mergedModificationUuids)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + + // this new composite will be generated inside the other composite because its first element was inside it + UUID twodepthCompositeUuid = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); + assertNotNull(twodepthCompositeUuid); + + // The root group should now contain the new composite and nothing else + rootModificationsAfterMerge = modificationRepository.getModifications(TEST_GROUP_ID, true, true); + assertEquals(1, rootModificationsAfterMerge.size()); + + // The first composite should contain the new composite, then the other untouched modification + compositeContentMap = mapper.readValue( + mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + new TypeReference<>() { }); + compositeContent = compositeContentMap.get(firstCompositeUuid); + + assertEquals(2, compositeContent.size()); + assertEquals(rootModUuids.get(1), compositeContent.get(0).getUuid()); + assertEquals(twodepthCompositeUuid, compositeContent.get(1).getUuid()); + + // The new 2 depth composite must now belong to the first composite, not to a group + CompositeModificationEntity twoDepthComposite = compositeRepository.findById(twodepthCompositeUuid).orElseThrow(); + assertNull(twoDepthComposite.getGroup()); + compositeContentMap = mapper.readValue( + mockMvc.perform(get(URI_GET_COMPOSITE_NETWORK_MODIF_CONTENT + "/network-modifications?uuids={id}", firstCompositeUuid)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + new TypeReference<>() { }); + assertTrue(compositeContentMap.get(firstCompositeUuid).stream() + .map(ModificationInfos::getUuid) + .anyMatch(twodepthCompositeUuid::equals)); } @Test From 97b0370efcd865f7f404e79f022f5d3b2882dc8f Mon Sep 17 00:00:00 2001 From: Mathieu DEHARBE Date: Tue, 19 May 2026 18:34:44 +0200 Subject: [PATCH 11/11] simplify endpoint Signed-off-by: Mathieu DEHARBE --- .../org/gridsuite/modification/server/CompositeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/modification/server/CompositeController.java b/src/main/java/org/gridsuite/modification/server/CompositeController.java index a500637bb..71930f5df 100644 --- a/src/main/java/org/gridsuite/modification/server/CompositeController.java +++ b/src/main/java/org/gridsuite/modification/server/CompositeController.java @@ -78,7 +78,7 @@ public ResponseEntity moveSubModification( return ResponseEntity.ok().build(); } - @PostMapping(value = "/composite-modification", consumes = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Merge some network modifications into a new composite modification") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The composite modification has been created")}) public ResponseEntity mergeNetworkModificationsIntoNewComposite(