diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1cd1fac63828ed80211b9d184b15ac2d2dd732..7caa7ee262585ee50766f2b7f27200f164961633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Tous les changements de ce projet seront documentés dans ce document. ## [Non livré] - Nettoyage : Supression des tables, Entities et Repositories plus utilisés (pour MixElectrique, ImpactEquipement, ImpactReseau & TypeEquipement) +- Lancer les calculs sur uniquement quelques critères et/ou étapes -> [Issue6](https://gitlab-forge.din.developpement-durable.gouv.fr/pub/numeco/m4g/numecoeval/-/issues/6) ## [2.0.1] - 2024-07-10 diff --git a/docs/MoteurDeCalculG4IT_V1.1.adoc b/docs/MoteurDeCalculG4IT_V1.1.adoc index a529f9cded3f9e22a37b086f02d51887d58b6040..2e8dba793ba9c680cd99514eba6a43a188f3d78d 100644 --- a/docs/MoteurDeCalculG4IT_V1.1.adoc +++ b/docs/MoteurDeCalculG4IT_V1.1.adoc @@ -246,6 +246,13 @@ pour obtenir l'objet ref_Hypothese correspondant == Moteur de calcul Le déclenchement des règles présentes dans le Bloc Calcul se réalise à la soumission des calculs sur un lot donné via le endpoint POST /entrees/calculs/soumission. + +Le body de la requête est composé : + +* du nomLot : qui permet de récupérer toutes les données d'entrées liées à cette demande de calcul +* d'une liste d'étapes facultative : permet de lancer le calcul uniquement sur un sous-ensemble d'étapes présentes dans les références. Si la liste n'est pas renseignée, alors on ne filtre pas (on lance le calcul sur toutes les étapes présentes dans la table ref_etapeacv) +* d'une liste de critères facultative : permet de lancer le calcul uniquement sur un sous-ensemble de critères présents dans les références. Si la liste n'est pas renseignée, alors on ne filtre pas (on lance le calcul sur tous les critères présents dans la table ref_critere) + Il existe 2 paramètres associés à cette requête : * mode : le mode de traitement, SYNC ou ASYNC (ASYNC est la valeur par défaut) diff --git a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/repository/DonneesEntreeRepository.java b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/repository/DonneesEntreeRepository.java index d23d6cdd462b80a38913537cc472d92cc74a64ca..58e771cf05e6cf42f1f1c0d2aff875efc8ba7ff8 100644 --- a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/repository/DonneesEntreeRepository.java +++ b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/repository/DonneesEntreeRepository.java @@ -45,4 +45,56 @@ public class DonneesEntreeRepository { } return result.getFirst(); } + + @Cacheable("Etapes") + public String findEtapes(String nomLot) { + List<String> result = new ArrayList<>(); + try (Connection conn = dataSource.getConnection()) { + try (var ps = conn.prepareStatement(""" + SELECT etapes + FROM en_donnees_entrees + WHERE nom_lot = ? + """)) { + + ps.setString(1, nomLot); + var rs = ps.executeQuery(); + while (rs.next()) { + result.add(rs.getString("etapes")); + } + } + } catch (SQLException e) { + log.error("Une erreur s'est produite lors de la selection dans PostgreSQL. Exception: ", e); + throw new DatabaseException(e); + } + if (result.isEmpty()) { + return null; + } + return result.getFirst(); + } + + @Cacheable("Criteres") + public String findCriteres(String nomLot) { + List<String> result = new ArrayList<>(); + try (Connection conn = dataSource.getConnection()) { + try (var ps = conn.prepareStatement(""" + SELECT criteres + FROM en_donnees_entrees + WHERE nom_lot = ? + """)) { + + ps.setString(1, nomLot); + var rs = ps.executeQuery(); + while (rs.next()) { + result.add(rs.getString("criteres")); + } + } + } catch (SQLException e) { + log.error("Une erreur s'est produite lors de la selection dans PostgreSQL. Exception: ", e); + throw new DatabaseException(e); + } + if (result.isEmpty()) { + return null; + } + return result.getFirst(); + } } diff --git a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementEquipementPhysiqueService.java b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementEquipementPhysiqueService.java index d3e3830c0e4c51a563a8af4b4221aef19a94df67..28043cc23e78f1ae48e7ddf688b6a4ff70002561 100644 --- a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementEquipementPhysiqueService.java +++ b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementEquipementPhysiqueService.java @@ -4,6 +4,9 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.mte.numecoeval.calculs.domain.model.CalculEquipementPhysique; import org.mte.numecoeval.calculs.infrastructure.client.ReferentielClient; +import org.mte.numecoeval.calculs.infrastructure.repository.DonneesEntreeRepository; +import org.mte.numecoeval.calculs.referentiels.generated.api.model.CritereDTO; +import org.mte.numecoeval.calculs.referentiels.generated.api.model.EtapeDTO; import org.mte.numecoeval.calculs.referentiels.generated.api.model.HypotheseDTO; import org.mte.numecoeval.topic.data.EquipementPhysiqueDTO; import org.springframework.beans.factory.annotation.Autowired; @@ -12,10 +15,13 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @Slf4j @Service public class EnrichissementEquipementPhysiqueService { + @Autowired + DonneesEntreeRepository donneesEntreeRepository; @Value("${numecoeval.hypotheses.equipementPhysique}") private String hypothesesEquipementPhysique; @@ -34,8 +40,35 @@ public class EnrichissementEquipementPhysiqueService { calculEquipementPhysique.setEquipementPhysique(equipementPhysiqueDTO); - calculEquipementPhysique.setEtapes(referentielClient.getEtapes()); - calculEquipementPhysique.setCriteres(referentielClient.getCriteres()); + String etapesFiltrees = donneesEntreeRepository.findEtapes(calculEquipementPhysique.getEquipementPhysique().getNomLot()); + List<String> etapesList = etapesFiltrees == null ? null : new ArrayList<>(Arrays.asList(etapesFiltrees.split("##"))); + // si etapesList est vide alors on ne filtre pas et on utilise toutes les étapes du référentiel + if (etapesList == null) { + calculEquipementPhysique.setEtapes(referentielClient.getEtapes()); + } else { + List<EtapeDTO> etapesCalcul = new ArrayList<>(); + for (var etape : referentielClient.getEtapes()) { + if (etapesList.contains(etape.getCode())) { + etapesCalcul.add(etape); + } + } + calculEquipementPhysique.setEtapes(etapesCalcul); + } + + String criteresFiltrees = donneesEntreeRepository.findCriteres(calculEquipementPhysique.getEquipementPhysique().getNomLot()); + List<String> criteresList = criteresFiltrees == null ? null : new ArrayList<>(Arrays.asList(criteresFiltrees.split("##"))); + // si criteresList est vide alors on ne filtre pas et on utilise tous les critères du référentiel + if (criteresList == null) { + calculEquipementPhysique.setCriteres(referentielClient.getCriteres()); + } else { + List<CritereDTO> criteresCalcul = new ArrayList<>(); + for (var critere : referentielClient.getCriteres()) { + if (criteresList.contains(critere.getNomCritere())) { + criteresCalcul.add(critere); + } + } + calculEquipementPhysique.setCriteres(criteresCalcul); + } var hypotheses = new ArrayList<HypotheseDTO>(); diff --git a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementOperationNonITService.java b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementOperationNonITService.java index e386fc9d020ed927499a789c258a4a12b9fd8a65..ef39f73e81226088b557b6b3bd519d29a77da726 100644 --- a/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementOperationNonITService.java +++ b/services/api-event-calculs/src/main/java/org/mte/numecoeval/calculs/infrastructure/service/enrichissement/EnrichissementOperationNonITService.java @@ -4,6 +4,9 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.mte.numecoeval.calculs.domain.model.CalculOperationNonIT; import org.mte.numecoeval.calculs.infrastructure.client.ReferentielClient; +import org.mte.numecoeval.calculs.infrastructure.repository.DonneesEntreeRepository; +import org.mte.numecoeval.calculs.referentiels.generated.api.model.CritereDTO; +import org.mte.numecoeval.calculs.referentiels.generated.api.model.EtapeDTO; import org.mte.numecoeval.calculs.referentiels.generated.api.model.HypotheseDTO; import org.mte.numecoeval.topic.data.OperationNonITDTO; import org.springframework.beans.factory.annotation.Autowired; @@ -12,11 +15,14 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @Slf4j @Service public class EnrichissementOperationNonITService { + @Autowired + DonneesEntreeRepository donneesEntreeRepository; @Value("${numecoeval.hypotheses.operationNonIt}") private String hypothesesOperationNonIt; @@ -34,8 +40,36 @@ public class EnrichissementOperationNonITService { calculOperationNonIT.setOperationNonIT(operationNonITDTO); - calculOperationNonIT.setEtapes(referentielClient.getEtapes()); - calculOperationNonIT.setCriteres(referentielClient.getCriteres()); + String etapesFiltrees = donneesEntreeRepository.findEtapes(calculOperationNonIT.getOperationNonIT().getNomLot()); + List<String> etapesList = etapesFiltrees == null ? null : new ArrayList<>(Arrays.asList(etapesFiltrees.split("##"))); + // si etapesList est vide alors on ne filtre pas et on utilise toutes les étapes du référentiel + if (etapesList == null) { + calculOperationNonIT.setEtapes(referentielClient.getEtapes()); + } else { + List<EtapeDTO> etapesCalcul = new ArrayList<>(); + for (var etape : referentielClient.getEtapes()) { + if (etapesList.contains(etape.getCode())) { + etapesCalcul.add(etape); + } + } + calculOperationNonIT.setEtapes(etapesCalcul); + } + + String criteresFiltrees = donneesEntreeRepository.findCriteres(calculOperationNonIT.getOperationNonIT().getNomLot()); + List<String> criteresList = criteresFiltrees == null ? null : new ArrayList<>(Arrays.asList(criteresFiltrees.split("##"))); + // si criteresList est vide alors on ne filtre pas et on utilise tous les critères du référentiel + if (criteresList == null) { + calculOperationNonIT.setCriteres(referentielClient.getCriteres()); + } else { + List<CritereDTO> criteresCalcul = new ArrayList<>(); + for (var critere : referentielClient.getCriteres()) { + if (criteresList.contains(critere.getNomCritere())) { + criteresCalcul.add(critere); + } + } + calculOperationNonIT.setCriteres(criteresCalcul); + } + calculOperationNonIT.setTypeItem(referentielClient.getTypeItem(operationNonITDTO.getType())); var hypotheses = new ArrayList<HypotheseDTO>(); diff --git a/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementEquipementPhysiqueServiceTest.java b/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementEquipementPhysiqueServiceTest.java index 7d7c105b55fc114e5714877d4a75f410ac0440b5..47f30be0050db5d8ddfddbf026b2ed76f7f8b6fc 100644 --- a/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementEquipementPhysiqueServiceTest.java +++ b/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementEquipementPhysiqueServiceTest.java @@ -12,6 +12,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mte.numecoeval.calculs.infrastructure.client.ReferentielClient; +import org.mte.numecoeval.calculs.infrastructure.repository.DonneesEntreeRepository; import org.mte.numecoeval.calculs.infrastructure.service.enrichissement.EnrichissementEquipementPhysiqueService; import org.mte.numecoeval.calculs.referentiels.generated.api.model.*; import org.mte.numecoeval.topic.data.EquipementPhysiqueDTO; @@ -38,6 +39,9 @@ class EnrichissementEquipementPhysiqueServiceTest { @Mock ReferentielClient referentielClient; + @Mock + DonneesEntreeRepository donneesEntreeRepository; + @Value("${numecoeval.hypotheses.equipementPhysique}") private String hypothesesEquipementPhysique; @@ -113,10 +117,38 @@ class EnrichissementEquipementPhysiqueServiceTest { mixFr.setPays("FR"); Mockito.lenient().when(referentielClient.getMixElectrique("France", "Climate change")).thenReturn(mixFr); Mockito.lenient().when(referentielClient.getMixElectrique(null, "Climate change")).thenReturn(null); + + } + + void initMocksEtapesAndCriteres() throws JsonProcessingException { + /* MOCK REFERENTIEL : Etapes */ + Mockito.lenient().when(referentielClient.getEtapes()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ "code": "FABRICATION", "libelle": "Manufacturing" }, + { "code": "DISTRIBUTION", "libelle": "Transportation" }, + { "code": "UTILISATION", "libelle": "Using" }, + { "code": "FIN_DE_VIE", "libelle": "End of Life" }] + """, EtapeDTO[].class))); + + Mockito.lenient().when(referentielClient.getCriteres()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ + "nomCritere": "Climate change", + "unite": "kg CO2 eq", + "description": "Greenhouse gases (GHG)" + }, + { + "nomCritere": "Particulate matter and respiratory inorganics" + }, + { + "nomCritere": "Ionising radiation" + }, + { + "nomCritere": "Acidification" + }] + """, CritereDTO[].class))); } @Test - void testServiceEnrichissementEquipementPhysqique_null() { + void testServiceEnrichissementEquipementPhysique_null() { Assertions.assertNull(enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(null)); } @@ -148,7 +180,6 @@ class EnrichissementEquipementPhysiqueServiceTest { Assertions.assertEquals("HAUTE", actual.getEquipementPhysique().getQualite()); } - @Test void testServiceEnrichissementEquipementPhysique_typeEquipement() throws JsonProcessingException { @@ -177,4 +208,44 @@ class EnrichissementEquipementPhysiqueServiceTest { Assertions.assertEquals(2.2, actual.getImpactsEquipement().get(0).getValeur()); Assertions.assertEquals("HAUTE", actual.getEquipementPhysique().getQualite()); } + + @Test + void testServiceEnrichissementEquipementPhysique_filterEtapes() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn("UTILISATION##FABRICATION"); + + var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO); + + Assertions.assertEquals(2, actual.getEtapes().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_doesNotFilterEtapesWhenDataNull() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn(null); + + var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO); + + Assertions.assertEquals(4, actual.getEtapes().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_filterCriteres() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn("Climate change##Ionising radiation##Acidification"); + + var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO); + + Assertions.assertEquals(3, actual.getCriteres().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_doesNotFilterCriteresWhenDataNull() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn(null); + + var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO); + + Assertions.assertEquals(4, actual.getCriteres().size()); + } } diff --git a/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementOperationNonITServiceTest.java b/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementOperationNonITServiceTest.java index 55f4b3872c65d3f641c321655226f2d4f9c13aa0..f9042c30515fa695907987f5544dd83cf2fe9eb3 100644 --- a/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementOperationNonITServiceTest.java +++ b/services/api-event-calculs/src/test/java/org/mte/numecoeval/calculs/infrastructure/service/calcul/EnrichissementOperationNonITServiceTest.java @@ -12,6 +12,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mte.numecoeval.calculs.infrastructure.client.ReferentielClient; +import org.mte.numecoeval.calculs.infrastructure.repository.DonneesEntreeRepository; import org.mte.numecoeval.calculs.infrastructure.service.enrichissement.EnrichissementOperationNonITService; import org.mte.numecoeval.calculs.referentiels.generated.api.model.*; import org.mte.numecoeval.topic.data.OperationNonITDTO; @@ -24,6 +25,7 @@ import org.springframework.test.util.ReflectionTestUtils; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; @ExtendWith({MockitoExtension.class, SpringExtension.class}) @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) @@ -36,6 +38,8 @@ class EnrichissementOperationNonITServiceTest { @Mock ReferentielClient referentielClient; + @Mock + DonneesEntreeRepository donneesEntreeRepository; @Value("${numecoeval.hypotheses.operationNonIt}") private String hypothesesOperationNonIt; @@ -125,6 +129,33 @@ class EnrichissementOperationNonITServiceTest { """, FacteurCaracterisationDTO.class)); } + void initMocksEtapesAndCriteres() throws JsonProcessingException { + /* MOCK REFERENTIEL : Etapes */ + Mockito.lenient().when(referentielClient.getEtapes()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ "code": "FABRICATION", "libelle": "Manufacturing" }, + { "code": "DISTRIBUTION", "libelle": "Transportation" }, + { "code": "UTILISATION", "libelle": "Using" }, + { "code": "FIN_DE_VIE", "libelle": "End of Life" }] + """, EtapeDTO[].class))); + + Mockito.lenient().when(referentielClient.getCriteres()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ + "nomCritere": "Climate change", + "unite": "kg CO2 eq", + "description": "Greenhouse gases (GHG)" + }, + { + "nomCritere": "Particulate matter and respiratory inorganics" + }, + { + "nomCritere": "Ionising radiation" + }, + { + "nomCritere": "Acidification" + }] + """, CritereDTO[].class))); + } + @Test void testServiceEnrichissementOperationNonIT_null() { assertNull(enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(null)); @@ -150,4 +181,44 @@ class EnrichissementOperationNonITServiceTest { Assertions.assertEquals("BASSE", actual.getOperationNonIT().getQualite()); } + + @Test + void testServiceEnrichissementEquipementPhysique_filterEtapes() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn("UTILISATION##FABRICATION"); + + var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO); + + Assertions.assertEquals(2, actual.getEtapes().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_doesNotFilterEtapesWhenDataNull() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn(null); + + var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO); + + Assertions.assertEquals(4, actual.getEtapes().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_filterCriteres() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn("Climate change##Ionising radiation##Acidification"); + + var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO); + + Assertions.assertEquals(3, actual.getCriteres().size()); + } + + @Test + void testServiceEnrichissementEquipementPhysique_doesNotFilterCriteresWhenDataNull() throws JsonProcessingException { + initMocksEtapesAndCriteres(); + Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn(null); + + var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO); + + Assertions.assertEquals(4, actual.getCriteres().size()); + } } diff --git a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/domain/model/DemandeCalcul.java b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/domain/model/DemandeCalcul.java index 97babf49679362f1b40fb4a3ba148e4aa9a8aa74..f8c83751401ca5f2a1df51cbc41980498a3e34f6 100644 --- a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/domain/model/DemandeCalcul.java +++ b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/domain/model/DemandeCalcul.java @@ -7,6 +7,8 @@ import lombok.Setter; import lombok.experimental.Accessors; import lombok.experimental.SuperBuilder; +import java.util.List; + @Getter @Setter @EqualsAndHashCode @@ -17,4 +19,6 @@ public class DemandeCalcul { String nomLot; String dateLot; String nomOrganisation; + List<String> etapes; + List<String> criteres; } diff --git a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/controller/CalculController.java b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/controller/CalculController.java index aea43dca0a4e592315a3650ad316dbcf5c99a91d..72c1feff750d9f401ba92f44a919c64535a29928 100644 --- a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/controller/CalculController.java +++ b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/controller/CalculController.java @@ -7,8 +7,8 @@ import org.mte.numecoeval.expositiondonneesentrees.domain.ports.input.Soumission import org.mte.numecoeval.expositiondonneesentrees.domain.ports.input.StatutPourCalculPort; import org.mte.numecoeval.expositiondonneesentrees.generated.api.model.*; import org.mte.numecoeval.expositiondonneesentrees.generated.api.server.CalculsApi; -import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.repository.DonneesEntreesRepository; import org.mte.numecoeval.expositiondonneesentrees.infrastructure.mapper.CalculRestMapper; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.service.DonneesEntreesService; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; @@ -27,8 +27,7 @@ public class CalculController implements CalculsApi { final SoumissionCalculSyncPort soumissionCalculSyncPort; final StatutPourCalculPort statutPourCalculPort; - - final DonneesEntreesRepository donneesEntreesRepository; + final DonneesEntreesService donneesEntreesService; @Override public ResponseEntity<StatutCalculRest> statutPourCalcul(String nomLot, String nomOrganisation) { @@ -44,10 +43,10 @@ public class CalculController implements CalculsApi { if (DureeUsage.REEL != modeDureeUsage) { modeDureeUsage = DureeUsage.FIXE; } - - log.info("Soumission de calcul pour nom_lot: {}, dureeUsage: {}, mode: {}", demandeCalculRest.getNomLot(), modeDureeUsage, mode); + log.info("Soumission de calcul pour nom_lot: {}, dureeUsage: {}, mode: {}, etapes:{}, criteres:{}", demandeCalculRest.getNomLot(), modeDureeUsage, mode, demandeCalculRest.getEtapes(), demandeCalculRest.getCriteres()); var demandeCalcul = calculRestMapper.toDomain(demandeCalculRest); - donneesEntreesRepository.updateDonneesEntreesDureeUsage(String.valueOf(modeDureeUsage), demandeCalculRest.getNomLot()); + + donneesEntreesService.manageDonneesEntrees(demandeCalculRest.getNomLot(), dureeUsage, demandeCalculRest.getEtapes(), demandeCalculRest.getCriteres()); var soumission = ModeRest.ASYNC == mode ? soumissionCalculPort.soumissionCalcul(demandeCalcul) : diff --git a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/entity/DonneesEntreesEntity.java b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/entity/DonneesEntreesEntity.java index bd22d5dcc795f261b2a96884559b1fde25c64c9e..cddc63a2bdb12ed5f9e9ae849d852ab4a7e12f73 100644 --- a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/entity/DonneesEntreesEntity.java +++ b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/entity/DonneesEntreesEntity.java @@ -54,4 +54,16 @@ public class DonneesEntreesEntity extends AbstractEntreeEntity { */ private String dureeUsage; + /** + * Liste des étapes pour lesquelles on veut calculer les impacts + * séparés par ## + */ + private String etapes; + /** + * Liste des critères pour lesquels on veut calculer les impacts + * séparés par ## + */ + private String criteres; + + } diff --git a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/repository/DonneesEntreesRepository.java b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/repository/DonneesEntreesRepository.java index 053371f23dee9ad46d4315f7c7b2b8c07488183a..12b6c978745c0091358c2e13fe8ec539574b7cce 100644 --- a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/repository/DonneesEntreesRepository.java +++ b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/jpa/repository/DonneesEntreesRepository.java @@ -2,23 +2,11 @@ package org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.repositor import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.entity.DonneesEntreesEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Repository public interface DonneesEntreesRepository extends JpaRepository<DonneesEntreesEntity, Long> { - @Transactional - @Modifying - @Query(""" - UPDATE DonneesEntreesEntity dee - SET dee.dureeUsage = :dureeUsage - WHERE dee.nomLot = :nomLot - """) - int updateDonneesEntreesDureeUsage( - @Param("dureeUsage") String dureeUsage, - @Param("nomLot") String nomLot - ); + List<DonneesEntreesEntity> findByNomLot(String nomLot); } diff --git a/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesService.java b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesService.java new file mode 100644 index 0000000000000000000000000000000000000000..f793e5af62894434ded9cad37eb735bc62bed27a --- /dev/null +++ b/services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesService.java @@ -0,0 +1,60 @@ +package org.mte.numecoeval.expositiondonneesentrees.infrastructure.service; + +import lombok.RequiredArgsConstructor; +import org.mte.numecoeval.expositiondonneesentrees.domain.exception.ValidationException; +import org.mte.numecoeval.expositiondonneesentrees.generated.api.model.DureeUsage; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.adapters.ReferentielRestClient; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.entity.DonneesEntreesEntity; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.repository.DonneesEntreesRepository; +import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.CritereDTO; +import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.EtapeDTO; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class DonneesEntreesService { + + @Value("${regle-par-defaut-duree-usage}") + private String defaultDureeUsage; + + final ReferentielRestClient referentielRestClient; + final DonneesEntreesRepository donneesEntreesRepository; + + public void manageDonneesEntrees(String nomLot, DureeUsage modeDureeUsage, List<String> etapes, List<String> criteres) { + var donneeEntreeEntity = donneesEntreesRepository.findByNomLot(nomLot).getFirst(); + donneeEntreeEntity = enrichi(donneeEntreeEntity, modeDureeUsage, etapes, criteres); + donneesEntreesRepository.save(donneeEntreeEntity); + } + + private DonneesEntreesEntity enrichi(DonneesEntreesEntity donneeEntreeEntity, DureeUsage dureeUsage, List<String> etapes, List<String> criteres) { + var modeDureeUsage = dureeUsage == null ? DureeUsage.fromValue(defaultDureeUsage) : dureeUsage; + if (DureeUsage.REEL != modeDureeUsage) { + modeDureeUsage = DureeUsage.FIXE; + } + donneeEntreeEntity.setDureeUsage(String.valueOf(modeDureeUsage)); + + if (etapes != null) { + var allEtapes = referentielRestClient.getAllEtapes().stream().map(EtapeDTO::getCode).toList(); + if (!new HashSet<>(allEtapes).containsAll(etapes)) { + throw new ValidationException( + "La liste d'étapes n'est pas valide, elle doit être comprise dans: " + allEtapes + ); + } + donneeEntreeEntity.setEtapes(String.join("##", etapes)); + } + if (criteres != null) { + var allCriteres = referentielRestClient.getAllCriteres().stream().map(CritereDTO::getNomCritere).toList(); + if (!new HashSet<>(allCriteres).containsAll(criteres)) { + throw new ValidationException( + "La liste de critères n'est pas valide, elle doit être comprise dans: " + allCriteres + ); + } + donneeEntreeEntity.setCriteres(String.join("##", criteres)); + } + return donneeEntreeEntity; + } +} diff --git a/services/api-expositiondonneesentrees/src/main/resources/schema.sql b/services/api-expositiondonneesentrees/src/main/resources/schema.sql index 9f3d48f38da7a19d55b0ac172e629ebafd43cec3..c5059800bae89349bc000bfc4454413bb9a87c99 100644 --- a/services/api-expositiondonneesentrees/src/main/resources/schema.sql +++ b/services/api-expositiondonneesentrees/src/main/resources/schema.sql @@ -1,17 +1,19 @@ CREATE TABLE IF NOT EXISTS en_donnees_entrees ( - id int8 NOT NULL, - date_update timestamp NULL, - date_creation timestamp NULL, - date_lot date NULL, - nom_organisation varchar(255) NULL, - nom_lot varchar(255) NULL, - nbr_applications int8 NULL, - nbr_data_center int8 NULL, - nbr_equipements_physiques int8 NULL, - nbr_equipements_virtuels int8 NULL, - nbr_messageries int8 NULL, - duree_usage varchar(255) NULL, + id int8 NOT NULL, + date_update timestamp NULL, + date_creation timestamp NULL, + date_lot date NULL, + nom_organisation varchar(255) NULL, + nom_lot varchar(255) NULL, + nbr_applications int8 NULL, + nbr_data_center int8 NULL, + nbr_equipements_physiques int8 NULL, + nbr_equipements_virtuels int8 NULL, + nbr_messageries int8 NULL, + duree_usage varchar(255) NULL, + etapes text NULL, + criteres text NULL, CONSTRAINT en_donnees_entrees_pkey PRIMARY KEY (id) ); @@ -198,6 +200,8 @@ ALTER TABLE IF EXISTS en_equipement_physique ADD COLUMN IF NOT EXISTS duree_usag ALTER TABLE IF EXISTS en_equipement_physique ADD COLUMN IF NOT EXISTS duree_usage_aval float8; ALTER TABLE IF EXISTS en_donnees_entrees ADD COLUMN IF NOT EXISTS duree_usage varchar(255); +ALTER TABLE IF EXISTS en_donnees_entrees ADD COLUMN IF NOT EXISTS etapes text; +ALTER TABLE IF EXISTS en_donnees_entrees ADD COLUMN IF NOT EXISTS criteres text; -- Creation indexes -- Accelere la recuperation des donnees depuis EquipementPhysiqueIntegrationConfig.java diff --git a/services/api-expositiondonneesentrees/src/main/resources/static/openapi.yaml b/services/api-expositiondonneesentrees/src/main/resources/static/openapi.yaml index 4eb839bcf5867a26c1e30279baa5c0d16fa9741f..e395cf1dc9a74f3ef4686b2e4eb4afcd7aa6ca3d 100644 --- a/services/api-expositiondonneesentrees/src/main/resources/static/openapi.yaml +++ b/services/api-expositiondonneesentrees/src/main/resources/static/openapi.yaml @@ -63,6 +63,9 @@ paths: <li>FIXE : La méthode de calcul de la durée de vie se base sur l'attribut 'dureeUsageInterne' de l'équipement physique (méthode utilisée par défaut si le paramètre dureeUsage n'est pas renseigné). </li> <li>REEL : La méthode de calcul de la durée de vie se base sur l'âge réel de l'équipement physique (dateRetrait-dateAchat). </li> </ul> + Vous pouvez également lancer le calcul sur uniquement quelques critères et/ou étapes spécifiques en renseignant leurs noms dans les listes "etapes" et "criteres" dans le body de la requête. + /!\ Il faut que les noms soient présents dans les tables ref_etapeacv et ref_critere (case sensitive). + Par défaut, si rien n'est renseigné, le calcul sera lancé sur toutes les étapes et tous les critères présents dans les tables ref_etapeacv et ref_critere. tags: - Calculs operationId: soumissionPourCalcul @@ -280,6 +283,16 @@ components: nomLot: description: "Nom du lot rattaché" type: string + etapes: + description: "Liste des étapes pour lesquelles on veut calculer les impacts" + type: array + items: + type: string + criteres: + description: "Liste des critères pour lesquels on veut calculer les impacts" + type: array + items: + type: string ModeRest: type: string enum: diff --git a/services/api-expositiondonneesentrees/src/test/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesServiceTest.java b/services/api-expositiondonneesentrees/src/test/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cd2bf078c31007c36f5e0b0a93da7c758fd544e9 --- /dev/null +++ b/services/api-expositiondonneesentrees/src/test/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesServiceTest.java @@ -0,0 +1,135 @@ +package org.mte.numecoeval.expositiondonneesentrees.infrastructure.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mte.numecoeval.expositiondonneesentrees.domain.exception.ValidationException; +import org.mte.numecoeval.expositiondonneesentrees.generated.api.model.DureeUsage; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.adapters.ReferentielRestClient; +import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.entity.DonneesEntreesEntity; +import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.CritereDTO; +import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.EtapeDTO; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +@ExtendWith({SpringExtension.class}) +@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) +public class DonneesEntreesServiceTest { + @InjectMocks + DonneesEntreesService donneesEntreesService; + @Mock + ReferentielRestClient referentielRestClient; + private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + + @Value("${regle-par-defaut-duree-usage}") + private String defaultDureeUsage; + + @BeforeEach + void init() throws JsonProcessingException { + ReflectionTestUtils.setField(donneesEntreesService, "defaultDureeUsage", defaultDureeUsage); + + /* MOCK REFERENTIEL : Etapes */ + Mockito.lenient().when(referentielRestClient.getAllEtapes()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ "code": "UTILISATION", "libelle": "Using" }, + { "code": "FABRICATION", "libelle": "Manufacturing" }, + { "code": "DISTRIBUTION", "libelle": "Transportation" }] + """, EtapeDTO[].class))); + + /* MOCK REFERENTIEL : Criteres */ + Mockito.lenient().when(referentielRestClient.getAllCriteres()).thenReturn(Arrays.asList(mapper.readValue(""" + [{ + "nomCritere": "Climate change", + "unite": "kg CO2 eq", + "description": "Greenhouse gases (GHG)" + }, + { + "nomCritere": "Particulate matter and respiratory inorganics" + }, + { + "nomCritere": "Ionising radiation" + }, + { + "nomCritere": "Acidification" + }] + """, CritereDTO[].class))); + } + + @Test + void enrichiDonneesEntrees_with_dureeUsageNull() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, null, null, null); + assertEquals("FIXE", actual.getDureeUsage()); + } + + @Test + void enrichiDonneesEntrees_with_dureeUsageFixe() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, null); + assertEquals("FIXE", actual.getDureeUsage()); + } + + @Test + void enrichiDonneesEntrees_with_dureeUsageReel() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("REEL"), null, null); + assertEquals("REEL", actual.getDureeUsage()); + } + + @Test + void enrichiDonneesEntrees_with_etapesFiltree_shouldFilter() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of("UTILISATION", "FABRICATION"), List.of()); + assertEquals("UTILISATION##FABRICATION", actual.getEtapes()); + } + + @Test + void enrichiDonneesEntrees_with_etapesFiltreeNull_shouldReturnNull() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, List.of()); + Assertions.assertNull(actual.getEtapes()); + } + + @Test + void enrichiDonneesEntrees_with_etapeFiltreeNotInReferentiel_shouldThrowError() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + var exception = assertThrows(ValidationException.class, () -> ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of("FAB"), List.of())); + assertEquals("La liste d'étapes n'est pas valide, elle doit être comprise dans: [UTILISATION, FABRICATION, DISTRIBUTION]", exception.getErreur()); + } + + @Test + void enrichiDonneesEntrees_with_criteresFiltree_shouldFilter() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of(), List.of("Acidification", "Ionising radiation", "Climate change")); + assertEquals("Acidification##Ionising radiation##Climate change", actual.getCriteres()); + } + + @Test + void enrichiDonneesEntrees_with_criteresFiltreeNull_shouldReturnNull() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, null); + Assertions.assertNull(actual.getCriteres()); + } + + @Test + void enrichiDonneesEntrees_with_critereFiltreeNotInReferentiel_shouldThrowError() { + DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null); + var exception = assertThrows(ValidationException.class, () -> ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, List.of("CLIMATE CHANGE"))); + Assertions.assertEquals("La liste de critères n'est pas valide, elle doit être comprise dans: [Climate change, Particulate matter and respiratory inorganics, Ionising radiation, Acidification]", exception.getErreur()); + } +}