From 2bc702d9bc7a8d61590d7a5c4edc13b641ee3b02 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 27 Aug 2026 10:37:54 +0200 Subject: [PATCH] feat: ajout du systeme d'historique et de persistance des exports CSV --- .../astalange/core/entity/ExportHistory.java | 115 +++++++++++ .../com/astalange/core/entity/TypeExport.java | 18 ++ .../repository/ExportHistoryRepository.java | 20 ++ .../core/service/ExportHistoryService.java | 55 +++++ .../migration/V46__create_export_history.sql | 12 ++ .../service/ExportHistoryServiceTest.java | 91 +++++++++ .../templates/fragments/sidebar.html | 1 + .../templates/parametrage/export_history.html | 191 ++++++++++++++++++ 8 files changed, 503 insertions(+) create mode 100644 as-talange-core/src/main/java/com/astalange/core/entity/ExportHistory.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/entity/TypeExport.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/repository/ExportHistoryRepository.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/service/ExportHistoryService.java create mode 100644 as-talange-core/src/main/resources/db/migration/V46__create_export_history.sql create mode 100644 as-talange-core/src/test/java/com/astalange/core/service/ExportHistoryServiceTest.java create mode 100644 as-talange-web/src/main/resources/templates/parametrage/export_history.html diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/ExportHistory.java b/as-talange-core/src/main/java/com/astalange/core/entity/ExportHistory.java new file mode 100644 index 0000000..e9ff9b6 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/ExportHistory.java @@ -0,0 +1,115 @@ +package com.astalange.core.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "export_history") +public class ExportHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(name = "type_export", nullable = false, length = 50) + private TypeExport typeExport; + + @Column(name = "nom_fichier", nullable = false) + private String nomFichier; + + @Column(name = "date_export", nullable = false) + private LocalDateTime dateExport; + + @Column(name = "nombre_elements") + private Integer nombreElements = 0; + + @Lob + @Column(name = "contenu", nullable = false, columnDefinition = "TEXT") + private String contenu; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "saison_id") + private Saison saison; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "utilisateur_id") + private AppUser utilisateur; + + public ExportHistory() { + } + + public ExportHistory(TypeExport typeExport, String nomFichier, LocalDateTime dateExport, Integer nombreElements, String contenu, Saison saison, AppUser utilisateur) { + this.typeExport = typeExport; + this.nomFichier = nomFichier; + this.dateExport = dateExport; + this.nombreElements = nombreElements; + this.contenu = contenu; + this.saison = saison; + this.utilisateur = utilisateur; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public TypeExport getTypeExport() { + return typeExport; + } + + public void setTypeExport(TypeExport typeExport) { + this.typeExport = typeExport; + } + + public String getNomFichier() { + return nomFichier; + } + + public void setNomFichier(String nomFichier) { + this.nomFichier = nomFichier; + } + + public LocalDateTime getDateExport() { + return dateExport; + } + + public void setDateExport(LocalDateTime dateExport) { + this.dateExport = dateExport; + } + + public Integer getNombreElements() { + return nombreElements; + } + + public void setNombreElements(Integer nombreElements) { + this.nombreElements = nombreElements; + } + + public String getContenu() { + return contenu; + } + + public void setContenu(String contenu) { + this.contenu = contenu; + } + + public Saison getSaison() { + return saison; + } + + public void setSaison(Saison saison) { + this.saison = saison; + } + + public AppUser getUtilisateur() { + return utilisateur; + } + + public void setUtilisateur(AppUser utilisateur) { + this.utilisateur = utilisateur; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/TypeExport.java b/as-talange-core/src/main/java/com/astalange/core/entity/TypeExport.java new file mode 100644 index 0000000..697a7bb --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/TypeExport.java @@ -0,0 +1,18 @@ +package com.astalange.core.entity; + +public enum TypeExport { + COMMANDE_EQUIPEMENT("Commande Équipementier (Regroupé)"), + FLOCAGE_INITIALES("Flocage Initiales"), + FLOCAGE_PRENOM_NUMERO("Flocage Prénom / N°"), + RECHERCHE_EQUIPEMENTS("Recherche Équipements"); + + private final String libelle; + + TypeExport(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/repository/ExportHistoryRepository.java b/as-talange-core/src/main/java/com/astalange/core/repository/ExportHistoryRepository.java new file mode 100644 index 0000000..da6e25e --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/repository/ExportHistoryRepository.java @@ -0,0 +1,20 @@ +package com.astalange.core.repository; + +import com.astalange.core.entity.ExportHistory; +import com.astalange.core.entity.Saison; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ExportHistoryRepository extends JpaRepository { + + @Query("SELECT e FROM ExportHistory e LEFT JOIN FETCH e.saison LEFT JOIN FETCH e.utilisateur ORDER BY e.dateExport DESC") + List findAllWithDetails(); + + @Query("SELECT e FROM ExportHistory e LEFT JOIN FETCH e.saison LEFT JOIN FETCH e.utilisateur WHERE e.saison = :saison ORDER BY e.dateExport DESC") + List findBySaisonWithDetails(@Param("saison") Saison saison); +} diff --git a/as-talange-core/src/main/java/com/astalange/core/service/ExportHistoryService.java b/as-talange-core/src/main/java/com/astalange/core/service/ExportHistoryService.java new file mode 100644 index 0000000..9f02d03 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/service/ExportHistoryService.java @@ -0,0 +1,55 @@ +package com.astalange.core.service; + +import com.astalange.core.entity.AppUser; +import com.astalange.core.entity.ExportHistory; +import com.astalange.core.entity.Saison; +import com.astalange.core.entity.TypeExport; +import com.astalange.core.repository.ExportHistoryRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class ExportHistoryService { + + private final ExportHistoryRepository exportHistoryRepository; + + public ExportHistoryService(ExportHistoryRepository exportHistoryRepository) { + this.exportHistoryRepository = exportHistoryRepository; + } + + @Transactional + public ExportHistory enregistrerExport(TypeExport typeExport, String nomFichier, String csvContent, Integer nombreElements, Saison saison, AppUser utilisateur) { + ExportHistory history = new ExportHistory( + typeExport, + nomFichier, + LocalDateTime.now(), + nombreElements, + csvContent, + saison, + utilisateur + ); + return exportHistoryRepository.save(history); + } + + @Transactional(readOnly = true) + public List obtenirToutLHistorique() { + return exportHistoryRepository.findAllWithDetails(); + } + + @Transactional(readOnly = true) + public List obtenirHistoriqueParSaison(Saison saison) { + if (saison == null) { + return obtenirToutLHistorique(); + } + return exportHistoryRepository.findBySaisonWithDetails(saison); + } + + @Transactional(readOnly = true) + public Optional trouverParId(Long id) { + return exportHistoryRepository.findById(id); + } +} diff --git a/as-talange-core/src/main/resources/db/migration/V46__create_export_history.sql b/as-talange-core/src/main/resources/db/migration/V46__create_export_history.sql new file mode 100644 index 0000000..a5c9ed9 --- /dev/null +++ b/as-talange-core/src/main/resources/db/migration/V46__create_export_history.sql @@ -0,0 +1,12 @@ +CREATE TABLE export_history ( + id BIGSERIAL PRIMARY KEY, + type_export VARCHAR(50) NOT NULL, + nom_fichier VARCHAR(255) NOT NULL, + date_export TIMESTAMP NOT NULL, + nombre_elements INT DEFAULT 0, + contenu TEXT NOT NULL, + saison_id BIGINT, + utilisateur_id BIGINT, + CONSTRAINT fk_export_history_saison FOREIGN KEY (saison_id) REFERENCES saison(id) ON DELETE SET NULL, + CONSTRAINT fk_export_history_user FOREIGN KEY (utilisateur_id) REFERENCES app_user(id) ON DELETE SET NULL +); diff --git a/as-talange-core/src/test/java/com/astalange/core/service/ExportHistoryServiceTest.java b/as-talange-core/src/test/java/com/astalange/core/service/ExportHistoryServiceTest.java new file mode 100644 index 0000000..ec96bef --- /dev/null +++ b/as-talange-core/src/test/java/com/astalange/core/service/ExportHistoryServiceTest.java @@ -0,0 +1,91 @@ +package com.astalange.core.service; + +import com.astalange.core.entity.AppUser; +import com.astalange.core.entity.ExportHistory; +import com.astalange.core.entity.Saison; +import com.astalange.core.entity.TypeExport; +import com.astalange.core.repository.ExportHistoryRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ExportHistoryServiceTest { + + @Mock + private ExportHistoryRepository exportHistoryRepository; + + @InjectMocks + private ExportHistoryService exportHistoryService; + + private Saison saison; + private AppUser user; + + @BeforeEach + public void setUp() { + saison = new Saison(); + saison.setId(1L); + saison.setNom("2026-2027"); + + user = new AppUser(); + user.setId(10L); + user.setUsername("admin"); + } + + @Test + @DisplayName("Enregistrer un export - doit sauvegarder les métadonnées et le contenu CSV") + public void testEnregistrerExport() { + String csv = "Header1;Header2\nValue1;Value2"; + + when(exportHistoryRepository.save(any(ExportHistory.class))).thenAnswer(invocation -> { + ExportHistory arg = invocation.getArgument(0); + arg.setId(100L); + return arg; + }); + + ExportHistory result = exportHistoryService.enregistrerExport( + TypeExport.COMMANDE_EQUIPEMENT, + "commande.csv", + csv, + 1, + saison, + user + ); + + assertNotNull(result); + assertEquals(100L, result.getId()); + assertEquals(TypeExport.COMMANDE_EQUIPEMENT, result.getTypeExport()); + assertEquals("commande.csv", result.getNomFichier()); + assertEquals(csv, result.getContenu()); + assertEquals(1, result.getNombreElements()); + assertEquals(saison, result.getSaison()); + assertEquals(user, result.getUtilisateur()); + verify(exportHistoryRepository, times(1)).save(any(ExportHistory.class)); + } + + @Test + @DisplayName("Obtenir l'historique par saison") + public void testObtenirHistoriqueParSaison() { + ExportHistory h = new ExportHistory(); + h.setId(1L); + h.setTypeExport(TypeExport.FLOCAGE_INITIALES); + + when(exportHistoryRepository.findBySaisonWithDetails(saison)).thenReturn(List.of(h)); + + List result = exportHistoryService.obtenirHistoriqueParSaison(saison); + + assertEquals(1, result.size()); + assertEquals(TypeExport.FLOCAGE_INITIALES, result.get(0).getTypeExport()); + verify(exportHistoryRepository, times(1)).findBySaisonWithDetails(saison); + } +} diff --git a/as-talange-web/src/main/resources/templates/fragments/sidebar.html b/as-talange-web/src/main/resources/templates/fragments/sidebar.html index e40bf20..3efb852 100644 --- a/as-talange-web/src/main/resources/templates/fragments/sidebar.html +++ b/as-talange-web/src/main/resources/templates/fragments/sidebar.html @@ -30,6 +30,7 @@
Recherches & Exports
Recherche Licences Recherche Équipements + Historique des Exports
Paramétrage
diff --git a/as-talange-web/src/main/resources/templates/parametrage/export_history.html b/as-talange-web/src/main/resources/templates/parametrage/export_history.html new file mode 100644 index 0000000..54cbce0 --- /dev/null +++ b/as-talange-web/src/main/resources/templates/parametrage/export_history.html @@ -0,0 +1,191 @@ + + + + + + Historique des Exports + + + + + + +
+ +
+
+

Historique des Exports

+ + ← Retour à la recherche équipements + +
+ +
+ +
+
+ + +
+
+ Tous les fichiers exportés sont conservés et téléchargeables à tout moment. +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Date & HeureType d'ExportNom du FichierSaisonÉlémentsGénéré parActions
Aucun export enregistré pour le moment.
+ 27/08/2026 10:30 + + + Commande Équipementier + + + Flocage Initiales + + + Flocage Prénom / N° + + + Recherche Équipements + + + commande_equipements.csv + + 2026/2027 + + + 42 + + + Admin User + + + + + Télécharger + +
+
+
+
+ + + + + + +