Alignement design systeme OK

This commit is contained in:
DahoudG
2025-09-20 03:56:11 +00:00
parent a1214bc116
commit 96a17eadbd
34 changed files with 11720 additions and 766 deletions

View File

@@ -0,0 +1,229 @@
package dev.lions.unionflow.server.api.dto.membre;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
/**
* DTO pour les critères de recherche avancée des membres
* Permet de filtrer les membres selon de multiples critères
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Critères de recherche avancée pour les membres")
public class MembreSearchCriteria {
/** Terme de recherche général (nom, prénom, email) */
@Schema(description = "Terme de recherche général dans nom, prénom ou email", example = "marie")
@Size(max = 100, message = "Le terme de recherche ne peut pas dépasser 100 caractères")
private String query;
/** Recherche par nom exact ou partiel */
@Schema(description = "Filtre par nom (recherche partielle)", example = "Dupont")
@Size(max = 50, message = "Le nom ne peut pas dépasser 50 caractères")
private String nom;
/** Recherche par prénom exact ou partiel */
@Schema(description = "Filtre par prénom (recherche partielle)", example = "Marie")
@Size(max = 50, message = "Le prénom ne peut pas dépasser 50 caractères")
private String prenom;
/** Recherche par email exact ou partiel */
@Schema(description = "Filtre par email (recherche partielle)", example = "@unionflow.com")
@Size(max = 100, message = "L'email ne peut pas dépasser 100 caractères")
private String email;
/** Filtre par numéro de téléphone */
@Schema(description = "Filtre par numéro de téléphone", example = "+221")
@Size(max = 20, message = "Le téléphone ne peut pas dépasser 20 caractères")
private String telephone;
/** Liste des IDs d'organisations */
@Schema(description = "Liste des IDs d'organisations à inclure")
private List<UUID> organisationIds;
/** Liste des rôles à rechercher */
@Schema(description = "Liste des rôles à rechercher", example = "[\"PRESIDENT\", \"SECRETAIRE\"]")
private List<String> roles;
/** Filtre par statut d'activité */
@Schema(description = "Filtre par statut d'activité", example = "ACTIF")
@Pattern(regexp = "^(ACTIF|INACTIF|SUSPENDU|RADIE)$", message = "Statut invalide")
private String statut;
/** Date d'adhésion minimum */
@Schema(description = "Date d'adhésion minimum", example = "2020-01-01")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateAdhesionMin;
/** Date d'adhésion maximum */
@Schema(description = "Date d'adhésion maximum", example = "2025-12-31")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate dateAdhesionMax;
/** Âge minimum */
@Schema(description = "Âge minimum", example = "18")
@Min(value = 0, message = "L'âge minimum doit être positif")
@Max(value = 120, message = "L'âge minimum ne peut pas dépasser 120 ans")
private Integer ageMin;
/** Âge maximum */
@Schema(description = "Âge maximum", example = "65")
@Min(value = 0, message = "L'âge maximum doit être positif")
@Max(value = 120, message = "L'âge maximum ne peut pas dépasser 120 ans")
private Integer ageMax;
/** Filtre par région */
@Schema(description = "Filtre par région", example = "Dakar")
@Size(max = 50, message = "La région ne peut pas dépasser 50 caractères")
private String region;
/** Filtre par ville */
@Schema(description = "Filtre par ville", example = "Dakar")
@Size(max = 50, message = "La ville ne peut pas dépasser 50 caractères")
private String ville;
/** Filtre par profession */
@Schema(description = "Filtre par profession", example = "Ingénieur")
@Size(max = 100, message = "La profession ne peut pas dépasser 100 caractères")
private String profession;
/** Filtre par nationalité */
@Schema(description = "Filtre par nationalité", example = "Sénégalaise")
@Size(max = 50, message = "La nationalité ne peut pas dépasser 50 caractères")
private String nationalite;
/** Filtre membres du bureau uniquement */
@Schema(description = "Filtre pour les membres du bureau uniquement")
private Boolean membreBureau;
/** Filtre responsables uniquement */
@Schema(description = "Filtre pour les responsables uniquement")
private Boolean responsable;
/** Inclure les membres inactifs dans la recherche */
@Schema(description = "Inclure les membres inactifs", defaultValue = "false")
@Builder.Default
private Boolean includeInactifs = false;
/**
* Vérifie si au moins un critère de recherche est défini
*
* @return true si au moins un critère est défini
*/
public boolean hasAnyCriteria() {
return query != null && !query.trim().isEmpty() ||
nom != null && !nom.trim().isEmpty() ||
prenom != null && !prenom.trim().isEmpty() ||
email != null && !email.trim().isEmpty() ||
telephone != null && !telephone.trim().isEmpty() ||
organisationIds != null && !organisationIds.isEmpty() ||
roles != null && !roles.isEmpty() ||
statut != null && !statut.trim().isEmpty() ||
dateAdhesionMin != null ||
dateAdhesionMax != null ||
ageMin != null ||
ageMax != null ||
region != null && !region.trim().isEmpty() ||
ville != null && !ville.trim().isEmpty() ||
profession != null && !profession.trim().isEmpty() ||
nationalite != null && !nationalite.trim().isEmpty() ||
membreBureau != null ||
responsable != null;
}
/**
* Valide la cohérence des critères de recherche
*
* @return true si les critères sont cohérents
*/
public boolean isValid() {
// Validation des dates
if (dateAdhesionMin != null && dateAdhesionMax != null) {
if (dateAdhesionMin.isAfter(dateAdhesionMax)) {
return false;
}
}
// Validation des âges
if (ageMin != null && ageMax != null) {
if (ageMin > ageMax) {
return false;
}
}
return true;
}
/**
* Nettoie les chaînes de caractères (trim et null si vide)
*/
public void sanitize() {
query = sanitizeString(query);
nom = sanitizeString(nom);
prenom = sanitizeString(prenom);
email = sanitizeString(email);
telephone = sanitizeString(telephone);
statut = sanitizeString(statut);
region = sanitizeString(region);
ville = sanitizeString(ville);
profession = sanitizeString(profession);
nationalite = sanitizeString(nationalite);
}
private String sanitizeString(String str) {
if (str == null) return null;
str = str.trim();
return str.isEmpty() ? null : str;
}
/**
* Retourne une description textuelle des critères actifs
*
* @return Description des critères
*/
public String getDescription() {
StringBuilder sb = new StringBuilder();
if (query != null) sb.append("Recherche: '").append(query).append("' ");
if (nom != null) sb.append("Nom: '").append(nom).append("' ");
if (prenom != null) sb.append("Prénom: '").append(prenom).append("' ");
if (email != null) sb.append("Email: '").append(email).append("' ");
if (statut != null) sb.append("Statut: ").append(statut).append(" ");
if (organisationIds != null && !organisationIds.isEmpty()) {
sb.append("Organisations: ").append(organisationIds.size()).append(" ");
}
if (roles != null && !roles.isEmpty()) {
sb.append("Rôles: ").append(String.join(", ", roles)).append(" ");
}
if (dateAdhesionMin != null) sb.append("Adhésion >= ").append(dateAdhesionMin).append(" ");
if (dateAdhesionMax != null) sb.append("Adhésion <= ").append(dateAdhesionMax).append(" ");
if (ageMin != null) sb.append("Âge >= ").append(ageMin).append(" ");
if (ageMax != null) sb.append("Âge <= ").append(ageMax).append(" ");
if (region != null) sb.append("Région: '").append(region).append("' ");
if (ville != null) sb.append("Ville: '").append(ville).append("' ");
if (profession != null) sb.append("Profession: '").append(profession).append("' ");
if (nationalite != null) sb.append("Nationalité: '").append(nationalite).append("' ");
if (Boolean.TRUE.equals(membreBureau)) sb.append("Membre bureau ");
if (Boolean.TRUE.equals(responsable)) sb.append("Responsable ");
return sb.toString().trim();
}
}

