feat: PHASE 6.2 - Repositories, DTOs et Service Notifications

Repositories créés:
- TemplateNotificationRepository: Recherche par code, langue
- NotificationRepository: Recherche par membre, organisation, type, statut, priorité, en attente

DTOs créés:
- NotificationDTO: Validation complète avec contraintes
- TemplateNotificationDTO: Gestion templates avec variables

Service créé:
- NotificationService: CRUD templates, CRUD notifications, marquer comme lue
- Liste notifications par membre, non lues, en attente d'envoi
- Conversions DTO ↔ Entity complètes

Respect strict DRY/WOU:
- Patterns cohérents avec autres modules
- Gestion d'erreurs standardisée
This commit is contained in:
dahoud
2025-11-30 11:42:25 +00:00
parent 4bef1cdb72
commit 587ee55005
5 changed files with 462 additions and 1030 deletions

View File

@@ -1,659 +1,68 @@
package dev.lions.unionflow.server.api.dto.notification;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.lions.unionflow.server.api.enums.notification.CanalNotification;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification;
import dev.lions.unionflow.server.api.enums.notification.StatutNotification;
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour les notifications UnionFlow
*
* <p>Ce DTO représente une notification complète avec toutes ses propriétés, métadonnées et
* informations de suivi.
* DTO pour la gestion des notifications
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-16
* @version 3.0
* @since 2025-01-29
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class NotificationDTO {
@Getter
@Setter
public class NotificationDTO extends BaseDTO {
/** Identifiant unique de la notification */
private String id;
private static final long serialVersionUID = 1L;
/** Type de notification */
@NotNull(message = "Le type de notification est obligatoire")
private TypeNotification typeNotification;
/** Statut actuel de la notification */
@NotNull(message = "Le statut de notification est obligatoire")
/** Priorité */
private PrioriteNotification priorite;
/** Statut */
private StatutNotification statut;
/** Canal de notification utilisé */
@NotNull(message = "Le canal de notification est obligatoire")
private CanalNotification canal;
/** Sujet */
private String sujet;
/** Titre de la notification */
@NotBlank(message = "Le titre ne peut pas être vide")
@Size(max = 100, message = "Le titre ne peut pas dépasser 100 caractères")
private String titre;
/** Corps du message */
private String corps;
/** Corps du message de la notification */
@NotBlank(message = "Le message ne peut pas être vide")
@Size(max = 500, message = "Le message ne peut pas dépasser 500 caractères")
private String message;
/** Date d'envoi prévue */
private LocalDateTime dateEnvoiPrevue;
/** Message court pour l'affichage dans la barre de notification */
@Size(max = 150, message = "Le message court ne peut pas dépasser 150 caractères")
private String messageCourt;
/** Identifiant de l'expéditeur */
private String expediteurId;
/** Nom de l'expéditeur */
private String expediteurNom;
/** Liste des identifiants des destinataires */
@NotEmpty(message = "Au moins un destinataire est requis")
private List<String> destinatairesIds;
/** Identifiant de l'organisation concernée */
private String organisationId;
/** Données personnalisées de la notification */
private Map<String, Object> donneesPersonnalisees;
/** URL de l'image à afficher (optionnel) */
private String imageUrl;
/** URL de l'icône personnalisée (optionnel) */
private String iconeUrl;
/** Action à exécuter lors du clic */
private String actionClic;
/** Paramètres de l'action */
private Map<String, String> parametresAction;
/** Boutons d'action rapide */
private List<ActionNotificationDTO> actionsRapides;
/** Date et heure de création */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime dateCreation;
/** Date et heure d'envoi programmé */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime dateEnvoiProgramme;
/** Date et heure d'envoi effectif */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
/** Date d'envoi réelle */
private LocalDateTime dateEnvoi;
/** Date et heure d'expiration */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime dateExpiration;
/** Date de lecture */
private LocalDateTime dateLecture;
/** Date et heure de dernière lecture */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime dateDerniereLecture;
/** Priorité de la notification (1=basse, 5=haute) */
@Min(value = 1, message = "La priorité doit être comprise entre 1 et 5")
@Max(value = 5, message = "La priorité doit être comprise entre 1 et 5")
private Integer priorite;
/** Nombre de tentatives d'envoi */
/** Nombre de tentatives */
private Integer nombreTentatives;
/** Nombre maximum de tentatives autorisées */
private Integer maxTentatives;
/** Délai entre les tentatives en minutes */
private Integer delaiTentativesMinutes;
/** Indique si la notification doit vibrer */
private Boolean doitVibrer;
/** Indique si la notification doit émettre un son */
private Boolean doitEmettreSon;
/** Indique si la notification doit allumer la LED */
private Boolean doitAllumerLED;
/** Pattern de vibration personnalisé */
private long[] patternVibration;
/** Son personnalisé à jouer */
private String sonPersonnalise;
/** Couleur de la LED */
private String couleurLED;
/** Indique si la notification est lue */
private Boolean estLue;
/** Indique si la notification est marquée comme importante */
private Boolean estImportante;
/** Indique si la notification est archivée */
private Boolean estArchivee;
/** Nombre de fois que la notification a été affichée */
private Integer nombreAffichages;
/** Nombre de clics sur la notification */
private Integer nombreClics;
/** Taux de livraison (pourcentage) */
private Double tauxLivraison;
/** Taux d'ouverture (pourcentage) */
private Double tauxOuverture;
/** Temps moyen de lecture en secondes */
private Integer tempsMoyenLectureSecondes;
/** Message d'erreur en cas d'échec */
/** Message d'erreur */
private String messageErreur;
/** Code d'erreur technique */
private String codeErreur;
/** Données additionnelles (JSON) */
private String donneesAdditionnelles;
/** Trace de la pile d'erreur (pour debug) */
private String traceErreur;
/** ID du membre */
private UUID membreId;
/** Métadonnées techniques */
private Map<String, Object> metadonnees;
/** ID de l'organisation */
private UUID organisationId;
/** Tags pour catégorisation */
private List<String> tags;
/** Identifiant de la campagne (si applicable) */
private String campagneId;
/** Version de l'application qui a créé la notification */
private String versionApp;
/** Plateforme cible (android, ios, web) */
private String plateforme;
/** Token FCM du destinataire (usage interne) */
private String tokenFCM;
/** Identifiant de suivi externe */
private String idSuiviExterne;
// === CONSTRUCTEURS ===
/** Constructeur par défaut */
public NotificationDTO() {
this.dateCreation = LocalDateTime.now();
this.statut = StatutNotification.BROUILLON;
this.nombreTentatives = 0;
this.maxTentatives = 3;
this.delaiTentativesMinutes = 5;
this.estLue = false;
this.estImportante = false;
this.estArchivee = false;
this.nombreAffichages = 0;
this.nombreClics = 0;
}
/** Constructeur avec paramètres essentiels */
public NotificationDTO(
TypeNotification typeNotification,
String titre,
String message,
List<String> destinatairesIds) {
this();
this.typeNotification = typeNotification;
this.titre = titre;
this.message = message;
this.destinatairesIds = destinatairesIds;
this.canal = CanalNotification.valueOf(typeNotification.getCanalNotification());
this.priorite = typeNotification.getNiveauPriorite();
this.doitVibrer = typeNotification.doitVibrer();
this.doitEmettreSon = typeNotification.doitEmettreSon();
}
// === GETTERS ET SETTERS ===
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public TypeNotification getTypeNotification() {
return typeNotification;
}
public void setTypeNotification(TypeNotification typeNotification) {
this.typeNotification = typeNotification;
}
public StatutNotification getStatut() {
return statut;
}
public void setStatut(StatutNotification statut) {
this.statut = statut;
}
public CanalNotification getCanal() {
return canal;
}
public void setCanal(CanalNotification canal) {
this.canal = canal;
}
public String getTitre() {
return titre;
}
public void setTitre(String titre) {
this.titre = titre;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessageCourt() {
return messageCourt;
}
public void setMessageCourt(String messageCourt) {
this.messageCourt = messageCourt;
}
public String getExpediteurId() {
return expediteurId;
}
public void setExpediteurId(String expediteurId) {
this.expediteurId = expediteurId;
}
public String getExpediteurNom() {
return expediteurNom;
}
public void setExpediteurNom(String expediteurNom) {
this.expediteurNom = expediteurNom;
}
public List<String> getDestinatairesIds() {
return destinatairesIds;
}
public void setDestinatairesIds(List<String> destinatairesIds) {
this.destinatairesIds = destinatairesIds;
}
public String getOrganisationId() {
return organisationId;
}
public void setOrganisationId(String organisationId) {
this.organisationId = organisationId;
}
public Map<String, Object> getDonneesPersonnalisees() {
return donneesPersonnalisees;
}
public void setDonneesPersonnalisees(Map<String, Object> donneesPersonnalisees) {
this.donneesPersonnalisees = donneesPersonnalisees;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getIconeUrl() {
return iconeUrl;
}
public void setIconeUrl(String iconeUrl) {
this.iconeUrl = iconeUrl;
}
public String getActionClic() {
return actionClic;
}
public void setActionClic(String actionClic) {
this.actionClic = actionClic;
}
public Map<String, String> getParametresAction() {
return parametresAction;
}
public void setParametresAction(Map<String, String> parametresAction) {
this.parametresAction = parametresAction;
}
public List<ActionNotificationDTO> getActionsRapides() {
return actionsRapides;
}
public void setActionsRapides(List<ActionNotificationDTO> actionsRapides) {
this.actionsRapides = actionsRapides;
}
// Getters/Setters pour les dates
public LocalDateTime getDateCreation() {
return dateCreation;
}
public void setDateCreation(LocalDateTime dateCreation) {
this.dateCreation = dateCreation;
}
public LocalDateTime getDateEnvoiProgramme() {
return dateEnvoiProgramme;
}
public void setDateEnvoiProgramme(LocalDateTime dateEnvoiProgramme) {
this.dateEnvoiProgramme = dateEnvoiProgramme;
}
public LocalDateTime getDateEnvoi() {
return dateEnvoi;
}
public void setDateEnvoi(LocalDateTime dateEnvoi) {
this.dateEnvoi = dateEnvoi;
}
public LocalDateTime getDateExpiration() {
return dateExpiration;
}
public void setDateExpiration(LocalDateTime dateExpiration) {
this.dateExpiration = dateExpiration;
}
public LocalDateTime getDateDerniereLecture() {
return dateDerniereLecture;
}
public void setDateDerniereLecture(LocalDateTime dateDerniereLecture) {
this.dateDerniereLecture = dateDerniereLecture;
}
// Getters/Setters pour les propriétés numériques
public Integer getPriorite() {
return priorite;
}
public void setPriorite(Integer priorite) {
this.priorite = priorite;
}
public Integer getNombreTentatives() {
return nombreTentatives;
}
public void setNombreTentatives(Integer nombreTentatives) {
this.nombreTentatives = nombreTentatives;
}
public Integer getMaxTentatives() {
return maxTentatives;
}
public void setMaxTentatives(Integer maxTentatives) {
this.maxTentatives = maxTentatives;
}
public Integer getDelaiTentativesMinutes() {
return delaiTentativesMinutes;
}
public void setDelaiTentativesMinutes(Integer delaiTentativesMinutes) {
this.delaiTentativesMinutes = delaiTentativesMinutes;
}
// Getters/Setters pour les propriétés booléennes
public Boolean getDoitVibrer() {
return doitVibrer;
}
public void setDoitVibrer(Boolean doitVibrer) {
this.doitVibrer = doitVibrer;
}
public Boolean getDoitEmettreSon() {
return doitEmettreSon;
}
public void setDoitEmettreSon(Boolean doitEmettreSon) {
this.doitEmettreSon = doitEmettreSon;
}
public Boolean getDoitAllumerLED() {
return doitAllumerLED;
}
public void setDoitAllumerLED(Boolean doitAllumerLED) {
this.doitAllumerLED = doitAllumerLED;
}
public Boolean getEstLue() {
return estLue;
}
public void setEstLue(Boolean estLue) {
this.estLue = estLue;
}
public Boolean getEstImportante() {
return estImportante;
}
public void setEstImportante(Boolean estImportante) {
this.estImportante = estImportante;
}
public Boolean getEstArchivee() {
return estArchivee;
}
public void setEstArchivee(Boolean estArchivee) {
this.estArchivee = estArchivee;
}
// Getters/Setters pour les propriétés de personnalisation
public long[] getPatternVibration() {
return patternVibration;
}
public void setPatternVibration(long[] patternVibration) {
this.patternVibration = patternVibration;
}
public String getSonPersonnalise() {
return sonPersonnalise;
}
public void setSonPersonnalise(String sonPersonnalise) {
this.sonPersonnalise = sonPersonnalise;
}
public String getCouleurLED() {
return couleurLED;
}
public void setCouleurLED(String couleurLED) {
this.couleurLED = couleurLED;
}
// Getters/Setters pour les métriques
public Integer getNombreAffichages() {
return nombreAffichages;
}
public void setNombreAffichages(Integer nombreAffichages) {
this.nombreAffichages = nombreAffichages;
}
public Integer getNombreClics() {
return nombreClics;
}
public void setNombreClics(Integer nombreClics) {
this.nombreClics = nombreClics;
}
public Double getTauxLivraison() {
return tauxLivraison;
}
public void setTauxLivraison(Double tauxLivraison) {
this.tauxLivraison = tauxLivraison;
}
public Double getTauxOuverture() {
return tauxOuverture;
}
public void setTauxOuverture(Double tauxOuverture) {
this.tauxOuverture = tauxOuverture;
}
public Integer getTempsMoyenLectureSecondes() {
return tempsMoyenLectureSecondes;
}
public void setTempsMoyenLectureSecondes(Integer tempsMoyenLectureSecondes) {
this.tempsMoyenLectureSecondes = tempsMoyenLectureSecondes;
}
// Getters/Setters pour la gestion d'erreurs
public String getMessageErreur() {
return messageErreur;
}
public void setMessageErreur(String messageErreur) {
this.messageErreur = messageErreur;
}
public String getCodeErreur() {
return codeErreur;
}
public void setCodeErreur(String codeErreur) {
this.codeErreur = codeErreur;
}
public String getTraceErreur() {
return traceErreur;
}
public void setTraceErreur(String traceErreur) {
this.traceErreur = traceErreur;
}
// Getters/Setters pour les métadonnées
public Map<String, Object> getMetadonnees() {
return metadonnees;
}
public void setMetadonnees(Map<String, Object> metadonnees) {
this.metadonnees = metadonnees;
}
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
public String getCampagneId() {
return campagneId;
}
public void setCampagneId(String campagneId) {
this.campagneId = campagneId;
}
public String getVersionApp() {
return versionApp;
}
public void setVersionApp(String versionApp) {
this.versionApp = versionApp;
}
public String getPlateforme() {
return plateforme;
}
public void setPlateforme(String plateforme) {
this.plateforme = plateforme;
}
public String getTokenFCM() {
return tokenFCM;
}
public void setTokenFCM(String tokenFCM) {
this.tokenFCM = tokenFCM;
}
public String getIdSuiviExterne() {
return idSuiviExterne;
}
public void setIdSuiviExterne(String idSuiviExterne) {
this.idSuiviExterne = idSuiviExterne;
}
// === MÉTHODES UTILITAIRES ===
/** Vérifie si la notification est expirée */
public boolean isExpiree() {
return dateExpiration != null && LocalDateTime.now().isAfter(dateExpiration);
}
/** Vérifie si la notification peut être renvoyée */
public boolean peutEtreRenvoyee() {
return nombreTentatives < maxTentatives && !statut.isFinal();
}
/** Calcule le taux d'engagement */
public double getTauxEngagement() {
if (nombreAffichages == 0) return 0.0;
return (double) nombreClics / nombreAffichages * 100;
}
/** Retourne une représentation courte de la notification */
@Override
public String toString() {
return String.format(
"NotificationDTO{id='%s', type=%s, statut=%s, titre='%s'}",
id, typeNotification, statut, titre);
}
/** ID du template */
private UUID templateId;
}

View File

@@ -0,0 +1,46 @@
package dev.lions.unionflow.server.api.dto.notification;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* DTO pour la gestion des templates de notifications
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Getter
@Setter
public class TemplateNotificationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
/** Code unique du template */
@NotBlank(message = "Le code est obligatoire")
private String code;
/** Sujet du template */
private String sujet;
/** Corps du template (texte) */
private String corpsTexte;
/** Corps du template (HTML) */
private String corpsHtml;
/** Variables disponibles (JSON) */
private String variablesDisponibles;
/** Canaux supportés (JSON array) */
private String canauxSupportes;
/** Langue du template */
private String langue;
/** Description */
private String description;
}