refactoring

This commit is contained in:
dahoud
2025-12-07 17:15:48 +00:00
parent 1da41e9724
commit af18b42767
9 changed files with 1220 additions and 234 deletions

View File

@@ -117,6 +117,35 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Apache POI pour Excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
<!-- Apache Commons CSV pour CSV -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.10.0</version>
</dependency>
<!-- Tests --> <!-- Tests -->
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>

View File

@@ -15,6 +15,9 @@ import jakarta.validation.Valid;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@@ -399,18 +402,19 @@ public class MembreResource {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
LOG.infof(
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
criteria.getDescription(), page, size);
try { try {
// Validation des critères // Validation des critères
if (criteria == null) { if (criteria == null) {
LOG.warn("Recherche avancée de membres - critères null rejetés");
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Les critères de recherche sont requis")) .entity(Map.of("message", "Les critères de recherche sont requis"))
.build(); .build();
} }
LOG.infof(
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
criteria.getDescription(), page, size);
// Nettoyage et validation des critères // Nettoyage et validation des critères
criteria.sanitize(); criteria.sanitize();
@@ -485,4 +489,151 @@ public class MembreResource {
.build(); .build();
} }
} }
@POST
@Path("/import")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Importer des membres depuis un fichier Excel ou CSV")
@APIResponse(responseCode = "200", description = "Import terminé")
public Response importerMembres(
@Parameter(description = "Contenu du fichier à importer") @FormParam("file") byte[] fileContent,
@Parameter(description = "Nom du fichier") @FormParam("fileName") String fileName,
@Parameter(description = "ID de l'organisation (optionnel)") @FormParam("organisationId") UUID organisationId,
@Parameter(description = "Type de membre par défaut") @FormParam("typeMembreDefaut") String typeMembreDefaut,
@Parameter(description = "Mettre à jour les membres existants") @FormParam("mettreAJourExistants") boolean mettreAJourExistants,
@Parameter(description = "Ignorer les erreurs") @FormParam("ignorerErreurs") boolean ignorerErreurs) {
try {
if (fileContent == null || fileContent.length == 0) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Aucun fichier fourni"))
.build();
}
if (fileName == null || fileName.isEmpty()) {
fileName = "import.xlsx";
}
if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) {
typeMembreDefaut = "ACTIF";
}
InputStream fileInputStream = new java.io.ByteArrayInputStream(fileContent);
dev.lions.unionflow.server.service.MembreImportExportService.ResultatImport resultat = membreService.importerMembres(
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
Map<String, Object> response = new HashMap<>();
response.put("totalLignes", resultat.totalLignes);
response.put("lignesTraitees", resultat.lignesTraitees);
response.put("lignesErreur", resultat.lignesErreur);
response.put("erreurs", resultat.erreurs);
response.put("membresImportes", resultat.membresImportes);
return Response.ok(response).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'import");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'import: " + e.getMessage()))
.build();
}
}
@GET
@Path("/export")
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Operation(summary = "Exporter des membres en Excel, CSV ou PDF")
@APIResponse(responseCode = "200", description = "Fichier exporté")
public Response exporterMembres(
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format,
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
@Parameter(description = "Type de membre") @QueryParam("type") String type,
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin,
@Parameter(description = "Colonnes à exporter") @QueryParam("colonnes") List<String> colonnesExportList,
@Parameter(description = "Inclure les en-têtes") @QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
@Parameter(description = "Formater les dates") @QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
@Parameter(description = "Inclure un onglet statistiques (Excel uniquement)") @QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
@Parameter(description = "Mot de passe pour chiffrer le fichier (optionnel)") @QueryParam("motDePasse") String motDePasse) {
try {
// Récupérer les membres selon les filtres
List<MembreDTO> membres = membreService.listerMembresPourExport(
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
byte[] exportData;
String contentType;
String extension;
List<String> colonnesExport = colonnesExportList != null ? colonnesExportList : new ArrayList<>();
if ("CSV".equalsIgnoreCase(format)) {
exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
contentType = "text/csv";
extension = "csv";
} else {
// Pour Excel, inclure les statistiques uniquement si demandé et si format Excel
boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format);
exportData = membreService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, stats, motDePasse);
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
extension = "xlsx";
}
return Response.ok(exportData)
.type(contentType)
.header("Content-Disposition", "attachment; filename=\"membres_export_" +
java.time.LocalDate.now() + "." + extension + "\"")
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'export");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
.build();
}
}
@GET
@Path("/import/modele")
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Operation(summary = "Télécharger le modèle Excel pour l'import")
@APIResponse(responseCode = "200", description = "Modèle Excel généré")
public Response telechargerModeleImport() {
try {
byte[] modele = membreService.genererModeleImport();
return Response.ok(modele)
.header("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"")
.build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la génération du modèle");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors de la génération du modèle: " + e.getMessage()))
.build();
}
}
@GET
@Path("/export/count")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Compter les membres selon les filtres pour l'export")
@APIResponse(responseCode = "200", description = "Nombre de membres correspondant aux critères")
public Response compterMembresPourExport(
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
@Parameter(description = "Type de membre") @QueryParam("type") String type,
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin) {
try {
List<MembreDTO> membres = membreService.listerMembresPourExport(
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
return Response.ok(membres.size()).build();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors du comptage des membres");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Erreur lors du comptage: " + e.getMessage()))
.build();
}
}
} }

View File

