feat(backend): ajout export PDF pour les membres

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 <noreply@anthropic.com>
This commit is contained in:
dahoud
2026-03-17 08:40:29 +00:00
parent 1c096f0ee1
commit d15324bd41
3 changed files with 235 additions and 0 deletions

View File

@@ -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);

View File

@@ -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<MembreResponse> membres, List<String> 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<MembreResponse> 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
*/

View File

@@ -853,6 +853,20 @@ public class MembreService {
}
}
/**
* Exporte des membres vers PDF
*/
public byte[] exporterVersPDF(List<MembreResponse> membres, List<String> 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
*/