From 658c81163445b8834e7c2d91f85019f9bd4cc969 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 27 Aug 2026 10:40:33 +0200 Subject: [PATCH] feat: ajout du filtre et de la fonction de relance des adherents avec tailles manquantes --- .../core/service/DotationService.java | 14 +- .../web/controller/DotationController.java | 187 +++++++++++++++--- .../parametrage/equipements_recherche.html | 67 +++++-- 3 files changed, 221 insertions(+), 47 deletions(-) diff --git a/as-talange-core/src/main/java/com/astalange/core/service/DotationService.java b/as-talange-core/src/main/java/com/astalange/core/service/DotationService.java index 8f13d43..65f95d2 100644 --- a/as-talange-core/src/main/java/com/astalange/core/service/DotationService.java +++ b/as-talange-core/src/main/java/com/astalange/core/service/DotationService.java @@ -89,15 +89,17 @@ public class DotationService { sb.append('\ufeff'); // Header - sb.append("Saison;Catégorie;Nom;Prénom;Poste;Sexe;Équipement;Référence;Taille;Flocage;Numéro;Fourni;Commandé;Date Commande\n"); + sb.append("Saison;Catégorie;Nom;Prénom;Email;Téléphone;Poste;Sexe;Équipement;Référence;Taille;Flocage;Numéro;Fourni;Commandé;Date Commande\n"); for (Dotation d : dotations) { String saison = d.getLicence().getSaison() != null ? d.getLicence().getSaison().getNom() : ""; String categorie = d.getLicence().getCategorie() != null ? d.getLicence().getCategorie().getNom() : ""; - String nom = d.getLicence().getAdherent().getNom(); - String prenom = d.getLicence().getAdherent().getPrenom(); - String poste = d.getLicence().getAdherent().getTypeMaillot() != null ? d.getLicence().getAdherent().getTypeMaillot() : ""; - String sexe = d.getLicence().getAdherent().getSexe() != null ? d.getLicence().getAdherent().getSexe() : ""; + String nom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getNom() : ""; + String prenom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getPrenom() : ""; + String email = d.getLicence().getAdherent() != null && d.getLicence().getAdherent().getEmail() != null ? d.getLicence().getAdherent().getEmail() : ""; + String telephone = d.getLicence().getAdherent() != null && d.getLicence().getAdherent().getTelephone() != null ? d.getLicence().getAdherent().getTelephone() : ""; + String poste = d.getLicence().getAdherent() != null && d.getLicence().getAdherent().getTypeMaillot() != null ? d.getLicence().getAdherent().getTypeMaillot() : ""; + String sexe = d.getLicence().getAdherent() != null && d.getLicence().getAdherent().getSexe() != null ? d.getLicence().getAdherent().getSexe() : ""; String equipement = d.getEquipement() != null ? d.getEquipement().getNom() : ""; String reference = d.getEquipement() != null && d.getEquipement().getReference() != null ? d.getEquipement().getReference() : ""; String taille = d.getTaille() != null ? d.getTaille() : ""; @@ -111,6 +113,8 @@ public class DotationService { .append(escapeCsv(categorie)).append(";") .append(escapeCsv(nom)).append(";") .append(escapeCsv(prenom)).append(";") + .append(escapeCsv(email)).append(";") + .append(escapeCsv(telephone)).append(";") .append(escapeCsv(poste)).append(";") .append(escapeCsv(sexe)).append(";") .append(escapeCsv(equipement)).append(";") diff --git a/as-talange-web/src/main/java/com/astalange/web/controller/DotationController.java b/as-talange-web/src/main/java/com/astalange/web/controller/DotationController.java index a568b12..420bea3 100644 --- a/as-talange-web/src/main/java/com/astalange/web/controller/DotationController.java +++ b/as-talange-web/src/main/java/com/astalange/web/controller/DotationController.java @@ -1,32 +1,34 @@ package com.astalange.web.controller; +import com.astalange.core.entity.AppUser; import com.astalange.core.entity.Dotation; +import com.astalange.core.entity.ExportHistory; +import com.astalange.core.entity.Saison; +import com.astalange.core.entity.TypeExport; +import com.astalange.core.repository.AppUserRepository; +import com.astalange.core.repository.CategorieRepository; import com.astalange.core.repository.DotationRepository; -import org.springframework.stereotype.Controller; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; - -import java.util.List; +import com.astalange.core.repository.EquipementRepository; +import com.astalange.core.repository.SaisonRepository; +import com.astalange.core.security.CustomUserDetails; +import com.astalange.core.service.DotationService; +import com.astalange.core.service.ExportHistoryService; +import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.http.ResponseEntity; +import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import java.time.LocalDate; - -import com.astalange.core.service.DotationService; -import com.astalange.core.repository.SaisonRepository; -import com.astalange.core.entity.Saison; - -import com.astalange.core.repository.EquipementRepository; -import com.astalange.core.repository.CategorieRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; -import org.springframework.data.jpa.domain.Specification; -import jakarta.persistence.criteria.Predicate; -import java.util.ArrayList; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; @Controller public class DotationController { @@ -36,16 +38,44 @@ public class DotationController { private final SaisonRepository saisonRepository; private final EquipementRepository equipementRepository; private final CategorieRepository categorieRepository; + private final ExportHistoryService exportHistoryService; + private final AppUserRepository appUserRepository; - public DotationController(DotationRepository dotationRepository, DotationService dotationService, SaisonRepository saisonRepository, EquipementRepository equipementRepository, CategorieRepository categorieRepository) { + public DotationController(DotationRepository dotationRepository, + DotationService dotationService, + SaisonRepository saisonRepository, + EquipementRepository equipementRepository, + CategorieRepository categorieRepository, + ExportHistoryService exportHistoryService, + AppUserRepository appUserRepository) { this.dotationRepository = dotationRepository; this.dotationService = dotationService; this.saisonRepository = saisonRepository; this.equipementRepository = equipementRepository; this.categorieRepository = categorieRepository; + this.exportHistoryService = exportHistoryService; + this.appUserRepository = appUserRepository; } - private Specification buildSpecification(Long equipementId, Long categorieId, Boolean fourni, Boolean commandee, Saison saisonActive) { + private AppUser getCurrentUser() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.getPrincipal() instanceof CustomUserDetails userDetails) { + return appUserRepository.findById(userDetails.getId()).orElse(null); + } + return null; + } + + private int compterElementsCsv(String csvContent) { + if (csvContent == null || csvContent.trim().isEmpty()) { + return 0; + } + int lines = (int) Arrays.stream(csvContent.split("\n")) + .filter(l -> !l.trim().isEmpty()) + .count(); + return Math.max(0, lines - 1); + } + + private Specification buildSpecification(Long equipementId, Long categorieId, Boolean fourni, Boolean commandee, Boolean tailleRenseignee, Saison saisonActive) { return (root, query, cb) -> { if (Long.class != query.getResultType() && long.class != query.getResultType()) { root.fetch("equipement", jakarta.persistence.criteria.JoinType.LEFT); @@ -70,6 +100,24 @@ public class DotationController { if (commandee != null) { predicates.add(cb.equal(root.get("commandee"), commandee)); } + if (tailleRenseignee != null) { + if (tailleRenseignee) { + predicates.add(cb.and( + cb.isNotNull(root.get("taille")), + cb.notEqual(cb.trim(root.get("taille")), "") + )); + } else { + // Uniquement les équipements proposant une grille de tailles mais dont la taille n'est pas encore saisie + predicates.add(cb.and( + cb.isNotNull(root.get("equipement").get("taillesDisponibles")), + cb.notEqual(cb.trim(root.get("equipement").get("taillesDisponibles")), ""), + cb.or( + cb.isNull(root.get("taille")), + cb.equal(cb.trim(root.get("taille")), "") + ) + )); + } + } return cb.and(predicates.toArray(new Predicate[0])); }; } @@ -80,12 +128,13 @@ public class DotationController { @RequestParam(required = false) Long categorieId, @RequestParam(required = false) Boolean fourni, @RequestParam(required = false) Boolean commandee, + @RequestParam(required = false) Boolean tailleRenseignee, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, Model model) { Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); - Specification spec = buildSpecification(equipementId, categorieId, fourni, commandee, saisonActive); + Specification spec = buildSpecification(equipementId, categorieId, fourni, commandee, tailleRenseignee, saisonActive); List allDotations = dotationRepository.findAll(spec); int totalElements = allDotations.size(); @@ -104,6 +153,7 @@ public class DotationController { model.addAttribute("categorieId", categorieId); model.addAttribute("fourni", fourni); model.addAttribute("commandee", commandee); + model.addAttribute("tailleRenseignee", tailleRenseignee); model.addAttribute("currentPage", page); model.addAttribute("totalPages", totalPages); @@ -118,17 +168,21 @@ public class DotationController { @RequestParam(required = false) Long equipementId, @RequestParam(required = false) Long categorieId, @RequestParam(required = false) Boolean fourni, - @RequestParam(required = false) Boolean commandee) { + @RequestParam(required = false) Boolean commandee, + @RequestParam(required = false) Boolean tailleRenseignee) { Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); - Specification spec = buildSpecification(equipementId, categorieId, fourni, commandee, saisonActive); + Specification spec = buildSpecification(equipementId, categorieId, fourni, commandee, tailleRenseignee, saisonActive); List dotations = dotationRepository.findAll(spec); String csvContent = dotationService.genererCsvSearchEquipement(dotations); + String filename = Boolean.FALSE.equals(tailleRenseignee) ? "relance_tailles_manquantes_" + LocalDate.now() + ".csv" : "recherche_equipements_" + LocalDate.now() + ".csv"; + exportHistoryService.enregistrerExport(TypeExport.RECHERCHE_EQUIPEMENTS, filename, csvContent, compterElementsCsv(csvContent), saisonActive, getCurrentUser()); + byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); HttpHeaders headers = new HttpHeaders(); - headers.setContentDispositionFormData("attachment", "recherche_equipements_" + LocalDate.now() + ".csv"); + headers.setContentDispositionFormData("attachment", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); @@ -140,10 +194,13 @@ public class DotationController { .orElseThrow(() -> new IllegalStateException("Aucune saison active")); String csvContent = dotationService.genererCsvCommandeEquipement(saisonActive); + String filename = "commande_equipements_regroupee_" + LocalDate.now() + ".csv"; + exportHistoryService.enregistrerExport(TypeExport.COMMANDE_EQUIPEMENT, filename, csvContent, compterElementsCsv(csvContent), saisonActive, getCurrentUser()); + byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); HttpHeaders headers = new HttpHeaders(); - headers.setContentDispositionFormData("attachment", "commande_equipements_regroupee_" + LocalDate.now() + ".csv"); + headers.setContentDispositionFormData("attachment", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); @@ -155,10 +212,13 @@ public class DotationController { .orElseThrow(() -> new IllegalStateException("Aucune saison active")); String csvContent = dotationService.genererCsvFlocageInitiales(saisonActive); + String filename = "flocage_initiales_" + LocalDate.now() + ".csv"; + exportHistoryService.enregistrerExport(TypeExport.FLOCAGE_INITIALES, filename, csvContent, compterElementsCsv(csvContent), saisonActive, getCurrentUser()); + byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); HttpHeaders headers = new HttpHeaders(); - headers.setContentDispositionFormData("attachment", "flocage_initiales_" + LocalDate.now() + ".csv"); + headers.setContentDispositionFormData("attachment", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); @@ -170,15 +230,82 @@ public class DotationController { .orElseThrow(() -> new IllegalStateException("Aucune saison active")); String csvContent = dotationService.genererCsvFlocagePrenomNumero(saisonActive); + String filename = "flocage_prenom_numero_" + LocalDate.now() + ".csv"; + exportHistoryService.enregistrerExport(TypeExport.FLOCAGE_PRENOM_NUMERO, filename, csvContent, compterElementsCsv(csvContent), saisonActive, getCurrentUser()); + byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8); HttpHeaders headers = new HttpHeaders(); - headers.setContentDispositionFormData("attachment", "flocage_prenom_numero_" + LocalDate.now() + ".csv"); + headers.setContentDispositionFormData("attachment", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); } + @GetMapping("/admin/exports/historique") + public String historiqueExports(@RequestParam(required = false) Long saisonId, Model model) { + List saisons = saisonRepository.findAll(); + Saison saisonFiltre = null; + if (saisonId != null) { + saisonFiltre = saisonRepository.findById(saisonId).orElse(null); + } + List historique = exportHistoryService.obtenirHistoriqueParSaison(saisonFiltre); + + model.addAttribute("historique", historique); + model.addAttribute("saisons", saisons); + model.addAttribute("saisonId", saisonId); + return "parametrage/export_history"; + } + + @GetMapping("/admin/exports/{id}/download") + public ResponseEntity downloadExport(@PathVariable Long id) { + ExportHistory history = exportHistoryService.trouverParId(id) + .orElseThrow(() -> new IllegalArgumentException("Export introuvable ID: " + id)); + + byte[] csvBytes = history.getContenu().getBytes(java.nio.charset.StandardCharsets.UTF_8); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentDispositionFormData("attachment", history.getNomFichier()); + headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); + + return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); + } + + @GetMapping("/admin/exports/{id}/apercu") + @ResponseBody + public ResponseEntity> apercuExport(@PathVariable Long id) { + ExportHistory history = exportHistoryService.trouverParId(id) + .orElseThrow(() -> new IllegalArgumentException("Export introuvable ID: " + id)); + + String content = history.getContenu(); + if (content.startsWith("\ufeff")) { + content = content.substring(1); + } + + String[] lines = content.split("\n"); + List headers = new ArrayList<>(); + List> rows = new ArrayList<>(); + + if (lines.length > 0) { + headers = Arrays.asList(lines[0].split(";")); + for (int i = 1; i < lines.length; i++) { + if (!lines[i].trim().isEmpty()) { + rows.add(Arrays.asList(lines[i].split(";", -1))); + } + } + } + + Map response = new HashMap<>(); + response.put("id", history.getId()); + response.put("typeExport", history.getTypeExport().getLibelle()); + response.put("nomFichier", history.getNomFichier()); + response.put("dateExport", history.getDateExport().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))); + response.put("headers", headers); + response.put("rows", rows); + + return ResponseEntity.ok(response); + } + @PostMapping("/dotations/{id}") public String updateDotation( diff --git a/as-talange-web/src/main/resources/templates/parametrage/equipements_recherche.html b/as-talange-web/src/main/resources/templates/parametrage/equipements_recherche.html index afee0e4..5340578 100644 --- a/as-talange-web/src/main/resources/templates/parametrage/equipements_recherche.html +++ b/as-talange-web/src/main/resources/templates/parametrage/equipements_recherche.html @@ -21,7 +21,7 @@
-
+
+
+ + +