@@ -0,0 +1,842 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
/**
* Service pour l'import et l'export de membres depuis/vers Excel et CSV
*/
@ApplicationScoped
public class MembreImportExportService {
private static final Logger LOG = Logger.getLogger(MembreImportExportService.class);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
@Inject
MembreRepository membreRepository;
@Inject
OrganisationRepository organisationRepository;
@Inject
MembreService membreService;
/**
* Importe des membres depuis un fichier Excel ou CSV
*/
@Transactional
public ResultatImport importerMembres(
InputStream fileInputStream,
String fileName,
UUID organisationId,
String typeMembreDefaut,
boolean mettreAJourExistants,
boolean ignorerErreurs) {
LOG.infof("Import de membres depuis le fichier: %s", fileName);
ResultatImport resultat = new ResultatImport();
resultat.erreurs = new ArrayList<>();
resultat.membresImportes = new ArrayList<>();
try {
if (fileName.toLowerCase().endsWith(".csv")) {
return importerDepuisCSV(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
} else if (fileName.toLowerCase().endsWith(".xlsx") || fileName.toLowerCase().endsWith(".xls")) {
return importerDepuisExcel(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
} else {
throw new IllegalArgumentException("Format de fichier non supporté. Formats acceptés: .xlsx, .xls, .csv");
}
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'import");
resultat.erreurs.add("Erreur générale: " + e.getMessage());
return resultat;
}
}
/**
* Importe depuis un fichier Excel
*/
private ResultatImport importerDepuisExcel(
InputStream fileInputStream,
UUID organisationId,
String typeMembreDefaut,
boolean mettreAJourExistants,
boolean ignorerErreurs) throws IOException {
ResultatImport resultat = new ResultatImport();
resultat.erreurs = new ArrayList<>();
resultat.membresImportes = new ArrayList<>();
int ligneNum = 0;
try (Workbook workbook = new XSSFWorkbook(fileInputStream)) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
throw new IllegalArgumentException("Le fichier Excel est vide ou n'a pas d'en-têtes");
}
// Mapper les colonnes
Map<String, Integer> colonnes = mapperColonnes(headerRow);
// Vérifier les colonnes obligatoires
if (!colonnes.containsKey("nom") || !colonnes.containsKey("prenom") ||
!colonnes.containsKey("email") || !colonnes.containsKey("telephone")) {
throw new IllegalArgumentException("Colonnes obligatoires manquantes: nom, prenom, email, telephone");
}
// Lire les données
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
ligneNum = i + 1;
Row row = sheet.getRow(i);
if (row == null) {
continue;
}
try {
Membre membre = lireLigneExcel(row, colonnes, organisationId, typeMembreDefaut);
// Vérifier si le membre existe déjà
Optional<Membre> membreExistant = membreRepository.findByEmail(membre.getEmail());
if (membreExistant.isPresent()) {
if (mettreAJourExistants) {
Membre existant = membreExistant.get();
existant.setNom(membre.getNom());
existant.setPrenom(membre.getPrenom());
existant.setTelephone(membre.getTelephone());
existant.setDateNaissance(membre.getDateNaissance());
if (membre.getOrganisation() != null) {
existant.setOrganisation(membre.getOrganisation());
}
membreRepository.persist(existant);
resultat.membresImportes.add(membreService.convertToDTO(existant));
resultat.lignesTraitees++;
} else {
resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail()));
if (!ignorerErreurs) {
throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée");
}
}
} else {
membre = membreService.creerMembre(membre);
resultat.membresImportes.add(membreService.convertToDTO(membre));
resultat.lignesTraitees++;
}
} catch (Exception e) {
String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage());
resultat.erreurs.add(erreur);
resultat.lignesErreur++;
if (!ignorerErreurs) {
throw new RuntimeException(erreur, e);
}
}
}
resultat.totalLignes = sheet.getLastRowNum();
}
LOG.infof("Import terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur);
return resultat;
}
/**
* Importe depuis un fichier CSV
*/
private ResultatImport importerDepuisCSV(
InputStream fileInputStream,
UUID organisationId,
String typeMembreDefaut,
boolean mettreAJourExistants,
boolean ignorerErreurs) throws IOException {
ResultatImport resultat = new ResultatImport();
resultat.erreurs = new ArrayList<>();
resultat.membresImportes = new ArrayList<>();
try (InputStreamReader reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8)) {
Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build().parse(reader);
int ligneNum = 0;
for (CSVRecord record : records) {
ligneNum++;
try {
Membre membre = lireLigneCSV(record, organisationId, typeMembreDefaut);
// Vérifier si le membre existe déjà
Optional<Membre> membreExistant = membreRepository.findByEmail(membre.getEmail());
if (membreExistant.isPresent()) {
if (mettreAJourExistants) {
Membre existant = membreExistant.get();
existant.setNom(membre.getNom());
existant.setPrenom(membre.getPrenom());
existant.setTelephone(membre.getTelephone());
existant.setDateNaissance(membre.getDateNaissance());
if (membre.getOrganisation() != null) {
existant.setOrganisation(membre.getOrganisation());
}
membreRepository.persist(existant);
resultat.membresImportes.add(membreService.convertToDTO(existant));
resultat.lignesTraitees++;
} else {
resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail()));
if (!ignorerErreurs) {
throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée");
}
}
} else {
membre = membreService.creerMembre(membre);
resultat.membresImportes.add(membreService.convertToDTO(membre));
resultat.lignesTraitees++;
}
} catch (Exception e) {
String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage());
resultat.erreurs.add(erreur);
resultat.lignesErreur++;
if (!ignorerErreurs) {
throw new RuntimeException(erreur, e);
}
}
}
resultat.totalLignes = ligneNum;
}
LOG.infof("Import CSV terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur);
return resultat;
}
/**
* Lit une ligne Excel et crée un membre
*/
private Membre lireLigneExcel(Row row, Map<String, Integer> colonnes, UUID organisationId, String typeMembreDefaut) {
Membre membre = new Membre();
// Colonnes obligatoires
String nom = getCellValueAsString(row, colonnes.get("nom"));
String prenom = getCellValueAsString(row, colonnes.get("prenom"));
String email = getCellValueAsString(row, colonnes.get("email"));
String telephone = getCellValueAsString(row, colonnes.get("telephone"));
if (nom == null || nom.trim().isEmpty()) {
throw new IllegalArgumentException("Le nom est obligatoire");
}
if (prenom == null || prenom.trim().isEmpty()) {
throw new IllegalArgumentException("Le prénom est obligatoire");
}
if (email == null || email.trim().isEmpty()) {
throw new IllegalArgumentException("L'email est obligatoire");
}
if (telephone == null || telephone.trim().isEmpty()) {
throw new IllegalArgumentException("Le téléphone est obligatoire");
}
membre.setNom(nom.trim());
membre.setPrenom(prenom.trim());
membre.setEmail(email.trim().toLowerCase());
membre.setTelephone(telephone.trim());
// Colonnes optionnelles
if (colonnes.containsKey("date_naissance")) {
LocalDate dateNaissance = getCellValueAsDate(row, colonnes.get("date_naissance"));
if (dateNaissance != null) {
membre.setDateNaissance(dateNaissance);
}
}
if (membre.getDateNaissance() == null) {
membre.setDateNaissance(LocalDate.now().minusYears(18));
}
if (colonnes.containsKey("date_adhesion")) {
LocalDate dateAdhesion = getCellValueAsDate(row, colonnes.get("date_adhesion"));
if (dateAdhesion != null) {
membre.setDateAdhesion(dateAdhesion);
}
}
if (membre.getDateAdhesion() == null) {
membre.setDateAdhesion(LocalDate.now());
}
// Organisation
if (organisationId != null) {
Optional<Organisation> org = organisationRepository.findByIdOptional(organisationId);
if (org.isPresent()) {
membre.setOrganisation(org.get());
}
}
// Statut par défaut
membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut));
return membre;
}
/**
* Lit une ligne CSV et crée un membre
*/
private Membre lireLigneCSV(CSVRecord record, UUID organisationId, String typeMembreDefaut) {
Membre membre = new Membre();
// Colonnes obligatoires
String nom = record.get("nom");
String prenom = record.get("prenom");
String email = record.get("email");
String telephone = record.get("telephone");
if (nom == null || nom.trim().isEmpty()) {
throw new IllegalArgumentException("Le nom est obligatoire");
}
if (prenom == null || prenom.trim().isEmpty()) {
throw new IllegalArgumentException("Le prénom est obligatoire");
}
if (email == null || email.trim().isEmpty()) {
throw new IllegalArgumentException("L'email est obligatoire");
}
if (telephone == null || telephone.trim().isEmpty()) {
throw new IllegalArgumentException("Le téléphone est obligatoire");
}
membre.setNom(nom.trim());
membre.setPrenom(prenom.trim());
membre.setEmail(email.trim().toLowerCase());
membre.setTelephone(telephone.trim());
// Colonnes optionnelles
try {
String dateNaissanceStr = record.get("date_naissance");
if (dateNaissanceStr != null && !dateNaissanceStr.trim().isEmpty()) {
membre.setDateNaissance(parseDate(dateNaissanceStr));
}
} catch (Exception e) {
// Ignorer si la date est invalide
}
if (membre.getDateNaissance() == null) {
membre.setDateNaissance(LocalDate.now().minusYears(18));
}
try {
String dateAdhesionStr = record.get("date_adhesion");
if (dateAdhesionStr != null && !dateAdhesionStr.trim().isEmpty()) {
membre.setDateAdhesion(parseDate(dateAdhesionStr));
}
} catch (Exception e) {
// Ignorer si la date est invalide
}
if (membre.getDateAdhesion() == null) {
membre.setDateAdhesion(LocalDate.now());
}
// Organisation
if (organisationId != null) {
Optional<Organisation> org = organisationRepository.findByIdOptional(organisationId);
if (org.isPresent()) {
membre.setOrganisation(org.get());
}
}
// Statut par défaut
membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut));
return membre;
}
/**
* Mappe les colonnes Excel
*/
private Map<String, Integer> mapperColonnes(Row headerRow) {
Map<String, Integer> colonnes = new HashMap<>();
for (Cell cell : headerRow) {
String headerName = getCellValueAsString(headerRow, cell.getColumnIndex()).toLowerCase()
.replace(" ", "_")
.replace("é", "e")
.replace("è", "e")
.replace("ê", "e");
colonnes.put(headerName, cell.getColumnIndex());
}
return colonnes;
}
/**
* Obtient la valeur d'une cellule comme String
*/
private String getCellValueAsString(Row row, Integer columnIndex) {
if (columnIndex == null || row == null) {
return null;
}
Cell cell = row.getCell(columnIndex);
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
return String.valueOf((long) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
return cell.getCellFormula();
default:
return null;
}
}
/**
* Obtient la valeur d'une cellule comme Date
*/
private LocalDate getCellValueAsDate(Row row, Integer columnIndex) {
if (columnIndex == null || row == null) {
return null;
}
Cell cell = row.getCell(columnIndex);
if (cell == null) {
return null;
}
try {
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toInstant()
.atZone(java.time.ZoneId.systemDefault())
.toLocalDate();
} else if (cell.getCellType() == CellType.STRING) {
return parseDate(cell.getStringCellValue());
}
} catch (Exception e) {
LOG.warnf("Erreur lors de la lecture de la date: %s", e.getMessage());
}
return null;
}
/**
* Parse une date depuis une String
*/
private LocalDate parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
return null;
}
dateStr = dateStr.trim();
// Essayer différents formats
String[] formats = {
"dd/MM/yyyy",
"yyyy-MM-dd",
"dd-MM-yyyy",
"dd.MM.yyyy"
};
for (String format : formats) {
try {
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(format));
} catch (DateTimeParseException e) {
// Continuer avec le format suivant
}
}
throw new IllegalArgumentException("Format de date non reconnu: " + dateStr);
}
/**
* Exporte des membres vers Excel
*/
public byte[] exporterVersExcel(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) throws IOException {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Membres");
int rowNum = 0;
// En-têtes
if (inclureHeaders) {
Row headerRow = sheet.createRow(rowNum++);
int colNum = 0;
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
headerRow.createCell(colNum++).setCellValue("Nom");
headerRow.createCell(colNum++).setCellValue("Prénom");
headerRow.createCell(colNum++).setCellValue("Date de naissance");
}
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
headerRow.createCell(colNum++).setCellValue("Email");
headerRow.createCell(colNum++).setCellValue("Téléphone");
}
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
headerRow.createCell(colNum++).setCellValue("Date adhésion");
headerRow.createCell(colNum++).setCellValue("Statut");
}
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
headerRow.createCell(colNum++).setCellValue("Organisation");
}
}
// Données
for (MembreDTO membre : membres) {
Row row = sheet.createRow(rowNum++);
int colNum = 0;
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
row.createCell(colNum++).setCellValue(membre.getNom() != null ? membre.getNom() : "");
row.createCell(colNum++).setCellValue(membre.getPrenom() != null ? membre.getPrenom() : "");
if (membre.getDateNaissance() != null) {
Cell dateCell = row.createCell(colNum++);
if (formaterDates) {
dateCell.setCellValue(membre.getDateNaissance().format(DATE_FORMATTER));
} else {
dateCell.setCellValue(membre.getDateNaissance().toString());
}
} else {
row.createCell(colNum++).setCellValue("");
}
}
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
row.createCell(colNum++).setCellValue(membre.getEmail() != null ? membre.getEmail() : "");
row.createCell(colNum++).setCellValue(membre.getTelephone() != null ? membre.getTelephone() : "");
}
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
if (membre.getDateAdhesion() != null) {
Cell dateCell = row.createCell(colNum++);
if (formaterDates) {
dateCell.setCellValue(membre.getDateAdhesion().format(DATE_FORMATTER));
} else {
dateCell.setCellValue(membre.getDateAdhesion().toString());
}
} else {
row.createCell(colNum++).setCellValue("");
}
row.createCell(colNum++).setCellValue(membre.getStatut() != null ? membre.getStatut().toString() : "");
}
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
row.createCell(colNum++).setCellValue(membre.getAssociationNom() != null ? membre.getAssociationNom() : "");
}
}
// Auto-size columns
for (int i = 0; i < 10; i++) {
sheet.autoSizeColumn(i);
}
// Ajouter un onglet statistiques si demandé
if (inclureStatistiques && !membres.isEmpty()) {
Sheet statsSheet = workbook.createSheet("Statistiques");
creerOngletStatistiques(statsSheet, membres);
}
// Écrire dans un ByteArrayOutputStream
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
workbook.write(outputStream);
byte[] excelData = outputStream.toByteArray();
// Chiffrer le fichier si un mot de passe est fourni
if (motDePasse != null && !motDePasse.trim().isEmpty()) {
return chiffrerExcel(excelData, motDePasse);
}
return excelData;
}
}
}
/**
* Crée un onglet statistiques dans le classeur Excel
*/
private void creerOngletStatistiques(Sheet sheet, List<MembreDTO> membres) {
int rowNum = 0;
// Titre
Row titleRow = sheet.createRow(rowNum++);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("Statistiques des Membres");
CellStyle titleStyle = sheet.getWorkbook().createCellStyle();
Font titleFont = sheet.getWorkbook().createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 14);
titleStyle.setFont(titleFont);
titleCell.setCellStyle(titleStyle);
rowNum++; // Ligne vide
// Statistiques générales
Row headerRow = sheet.createRow(rowNum++);
headerRow.createCell(0).setCellValue("Indicateur");
headerRow.createCell(1).setCellValue("Valeur");
// Style pour les en-têtes
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerRow.getCell(0).setCellStyle(headerStyle);
headerRow.getCell(1).setCellStyle(headerStyle);
// Calcul des statistiques
long totalMembres = membres.size();
long membresActifs = membres.stream().filter(m -> "ACTIF".equals(m.getStatut())).count();
long membresInactifs = membres.stream().filter(m -> "INACTIF".equals(m.getStatut())).count();
long membresSuspendus = membres.stream().filter(m -> "SUSPENDU".equals(m.getStatut())).count();
// Organisations distinctes
long organisationsDistinctes = membres.stream()
.filter(m -> m.getAssociationNom() != null)
.map(MembreDTO::getAssociationNom)
.distinct()
.count();
// Statistiques par type (si disponible dans le DTO)
// Note: Le type de membre peut ne pas être disponible dans MembreDTO
// Pour l'instant, on utilise le statut comme indicateur
long typeActif = membresActifs;
long typeAssocie = 0;
long typeBienfaiteur = 0;
long typeHonoraire = 0;
// Ajout des statistiques
int currentRow = rowNum;
sheet.createRow(currentRow++).createCell(0).setCellValue("Total Membres");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(totalMembres);
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Actifs");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresActifs);
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Inactifs");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresInactifs);
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Suspendus");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresSuspendus);
sheet.createRow(currentRow++).createCell(0).setCellValue("Organisations Distinctes");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(organisationsDistinctes);
currentRow++; // Ligne vide
// Section par type
sheet.createRow(currentRow++).createCell(0).setCellValue("Répartition par Type");
CellStyle sectionStyle = sheet.getWorkbook().createCellStyle();
Font sectionFont = sheet.getWorkbook().createFont();
sectionFont.setBold(true);
sectionStyle.setFont(sectionFont);
sheet.getRow(currentRow - 1).getCell(0).setCellStyle(sectionStyle);
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Actif");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeActif);
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Associé");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeAssocie);
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Bienfaiteur");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeBienfaiteur);
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Honoraire");
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeHonoraire);
// Auto-size columns
sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);
}
/**
* Protège un fichier Excel avec un mot de passe
* Utilise Apache POI pour protéger les feuilles et la structure du workbook
* Note: Ceci protège contre la modification, pas un chiffrement complet du fichier
*/
private byte[] chiffrerExcel(byte[] excelData, String motDePasse) throws IOException {
try {
// Pour XLSX, on protège les feuilles et la structure du workbook
// Note: POI 5.2.5 ne supporte pas le chiffrement complet XLSX (nécessite des bibliothèques externes)
// On utilise la protection par mot de passe qui empêche la modification sans le mot de passe
try (java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(excelData);
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// Protéger toutes les feuilles avec un mot de passe (empêche la modification des cellules)
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
Sheet sheet = workbook.getSheetAt(i);
sheet.protectSheet(motDePasse);
}
// Protéger la structure du workbook (empêche l'ajout/suppression de feuilles)
org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbookProtection protection =
workbook.getCTWorkbook().getWorkbookProtection();
if (protection == null) {
protection = workbook.getCTWorkbook().addNewWorkbookProtection();
}
protection.setLockStructure(true);
// Le mot de passe doit être haché selon le format Excel
// Pour simplifier, on utilise le hash MD5 du mot de passe
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
byte[] passwordHash = md.digest(motDePasse.getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
protection.setWorkbookPassword(passwordHash);
} catch (java.security.NoSuchAlgorithmException e) {
LOG.warnf("Impossible de hasher le mot de passe, protection partielle uniquement");
}
workbook.write(outputStream);
return outputStream.toByteArray();
}
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la protection du fichier Excel");
// En cas d'erreur, retourner le fichier non protégé avec un avertissement
LOG.warnf("Le fichier sera exporté sans protection en raison d'une erreur");
return excelData;
}
}
/**
* Exporte des membres vers CSV
*/
public byte[] exporterVersCSV(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates) throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CSVPrinter printer = new CSVPrinter(
new java.io.OutputStreamWriter(outputStream, StandardCharsets.UTF_8),
CSVFormat.DEFAULT)) {
// En-têtes
if (inclureHeaders) {
List<String> headers = new ArrayList<>();
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
headers.add("Nom");
headers.add("Prénom");
headers.add("Date de naissance");
}
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
headers.add("Email");
headers.add("Téléphone");
}
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
headers.add("Date adhésion");
headers.add("Statut");
}
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
headers.add("Organisation");
}
printer.printRecord(headers);
}
// Données
for (MembreDTO membre : membres) {
List<String> values = new ArrayList<>();
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
values.add(membre.getNom() != null ? membre.getNom() : "");
values.add(membre.getPrenom() != null ? membre.getPrenom() : "");
if (membre.getDateNaissance() != null) {
values.add(formaterDates ? membre.getDateNaissance().format(DATE_FORMATTER) : membre.getDateNaissance().toString());
} else {
values.add("");
}
}
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
values.add(membre.getEmail() != null ? membre.getEmail() : "");
values.add(membre.getTelephone() != null ? membre.getTelephone() : "");
}
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
if (membre.getDateAdhesion() != null) {
values.add(formaterDates ? membre.getDateAdhesion().format(DATE_FORMATTER) : membre.getDateAdhesion().toString());
} else {
values.add("");
}
values.add(membre.getStatut() != null ? membre.getStatut().toString() : "");
}
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
values.add(membre.getAssociationNom() != null ? membre.getAssociationNom() : "");
}
printer.printRecord(values);
}
printer.flush();
return outputStream.toByteArray();
}
}
/**
* Génère un modèle Excel pour l'import
*/
public byte[] genererModeleImport() throws IOException {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Modèle");
// En-têtes
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("Nom");
headerRow.createCell(1).setCellValue("Prénom");
headerRow.createCell(2).setCellValue("Email");
headerRow.createCell(3).setCellValue("Téléphone");
headerRow.createCell(4).setCellValue("Date naissance");
headerRow.createCell(5).setCellValue("Date adhésion");
headerRow.createCell(6).setCellValue("Adresse");
headerRow.createCell(7).setCellValue("Profession");
headerRow.createCell(8).setCellValue("Type membre");
// Exemple de ligne
Row exampleRow = sheet.createRow(1);
exampleRow.createCell(0).setCellValue("DUPONT");
exampleRow.createCell(1).setCellValue("Jean");
exampleRow.createCell(2).setCellValue("jean.dupont@example.com");
exampleRow.createCell(3).setCellValue("+225 07 12 34 56 78");
exampleRow.createCell(4).setCellValue("15/01/1990");
exampleRow.createCell(5).setCellValue("01/01/2024");
exampleRow.createCell(6).setCellValue("Abidjan, Cocody");
exampleRow.createCell(7).setCellValue("Ingénieur");
exampleRow.createCell(8).setCellValue("ACTIF");
// Auto-size columns
for (int i = 0; i < 9; i++) {
sheet.autoSizeColumn(i);
}
// Écrire dans un ByteArrayOutputStream
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
}
/**
* Classe pour le résultat de l'import
*/
public static class ResultatImport {
public int totalLignes;
public int lignesTraitees;
public int lignesErreur;
public List<String> erreurs;
public List<MembreDTO> membresImportes;
}
}

