feat: PHASE 5 et 6.1 - DocumentResource et Entités Notifications
PHASE 5 - COMPLÉTÉE: ✅ Resource REST: DocumentResource avec endpoints complets PHASE 6.1 - Entités Notifications: ✅ TemplateNotification: Templates réutilisables avec variables ✅ Notification: Notifications avec types, priorités, statuts ✅ Enums: TypeNotification, PrioriteNotification (module API) ✅ Relations: Membre, Organisation, TemplateNotification Fonctionnalités: - Templates avec variables JSON - Support multi-canaux (EMAIL, SMS, PUSH, IN_APP, SYSTEME) - Priorités: CRITIQUE, HAUTE, NORMALE, BASSE - Statuts: EN_ATTENTE, ENVOYEE, LUE, ECHOUE, ANNULEE - Gestion tentatives d'envoi - Dates envoi prévue/réelle/lecture Respect strict DRY/WOU: - Patterns cohérents avec autres modules - Enums dans module API réutilisables
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.lions.unionflow.server.api.enums.notification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Énumération des priorités de notifications
|
||||||
|
*
|
||||||
|
* @author UnionFlow Team
|
||||||
|
* @version 3.0
|
||||||
|
* @since 2025-01-29
|
||||||
|
*/
|
||||||
|
public enum PrioriteNotification {
|
||||||
|
CRITIQUE("Critique"),
|
||||||
|
HAUTE("Haute"),
|
||||||
|
NORMALE("Normale"),
|
||||||
|
BASSE("Basse");
|
||||||
|
|
||||||
|
private final String libelle;
|
||||||
|
|
||||||
|
PrioriteNotification(String libelle) {
|
||||||
|
this.libelle = libelle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLibelle() {
|
||||||
|
return libelle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,290 +1,26 @@
|
|||||||
package dev.lions.unionflow.server.api.enums.notification;
|
package dev.lions.unionflow.server.api.enums.notification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Énumération des types de notifications disponibles dans UnionFlow
|
* Énumération des types de notifications
|
||||||
*
|
|
||||||
* <p>Cette énumération définit les différents types de notifications qui peuvent être envoyées aux
|
|
||||||
* utilisateurs de l'application UnionFlow.
|
|
||||||
*
|
*
|
||||||
* @author UnionFlow Team
|
* @author UnionFlow Team
|
||||||
* @version 1.0
|
* @version 3.0
|
||||||
* @since 2025-01-16
|
* @since 2025-01-29
|
||||||
*/
|
*/
|
||||||
public enum TypeNotification {
|
public enum TypeNotification {
|
||||||
|
EMAIL("Email"),
|
||||||
// === NOTIFICATIONS ÉVÉNEMENTS ===
|
SMS("SMS"),
|
||||||
NOUVEL_EVENEMENT("Nouvel événement", "evenements", "info", "event", "#FF9800", true, true),
|
PUSH("Push Notification"),
|
||||||
RAPPEL_EVENEMENT(
|
IN_APP("Notification In-App"),
|
||||||
"Rappel d'événement", "evenements", "reminder", "schedule", "#2196F3", true, true),
|
SYSTEME("Notification Système");
|
||||||
EVENEMENT_ANNULE(
|
|
||||||
"Événement annulé", "evenements", "warning", "event_busy", "#F44336", true, true),
|
|
||||||
EVENEMENT_MODIFIE("Événement modifié", "evenements", "info", "edit", "#FF9800", true, false),
|
|
||||||
INSCRIPTION_CONFIRMEE(
|
|
||||||
"Inscription confirmée", "evenements", "success", "check_circle", "#4CAF50", true, false),
|
|
||||||
INSCRIPTION_REFUSEE(
|
|
||||||
"Inscription refusée", "evenements", "error", "cancel", "#F44336", true, false),
|
|
||||||
LISTE_ATTENTE(
|
|
||||||
"Mis en liste d'attente", "evenements", "info", "hourglass_empty", "#FF9800", true, false),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS COTISATIONS ===
|
|
||||||
COTISATION_DUE("Cotisation due", "cotisations", "reminder", "payment", "#FF5722", true, true),
|
|
||||||
COTISATION_PAYEE("Cotisation payée", "cotisations", "success", "paid", "#4CAF50", true, false),
|
|
||||||
COTISATION_RETARD(
|
|
||||||
"Cotisation en retard", "cotisations", "warning", "schedule", "#F44336", true, true),
|
|
||||||
RAPPEL_COTISATION(
|
|
||||||
"Rappel de cotisation", "cotisations", "reminder", "notifications", "#FF9800", true, true),
|
|
||||||
PAIEMENT_CONFIRME(
|
|
||||||
"Paiement confirmé", "cotisations", "success", "check_circle", "#4CAF50", true, false),
|
|
||||||
PAIEMENT_ECHOUE("Paiement échoué", "cotisations", "error", "error", "#F44336", true, true),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS SOLIDARITÉ ===
|
|
||||||
NOUVELLE_DEMANDE_AIDE(
|
|
||||||
"Nouvelle demande d'aide", "solidarite", "info", "help", "#E91E63", false, true),
|
|
||||||
DEMANDE_AIDE_APPROUVEE(
|
|
||||||
"Demande d'aide approuvée", "solidarite", "success", "thumb_up", "#4CAF50", true, false),
|
|
||||||
DEMANDE_AIDE_REFUSEE(
|
|
||||||
"Demande d'aide refusée", "solidarite", "error", "thumb_down", "#F44336", true, false),
|
|
||||||
AIDE_DISPONIBLE(
|
|
||||||
"Aide disponible", "solidarite", "info", "volunteer_activism", "#E91E63", true, false),
|
|
||||||
APPEL_SOLIDARITE(
|
|
||||||
"Appel à la solidarité", "solidarite", "urgent", "campaign", "#E91E63", true, true),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS MEMBRES ===
|
|
||||||
NOUVEAU_MEMBRE("Nouveau membre", "membres", "info", "person_add", "#2196F3", false, false),
|
|
||||||
ANNIVERSAIRE_MEMBRE(
|
|
||||||
"Anniversaire de membre", "membres", "celebration", "cake", "#FF9800", true, false),
|
|
||||||
MEMBRE_INACTIF("Membre inactif", "membres", "warning", "person_off", "#FF5722", false, false),
|
|
||||||
REACTIVATION_MEMBRE(
|
|
||||||
"Réactivation de membre", "membres", "success", "person", "#4CAF50", false, false),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS ORGANISATION ===
|
|
||||||
ANNONCE_GENERALE("Annonce générale", "organisation", "info", "campaign", "#2196F3", true, true),
|
|
||||||
REUNION_PROGRAMMEE("Réunion programmée", "organisation", "info", "groups", "#2196F3", true, true),
|
|
||||||
CHANGEMENT_REGLEMENT(
|
|
||||||
"Changement de règlement", "organisation", "important", "gavel", "#FF5722", true, true),
|
|
||||||
ELECTION_OUVERTE(
|
|
||||||
"Élection ouverte", "organisation", "info", "how_to_vote", "#2196F3", true, true),
|
|
||||||
RESULTAT_ELECTION("Résultat d'élection", "organisation", "info", "poll", "#4CAF50", true, false),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS SYSTÈME ===
|
|
||||||
MISE_A_JOUR_APP(
|
|
||||||
"Mise à jour disponible", "systeme", "info", "system_update", "#2196F3", true, false),
|
|
||||||
MAINTENANCE_PROGRAMMEE(
|
|
||||||
"Maintenance programmée", "systeme", "warning", "build", "#FF9800", true, true),
|
|
||||||
PROBLEME_TECHNIQUE("Problème technique", "systeme", "error", "error", "#F44336", true, true),
|
|
||||||
SAUVEGARDE_REUSSIE("Sauvegarde réussie", "systeme", "success", "backup", "#4CAF50", false, false),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS PERSONNALISÉES ===
|
|
||||||
MESSAGE_PRIVE("Message privé", "messages", "info", "mail", "#2196F3", true, false),
|
|
||||||
MENTION("Mention", "messages", "info", "alternate_email", "#FF9800", true, false),
|
|
||||||
COMMENTAIRE("Nouveau commentaire", "messages", "info", "comment", "#2196F3", true, false),
|
|
||||||
|
|
||||||
// === NOTIFICATIONS GÉOLOCALISÉES ===
|
|
||||||
EVENEMENT_PROXIMITE(
|
|
||||||
"Événement à proximité", "geolocalisation", "info", "location_on", "#4CAF50", true, false),
|
|
||||||
MEMBRE_PROXIMITE(
|
|
||||||
"Membre à proximité", "geolocalisation", "info", "people", "#2196F3", true, false),
|
|
||||||
URGENCE_LOCALE("Urgence locale", "geolocalisation", "urgent", "warning", "#F44336", true, true);
|
|
||||||
|
|
||||||
private final String libelle;
|
private final String libelle;
|
||||||
private final String categorie;
|
|
||||||
private final String priorite;
|
|
||||||
private final String icone;
|
|
||||||
private final String couleur;
|
|
||||||
private final boolean visibleUtilisateur;
|
|
||||||
private final boolean activeeParDefaut;
|
|
||||||
|
|
||||||
/**
|
TypeNotification(String libelle) {
|
||||||
* Constructeur de l'énumération TypeNotification
|
|
||||||
*
|
|
||||||
* @param libelle Le libellé affiché à l'utilisateur
|
|
||||||
* @param categorie La catégorie de la notification
|
|
||||||
* @param priorite Le niveau de priorité (info, reminder, warning, error, success, urgent,
|
|
||||||
* important, celebration)
|
|
||||||
* @param icone L'icône Material Design
|
|
||||||
* @param couleur La couleur hexadécimale
|
|
||||||
* @param visibleUtilisateur true si visible dans les préférences utilisateur
|
|
||||||
* @param activeeParDefaut true si activée par défaut
|
|
||||||
*/
|
|
||||||
TypeNotification(
|
|
||||||
String libelle,
|
|
||||||
String categorie,
|
|
||||||
String priorite,
|
|
||||||
String icone,
|
|
||||||
String couleur,
|
|
||||||
boolean visibleUtilisateur,
|
|
||||||
boolean activeeParDefaut) {
|
|
||||||
this.libelle = libelle;
|
this.libelle = libelle;
|
||||||
this.categorie = categorie;
|
|
||||||
this.priorite = priorite;
|
|
||||||
this.icone = icone;
|
|
||||||
this.couleur = couleur;
|
|
||||||
this.visibleUtilisateur = visibleUtilisateur;
|
|
||||||
this.activeeParDefaut = activeeParDefaut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne le libellé de la notification
|
|
||||||
*
|
|
||||||
* @return Le libellé affiché à l'utilisateur
|
|
||||||
*/
|
|
||||||
public String getLibelle() {
|
public String getLibelle() {
|
||||||
return libelle;
|
return libelle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne la catégorie de la notification
|
|
||||||
*
|
|
||||||
* @return La catégorie (evenements, cotisations, solidarite, etc.)
|
|
||||||
*/
|
|
||||||
public String getCategorie() {
|
|
||||||
return categorie;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne la priorité de la notification
|
|
||||||
*
|
|
||||||
* @return Le niveau de priorité
|
|
||||||
*/
|
|
||||||
public String getPriorite() {
|
|
||||||
return priorite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne l'icône de la notification
|
|
||||||
*
|
|
||||||
* @return L'icône Material Design
|
|
||||||
*/
|
|
||||||
public String getIcone() {
|
|
||||||
return icone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne la couleur de la notification
|
|
||||||
*
|
|
||||||
* @return Le code couleur hexadécimal
|
|
||||||
*/
|
|
||||||
public String getCouleur() {
|
|
||||||
return couleur;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification est visible dans les préférences utilisateur
|
|
||||||
*
|
|
||||||
* @return true si visible dans les préférences
|
|
||||||
*/
|
|
||||||
public boolean isVisibleUtilisateur() {
|
|
||||||
return visibleUtilisateur;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification est activée par défaut
|
|
||||||
*
|
|
||||||
* @return true si activée par défaut
|
|
||||||
*/
|
|
||||||
public boolean isActiveeParDefaut() {
|
|
||||||
return activeeParDefaut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification est critique (urgent ou error)
|
|
||||||
*
|
|
||||||
* @return true si la notification est critique
|
|
||||||
*/
|
|
||||||
public boolean isCritique() {
|
|
||||||
return "urgent".equals(priorite) || "error".equals(priorite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification est un rappel
|
|
||||||
*
|
|
||||||
* @return true si c'est un rappel
|
|
||||||
*/
|
|
||||||
public boolean isRappel() {
|
|
||||||
return "reminder".equals(priorite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification est positive (success ou celebration)
|
|
||||||
*
|
|
||||||
* @return true si la notification est positive
|
|
||||||
*/
|
|
||||||
public boolean isPositive() {
|
|
||||||
return "success".equals(priorite) || "celebration".equals(priorite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne le niveau de priorité numérique pour le tri
|
|
||||||
*
|
|
||||||
* @return Niveau de priorité (1=urgent, 2=error, 3=warning, 4=important, 5=reminder, 6=info,
|
|
||||||
* 7=success, 8=celebration)
|
|
||||||
*/
|
|
||||||
public int getNiveauPriorite() {
|
|
||||||
return switch (priorite) {
|
|
||||||
case "urgent" -> 1;
|
|
||||||
case "error" -> 2;
|
|
||||||
case "warning" -> 3;
|
|
||||||
case "important" -> 4;
|
|
||||||
case "reminder" -> 5;
|
|
||||||
case "info" -> 6;
|
|
||||||
case "success" -> 7;
|
|
||||||
case "celebration" -> 8;
|
|
||||||
default -> 6;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne le délai d'expiration par défaut en heures
|
|
||||||
*
|
|
||||||
* @return Délai d'expiration en heures
|
|
||||||
*/
|
|
||||||
public int getDelaiExpirationHeures() {
|
|
||||||
return switch (priorite) {
|
|
||||||
case "urgent" -> 1; // 1 heure
|
|
||||||
case "error" -> 24; // 24 heures
|
|
||||||
case "warning" -> 48; // 48 heures
|
|
||||||
case "important" -> 72; // 72 heures
|
|
||||||
case "reminder" -> 24; // 24 heures
|
|
||||||
case "info" -> 168; // 1 semaine
|
|
||||||
case "success" -> 48; // 48 heures
|
|
||||||
case "celebration" -> 72; // 72 heures
|
|
||||||
default -> 168;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification doit vibrer
|
|
||||||
*
|
|
||||||
* @return true si la notification doit faire vibrer l'appareil
|
|
||||||
*/
|
|
||||||
public boolean doitVibrer() {
|
|
||||||
return isCritique() || isRappel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vérifie si la notification doit émettre un son
|
|
||||||
*
|
|
||||||
* @return true si la notification doit émettre un son
|
|
||||||
*/
|
|
||||||
public boolean doitEmettreSon() {
|
|
||||||
return isCritique() || isRappel() || "important".equals(priorite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne le canal de notification Android approprié
|
|
||||||
*
|
|
||||||
* @return L'ID du canal de notification
|
|
||||||
*/
|
|
||||||
public String getCanalNotification() {
|
|
||||||
return switch (priorite) {
|
|
||||||
case "urgent" -> "URGENT_CHANNEL";
|
|
||||||
case "error" -> "ERROR_CHANNEL";
|
|
||||||
case "warning" -> "WARNING_CHANNEL";
|
|
||||||
case "important" -> "IMPORTANT_CHANNEL";
|
|
||||||
case "reminder" -> "REMINDER_CHANNEL";
|
|
||||||
case "success" -> "SUCCESS_CHANNEL";
|
|
||||||
case "celebration" -> "CELEBRATION_CHANNEL";
|
|
||||||
default -> "DEFAULT_CHANNEL";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification;
|
||||||
|
import dev.lions.unionflow.server.api.enums.notification.TypeNotification;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entité Notification pour la gestion des notifications
|
||||||
|
*
|
||||||
|
* @author UnionFlow Team
|
||||||
|
* @version 3.0
|
||||||
|
* @since 2025-01-29
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "notifications",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_notification_type", columnList = "type_notification"),
|
||||||
|
@Index(name = "idx_notification_statut", columnList = "statut"),
|
||||||
|
@Index(name = "idx_notification_priorite", columnList = "priorite"),
|
||||||
|
@Index(name = "idx_notification_membre", columnList = "membre_id"),
|
||||||
|
@Index(name = "idx_notification_organisation", columnList = "organisation_id"),
|
||||||
|
@Index(name = "idx_notification_date_envoi", columnList = "date_envoi")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class Notification extends BaseEntity {
|
||||||
|
|
||||||
|
/** Type de notification */
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "type_notification", nullable = false, length = 30)
|
||||||
|
private TypeNotification typeNotification;
|
||||||
|
|
||||||
|
/** Priorité */
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "priorite", length = 20)
|
||||||
|
private PrioriteNotification priorite = PrioriteNotification.NORMALE;
|
||||||
|
|
||||||
|
/** Statut */
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "statut", length = 30)
|
||||||
|
private dev.lions.unionflow.server.api.enums.notification.StatutNotification statut =
|
||||||
|
dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE;
|
||||||
|
|
||||||
|
/** Sujet */
|
||||||
|
@Column(name = "sujet", length = 500)
|
||||||
|
private String sujet;
|
||||||
|
|
||||||
|
/** Corps du message */
|
||||||
|
@Column(name = "corps", columnDefinition = "TEXT")
|
||||||
|
private String corps;
|
||||||
|
|
||||||
|
/** Date d'envoi prévue */
|
||||||
|
@Column(name = "date_envoi_prevue")
|
||||||
|
private LocalDateTime dateEnvoiPrevue;
|
||||||
|
|
||||||
|
/** Date d'envoi réelle */
|
||||||
|
@Column(name = "date_envoi")
|
||||||
|
private LocalDateTime dateEnvoi;
|
||||||
|
|
||||||
|
/** Date de lecture */
|
||||||
|
@Column(name = "date_lecture")
|
||||||
|
private LocalDateTime dateLecture;
|
||||||
|
|
||||||
|
/** Nombre de tentatives d'envoi */
|
||||||
|
@Builder.Default
|
||||||
|
@Column(name = "nombre_tentatives", nullable = false)
|
||||||
|
private Integer nombreTentatives = 0;
|
||||||
|
|
||||||
|
/** Message d'erreur (si échec) */
|
||||||
|
@Column(name = "message_erreur", length = 1000)
|
||||||
|
private String messageErreur;
|
||||||
|
|
||||||
|
/** Données additionnelles (JSON) */
|
||||||
|
@Column(name = "donnees_additionnelles", columnDefinition = "TEXT")
|
||||||
|
private String donneesAdditionnelles;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "membre_id")
|
||||||
|
private Membre membre;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "organisation_id")
|
||||||
|
private Organisation organisation;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "template_id")
|
||||||
|
private TemplateNotification template;
|
||||||
|
|
||||||
|
/** Méthode métier pour vérifier si la notification est envoyée */
|
||||||
|
public boolean isEnvoyee() {
|
||||||
|
return dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.equals(statut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Méthode métier pour vérifier si la notification est lue */
|
||||||
|
public boolean isLue() {
|
||||||
|
return dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.equals(statut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Callback JPA avant la persistance */
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
if (priorite == null) {
|
||||||
|
priorite = PrioriteNotification.NORMALE;
|
||||||
|
}
|
||||||
|
if (statut == null) {
|
||||||
|
statut = dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE;
|
||||||
|
}
|
||||||
|
if (nombreTentatives == null) {
|
||||||
|
nombreTentatives = 0;
|
||||||
|
}
|
||||||
|
if (dateEnvoiPrevue == null) {
|
||||||
|
dateEnvoiPrevue = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package dev.lions.unionflow.server.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entité TemplateNotification pour les templates de notifications réutilisables
|
||||||
|
*
|
||||||
|
* @author UnionFlow Team
|
||||||
|
* @version 3.0
|
||||||
|
* @since 2025-01-29
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "templates_notifications",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_template_code", columnList = "code", unique = true),
|
||||||
|
@Index(name = "idx_template_actif", columnList = "actif")
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TemplateNotification extends BaseEntity {
|
||||||
|
|
||||||
|
/** Code unique du template */
|
||||||
|
@NotBlank
|
||||||
|
@Column(name = "code", unique = true, nullable = false, length = 100)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** Sujet du template */
|
||||||
|
@Column(name = "sujet", length = 500)
|
||||||
|
private String sujet;
|
||||||
|
|
||||||
|
/** Corps du template (texte) */
|
||||||
|
@Column(name = "corps_texte", columnDefinition = "TEXT")
|
||||||
|
private String corpsTexte;
|
||||||
|
|
||||||
|
/** Corps du template (HTML) */
|
||||||
|
@Column(name = "corps_html", columnDefinition = "TEXT")
|
||||||
|
private String corpsHtml;
|
||||||
|
|
||||||
|
/** Variables disponibles (JSON) */
|
||||||
|
@Column(name = "variables_disponibles", columnDefinition = "TEXT")
|
||||||
|
private String variablesDisponibles;
|
||||||
|
|
||||||
|
/** Canaux supportés (JSON array) */
|
||||||
|
@Column(name = "canaux_supportes", length = 500)
|
||||||
|
private String canauxSupportes;
|
||||||
|
|
||||||
|
/** Langue du template */
|
||||||
|
@Column(name = "langue", length = 10)
|
||||||
|
private String langue;
|
||||||
|
|
||||||
|
/** Description */
|
||||||
|
@Column(name = "description", length = 1000)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** Notifications utilisant ce template */
|
||||||
|
@OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
|
@Builder.Default
|
||||||
|
private List<Notification> notifications = new ArrayList<>();
|
||||||
|
|
||||||
|
/** Callback JPA avant la persistance */
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
if (langue == null || langue.isEmpty()) {
|
||||||
|
langue = "fr";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package dev.lions.unionflow.server.resource;
|
||||||
|
|
||||||
|
import dev.lions.unionflow.server.api.dto.document.DocumentDTO;
|
||||||
|
import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO;
|
||||||
|
import dev.lions.unionflow.server.service.DocumentService;
|
||||||
|
import jakarta.annotation.security.PermitAll;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource REST pour la gestion documentaire
|
||||||
|
*
|
||||||
|
* @author UnionFlow Team
|
||||||
|
* @version 3.0
|
||||||
|
* @since 2025-01-29
|
||||||
|
*/
|
||||||
|
@Path("/api/documents")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@PermitAll
|
||||||
|
public class DocumentResource {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(DocumentResource.class);
|
||||||
|
|
||||||
|
@Inject DocumentService documentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un nouveau document
|
||||||
|
*
|
||||||
|
* @param documentDTO DTO du document à créer
|
||||||
|
* @return Document créé
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
public Response creerDocument(@Valid DocumentDTO documentDTO) {
|
||||||
|
try {
|
||||||
|
DocumentDTO result = documentService.creerDocument(documentDTO);
|
||||||
|
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.errorf(e, "Erreur lors de la création du document");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trouve un document par son ID
|
||||||
|
*
|
||||||
|
* @param id ID du document
|
||||||
|
* @return Document
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/{id}")
|
||||||
|
public Response trouverParId(@PathParam("id") UUID id) {
|
||||||
|
try {
|
||||||
|
DocumentDTO result = documentService.trouverParId(id);
|
||||||
|
return Response.ok(result).build();
|
||||||
|
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity(new ErrorResponse("Document non trouvé"))
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.errorf(e, "Erreur lors de la recherche du document");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enregistre un téléchargement de document
|
||||||
|
*
|
||||||
|
* @param id ID du document
|
||||||
|
* @return Succès
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/{id}/telechargement")
|
||||||
|
public Response enregistrerTelechargement(@PathParam("id") UUID id) {
|
||||||
|
try {
|
||||||
|
documentService.enregistrerTelechargement(id);
|
||||||
|
return Response.ok().build();
|
||||||
|
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity(new ErrorResponse("Document non trouvé"))
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(
|
||||||
|
new ErrorResponse(
|
||||||
|
"Erreur lors de l'enregistrement du téléchargement: " + e.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une pièce jointe
|
||||||
|
*
|
||||||
|
* @param pieceJointeDTO DTO de la pièce jointe à créer
|
||||||
|
* @return Pièce jointe créée
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/pieces-jointes")
|
||||||
|
public Response creerPieceJointe(@Valid PieceJointeDTO pieceJointeDTO) {
|
||||||
|
try {
|
||||||
|
PieceJointeDTO result = documentService.creerPieceJointe(pieceJointeDTO);
|
||||||
|
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(new ErrorResponse(e.getMessage()))
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.errorf(e, "Erreur lors de la création de la pièce jointe");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste toutes les pièces jointes d'un document
|
||||||
|
*
|
||||||
|
* @param documentId ID du document
|
||||||
|
* @return Liste des pièces jointes
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/{documentId}/pieces-jointes")
|
||||||
|
public Response listerPiecesJointesParDocument(@PathParam("documentId") UUID documentId) {
|
||||||
|
try {
|
||||||
|
List<PieceJointeDTO> result = documentService.listerPiecesJointesParDocument(documentId);
|
||||||
|
return Response.ok(result).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.errorf(e, "Erreur lors de la liste des pièces jointes");
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Classe interne pour les réponses d'erreur */
|
||||||
|
public static class ErrorResponse {
|
||||||
|
public String error;
|
||||||
|
|
||||||
|
public ErrorResponse(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user