View File

@@ -0,0 +1,204 @@
package dev.lions.unionflow.server.api.dto.membre;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.List;
/**
* DTO pour les résultats de recherche avancée des membres
* Contient les résultats paginés et les métadonnées de recherche
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Résultats de recherche avancée des membres avec pagination")
public class MembreSearchResultDTO {
/** Liste des membres trouvés */
@Schema(description = "Liste des membres correspondant aux critères")
private List<MembreDTO> membres;
/** Nombre total de résultats (toutes pages confondues) */
@Schema(description = "Nombre total de résultats trouvés", example = "247")
private long totalElements;
/** Nombre total de pages */
@Schema(description = "Nombre total de pages", example = "13")
private int totalPages;
/** Numéro de la page actuelle (0-based) */
@Schema(description = "Numéro de la page actuelle", example = "0")
private int currentPage;
/** Taille de la page */
@Schema(description = "Nombre d'éléments par page", example = "20")
private int pageSize;
/** Nombre d'éléments sur la page actuelle */
@Schema(description = "Nombre d'éléments sur cette page", example = "20")
private int numberOfElements;
/** Indique s'il y a une page suivante */
@Schema(description = "Indique s'il y a une page suivante")
private boolean hasNext;
/** Indique s'il y a une page précédente */
@Schema(description = "Indique s'il y a une page précédente")
private boolean hasPrevious;
/** Indique si c'est la première page */
@Schema(description = "Indique si c'est la première page")
private boolean isFirst;
/** Indique si c'est la dernière page */
@Schema(description = "Indique si c'est la dernière page")
private boolean isLast;
/** Critères de recherche utilisés */
@Schema(description = "Critères de recherche qui ont été appliqués")
private MembreSearchCriteria criteria;
/** Temps d'exécution de la recherche en millisecondes */
@Schema(description = "Temps d'exécution de la recherche en ms", example = "45")
private long executionTimeMs;
/** Statistiques de recherche */
@Schema(description = "Statistiques sur les résultats de recherche")
private SearchStatistics statistics;
/**
* Statistiques sur les résultats de recherche
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "Statistiques sur les résultats de recherche")
public static class SearchStatistics {
/** Répartition par statut */
@Schema(description = "Nombre de membres actifs dans les résultats")
private long membresActifs;
@Schema(description = "Nombre de membres inactifs dans les résultats")
private long membresInactifs;
/** Répartition par âge */
@Schema(description = "Âge moyen des membres trouvés")
private double ageMoyen;
@Schema(description = "Âge minimum des membres trouvés")
private int ageMin;
@Schema(description = "Âge maximum des membres trouvés")
private int ageMax;
/** Répartition par organisation */
@Schema(description = "Nombre d'organisations représentées")
private long nombreOrganisations;
/** Répartition par région */
@Schema(description = "Nombre de régions représentées")
private long nombreRegions;
/** Ancienneté moyenne */
@Schema(description = "Ancienneté moyenne en années")
private double ancienneteMoyenne;
}
/**
* Calcule et met à jour les indicateurs de pagination
*/
public void calculatePaginationFlags() {
this.isFirst = currentPage == 0;
this.isLast = currentPage >= totalPages - 1;
this.hasPrevious = currentPage > 0;
this.hasNext = currentPage < totalPages - 1;
this.numberOfElements = membres != null ? membres.size() : 0;
}
/**
* Vérifie si les résultats sont vides
*
* @return true si aucun résultat
*/
public boolean isEmpty() {
return membres == null || membres.isEmpty();
}
/**
* Retourne le numéro de la page suivante (1-based pour affichage)
*
* @return Numéro de page suivante ou -1 si pas de page suivante
*/
public int getNextPageNumber() {
return hasNext ? currentPage + 2 : -1;
}
/**
* Retourne le numéro de la page précédente (1-based pour affichage)
*
* @return Numéro de page précédente ou -1 si pas de page précédente
*/
public int getPreviousPageNumber() {
return hasPrevious ? currentPage : -1;
}
/**
* Retourne une description textuelle des résultats
*
* @return Description des résultats
*/
public String getResultDescription() {
if (isEmpty()) {
return "Aucun membre trouvé";
}
if (totalElements == 1) {
return "1 membre trouvé";
}
if (totalPages == 1) {
return String.format("%d membres trouvés", totalElements);
}
int startElement = currentPage * pageSize + 1;
int endElement = Math.min(startElement + numberOfElements - 1, (int) totalElements);
return String.format("Membres %d-%d sur %d (page %d/%d)",
startElement, endElement, totalElements,
currentPage + 1, totalPages);
}
/**
* Factory method pour créer un résultat vide
*
* @param criteria Critères de recherche
* @return Résultat vide
*/
public static MembreSearchResultDTO empty(MembreSearchCriteria criteria) {
return MembreSearchResultDTO.builder()
.membres(List.of())
.totalElements(0)
.totalPages(0)
.currentPage(0)
.pageSize(20)
.numberOfElements(0)
.hasNext(false)
.hasPrevious(false)
.isFirst(true)
.isLast(true)
.criteria(criteria)
.executionTimeMs(0)
.build();
}
}