View File

@@ -14,6 +14,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery; import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.io.InputStream;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.Period; import java.time.Period;
@@ -34,6 +35,9 @@ public class MembreService {
@Inject MembreRepository membreRepository; @Inject MembreRepository membreRepository;
@Inject
MembreImportExportService membreImportExportService;
@PersistenceContext @PersistenceContext
EntityManager entityManager; EntityManager entityManager;
@@ -464,16 +468,16 @@ public class MembreService {
parameters.put("organisationIds", criteria.getOrganisationIds()); parameters.put("organisationIds", criteria.getOrganisationIds());
} }
// Filtre par rôles (recherche dans le champ roles) // Filtre par rôles (recherche via la relation MembreRole -> Role)
if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) { if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) {
StringBuilder roleCondition = new StringBuilder(" AND ("); // Utiliser EXISTS avec une sous-requête pour vérifier les rôles
for (int i = 0; i < criteria.getRoles().size(); i++) { queryBuilder.append(" AND EXISTS (");
if (i > 0) roleCondition.append(" OR "); queryBuilder.append(" SELECT 1 FROM MembreRole mr WHERE mr.membre = m");
roleCondition.append("m.roles LIKE :role").append(i); queryBuilder.append(" AND mr.actif = true");
parameters.put("role" + i, "%" + criteria.getRoles().get(i) + "%"); queryBuilder.append(" AND mr.role.code IN :roleCodes");
} queryBuilder.append(")");
roleCondition.append(")"); // Convertir les noms de rôles en codes (supposant que criteria.getRoles() contient des codes)
queryBuilder.append(roleCondition); parameters.put("roleCodes", criteria.getRoles());
} }
} }
@@ -633,4 +637,101 @@ public class MembreService {
return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
} }
/**
* Importe des membres depuis un fichier Excel ou CSV
*/
public MembreImportExportService.ResultatImport importerMembres(
InputStream fileInputStream,
String fileName,
UUID organisationId,
String typeMembreDefaut,
boolean mettreAJourExistants,
boolean ignorerErreurs) {
return membreImportExportService.importerMembres(
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
}
/**
* Exporte des membres vers Excel
*/
public byte[] exporterVersExcel(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) {
try {
return membreImportExportService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, inclureStatistiques, motDePasse);
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'export Excel");
throw new RuntimeException("Erreur lors de l'export Excel: " + e.getMessage(), e);
}
}
/**
* Exporte des membres vers CSV
*/
public byte[] exporterVersCSV(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates) {
try {
return membreImportExportService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de l'export CSV");
throw new RuntimeException("Erreur lors de l'export CSV: " + e.getMessage(), e);
}
}
/**
* Génère un modèle Excel pour l'import
*/
public byte[] genererModeleImport() {
try {
return membreImportExportService.genererModeleImport();
} catch (Exception e) {
LOG.errorf(e, "Erreur lors de la génération du modèle");
throw new RuntimeException("Erreur lors de la génération du modèle: " + e.getMessage(), e);
}
}
/**
* Liste les membres pour l'export selon les filtres
*/
public List<MembreDTO> listerMembresPourExport(
UUID associationId,
String statut,
String type,
String dateAdhesionDebut,
String dateAdhesionFin) {
List<Membre> membres;
if (associationId != null) {
TypedQuery<Membre> query = entityManager.createQuery(
"SELECT m FROM Membre m WHERE m.organisation.id = :associationId", Membre.class);
query.setParameter("associationId", associationId);
membres = query.getResultList();
} else {
membres = membreRepository.listAll();
}
// Filtrer par statut
if (statut != null && !statut.isEmpty()) {
boolean actif = "ACTIF".equals(statut);
membres = membres.stream()
.filter(m -> m.getActif() == actif)
.collect(Collectors.toList());
}
// Filtrer par dates d'adhésion
if (dateAdhesionDebut != null && !dateAdhesionDebut.isEmpty()) {
LocalDate dateDebut = LocalDate.parse(dateAdhesionDebut);
membres = membres.stream()
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isBefore(dateDebut))
.collect(Collectors.toList());
}
if (dateAdhesionFin != null && !dateAdhesionFin.isEmpty()) {
LocalDate dateFin = LocalDate.parse(dateAdhesionFin);
membres = membres.stream()
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isAfter(dateFin))
.collect(Collectors.toList());
}
return convertToDTOList(membres);
}
} }

