diff --git a/src/main/java/org/gridsuite/modification/server/entities/EntityRegistry.java b/src/main/java/org/gridsuite/modification/server/entities/EntityRegistry.java index a57a02f71..9d6f9d491 100644 --- a/src/main/java/org/gridsuite/modification/server/entities/EntityRegistry.java +++ b/src/main/java/org/gridsuite/modification/server/entities/EntityRegistry.java @@ -46,6 +46,8 @@ private EntityRegistry() { register(StaticVarCompensatorCreationInfos.class, StaticCompensatorCreationEntity.class); register(VscCreationInfos.class, VscCreationEntity.class); register(ConverterStationCreationInfos.class, ConverterStationCreationEntity.class); + register(LccCreationInfos.class, LccCreationEntity.class); + register(LccConverterStationCreationInfos.class, LccConverterStationCreationEntity.class); register(TabularCreationInfos.class, TabularCreationEntity.class); // // modification diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccConverterStationCreationEntity.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccConverterStationCreationEntity.java new file mode 100644 index 000000000..6ca292a14 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccConverterStationCreationEntity.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.modification.server.entities.equipment.creation; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.modification.dto.LccConverterStationCreationInfos; + +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.modification.server.entities.equipment.creation.ShuntCompensatorCreationEmbeddable.fromEmbeddableShuntCompensatorCreation; +import static org.gridsuite.modification.server.entities.equipment.creation.ShuntCompensatorCreationEmbeddable.toEmbeddableShuntCompensatorCreation; + +/** + * @author Ghazwa Rehili + */ + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Entity +@Table(name = "lccConverterStationCreation") +@PrimaryKeyJoinColumn(foreignKey = @ForeignKey(name = "lcc_converter_station_creation_id_fk_constraint")) +public class LccConverterStationCreationEntity extends InjectionCreationEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column + private Float lossFactor; + + @Column + private Float powerFactor; + + @ElementCollection + @CollectionTable(name = "shunt_compensator_on_side", + foreignKey = @ForeignKey(name = "lcc_converter_station_creation_shunt_compensators_on_side_fk")) + private List shuntCompensatorsOnSide; + + public LccConverterStationCreationEntity(LccConverterStationCreationInfos converterStationCreationInfos) { + super(converterStationCreationInfos); + assignAttributes(converterStationCreationInfos); + } + + private void assignAttributes(LccConverterStationCreationInfos converterStationCreationInfos) { + this.lossFactor = converterStationCreationInfos.getLossFactor(); + this.powerFactor = converterStationCreationInfos.getPowerFactor(); + this.shuntCompensatorsOnSide = toEmbeddableShuntCompensatorCreation(converterStationCreationInfos.getShuntCompensatorsOnSide()); + } + + public LccConverterStationCreationInfos toLccConverterStationInfos() { + return LccConverterStationCreationInfos.builder() + .equipmentId(getEquipmentId()) + .equipmentName(getEquipmentName()) + // Injection + .voltageLevelId(getVoltageLevelId()) + .busOrBusbarSectionId(getBusOrBusbarSectionId()) + .connectionName(getConnectionName()) + .connectionPosition(getConnectionPosition()) + .connectionDirection(getConnectionDirection()) + .terminalConnected(isTerminalConnected()) + // ConverterStation + .lossFactor(getLossFactor()) + .powerFactor(getPowerFactor()) + .shuntCompensatorsOnSide(fromEmbeddableShuntCompensatorCreation(getShuntCompensatorsOnSide())) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccCreationEntity.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccCreationEntity.java new file mode 100644 index 000000000..4e5780e39 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/LccCreationEntity.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.modification.server.entities.equipment.creation; + +import com.powsybl.iidm.network.HvdcLine; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import org.gridsuite.modification.dto.LccConverterStationCreationInfos; +import org.gridsuite.modification.dto.LccCreationInfos; +import org.gridsuite.modification.dto.ModificationInfos; +import org.gridsuite.modification.server.entities.equipment.modification.FreePropertyEntity; +import org.springframework.util.CollectionUtils; + +/** + * @author Rehili Ghazwa + */ + +@NoArgsConstructor +@Getter +@Entity +@Table(name = "lccCreation") +@PrimaryKeyJoinColumn(foreignKey = @ForeignKey(name = "lcc_creation_id_fk_constraint")) +public class LccCreationEntity extends EquipmentCreationEntity { + @Column + private Double nominalV; + + @Column + private Double r; + + @Column + private Double maxP; + + @Column + @Enumerated(EnumType.STRING) + private HvdcLine.ConvertersMode convertersMode; + + @Column + private Double activePowerSetpoint; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JoinColumn( + name = "lcc_converter_station_1_id", + referencedColumnName = "id", + foreignKey = @ForeignKey( + name = "lcc_converter_station_1_id_fk" + )) + private LccConverterStationCreationEntity converterStation1; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JoinColumn( + name = "lcc_converter_station_2_id", + referencedColumnName = "id", + foreignKey = @ForeignKey( + name = "lcc_converter_station_2_id_fk" + )) + private LccConverterStationCreationEntity converterStation2; + + public LccCreationEntity(@NonNull LccCreationInfos lccCreationInfos) { + super(lccCreationInfos); + assignAttributes(lccCreationInfos); + } + + @Override + public void update(@NonNull ModificationInfos modificationInfos) { + super.update(modificationInfos); + assignAttributes((LccCreationInfos) modificationInfos); + } + + private void assignAttributes(LccCreationInfos lccCreationInfos) { + this.nominalV = lccCreationInfos.getNominalV(); + this.r = lccCreationInfos.getR(); + this.maxP = lccCreationInfos.getMaxP(); + this.activePowerSetpoint = lccCreationInfos.getActivePowerSetpoint(); + this.convertersMode = lccCreationInfos.getConvertersMode(); + this.converterStation1 = new LccConverterStationCreationEntity(lccCreationInfos.getConverterStation1()); + this.converterStation2 = new LccConverterStationCreationEntity(lccCreationInfos.getConverterStation2()); + } + + @Override + public LccCreationInfos toModificationInfos() { + return toLccCreationInfosBuilder().build(); + } + + private LccCreationInfos.LccCreationInfosBuilder toLccCreationInfosBuilder() { + LccConverterStationCreationInfos converterStationCreationInfos1 = getConverterStation1() == null ? null : getConverterStation1().toLccConverterStationInfos(); + LccConverterStationCreationInfos converterStationCreationInfos2 = getConverterStation2() == null ? null : getConverterStation2().toLccConverterStationInfos(); + return LccCreationInfos.builder() + .uuid(getId()) + .date(getDate()) + .stashed(getStashed()) + .activated(getActivated()) + .equipmentId(getEquipmentId()) + .equipmentName(getEquipmentName()) + .nominalV(getNominalV()) + .r(getR()) + .maxP(getMaxP()) + .activePowerSetpoint(getActivePowerSetpoint()) + .convertersMode(getConvertersMode()) + .converterStation1(converterStationCreationInfos1) + .converterStation2(converterStationCreationInfos2) + // properties + .properties(CollectionUtils.isEmpty(getProperties()) ? null : + getProperties().stream() + .map(FreePropertyEntity::toInfos) + .toList()); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/ShuntCompensatorCreationEmbeddable.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/ShuntCompensatorCreationEmbeddable.java new file mode 100644 index 000000000..97b996413 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/creation/ShuntCompensatorCreationEmbeddable.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.modification.server.entities.equipment.creation; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.modification.dto.LccConverterStationCreationInfos; + +import java.util.List; + +/** + * @author Ghazwa Rehili + */ + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Embeddable +public class ShuntCompensatorCreationEmbeddable { + @Column(name = "shuntCompensatorId") + private String id; + + @Column(name = "shuntCompensatorName") + private String name; + + @Column + private Double maxQAtNominalV; + + @Column + private Boolean connectedToHvdc; + + public static List toEmbeddableShuntCompensatorCreation(List compensatorCreationInfos) { + return compensatorCreationInfos == null ? null : + compensatorCreationInfos.stream() + .map(compensatorCreationInfo -> new ShuntCompensatorCreationEmbeddable(compensatorCreationInfo.getId(), + compensatorCreationInfo.getName(), + compensatorCreationInfo.getMaxQAtNominalV(), + compensatorCreationInfo.getConnectedToHvdc())) + .toList(); + } + + public static List fromEmbeddableShuntCompensatorCreation(List compensatorCreationEmbeddables) { + return compensatorCreationEmbeddables == null ? null : + compensatorCreationEmbeddables.stream() + .map(compensatorCreationEmbeddable -> new LccConverterStationCreationInfos.ShuntCompensatorInfos(compensatorCreationEmbeddable.getId(), + compensatorCreationEmbeddable.getName(), + compensatorCreationEmbeddable.getMaxQAtNominalV(), + compensatorCreationEmbeddable.getConnectedToHvdc())) + .toList(); + } +} diff --git a/src/main/resources/db/changelog/changesets/changelog_20241206T123930Z.xml b/src/main/resources/db/changelog/changesets/changelog_20241206T123930Z.xml new file mode 100644 index 000000000..fa48dd055 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20241206T123930Z.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 32d3e8bde..6f2ec9372 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -317,4 +317,7 @@ databaseChangeLog: file: changesets/changelog_20240912T130742Z.xml - include: relativeToChangelogFile: true - file: changesets/changelog_20241015T130742Z.xml \ No newline at end of file + file: changesets/changelog_20241015T130742Z.xml + - include: + relativeToChangelogFile: true + file: changesets/changelog_20241206T123930Z.xml \ No newline at end of file diff --git a/src/test/java/org/gridsuite/modification/server/modifications/LccCreationTest.java b/src/test/java/org/gridsuite/modification/server/modifications/LccCreationTest.java new file mode 100644 index 000000000..142780c9d --- /dev/null +++ b/src/test/java/org/gridsuite/modification/server/modifications/LccCreationTest.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.modification.server.modifications; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.powsybl.iidm.network.HvdcLine; +import com.powsybl.iidm.network.LccConverterStation; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.ConnectablePosition; +import org.gridsuite.modification.NetworkModificationException; +import org.gridsuite.modification.dto.FreePropertyInfos; +import org.gridsuite.modification.dto.LccConverterStationCreationInfos; +import org.gridsuite.modification.dto.LccCreationInfos; +import org.gridsuite.modification.dto.ModificationInfos; +import org.gridsuite.modification.server.utils.NetworkCreation; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.gridsuite.modification.NetworkModificationException.Type.HVDC_LINE_ALREADY_EXISTS; +import static org.gridsuite.modification.NetworkModificationException.Type.VOLTAGE_LEVEL_NOT_FOUND; +import static org.gridsuite.modification.server.utils.TestUtils.assertLogMessage; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Ghazwa Rehili + */ +class LccCreationTest extends AbstractNetworkModificationTest { + private static final String PROPERTY_NAME = "property-name"; + private static final String PROPERTY_VALUE = "property-value"; + + @Override + protected Network createNetwork(UUID networkUuid) { + return NetworkCreation.create(networkUuid, true); + } + + @Override + protected ModificationInfos buildModification() { + return LccCreationInfos.builder() + .stashed(false) + .equipmentId("lcc1") + .equipmentName("lcc1Name") + .nominalV(39.) + .r(4.) + .maxP(56.) + .activePowerSetpoint(5.) + .convertersMode(HvdcLine.ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER) + .converterStation1(buildLccConverterStation("lccStationId1", "lccStationName1", "v1")) + .converterStation2(buildLccConverterStation("lccStationId2", "lccStationName2", "v2")) + .properties(List.of(FreePropertyInfos.builder().name(PROPERTY_NAME).value(PROPERTY_VALUE).build())) + .build(); + } + + private static LccConverterStationCreationInfos buildLccConverterStation(String equipmentId, String equipmentName, String voltageLevel) { + return LccConverterStationCreationInfos.builder() + .equipmentId(equipmentId) + .equipmentName(equipmentName) + .lossFactor(40F) + .powerFactor(1F) + .shuntCompensatorsOnSide(List.of()) + .voltageLevelId(voltageLevel) + .busOrBusbarSectionId("1.1") + .connectionName("top") + .connectionDirection(ConnectablePosition.Direction.TOP) + .build(); + } + + @Override + protected ModificationInfos buildModificationUpdate() { + return LccCreationInfos.builder() + .stashed(false) + .equipmentId("lcc1Edited") + .equipmentName("lcc2Name") + .nominalV(53.) + .r(2.) + .maxP(77.) + .convertersMode(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER) + .activePowerSetpoint(7.) + .converterStation1(buildLccConverterStation("lccStationId1", "lccStationName1", "v1")) + .converterStation2(buildLccConverterStation("lccStationId2", "lccStationName2", "v2")) + .build(); + } + + @Override + protected void assertAfterNetworkModificationCreation() { + assertNotNull(getNetwork().getHvdcLine("lcc1")); + + assertEquals(1, getNetwork().getVoltageLevel("v1").getLccConverterStationStream() + .filter(converterStation -> converterStation.getId().equals("lccStationId1")).count()); + + assertEquals(1, getNetwork().getVoltageLevel("v2").getLccConverterStationStream() + .filter(converterStation -> converterStation.getId().equals("lccStationId2")).count()); + + HvdcLine hvdcLine = getNetwork().getHvdcLine("lcc1"); + assertNotNull(hvdcLine); + assertEquals(HvdcLine.ConvertersMode.SIDE_1_INVERTER_SIDE_2_RECTIFIER, hvdcLine.getConvertersMode()); + assertEquals(39, hvdcLine.getNominalV(), 0); + assertEquals(4, hvdcLine.getR(), 0); + assertEquals(PROPERTY_VALUE, hvdcLine.getProperty(PROPERTY_NAME)); + LccConverterStation lccConverterStation1 = (LccConverterStation) hvdcLine.getConverterStation1(); + assertNotNull(lccConverterStation1); + assertEquals(40, lccConverterStation1.getLossFactor(), 0); + assertEquals("v1", lccConverterStation1.getTerminal().getVoltageLevel().getId()); + LccConverterStation lccConverterStation2 = (LccConverterStation) hvdcLine.getConverterStation2(); + assertNotNull(lccConverterStation2); + assertEquals(40, lccConverterStation2.getLossFactor(), 0); + assertEquals("v2", lccConverterStation2.getTerminal().getVoltageLevel().getId()); + } + + @Override + protected void testCreationModificationMessage(ModificationInfos modificationInfos) throws Exception { + assertEquals("LCC_CREATION", modificationInfos.getMessageType()); + Map createdValues = mapper.readValue(modificationInfos.getMessageValues(), new TypeReference<>() { }); + assertEquals("lcc1", createdValues.get("equipmentId")); + } + + @Override + protected void testUpdateModificationMessage(ModificationInfos modificationInfos) throws Exception { + assertEquals("LCC_CREATION", modificationInfos.getMessageType()); + Map updatedValues = mapper.readValue(modificationInfos.getMessageValues(), new TypeReference<>() { }); + assertEquals("lcc1Edited", updatedValues.get("equipmentId")); + } + + @Override + protected void assertAfterNetworkModificationDeletion() { + assertNull(getNetwork().getHvdcLine("lcc1")); + + assertEquals(0, getNetwork().getVoltageLevel("v1").getLccConverterStationStream() + .filter(converterStation -> converterStation.getId().equals("stationId1")).count()); + + assertEquals(0, getNetwork().getVoltageLevel("v2").getLccConverterStationStream() + .filter(converterStation -> converterStation.getId().equals("stationId2")).count()); + } + + @Test + void testCreateWithErrors() throws Exception { + LccCreationInfos lccCreationInfos = (LccCreationInfos) buildModification(); + lccCreationInfos.setEquipmentId(""); + String lccCreationInfosJson = mapper.writeValueAsString(lccCreationInfos); + mockMvc.perform(post(getNetworkModificationUri()).content(lccCreationInfosJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + assertLogMessage("Invalid id ''", lccCreationInfos.getErrorType().name(), reportService); + + // not found voltage level + lccCreationInfos.setEquipmentId("lccId"); + LccConverterStationCreationInfos converterStationCreationInfos = buildLccConverterStation("lccStationId1", "lccStationName1", "v1"); + converterStationCreationInfos.setVoltageLevelId("notFoundVoltageLevelId"); + lccCreationInfos.setConverterStation2(converterStationCreationInfos); + lccCreationInfosJson = mapper.writeValueAsString(lccCreationInfos); + mockMvc.perform(post(getNetworkModificationUri()).content(lccCreationInfosJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + assertLogMessage(new NetworkModificationException(VOLTAGE_LEVEL_NOT_FOUND, "notFoundVoltageLevelId").getMessage(), + lccCreationInfos.getErrorType().name(), reportService); + + // try to create an existing lcc + lccCreationInfos = (LccCreationInfos) buildModification(); + lccCreationInfos.setEquipmentId("hvdcLine"); + lccCreationInfosJson = mapper.writeValueAsString(lccCreationInfos); + mockMvc.perform(post(getNetworkModificationUri()).content(lccCreationInfosJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + assertLogMessage(new NetworkModificationException(HVDC_LINE_ALREADY_EXISTS, "hvdcLine").getMessage(), + lccCreationInfos.getErrorType().name(), reportService); + } +}