From d15324bd41dbb8ee5de13bfc45b02d29c5aa4386 Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:40:29 +0000 Subject: [PATCH] feat(backend): ajout export PDF pour les membres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau format d'export : - GET /api/membres/export?format=PDF - Export membres en PDF (OpenPDF) - Support formats : EXCEL, CSV, PDF (dans MembreResource) MembreImportExportService : - exporterVersPDF() : 193 lignes, génération PDF professionnelle * Document A4 landscape pour + de colonnes * Titre + métadonnées (date, total) * Table avec colonnes configurables (PERSO, CONTACT, ADHESION, ORGANISATION) * Styles : headers (bleu foncé, blanc), données (noir, aligné) * Page statistiques optionnelle (total, actifs/inactifs/suspendus, orgs) - Méthodes helper : createHeaderCell, createDataCell, createStatsCell, ajouterStatistiquesPDF MembreService : - exporterVersPDF() : délégation vers MembreImportExportService MembreResource : - Mise à jour endpoint /export : if ("PDF".equalsIgnoreCase(format)) - Content-Type: application/pdf - Filename: membres_export_YYYY-MM-DD.pdf Correctifs imports : - Résolution conflits OpenPDF vs Apache POI (List, Row, Font) - Imports spécifiques : com.lowagie.text.Font, Document, PdfPTable, etc. - Qualification complète pour éviter ambiguïtés (java.util.List vs com.lowagie.text.List) Résultat : Export membres en 3 formats (EXCEL, CSV, PDF) ✅ Co-Authored-By: Claude Sonnet 4.5 --- .../server/resource/MembreResource.java | 4 + .../service/MembreImportExportService.java | 217 ++++++++++++++++++ .../server/service/MembreService.java | 14 ++ 3 files changed, 235 insertions(+) diff --git a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java index 6eff20e..36bc9d0 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java @@ -646,6 +646,10 @@ public class MembreResource { exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates); contentType = "text/csv"; extension = "csv"; + } else if ("PDF".equalsIgnoreCase(format)) { + exportData = membreService.exporterVersPDF(membres, colonnesExport, inclureHeaders, formaterDates, inclureStatistiques); + contentType = "application/pdf"; + extension = "pdf"; } else { // Pour Excel, inclure les statistiques uniquement si demandé et si format Excel boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format); diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java index 0f9bf33..890b022 100644 --- a/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java +++ b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java @@ -19,6 +19,16 @@ import org.apache.commons.csv.CSVRecord; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.jboss.logging.Logger; +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Element; +import com.lowagie.text.PageSize; +import com.lowagie.text.Paragraph; +import com.lowagie.text.Phrase; +import com.lowagie.text.pdf.PdfPCell; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.PdfWriter; +import java.awt.Color; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -866,6 +876,213 @@ public class MembreImportExportService { } } + /** + * Exporte des membres vers PDF + */ + public byte[] exporterVersPDF(List membres, List colonnesExport, boolean inclureHeaders, + boolean formaterDates, boolean inclureStatistiques) throws IOException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + Document document = new Document(PageSize.A4.rotate()); // Landscape pour plus de colonnes + PdfWriter.getInstance(document, outputStream); + document.open(); + + // Titre + com.lowagie.text.Font titleFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 18, com.lowagie.text.Font.BOLD, Color.DARK_GRAY); + Paragraph title = new Paragraph("Export des Membres", titleFont); + title.setAlignment(Element.ALIGN_CENTER); + title.setSpacingAfter(20); + document.add(title); + + // Métadonnées + com.lowagie.text.Font metaFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 10, com.lowagie.text.Font.NORMAL, Color.GRAY); + Paragraph meta = new Paragraph("Généré le " + LocalDate.now().format(DATE_FORMATTER) + " | Total: " + membres.size() + " membres", metaFont); + meta.setAlignment(Element.ALIGN_CENTER); + meta.setSpacingAfter(15); + document.add(meta); + + // Déterminer les colonnes à afficher + boolean inclurePerso = colonnesExport.contains("PERSO") || colonnesExport.isEmpty(); + boolean inclureContact = colonnesExport.contains("CONTACT") || colonnesExport.isEmpty(); + boolean inclureAdhesion = colonnesExport.contains("ADHESION") || colonnesExport.isEmpty(); + boolean inclureOrg = colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty(); + + // Calculer nombre de colonnes + int colCount = 0; + if (inclurePerso) colCount += 3; // Nom, Prénom, Date naissance + if (inclureContact) colCount += 2; // Email, Téléphone + if (inclureAdhesion) colCount += 2; // Date adhésion, Statut + if (inclureOrg) colCount += 1; // Organisation + + // Créer le tableau + PdfPTable table = new PdfPTable(colCount); + table.setWidthPercentage(100); + table.setSpacingBefore(10); + + // Style des en-têtes + com.lowagie.text.Font headerFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 10, com.lowagie.text.Font.BOLD, Color.WHITE); + Color headerBgColor = new Color(52, 73, 94); // Bleu foncé + + // En-têtes + if (inclureHeaders) { + if (inclurePerso) { + table.addCell(createHeaderCell("Nom", headerFont, headerBgColor)); + table.addCell(createHeaderCell("Prénom", headerFont, headerBgColor)); + table.addCell(createHeaderCell("Date Naissance", headerFont, headerBgColor)); + } + if (inclureContact) { + table.addCell(createHeaderCell("Email", headerFont, headerBgColor)); + table.addCell(createHeaderCell("Téléphone", headerFont, headerBgColor)); + } + if (inclureAdhesion) { + table.addCell(createHeaderCell("Date Adhésion", headerFont, headerBgColor)); + table.addCell(createHeaderCell("Statut", headerFont, headerBgColor)); + } + if (inclureOrg) { + table.addCell(createHeaderCell("Organisation", headerFont, headerBgColor)); + } + } + + // Style des cellules de données + com.lowagie.text.Font dataFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 9, com.lowagie.text.Font.NORMAL); + + // Données + for (MembreResponse membre : membres) { + if (inclurePerso) { + table.addCell(createDataCell(membre.getNom() != null ? membre.getNom() : "", dataFont)); + table.addCell(createDataCell(membre.getPrenom() != null ? membre.getPrenom() : "", dataFont)); + String dateNaissance = ""; + if (membre.getDateNaissance() != null) { + dateNaissance = formaterDates + ? membre.getDateNaissance().format(DATE_FORMATTER) + : membre.getDateNaissance().toString(); + } + table.addCell(createDataCell(dateNaissance, dataFont)); + } + if (inclureContact) { + table.addCell(createDataCell(membre.getEmail() != null ? membre.getEmail() : "", dataFont)); + table.addCell(createDataCell(membre.getTelephone() != null ? membre.getTelephone() : "", dataFont)); + } + if (inclureAdhesion) { + table.addCell(createDataCell("", dataFont)); // Date adhésion (pas dans DTO) + table.addCell(createDataCell(membre.getStatutCompte() != null ? membre.getStatutCompte() : "", dataFont)); + } + if (inclureOrg) { + table.addCell(createDataCell(membre.getAssociationNom() != null ? membre.getAssociationNom() : "", dataFont)); + } + } + + document.add(table); + + // Ajouter statistiques si demandé + if (inclureStatistiques && !membres.isEmpty()) { + document.newPage(); + ajouterStatistiquesPDF(document, membres); + } + + document.close(); + return outputStream.toByteArray(); + } catch (DocumentException e) { + LOG.errorf(e, "Erreur lors de la génération du PDF"); + throw new IOException("Erreur lors de la génération du PDF", e); + } + } + + /** + * Crée une cellule d'en-tête PDF + */ + private PdfPCell createHeaderCell(String text, com.lowagie.text.Font font, Color bgColor) { + PdfPCell cell = new PdfPCell(new Phrase(text, font)); + cell.setBackgroundColor(bgColor); + cell.setHorizontalAlignment(Element.ALIGN_CENTER); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setPadding(8); + return cell; + } + + /** + * Crée une cellule de données PDF + */ + private PdfPCell createDataCell(String text, com.lowagie.text.Font font) { + PdfPCell cell = new PdfPCell(new Phrase(text, font)); + cell.setHorizontalAlignment(Element.ALIGN_LEFT); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setPadding(5); + return cell; + } + + /** + * Ajoute une page de statistiques au PDF + */ + private void ajouterStatistiquesPDF(Document document, java.util.List membres) throws DocumentException { + // Titre section statistiques + com.lowagie.text.Font titleFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 16, com.lowagie.text.Font.BOLD, Color.DARK_GRAY); + Paragraph statsTitle = new Paragraph("Statistiques des Membres", titleFont); + statsTitle.setAlignment(Element.ALIGN_CENTER); + statsTitle.setSpacingAfter(20); + document.add(statsTitle); + + // Calcul des statistiques + long totalMembres = membres.size(); + long membresActifs = membres.stream() + .filter(m -> dev.lions.unionflow.server.api.enums.membre.StatutMembre.ACTIF.name() + .equals(m.getStatutCompte())) + .count(); + long membresInactifs = membres.stream() + .filter(m -> dev.lions.unionflow.server.api.enums.membre.StatutMembre.INACTIF.name() + .equals(m.getStatutCompte())) + .count(); + long membresSuspendus = membres.stream() + .filter(m -> dev.lions.unionflow.server.api.enums.membre.StatutMembre.SUSPENDU.name() + .equals(m.getStatutCompte())) + .count(); + long organisationsDistinctes = membres.stream() + .filter(m -> m.getAssociationNom() != null) + .map(MembreResponse::getAssociationNom) + .distinct() + .count(); + + // Créer tableau statistiques + PdfPTable statsTable = new PdfPTable(2); + statsTable.setWidthPercentage(60); + statsTable.setHorizontalAlignment(Element.ALIGN_CENTER); + statsTable.setSpacingBefore(10); + + com.lowagie.text.Font labelFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 11, com.lowagie.text.Font.BOLD); + com.lowagie.text.Font valueFont = new com.lowagie.text.Font(com.lowagie.text.Font.HELVETICA, 11, com.lowagie.text.Font.NORMAL); + + // Ajouter lignes + statsTable.addCell(createStatsCell("Total Membres", labelFont, true)); + statsTable.addCell(createStatsCell(String.valueOf(totalMembres), valueFont, false)); + + statsTable.addCell(createStatsCell("Membres Actifs", labelFont, true)); + statsTable.addCell(createStatsCell(String.valueOf(membresActifs), valueFont, false)); + + statsTable.addCell(createStatsCell("Membres Inactifs", labelFont, true)); + statsTable.addCell(createStatsCell(String.valueOf(membresInactifs), valueFont, false)); + + statsTable.addCell(createStatsCell("Membres Suspendus", labelFont, true)); + statsTable.addCell(createStatsCell(String.valueOf(membresSuspendus), valueFont, false)); + + statsTable.addCell(createStatsCell("Organisations Distinctes", labelFont, true)); + statsTable.addCell(createStatsCell(String.valueOf(organisationsDistinctes), valueFont, false)); + + document.add(statsTable); + } + + /** + * Crée une cellule de statistiques PDF + */ + private PdfPCell createStatsCell(String text, com.lowagie.text.Font font, boolean isLabel) { + PdfPCell cell = new PdfPCell(new Phrase(text, font)); + cell.setHorizontalAlignment(isLabel ? Element.ALIGN_LEFT : Element.ALIGN_RIGHT); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setPadding(8); + if (isLabel) { + cell.setBackgroundColor(new Color(236, 240, 241)); // Gris clair + } + return cell; + } + /** * Génère un modèle Excel pour l'import */ diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreService.java b/src/main/java/dev/lions/unionflow/server/service/MembreService.java index 5af27d5..4be6b1f 100644 --- a/src/main/java/dev/lions/unionflow/server/service/MembreService.java +++ b/src/main/java/dev/lions/unionflow/server/service/MembreService.java @@ -853,6 +853,20 @@ public class MembreService { } } + /** + * Exporte des membres vers PDF + */ + public byte[] exporterVersPDF(List membres, List colonnesExport, boolean inclureHeaders, + boolean formaterDates, boolean inclureStatistiques) { + try { + return membreImportExportService.exporterVersPDF(membres, colonnesExport, inclureHeaders, formaterDates, + inclureStatistiques); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'export PDF"); + throw new RuntimeException("Erreur lors de l'export PDF: " + e.getMessage(), e); + } + } + /** * Génère un modèle Excel pour l'import */