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,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