Alignement design systeme OK
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user