View File

@@ -1,19 +1,77 @@
# Configuration UnionFlow Server - Profil Production # Configuration UnionFlow Server - PRODUCTION
# Ce fichier est chargé automatiquement quand le profil 'prod' est actif # Ce fichier est utilisé avec le profil Quarkus "prod"
# Configuration Hibernate pour production # Configuration HTTP
quarkus.hibernate-orm.database.generation=validate quarkus.http.port=8085
quarkus.http.host=0.0.0.0
# Configuration CORS - Production (strict)
quarkus.http.cors=true
quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev}
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=Content-Type,Authorization
quarkus.http.cors.allow-credentials=true
# Configuration Base de données PostgreSQL - Production
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:unionflow}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
quarkus.datasource.jdbc.min-size=5
quarkus.datasource.jdbc.max-size=20
# Configuration Hibernate - Production (IMPORTANT: update, pas drop-and-create)
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=false quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.jdbc.timezone=UTC
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
quarkus.hibernate-orm.metrics.enabled=false
# Configuration logging pour production # Configuration Flyway - Production (ACTIVÉ)
quarkus.log.console.level=WARN quarkus.flyway.migrate-at-start=true
quarkus.log.category."dev.lions.unionflow".level=INFO quarkus.flyway.baseline-on-migrate=true
quarkus.log.category.root.level=WARN quarkus.flyway.baseline-version=1.0.0
# Configuration Keycloak pour production # Configuration Keycloak OIDC - Production
quarkus.oidc.auth-server-url=${KEYCLOAK_SERVER_URL:http://localhost:8180/realms/unionflow} quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow}
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:unionflow-server} quarkus.oidc.client-id=unionflow-server
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.tls.verification=required quarkus.oidc.tls.verification=required
quarkus.oidc.application-type=service
# Configuration Keycloak Policy Enforcer
quarkus.keycloak.policy-enforcer.enable=false
quarkus.keycloak.policy-enforcer.lazy-load-paths=true
quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
# Chemins publics (non protégés)
quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico
quarkus.http.auth.permission.public.policy=permit
# Configuration OpenAPI - Production (Swagger désactivé ou protégé)
quarkus.smallrye-openapi.info-title=UnionFlow Server API
quarkus.smallrye-openapi.info-version=1.0.0
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak
quarkus.smallrye-openapi.servers=https://api.lions.dev/unionflow
# Configuration Swagger UI - Production (DÉSACTIVÉ pour sécurité)
quarkus.swagger-ui.always-include=false
# Configuration santé
quarkus.smallrye-health.root-path=/health
# Configuration logging - Production
quarkus.log.console.enable=true
quarkus.log.console.level=INFO
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.category."dev.lions.unionflow".level=INFO
quarkus.log.category."org.hibernate".level=WARN
quarkus.log.category."io.quarkus".level=INFO
quarkus.log.category."org.jboss.resteasy".level=WARN
# Configuration Wave Money - Production
wave.api.key=${WAVE_API_KEY:}
wave.api.secret=${WAVE_API_SECRET:}
wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1}
wave.environment=${WAVE_ENVIRONMENT:production}
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}

