From 889dc03dd68843571c45da1a17f746f6bce54759 Mon Sep 17 00:00:00 2001 From: OURY Sarah <sarah.oury@soprasteria.com> Date: Mon, 22 Jul 2024 16:27:10 +0000 Subject: [PATCH] [SOY] Filtrer etape critere --- CHANGELOG.md | 1 + docs/MoteurDeCalculG4IT_V1.1.adoc | 7 + .../repository/DonneesEntreeRepository.java | 52 +++++++ ...richissementEquipementPhysiqueService.java | 37 ++++- .../EnrichissementOperationNonITService.java | 38 ++++- ...issementEquipementPhysiqueServiceTest.java | 75 +++++++++- ...richissementOperationNonITServiceTest.java | 71 +++++++++ .../domain/model/DemandeCalcul.java | 4 + .../controller/CalculController.java | 11 +- .../jpa/entity/DonneesEntreesEntity.java | 12 ++ .../repository/DonneesEntreesRepository.java | 18 +-- .../service/DonneesEntreesService.java | 60 ++++++++ .../src/main/resources/schema.sql | 28 ++-- .../src/main/resources/static/openapi.yaml | 13 ++ .../service/DonneesEntreesServiceTest.java | 135 ++++++++++++++++++ 15 files changed, 523 insertions(+), 39 deletions(-) create mode 100644 services/api-expositiondonneesentrees/src/main/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesService.java create mode 100644 services/api-expositiondonneesentrees/src/test/java/org/mte/numecoeval/expositiondonneesentrees/infrastructure/service/DonneesEntreesServiceTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1cd1fa..7caa7ee2 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 a529f9cd..2e8dba79 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 d23d6cdd..58e771cf 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 d3e3830c..28043cc2 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 e386fc9d..ef39f73e 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 7d7c105b..47f30be0 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 55f4b387..f9042c30 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 97babf49..f8c83751 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 aea43dca..72c1feff 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 bd22d5dc..cddc63a2 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 053371f2..12b6c978 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 00000000..f793e5af --- /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 9f3d48f3..c5059800 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 4eb839bc..e395cf1d 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 00000000..cd2bf078 --- /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()); + } +} -- GitLab