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 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.data.jpa.domain.Specification; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; 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.web.bind.annotation.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; @Controller public class DotationController { private final DotationRepository dotationRepository; private final DotationService dotationService; 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, 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 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); jakarta.persistence.criteria.Fetch licenceFetch = root.fetch("licence", jakarta.persistence.criteria.JoinType.LEFT); licenceFetch.fetch("adherent", jakarta.persistence.criteria.JoinType.LEFT); licenceFetch.fetch("categorie", jakarta.persistence.criteria.JoinType.LEFT); } List predicates = new ArrayList<>(); if (saisonActive != null) { predicates.add(cb.equal(root.get("licence").get("saison"), saisonActive)); } if (equipementId != null) { predicates.add(cb.equal(root.get("equipement").get("id"), equipementId)); } if (categorieId != null) { predicates.add(cb.equal(root.get("licence").get("categorie").get("id"), categorieId)); } if (fourni != null) { predicates.add(cb.equal(root.get("fourni"), fourni)); } 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])); }; } @GetMapping("/admin/equipements/recherche") public String rechercheEquipements( @RequestParam(required = false) Long equipementId, @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, tailleRenseignee, saisonActive); List allDotations = dotationRepository.findAll(spec); int totalElements = allDotations.size(); int totalPages = (int) Math.ceil((double) totalElements / size); if (page < 0) page = 0; if (page >= totalPages && totalPages > 0) page = totalPages - 1; int start = page * size; int end = Math.min(start + size, totalElements); List pageContent = (start < totalElements) ? allDotations.subList(start, end) : List.of(); model.addAttribute("dotations", pageContent); model.addAttribute("equipements", equipementRepository.findAll()); model.addAttribute("categories", categorieRepository.findAll()); model.addAttribute("equipementId", equipementId); model.addAttribute("categorieId", categorieId); model.addAttribute("fourni", fourni); model.addAttribute("commandee", commandee); model.addAttribute("tailleRenseignee", tailleRenseignee); model.addAttribute("currentPage", page); model.addAttribute("totalPages", totalPages); model.addAttribute("totalElements", totalElements); model.addAttribute("pageSize", size); return "parametrage/equipements_recherche"; } @GetMapping("/admin/equipements/recherche/export") public ResponseEntity exportRechercheEquipements( @RequestParam(required = false) Long equipementId, @RequestParam(required = false) Long categorieId, @RequestParam(required = false) Boolean fourni, @RequestParam(required = false) Boolean commandee, @RequestParam(required = false) Boolean tailleRenseignee) { Saison saisonActive = saisonRepository.findByEstActiveTrue().orElse(null); 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", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); } @GetMapping("/admin/equipements/export-commande") public ResponseEntity exportCommande() { Saison saisonActive = saisonRepository.findByEstActiveTrue() .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", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); } @GetMapping("/admin/equipements/export-flocage-initiales") public ResponseEntity exportFlocageInitiales() { Saison saisonActive = saisonRepository.findByEstActiveTrue() .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", filename); headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8")); return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK); } @GetMapping("/admin/equipements/export-flocage-prenom-numero") public ResponseEntity exportFlocagePrenomNumero() { Saison saisonActive = saisonRepository.findByEstActiveTrue() .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", 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( @PathVariable Long id, @RequestParam Long adherentId, @RequestParam(required = false, defaultValue = "") String taille, @RequestParam(required = false, defaultValue = "") String flocage, @RequestParam(required = false, defaultValue = "") String numero, @RequestParam(required = false) Boolean fourni, @RequestParam(required = false) Boolean choisi) { Dotation dotation = dotationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Invalid Dotation ID: " + id)); dotation.setTaille(taille); dotation.setFlocage(flocage); dotation.setNumero(numero); dotation.setFourni(fourni != null && fourni); if (dotation.getLicence().getCategorie().isEquipementObligatoire(dotation.getEquipement().getId())) { dotation.setChoisi(true); } else { dotation.setChoisi(choisi != null && choisi); } dotationRepository.save(dotation); return "redirect:/adherents/" + adherentId + "/edit"; } @Transactional @PostMapping("/dotations/bulk") public String bulkUpdateDotations( @RequestParam Long adherentId, @RequestParam("dotationIds") List dotationIds, HttpServletRequest request) { for (Long id : dotationIds) { Dotation dotation = dotationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Invalid Dotation ID: " + id)); String taille = request.getParameter("taille_" + id); String couleur = request.getParameter("couleur_" + id); String numero = request.getParameter("numero_" + id); String flocage = request.getParameter("flocage_" + id); String choisiParam = request.getParameter("choisi_" + id); String fourniParam = request.getParameter("fourni_" + id); if (taille != null) dotation.setTaille(taille); if (couleur != null) dotation.setCouleur(couleur); if (numero != null) dotation.setNumero(numero); if (flocage != null) dotation.setFlocage(flocage); if (dotation.getLicence().getCategorie().isEquipementObligatoire(dotation.getEquipement().getId())) { dotation.setChoisi(true); } else { dotation.setChoisi(choisiParam != null); } dotation.setFourni(fourniParam != null); dotationRepository.save(dotation); } return "redirect:/adherents/" + adherentId + "/edit"; } }