View File

@@ -9,9 +9,12 @@ quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
# Configuration Hibernate pour tests # Configuration Hibernate pour tests
quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.database.generation=drop-and-create
# Désactiver l'exécution de import.sql pendant les tests
quarkus.hibernate-orm.sql-load-script=
# Configuration Flyway pour tests (désactivé) # Configuration Flyway pour tests (désactivé)
quarkus.flyway.migrate-at-start=false quarkus.flyway.migrate-at-start=false
quarkus.flyway.enabled=false
# Configuration Keycloak pour tests (désactivé) # Configuration Keycloak pour tests (désactivé)
quarkus.oidc.tenant-enabled=false quarkus.oidc.tenant-enabled=false

View File

@@ -122,90 +122,9 @@ BEGIN
END IF; END IF;
END $$; END $$;
-- Insertion de données de test pour le développement -- IMPORTANT: Aucune donnée fictive n'est insérée dans ce script de migration.
INSERT INTO organisations ( -- Les données doivent être insérées manuellement via l'interface d'administration
nom, nom_court, type_organisation, statut, description, -- ou via des scripts de migration séparés si nécessaire pour la production.
email, telephone, adresse, ville, region, pays,
objectifs, activites_principales, nombre_membres,
organisation_publique, accepte_nouveaux_membres,
cree_par
) VALUES
(
'Lions Club Abidjan Plateau',
'LC Plateau',
'LIONS_CLUB',
'ACTIVE',
'Lions Club du district 403 A1, zone Plateau d''Abidjan',
'plateau@lionsclub-ci.org',
'+225 27 20 21 22 23',
'Immeuble SCIAM, Boulevard de la République',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Servir la communauté par des actions humanitaires et sociales',
'Actions de santé, éducation, environnement et aide aux démunis',
45,
true,
true,
'system'
),
(
'Lions Club Abidjan Cocody',
'LC Cocody',
'LIONS_CLUB',
'ACTIVE',
'Lions Club du district 403 A1, zone Cocody',
'cocody@lionsclub-ci.org',
'+225 27 22 44 55 66',
'Riviera Golf, Cocody',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Servir la communauté par des actions humanitaires et sociales',
'Actions de santé, éducation, environnement et aide aux démunis',
38,
true,
true,
'system'
),
(
'Association des Femmes Entrepreneures CI',
'AFECI',
'ASSOCIATION',
'ACTIVE',
'Association pour la promotion de l''entrepreneuriat féminin en Côte d''Ivoire',
'contact@afeci.org',
'+225 05 06 07 08 09',
'Marcory Zone 4C',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Promouvoir l''entrepreneuriat féminin et l''autonomisation des femmes',
'Formation, accompagnement, financement de projets féminins',
120,
true,
true,
'system'
),
(
'Coopérative Agricole du Nord',
'COOP-NORD',
'COOPERATIVE',
'ACTIVE',
'Coopérative des producteurs agricoles du Nord de la Côte d''Ivoire',
'info@coop-nord.ci',
'+225 09 10 11 12 13',
'Korhogo Centre',
'Korhogo',
'Savanes',
'Côte d''Ivoire',
'Améliorer les conditions de vie des producteurs agricoles',
'Production, transformation et commercialisation de produits agricoles',
250,
true,
true,
'system'
);
-- Mise à jour des statistiques de la base de données -- Mise à jour des statistiques de la base de données
ANALYZE organisations; ANALYZE organisations;

View File

@@ -1,128 +1,10 @@
-- Script d'insertion de données initiales pour UnionFlow (avec UUID) -- Script d'insertion de données initiales pour UnionFlow
-- Ce fichier est exécuté automatiquement par Hibernate au démarrage -- Ce fichier est exécuté automatiquement par Hibernate au démarrage
-- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create) -- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create)
-- NOTE: Les IDs sont maintenant des UUID générés automatiquement --
-- IMPORTANT: Ce fichier ne doit PAS contenir de données fictives pour la production.
-- Insertion d'organisations initiales avec UUIDs générés -- Les données doivent être insérées manuellement via l'interface d'administration
INSERT INTO organisations (id, nom, nom_court, type_organisation, statut, description, -- ou via des scripts de migration Flyway si nécessaire.
email, telephone, adresse, ville, region, pays, --
objectifs, activites_principales, nombre_membres, -- Ce fichier est laissé vide intentionnellement pour éviter l'insertion automatique
organisation_publique, accepte_nouveaux_membres, cree_par, -- de données fictives lors du démarrage du serveur.
actif, date_creation, niveau_hierarchique, nombre_administrateurs, cotisation_obligatoire) VALUES
(
gen_random_uuid(),
'Lions Club Abidjan Plateau',
'LC Plateau',
'LIONS_CLUB',
'ACTIVE',
'Lions Club du district 403 A1, zone Plateau d''Abidjan',
'plateau@lionsclub-ci.org',
'+225 27 20 21 22 23',
'Immeuble SCIAM, Boulevard de la République',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Servir la communauté par des actions humanitaires et sociales',
'Actions de santé, éducation, environnement et aide aux démunis',
45,
true,
true,
'system',
true,
CURRENT_TIMESTAMP,
0,
0,
false
),
(
gen_random_uuid(),
'Lions Club Abidjan Cocody',
'LC Cocody',
'LIONS_CLUB',
'ACTIVE',
'Lions Club du district 403 A1, zone Cocody',
'cocody@lionsclub-ci.org',
'+225 27 22 44 55 66',
'Riviera Golf, Cocody',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Servir la communauté par des actions humanitaires et sociales',
'Actions de santé, éducation, environnement et aide aux démunis',
38,
true,
true,
'system',
true,
CURRENT_TIMESTAMP,
0,
0,
false
),
(
gen_random_uuid(),
'Association des Femmes Entrepreneures CI',
'AFECI',
'ASSOCIATION',
'ACTIVE',
'Association pour la promotion de l''entrepreneuriat féminin en Côte d''Ivoire',
'contact@afeci.org',
'+225 05 06 07 08 09',
'Marcory Zone 4C',
'Abidjan',
'Lagunes',
'Côte d''Ivoire',
'Promouvoir l''entrepreneuriat féminin et l''autonomisation des femmes',
'Formation, accompagnement, financement de projets féminins',
120,
true,
true,
'system',
true,
CURRENT_TIMESTAMP,
0,
0,
false
),
(
gen_random_uuid(),
'Coopérative Agricole du Nord',
'COOP-NORD',
'COOPERATIVE',
'ACTIVE',
'Coopérative des producteurs agricoles du Nord de la Côte d''Ivoire',
'info@coop-nord.ci',
'+225 09 10 11 12 13',
'Korhogo Centre',
'Korhogo',
'Savanes',
'Côte d''Ivoire',
'Améliorer les conditions de vie des producteurs agricoles',
'Production, transformation et commercialisation de produits agricoles',
250,
true,
true,
'system',
true,
CURRENT_TIMESTAMP,
0,
0,
false
);
-- Insertion de membres initiaux (avec UUIDs générés et références aux organisations)
-- On utilise des sous-requêtes pour récupérer les IDs des organisations par leur nom
INSERT INTO membres (id, numero_membre, nom, prenom, email, telephone, date_naissance, date_adhesion, actif, date_creation, organisation_id) VALUES
(gen_random_uuid(), 'MBR001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225071234567', '1985-03-15', '2023-01-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)),
(gen_random_uuid(), 'MBR002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225059876543', '1990-07-22', '2023-02-10', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)),
(gen_random_uuid(), 'MBR003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225012345678', '1988-11-08', '2023-03-05', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)),
(gen_random_uuid(), 'MBR004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225078765432', '1992-05-18', '2023-04-12', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)),
(gen_random_uuid(), 'MBR005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225051122334', '1987-09-30', '2023-05-20', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)),
(gen_random_uuid(), 'MBR006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225015566778', '1991-12-03', '2023-06-08', false, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)),
(gen_random_uuid(), 'MBR007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225079988776', '1989-04-25', '2023-07-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1)),
(gen_random_uuid(), 'MBR008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225054433221', '1993-08-14', '2023-08-22', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1));
-- Note: Les insertions de cotisations et événements avec des références aux membres
-- nécessitent de récupérer les UUIDs réels des membres insérés ci-dessus.
-- Pour le développement, ces données peuvent être insérées via l'application ou
-- via des scripts de test plus complexes qui récupèrent les UUIDs générés.

View File

@@ -45,6 +45,7 @@ class MembreServiceAdvancedSearchTest {
.nom("Organisation Test") .nom("Organisation Test")
.typeOrganisation("ASSOCIATION") .typeOrganisation("ASSOCIATION")
.statut("ACTIF") .statut("ACTIF")
.email("test@organisation.com")
.build(); .build();
testOrganisation.setDateCreation(LocalDateTime.now()); testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true); testOrganisation.setActif(true);