Configure Maven repository for unionflow-server-api dependency

This commit is contained in:
dahoud
2025-12-10 01:08:17 +00:00
commit 4a0c5f9d33
320 changed files with 33373 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Adhesion avec UUID
* Représente une demande d'adhésion d'un membre à une organisation
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-17
*/
@Entity
@Table(
name = "adhesions",
indexes = {
@Index(name = "idx_adhesion_membre", columnList = "membre_id"),
@Index(name = "idx_adhesion_organisation", columnList = "organisation_id"),
@Index(name = "idx_adhesion_reference", columnList = "numero_reference", unique = true),
@Index(name = "idx_adhesion_statut", columnList = "statut"),
@Index(name = "idx_adhesion_date_demande", columnList = "date_demande")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Adhesion extends BaseEntity {
@NotBlank
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
private String numeroReference;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
@NotNull
@Column(name = "date_demande", nullable = false)
private LocalDate dateDemande;
@NotNull
@DecimalMin(value = "0.0", message = "Le montant des frais d'adhésion doit être positif")
@Digits(integer = 10, fraction = 2)
@Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2)
private BigDecimal fraisAdhesion;
@Builder.Default
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
@Digits(integer = 10, fraction = 2)
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
private BigDecimal montantPaye = BigDecimal.ZERO;
@NotBlank
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
@Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise;
@NotBlank
@Pattern(
regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$",
message = "Statut invalide")
@Column(name = "statut", nullable = false, length = 30)
private String statut;
@Column(name = "date_approbation")
private LocalDate dateApprobation;
@Column(name = "date_paiement")
private LocalDateTime datePaiement;
@Size(max = 20)
@Column(name = "methode_paiement", length = 20)
private String methodePaiement;
@Size(max = 100)
@Column(name = "reference_paiement", length = 100)
private String referencePaiement;
@Size(max = 1000)
@Column(name = "motif_rejet", length = 1000)
private String motifRejet;
@Size(max = 1000)
@Column(name = "observations", length = 1000)
private String observations;
@Column(name = "approuve_par", length = 255)
private String approuvePar;
@Column(name = "date_validation")
private LocalDate dateValidation;
/** Méthode métier pour vérifier si l'adhésion est payée intégralement */
public boolean isPayeeIntegralement() {
return montantPaye != null
&& fraisAdhesion != null
&& montantPaye.compareTo(fraisAdhesion) >= 0;
}
/** Méthode métier pour vérifier si l'adhésion est en attente de paiement */
public boolean isEnAttentePaiement() {
return "APPROUVEE".equals(statut) && !isPayeeIntegralement();
}
/** Méthode métier pour calculer le montant restant à payer */
public BigDecimal getMontantRestant() {
if (fraisAdhesion == null) return BigDecimal.ZERO;
if (montantPaye == null) return fraisAdhesion;
BigDecimal restant = fraisAdhesion.subtract(montantPaye);
return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO;
}
}

View File

@@ -0,0 +1,154 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Adresse pour la gestion des adresses des organisations, membres et événements
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "adresses",
indexes = {
@Index(name = "idx_adresse_ville", columnList = "ville"),
@Index(name = "idx_adresse_pays", columnList = "pays"),
@Index(name = "idx_adresse_type", columnList = "type_adresse"),
@Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
@Index(name = "idx_adresse_membre", columnList = "membre_id"),
@Index(name = "idx_adresse_evenement", columnList = "evenement_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Adresse extends BaseEntity {
/** Type d'adresse */
@Enumerated(EnumType.STRING)
@Column(name = "type_adresse", nullable = false, length = 50)
private TypeAdresse typeAdresse;
/** Adresse complète */
@Column(name = "adresse", length = 500)
private String adresse;
/** Complément d'adresse */
@Column(name = "complement_adresse", length = 200)
private String complementAdresse;
/** Code postal */
@Column(name = "code_postal", length = 20)
private String codePostal;
/** Ville */
@Column(name = "ville", length = 100)
private String ville;
/** Région */
@Column(name = "region", length = 100)
private String region;
/** Pays */
@Column(name = "pays", length = 100)
private String pays;
/** Coordonnées géographiques - Latitude */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6)
@Column(name = "latitude", precision = 9, scale = 6)
private BigDecimal latitude;
/** Coordonnées géographiques - Longitude */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6)
@Column(name = "longitude", precision = 9, scale = 6)
private BigDecimal longitude;
/** Adresse principale (une seule par entité) */
@Builder.Default
@Column(name = "principale", nullable = false)
private Boolean principale = false;
/** Libellé personnalisé */
@Column(name = "libelle", length = 100)
private String libelle;
/** Notes et commentaires */
@Column(name = "notes", length = 500)
private String notes;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id")
private Membre membre;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id")
private Evenement evenement;
/** Méthode métier pour obtenir l'adresse complète formatée */
public String getAdresseComplete() {
StringBuilder sb = new StringBuilder();
if (adresse != null && !adresse.isEmpty()) {
sb.append(adresse);
}
if (complementAdresse != null && !complementAdresse.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(complementAdresse);
}
if (codePostal != null && !codePostal.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(codePostal);
}
if (ville != null && !ville.isEmpty()) {
if (sb.length() > 0) sb.append(" ");
sb.append(ville);
}
if (region != null && !region.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(region);
}
if (pays != null && !pays.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(pays);
}
return sb.toString();
}
/** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
public boolean hasCoordinates() {
return latitude != null && longitude != null;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity
if (typeAdresse == null) {
typeAdresse = dev.lions.unionflow.server.api.enums.adresse.TypeAdresse.AUTRE;
}
if (principale == null) {
principale = false;
}
}
}

View File

@@ -0,0 +1,81 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
/**
* Entité pour les logs d'audit
* Enregistre toutes les actions importantes du système
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-17
*/
@Entity
@Table(name = "audit_logs", indexes = {
@Index(name = "idx_audit_date_heure", columnList = "date_heure"),
@Index(name = "idx_audit_utilisateur", columnList = "utilisateur"),
@Index(name = "idx_audit_module", columnList = "module"),
@Index(name = "idx_audit_type_action", columnList = "type_action"),
@Index(name = "idx_audit_severite", columnList = "severite")
})
@Getter
@Setter
public class AuditLog extends BaseEntity {
@Column(name = "type_action", nullable = false, length = 50)
private String typeAction;
@Column(name = "severite", nullable = false, length = 20)
private String severite;
@Column(name = "utilisateur", length = 255)
private String utilisateur;
@Column(name = "role", length = 50)
private String role;
@Column(name = "module", length = 50)
private String module;
@Column(name = "description", length = 500)
private String description;
@Column(name = "details", columnDefinition = "TEXT")
private String details;
@Column(name = "ip_address", length = 45)
private String ipAddress;
@Column(name = "user_agent", length = 500)
private String userAgent;
@Column(name = "session_id", length = 255)
private String sessionId;
@Column(name = "date_heure", nullable = false)
private LocalDateTime dateHeure;
@Column(name = "donnees_avant", columnDefinition = "TEXT")
private String donneesAvant;
@Column(name = "donnees_apres", columnDefinition = "TEXT")
private String donneesApres;
@Column(name = "entite_id", length = 255)
private String entiteId;
@Column(name = "entite_type", length = 100)
private String entiteType;
@PrePersist
protected void onCreate() {
if (dateHeure == null) {
dateHeure = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,141 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* Classe de base pour les entités UnionFlow utilisant UUID comme identifiant
*
* <p>Remplace PanacheEntity pour utiliser UUID au lieu de Long comme ID.
* Fournit les fonctionnalités de base de Panache avec UUID.
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
@Column(name = "date_creation", nullable = false, updatable = false)
protected LocalDateTime dateCreation;
@Column(name = "date_modification")
protected LocalDateTime dateModification;
@Column(name = "cree_par", length = 255)
protected String creePar;
@Column(name = "modifie_par", length = 255)
protected String modifiePar;
@Version
@Column(name = "version")
protected Long version;
@Column(name = "actif", nullable = false)
protected Boolean actif = true;
// Constructeur par défaut
public BaseEntity() {
this.dateCreation = LocalDateTime.now();
this.actif = true;
this.version = 0L;
}
// Getters et Setters
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public LocalDateTime getDateCreation() {
return dateCreation;
}
public void setDateCreation(LocalDateTime dateCreation) {
this.dateCreation = dateCreation;
}
public LocalDateTime getDateModification() {
return dateModification;
}
public void setDateModification(LocalDateTime dateModification) {
this.dateModification = dateModification;
}
public String getCreePar() {
return creePar;
}
public void setCreePar(String creePar) {
this.creePar = creePar;
}
public String getModifiePar() {
return modifiePar;
}
public void setModifiePar(String modifiePar) {
this.modifiePar = modifiePar;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Boolean getActif() {
return actif;
}
public void setActif(Boolean actif) {
this.actif = actif;
}
// Callbacks JPA
@PrePersist
protected void onCreate() {
if (this.dateCreation == null) {
this.dateCreation = LocalDateTime.now();
}
if (this.actif == null) {
this.actif = true;
}
if (this.version == null) {
this.version = 0L;
}
}
@PreUpdate
protected void onUpdate() {
this.dateModification = LocalDateTime.now();
}
// Méthodes utilitaires Panache-like
public void persist() {
// Cette méthode sera implémentée par les repositories ou services
// Pour l'instant, elle est là pour compatibilité avec le code existant
throw new UnsupportedOperationException(
"Utilisez le repository approprié pour persister cette entité");
}
public static <T extends BaseEntity> T findById(UUID id) {
// Cette méthode sera implémentée par les repositories
throw new UnsupportedOperationException(
"Utilisez le repository approprié pour rechercher par ID");
}
}

View File

@@ -0,0 +1,120 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité CompteComptable pour le plan comptable
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "comptes_comptables",
indexes = {
@Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true),
@Index(name = "idx_compte_type", columnList = "type_compte"),
@Index(name = "idx_compte_classe", columnList = "classe_comptable")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class CompteComptable extends BaseEntity {
/** Numéro de compte unique (ex: 411000, 512000) */
@NotBlank
@Column(name = "numero_compte", unique = true, nullable = false, length = 10)
private String numeroCompte;
/** Libellé du compte */
@NotBlank
@Column(name = "libelle", nullable = false, length = 200)
private String libelle;
/** Type de compte */
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "type_compte", nullable = false, length = 30)
private TypeCompteComptable typeCompte;
/** Classe comptable (1-7) */
@NotNull
@Min(value = 1, message = "La classe comptable doit être entre 1 et 7")
@Max(value = 7, message = "La classe comptable doit être entre 1 et 7")
@Column(name = "classe_comptable", nullable = false)
private Integer classeComptable;
/** Solde initial */
@Builder.Default
@DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul")
@Digits(integer = 12, fraction = 2)
@Column(name = "solde_initial", precision = 14, scale = 2)
private BigDecimal soldeInitial = BigDecimal.ZERO;
/** Solde actuel (calculé) */
@Builder.Default
@Digits(integer = 12, fraction = 2)
@Column(name = "solde_actuel", precision = 14, scale = 2)
private BigDecimal soldeActuel = BigDecimal.ZERO;
/** Compte collectif (regroupe plusieurs sous-comptes) */
@Builder.Default
@Column(name = "compte_collectif", nullable = false)
private Boolean compteCollectif = false;
/** Compte analytique */
@Builder.Default
@Column(name = "compte_analytique", nullable = false)
private Boolean compteAnalytique = false;
/** Description du compte */
@Column(name = "description", length = 500)
private String description;
/** Lignes d'écriture associées */
@OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<LigneEcriture> lignesEcriture = new ArrayList<>();
/** Méthode métier pour obtenir le numéro formaté */
public String getNumeroFormate() {
return String.format("%-10s", numeroCompte);
}
/** Méthode métier pour vérifier si c'est un compte de trésorerie */
public boolean isTresorerie() {
return TypeCompteComptable.TRESORERIE.equals(typeCompte);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (soldeInitial == null) {
soldeInitial = BigDecimal.ZERO;
}
if (soldeActuel == null) {
soldeActuel = soldeInitial;
}
if (compteCollectif == null) {
compteCollectif = false;
}
if (compteAnalytique == null) {
compteAnalytique = false;
}
}
}

View File

@@ -0,0 +1,107 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité CompteWave pour la gestion des comptes Wave Mobile Money
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "comptes_wave",
indexes = {
@Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true),
@Index(name = "idx_compte_wave_statut", columnList = "statut_compte"),
@Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"),
@Index(name = "idx_compte_wave_membre", columnList = "membre_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class CompteWave extends BaseEntity {
/** Numéro de téléphone Wave (format +225XXXXXXXX) */
@NotBlank
@Pattern(
regexp = "^\\+225[0-9]{8}$",
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
@Column(name = "numero_telephone", unique = true, nullable = false, length = 13)
private String numeroTelephone;
/** Statut du compte */
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "statut_compte", nullable = false, length = 30)
private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE;
/** Identifiant Wave API (encrypté) */
@Column(name = "wave_account_id", length = 255)
private String waveAccountId;
/** Clé API Wave (encryptée) */
@Column(name = "wave_api_key", length = 500)
private String waveApiKey;
/** Environnement (SANDBOX ou PRODUCTION) */
@Column(name = "environnement", length = 20)
private String environnement;
/** Date de dernière vérification */
@Column(name = "date_derniere_verification")
private java.time.LocalDateTime dateDerniereVerification;
/** Commentaires */
@Column(name = "commentaire", length = 500)
private String commentaire;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id")
private Membre membre;
@OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<TransactionWave> transactions = new ArrayList<>();
/** Méthode métier pour vérifier si le compte est vérifié */
public boolean isVerifie() {
return StatutCompteWave.VERIFIE.equals(statutCompte);
}
/** Méthode métier pour vérifier si le compte peut être utilisé */
public boolean peutEtreUtilise() {
return StatutCompteWave.VERIFIE.equals(statutCompte);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (statutCompte == null) {
statutCompte = StatutCompteWave.NON_VERIFIE;
}
if (environnement == null || environnement.isEmpty()) {
environnement = "SANDBOX";
}
}
}

View File

@@ -0,0 +1,69 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité ConfigurationWave pour la configuration de l'intégration Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "configurations_wave",
indexes = {
@Index(name = "idx_config_wave_cle", columnList = "cle", unique = true)
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class ConfigurationWave extends BaseEntity {
/** Clé de configuration */
@NotBlank
@Column(name = "cle", unique = true, nullable = false, length = 100)
private String cle;
/** Valeur de configuration (peut être encryptée) */
@Column(name = "valeur", columnDefinition = "TEXT")
private String valeur;
/** Description de la configuration */
@Column(name = "description", length = 500)
private String description;
/** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */
@Column(name = "type_valeur", length = 20)
private String typeValeur;
/** Environnement (SANDBOX, PRODUCTION, COMMON) */
@Column(name = "environnement", length = 20)
private String environnement;
/** Méthode métier pour vérifier si la valeur est encryptée */
public boolean isEncryptee() {
return "ENCRYPTED".equals(typeValeur);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (typeValeur == null || typeValeur.isEmpty()) {
typeValeur = "STRING";
}
if (environnement == null || environnement.isEmpty()) {
environnement = "COMMON";
}
}
}

View File

@@ -0,0 +1,184 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Cotisation avec UUID Représente une cotisation d'un membre à son organisation
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@Entity
@Table(
name = "cotisations",
indexes = {
@Index(name = "idx_cotisation_membre", columnList = "membre_id"),
@Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true),
@Index(name = "idx_cotisation_statut", columnList = "statut"),
@Index(name = "idx_cotisation_echeance", columnList = "date_echeance"),
@Index(name = "idx_cotisation_type", columnList = "type_cotisation"),
@Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Cotisation extends BaseEntity {
@NotBlank
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
private String numeroReference;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
@NotBlank
@Column(name = "type_cotisation", nullable = false, length = 50)
private String typeCotisation;
@NotNull
@DecimalMin(value = "0.0", message = "Le montant dû doit être positif")
@Digits(integer = 10, fraction = 2)
@Column(name = "montant_du", nullable = false, precision = 12, scale = 2)
private BigDecimal montantDu;
@Builder.Default
@DecimalMin(value = "0.0", message = "Le montant payé doit être positif")
@Digits(integer = 10, fraction = 2)
@Column(name = "montant_paye", nullable = false, precision = 12, scale = 2)
private BigDecimal montantPaye = BigDecimal.ZERO;
@NotBlank
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
@Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise;
@NotBlank
@Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$")
@Column(name = "statut", nullable = false, length = 30)
private String statut;
@NotNull
@Column(name = "date_echeance", nullable = false)
private LocalDate dateEcheance;
@Column(name = "date_paiement")
private LocalDateTime datePaiement;
@Size(max = 500)
@Column(name = "description", length = 500)
private String description;
@Size(max = 20)
@Column(name = "periode", length = 20)
private String periode;
@NotNull
@Min(value = 2020, message = "L'année doit être supérieure à 2020")
@Max(value = 2100, message = "L'année doit être inférieure à 2100")
@Column(name = "annee", nullable = false)
private Integer annee;
@Min(value = 1, message = "Le mois doit être entre 1 et 12")
@Max(value = 12, message = "Le mois doit être entre 1 et 12")
@Column(name = "mois")
private Integer mois;
@Size(max = 1000)
@Column(name = "observations", length = 1000)
private String observations;
@Builder.Default
@Column(name = "recurrente", nullable = false)
private Boolean recurrente = false;
@Builder.Default
@Min(value = 0, message = "Le nombre de rappels doit être positif")
@Column(name = "nombre_rappels", nullable = false)
private Integer nombreRappels = 0;
@Column(name = "date_dernier_rappel")
private LocalDateTime dateDernierRappel;
@Column(name = "valide_par_id")
private UUID valideParId;
@Size(max = 100)
@Column(name = "nom_validateur", length = 100)
private String nomValidateur;
@Column(name = "date_validation")
private LocalDateTime dateValidation;
@Size(max = 50)
@Column(name = "methode_paiement", length = 50)
private String methodePaiement;
@Size(max = 100)
@Column(name = "reference_paiement", length = 100)
private String referencePaiement;
/** Méthode métier pour calculer le montant restant à payer */
public BigDecimal getMontantRestant() {
if (montantDu == null || montantPaye == null) {
return BigDecimal.ZERO;
}
return montantDu.subtract(montantPaye);
}
/** Méthode métier pour vérifier si la cotisation est entièrement payée */
public boolean isEntierementPayee() {
return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0;
}
/** Méthode métier pour vérifier si la cotisation est en retard */
public boolean isEnRetard() {
return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee();
}
/** Méthode métier pour générer un numéro de référence unique */
public static String genererNumeroReference() {
return "COT-"
+ LocalDate.now().getYear()
+ "-"
+ String.format("%08d", System.currentTimeMillis() % 100000000);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity
if (numeroReference == null || numeroReference.isEmpty()) {
numeroReference = genererNumeroReference();
}
if (codeDevise == null) {
codeDevise = "XOF";
}
if (statut == null) {
statut = "EN_ATTENTE";
}
if (montantPaye == null) {
montantPaye = BigDecimal.ZERO;
}
if (nombreRappels == null) {
nombreRappels = 0;
}
if (recurrente == null) {
recurrente = false;
}
}
}

View File

@@ -0,0 +1,130 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/** Entité représentant une demande d'aide dans le système de solidarité */
@Entity
@Table(name = "demandes_aide")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class DemandeAide extends BaseEntity {
@Column(name = "titre", nullable = false, length = 200)
private String titre;
@Column(name = "description", nullable = false, columnDefinition = "TEXT")
private String description;
@Enumerated(EnumType.STRING)
@Column(name = "type_aide", nullable = false)
private TypeAide typeAide;
@Enumerated(EnumType.STRING)
@Column(name = "statut", nullable = false)
private StatutAide statut;
@Column(name = "montant_demande", precision = 10, scale = 2)
private BigDecimal montantDemande;
@Column(name = "montant_approuve", precision = 10, scale = 2)
private BigDecimal montantApprouve;
@Column(name = "date_demande", nullable = false)
private LocalDateTime dateDemande;
@Column(name = "date_evaluation")
private LocalDateTime dateEvaluation;
@Column(name = "date_versement")
private LocalDateTime dateVersement;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demandeur_id", nullable = false)
private Membre demandeur;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evaluateur_id")
private Membre evaluateur;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
@Column(name = "justification", columnDefinition = "TEXT")
private String justification;
@Column(name = "commentaire_evaluation", columnDefinition = "TEXT")
private String commentaireEvaluation;
@Column(name = "urgence", nullable = false)
@Builder.Default
private Boolean urgence = false;
@Column(name = "documents_fournis")
private String documentsFournis;
@PrePersist
protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity
if (dateDemande == null) {
dateDemande = LocalDateTime.now();
}
if (statut == null) {
statut = StatutAide.EN_ATTENTE;
}
if (urgence == null) {
urgence = false;
}
}
@PreUpdate
protected void onUpdate() {
// Méthode appelée avant mise à jour
}
/** Vérifie si la demande est en attente */
public boolean isEnAttente() {
return StatutAide.EN_ATTENTE.equals(statut);
}
/** Vérifie si la demande est approuvée */
public boolean isApprouvee() {
return StatutAide.APPROUVEE.equals(statut);
}
/** Vérifie si la demande est rejetée */
public boolean isRejetee() {
return StatutAide.REJETEE.equals(statut);
}
/** Vérifie si la demande est urgente */
public boolean isUrgente() {
return Boolean.TRUE.equals(urgence);
}
/** Calcule le pourcentage d'approbation par rapport au montant demandé */
public BigDecimal getPourcentageApprobation() {
if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
if (montantApprouve == null) {
return BigDecimal.ZERO;
}
return montantApprouve
.divide(montantDemande, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
}
}

View File

@@ -0,0 +1,128 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.document.TypeDocument;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Document pour la gestion documentaire sécurisée
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "documents",
indexes = {
@Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"),
@Index(name = "idx_document_type", columnList = "type_document"),
@Index(name = "idx_document_hash_md5", columnList = "hash_md5"),
@Index(name = "idx_document_hash_sha256", columnList = "hash_sha256")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Document extends BaseEntity {
/** Nom du fichier original */
@NotBlank
@Column(name = "nom_fichier", nullable = false, length = 255)
private String nomFichier;
/** Nom original du fichier (tel que téléchargé) */
@Column(name = "nom_original", length = 255)
private String nomOriginal;
/** Chemin de stockage */
@NotBlank
@Column(name = "chemin_stockage", nullable = false, length = 1000)
private String cheminStockage;
/** Type MIME */
@Column(name = "type_mime", length = 100)
private String typeMime;
/** Taille du fichier en octets */
@NotNull
@Min(value = 0, message = "La taille doit être positive")
@Column(name = "taille_octets", nullable = false)
private Long tailleOctets;
/** Type de document */
@Enumerated(EnumType.STRING)
@Column(name = "type_document", length = 50)
private TypeDocument typeDocument;
/** Hash MD5 pour vérification d'intégrité */
@Column(name = "hash_md5", length = 32)
private String hashMd5;
/** Hash SHA256 pour vérification d'intégrité */
@Column(name = "hash_sha256", length = 64)
private String hashSha256;
/** Description du document */
@Column(name = "description", length = 1000)
private String description;
/** Nombre de téléchargements */
@Builder.Default
@Column(name = "nombre_telechargements", nullable = false)
private Integer nombreTelechargements = 0;
/** Date de dernier téléchargement */
@Column(name = "date_dernier_telechargement")
private java.time.LocalDateTime dateDernierTelechargement;
/** Pièces jointes associées */
@OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<PieceJointe> piecesJointes = new ArrayList<>();
/** Méthode métier pour vérifier l'intégrité avec MD5 */
public boolean verifierIntegriteMd5(String hashAttendu) {
return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu);
}
/** Méthode métier pour vérifier l'intégrité avec SHA256 */
public boolean verifierIntegriteSha256(String hashAttendu) {
return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu);
}
/** Méthode métier pour obtenir la taille formatée */
public String getTailleFormatee() {
if (tailleOctets == null) {
return "0 B";
}
if (tailleOctets < 1024) {
return tailleOctets + " B";
} else if (tailleOctets < 1024 * 1024) {
return String.format("%.2f KB", tailleOctets / 1024.0);
} else {
return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0));
}
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (nombreTelechargements == null) {
nombreTelechargements = 0;
}
if (typeDocument == null) {
typeDocument = TypeDocument.AUTRE;
}
}
}

View File

@@ -0,0 +1,172 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité EcritureComptable pour les écritures comptables
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "ecritures_comptables",
indexes = {
@Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
@Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
@Index(name = "idx_ecriture_journal", columnList = "journal_id"),
@Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
@Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class EcritureComptable extends BaseEntity {
/** Numéro de pièce unique */
@NotBlank
@Column(name = "numero_piece", unique = true, nullable = false, length = 50)
private String numeroPiece;
/** Date de l'écriture */
@NotNull
@Column(name = "date_ecriture", nullable = false)
private LocalDate dateEcriture;
/** Libellé de l'écriture */
@NotBlank
@Column(name = "libelle", nullable = false, length = 500)
private String libelle;
/** Référence externe */
@Column(name = "reference", length = 100)
private String reference;
/** Lettrage (pour rapprochement) */
@Column(name = "lettrage", length = 20)
private String lettrage;
/** Pointage (pour rapprochement bancaire) */
@Builder.Default
@Column(name = "pointe", nullable = false)
private Boolean pointe = false;
/** Montant total débit (somme des lignes) */
@Builder.Default
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_debit", precision = 14, scale = 2)
private BigDecimal montantDebit = BigDecimal.ZERO;
/** Montant total crédit (somme des lignes) */
@Builder.Default
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_credit", precision = 14, scale = 2)
private BigDecimal montantCredit = BigDecimal.ZERO;
/** Commentaires */
@Column(name = "commentaire", length = 1000)
private String commentaire;
// Relations
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "journal_id", nullable = false)
private JournalComptable journal;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id")
private Paiement paiement;
/** Lignes d'écriture */
@OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@Builder.Default
private List<LigneEcriture> lignes = new ArrayList<>();
/** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
public boolean isEquilibree() {
if (montantDebit == null || montantCredit == null) {
return false;
}
return montantDebit.compareTo(montantCredit) == 0;
}
/** Méthode métier pour calculer les totaux à partir des lignes */
public void calculerTotaux() {
if (lignes == null || lignes.isEmpty()) {
montantDebit = BigDecimal.ZERO;
montantCredit = BigDecimal.ZERO;
return;
}
montantDebit =
lignes.stream()
.map(LigneEcriture::getMontantDebit)
.filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
montantCredit =
lignes.stream()
.map(LigneEcriture::getMontantCredit)
.filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/** Méthode métier pour générer un numéro de pièce unique */
public static String genererNumeroPiece(String prefixe, LocalDate date) {
return String.format(
"%s-%04d%02d%02d-%012d",
prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
System.currentTimeMillis() % 1000000000000L);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (numeroPiece == null || numeroPiece.isEmpty()) {
numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
}
if (dateEcriture == null) {
dateEcriture = LocalDate.now();
}
if (montantDebit == null) {
montantDebit = BigDecimal.ZERO;
}
if (montantCredit == null) {
montantCredit = BigDecimal.ZERO;
}
if (pointe == null) {
pointe = false;
}
// Calculer les totaux si les lignes sont déjà présentes
if (lignes != null && !lignes.isEmpty()) {
calculerTotaux();
}
}
/** Callback JPA avant la mise à jour */
@PreUpdate
protected void onUpdate() {
calculerTotaux();
}
}

View File

@@ -0,0 +1,267 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.*;
/**
* Entité Événement pour la gestion des événements de l'union
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@Entity
@Table(
name = "evenements",
indexes = {
@Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
@Index(name = "idx_evenement_statut", columnList = "statut"),
@Index(name = "idx_evenement_type", columnList = "type_evenement"),
@Index(name = "idx_evenement_organisation", columnList = "organisation_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Evenement extends BaseEntity {
@NotBlank
@Size(min = 3, max = 200)
@Column(name = "titre", nullable = false, length = 200)
private String titre;
@Size(max = 2000)
@Column(name = "description", length = 2000)
private String description;
@NotNull
@Column(name = "date_debut", nullable = false)
private LocalDateTime dateDebut;
@Column(name = "date_fin")
private LocalDateTime dateFin;
@Size(max = 500)
@Column(name = "lieu", length = 500)
private String lieu;
@Size(max = 1000)
@Column(name = "adresse", length = 1000)
private String adresse;
@Enumerated(EnumType.STRING)
@Column(name = "type_evenement", length = 50)
private TypeEvenement typeEvenement;
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "statut", nullable = false, length = 30)
private StatutEvenement statut = StatutEvenement.PLANIFIE;
@Min(0)
@Column(name = "capacite_max")
private Integer capaciteMax;
@DecimalMin("0.00")
@Digits(integer = 8, fraction = 2)
@Column(name = "prix", precision = 10, scale = 2)
private BigDecimal prix;
@Builder.Default
@Column(name = "inscription_requise", nullable = false)
private Boolean inscriptionRequise = false;
@Column(name = "date_limite_inscription")
private LocalDateTime dateLimiteInscription;
@Size(max = 1000)
@Column(name = "instructions_particulieres", length = 1000)
private String instructionsParticulieres;
@Size(max = 500)
@Column(name = "contact_organisateur", length = 500)
private String contactOrganisateur;
@Size(max = 2000)
@Column(name = "materiel_requis", length = 2000)
private String materielRequis;
@Builder.Default
@Column(name = "visible_public", nullable = false)
private Boolean visiblePublic = true;
@Builder.Default
@Column(name = "actif", nullable = false)
private Boolean actif = true;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisateur_id")
private Membre organisateur;
@OneToMany(
mappedBy = "evenement",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
@Builder.Default
private List<InscriptionEvenement> inscriptions = new ArrayList<>();
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
/** Types d'événements */
public enum TypeEvenement {
ASSEMBLEE_GENERALE("Assemblée Générale"),
REUNION("Réunion"),
FORMATION("Formation"),
CONFERENCE("Conférence"),
ATELIER("Atelier"),
SEMINAIRE("Séminaire"),
EVENEMENT_SOCIAL("Événement Social"),
MANIFESTATION("Manifestation"),
CELEBRATION("Célébration"),
AUTRE("Autre");
private final String libelle;
TypeEvenement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Statuts d'événement */
public enum StatutEvenement {
PLANIFIE("Planifié"),
CONFIRME("Confirmé"),
EN_COURS("En cours"),
TERMINE("Terminé"),
ANNULE("Annulé"),
REPORTE("Reporté");
private final String libelle;
StatutEvenement(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
// Méthodes métier
/** Vérifie si l'événement est ouvert aux inscriptions */
public boolean isOuvertAuxInscriptions() {
if (!inscriptionRequise || !actif) {
return false;
}
LocalDateTime maintenant = LocalDateTime.now();
// Vérifier si la date limite d'inscription n'est pas dépassée
if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
return false;
}
// Vérifier si l'événement n'a pas déjà commencé
if (maintenant.isAfter(dateDebut)) {
return false;
}
// Vérifier la capacité
if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
return false;
}
return statut == StatutEvenement.PLANIFIE || statut == StatutEvenement.CONFIRME;
}
/** Obtient le nombre d'inscrits à l'événement */
public int getNombreInscrits() {
return inscriptions != null
? (int)
inscriptions.stream()
.filter(
inscription ->
inscription.getStatut() == InscriptionEvenement.StatutInscription.CONFIRMEE)
.count()
: 0;
}
/** Vérifie si l'événement est complet */
public boolean isComplet() {
return capaciteMax != null && getNombreInscrits() >= capaciteMax;
}
/** Vérifie si l'événement est en cours */
public boolean isEnCours() {
LocalDateTime maintenant = LocalDateTime.now();
return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
}
/** Vérifie si l'événement est terminé */
public boolean isTermine() {
if (statut == StatutEvenement.TERMINE) {
return true;
}
LocalDateTime maintenant = LocalDateTime.now();
return dateFin != null && maintenant.isAfter(dateFin);
}
/** Calcule la durée de l'événement en heures */
public Long getDureeEnHeures() {
if (dateFin == null) {
return null;
}
return java.time.Duration.between(dateDebut, dateFin).toHours();
}
/** Obtient le nombre de places restantes */
public Integer getPlacesRestantes() {
if (capaciteMax == null) {
return null; // Capacité illimitée
}
return Math.max(0, capaciteMax - getNombreInscrits());
}
/** Vérifie si un membre est inscrit à l'événement */
public boolean isMemberInscrit(UUID membreId) {
return inscriptions != null
&& inscriptions.stream()
.anyMatch(
inscription ->
inscription.getMembre().getId().equals(membreId)
&& inscription.getStatut()
== InscriptionEvenement.StatutInscription.CONFIRMEE);
}
/** Obtient le taux de remplissage en pourcentage */
public Double getTauxRemplissage() {
if (capaciteMax == null || capaciteMax == 0) {
return null;
}
return (double) getNombreInscrits() / capaciteMax * 100;
}
}

View File

@@ -0,0 +1,156 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import lombok.*;
/**
* Entité InscriptionEvenement représentant l'inscription d'un membre à un événement
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@Entity
@Table(
name = "inscriptions_evenement",
indexes = {
@Index(name = "idx_inscription_membre", columnList = "membre_id"),
@Index(name = "idx_inscription_evenement", columnList = "evenement_id"),
@Index(name = "idx_inscription_date", columnList = "date_inscription")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class InscriptionEvenement extends BaseEntity {
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id", nullable = false)
private Evenement evenement;
@Builder.Default
@Column(name = "date_inscription", nullable = false)
private LocalDateTime dateInscription = LocalDateTime.now();
@Enumerated(EnumType.STRING)
@Column(name = "statut", length = 20)
@Builder.Default
private StatutInscription statut = StatutInscription.CONFIRMEE;
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Énumération des statuts d'inscription */
public enum StatutInscription {
CONFIRMEE("Confirmée"),
EN_ATTENTE("En attente"),
ANNULEE("Annulée"),
REFUSEE("Refusée");
private final String libelle;
StatutInscription(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
// Méthodes utilitaires
/**
* Vérifie si l'inscription est confirmée
*
* @return true si l'inscription est confirmée
*/
public boolean isConfirmee() {
return StatutInscription.CONFIRMEE.equals(this.statut);
}
/**
* Vérifie si l'inscription est en attente
*
* @return true si l'inscription est en attente
*/
public boolean isEnAttente() {
return StatutInscription.EN_ATTENTE.equals(this.statut);
}
/**
* Vérifie si l'inscription est annulée
*
* @return true si l'inscription est annulée
*/
public boolean isAnnulee() {
return StatutInscription.ANNULEE.equals(this.statut);
}
/** Confirme l'inscription */
public void confirmer() {
this.statut = StatutInscription.CONFIRMEE;
this.dateModification = LocalDateTime.now();
}
/**
* Annule l'inscription
*
* @param commentaire le commentaire d'annulation
*/
public void annuler(String commentaire) {
this.statut = StatutInscription.ANNULEE;
this.commentaire = commentaire;
this.dateModification = LocalDateTime.now();
}
/**
* Met l'inscription en attente
*
* @param commentaire le commentaire de mise en attente
*/
public void mettreEnAttente(String commentaire) {
this.statut = StatutInscription.EN_ATTENTE;
this.commentaire = commentaire;
this.dateModification = LocalDateTime.now();
}
/**
* Refuse l'inscription
*
* @param commentaire le commentaire de refus
*/
public void refuser(String commentaire) {
this.statut = StatutInscription.REFUSEE;
this.commentaire = commentaire;
this.dateModification = LocalDateTime.now();
}
// Callbacks JPA
@PreUpdate
public void preUpdate() {
super.onUpdate(); // Appelle le onUpdate de BaseEntity
this.dateModification = LocalDateTime.now();
}
@Override
public String toString() {
return String.format(
"InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}",
getId(),
membre != null ? membre.getEmail() : "null",
evenement != null ? evenement.getTitre() : "null",
statut,
dateInscription);
}
}

View File

@@ -0,0 +1,98 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité JournalComptable pour la gestion des journaux
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "journaux_comptables",
indexes = {
@Index(name = "idx_journal_code", columnList = "code", unique = true),
@Index(name = "idx_journal_type", columnList = "type_journal"),
@Index(name = "idx_journal_periode", columnList = "date_debut, date_fin")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class JournalComptable extends BaseEntity {
/** Code unique du journal */
@NotBlank
@Column(name = "code", unique = true, nullable = false, length = 10)
private String code;
/** Libellé du journal */
@NotBlank
@Column(name = "libelle", nullable = false, length = 100)
private String libelle;
/** Type de journal */
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "type_journal", nullable = false, length = 30)
private TypeJournalComptable typeJournal;
/** Date de début de la période */
@Column(name = "date_debut")
private LocalDate dateDebut;
/** Date de fin de la période */
@Column(name = "date_fin")
private LocalDate dateFin;
/** Statut du journal (OUVERT, FERME, ARCHIVE) */
@Builder.Default
@Column(name = "statut", length = 20)
private String statut = "OUVERT";
/** Description */
@Column(name = "description", length = 500)
private String description;
/** Écritures comptables associées */
@OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<EcritureComptable> ecritures = new ArrayList<>();
/** Méthode métier pour vérifier si le journal est ouvert */
public boolean isOuvert() {
return "OUVERT".equals(statut);
}
/** Méthode métier pour vérifier si une date est dans la période */
public boolean estDansPeriode(LocalDate date) {
if (dateDebut == null || dateFin == null) {
return true; // Période illimitée
}
return !date.isBefore(dateDebut) && !date.isAfter(dateFin);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (statut == null || statut.isEmpty()) {
statut = "OUVERT";
}
}
}

View File

@@ -0,0 +1,100 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité LigneEcriture pour les lignes d'une écriture comptable
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "lignes_ecriture",
indexes = {
@Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"),
@Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class LigneEcriture extends BaseEntity {
/** Numéro de ligne */
@NotNull
@Min(value = 1, message = "Le numéro de ligne doit être positif")
@Column(name = "numero_ligne", nullable = false)
private Integer numeroLigne;
/** Montant débit */
@DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_debit", precision = 14, scale = 2)
private BigDecimal montantDebit;
/** Montant crédit */
@DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_credit", precision = 14, scale = 2)
private BigDecimal montantCredit;
/** Libellé de la ligne */
@Column(name = "libelle", length = 500)
private String libelle;
/** Référence */
@Column(name = "reference", length = 100)
private String reference;
// Relations
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ecriture_id", nullable = false)
private EcritureComptable ecriture;
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_comptable_id", nullable = false)
private CompteComptable compteComptable;
/** Méthode métier pour vérifier que la ligne a soit un débit soit un crédit (pas les deux) */
public boolean isValide() {
boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0;
boolean aCredit = montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0;
return aDebit != aCredit; // XOR : soit débit, soit crédit, pas les deux
}
/** Méthode métier pour obtenir le montant (débit ou crédit) */
public BigDecimal getMontant() {
if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) {
return montantDebit;
}
if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) {
return montantCredit;
}
return BigDecimal.ZERO;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (montantDebit == null) {
montantDebit = BigDecimal.ZERO;
}
if (montantCredit == null) {
montantCredit = BigDecimal.ZERO;
}
}
}

View File

@@ -0,0 +1,106 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/** Entité Membre avec UUID */
@Entity
@Table(
name = "membres",
indexes = {
@Index(name = "idx_membre_email", columnList = "email", unique = true),
@Index(name = "idx_membre_numero", columnList = "numero_membre", unique = true),
@Index(name = "idx_membre_actif", columnList = "actif")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Membre extends BaseEntity {
@NotBlank
@Column(name = "numero_membre", unique = true, nullable = false, length = 20)
private String numeroMembre;
@NotBlank
@Column(name = "prenom", nullable = false, length = 100)
private String prenom;
@NotBlank
@Column(name = "nom", nullable = false, length = 100)
private String nom;
@Email
@NotBlank
@Column(name = "email", unique = true, nullable = false, length = 255)
private String email;
@Column(name = "mot_de_passe", length = 255)
private String motDePasse;
@Column(name = "telephone", length = 20)
private String telephone;
@Pattern(
regexp = "^\\+225[0-9]{8}$",
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
@Column(name = "telephone_wave", length = 13)
private String telephoneWave;
@NotNull
@Column(name = "date_naissance", nullable = false)
private LocalDate dateNaissance;
@NotNull
@Column(name = "date_adhesion", nullable = false)
private LocalDate dateAdhesion;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<MembreRole> roles = new ArrayList<>();
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<CompteWave> comptesWave = new ArrayList<>();
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Paiement> paiements = new ArrayList<>();
/** Méthode métier pour obtenir le nom complet */
public String getNomComplet() {
return prenom + " " + nom;
}
/** Méthode métier pour vérifier si le membre est majeur */
public boolean isMajeur() {
return dateNaissance.isBefore(LocalDate.now().minusYears(18));
}
/** Méthode métier pour calculer l'âge */
public int getAge() {
return LocalDate.now().getYear() - dateNaissance.getYear();
}
}

View File

@@ -0,0 +1,88 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Membre et Role
* Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "membres_roles",
indexes = {
@Index(name = "idx_membre_role_membre", columnList = "membre_id"),
@Index(name = "idx_membre_role_role", columnList = "role_id"),
@Index(name = "idx_membre_role_actif", columnList = "actif")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_membre_role",
columnNames = {"membre_id", "role_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class MembreRole extends BaseEntity {
/** Membre */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
/** Rôle */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private Role role;
/** Date de début d'attribution */
@Column(name = "date_debut")
private LocalDate dateDebut;
/** Date de fin d'attribution (null = permanent) */
@Column(name = "date_fin")
private LocalDate dateFin;
/** Commentaire sur l'attribution */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Méthode métier pour vérifier si l'attribution est active */
public boolean isActif() {
if (!Boolean.TRUE.equals(getActif())) {
return false;
}
LocalDate aujourdhui = LocalDate.now();
if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
return false;
}
if (dateFin != null && aujourdhui.isAfter(dateFin)) {
return false;
}
return true;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateDebut == null) {
dateDebut = LocalDate.now();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,308 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Organisation avec UUID Représente une organisation (Lions Club, Association,
* Coopérative, etc.)
*
* @author UnionFlow Team
* @version 2.0
* @since 2025-01-16
*/
@Entity
@Table(
name = "organisations",
indexes = {
@Index(name = "idx_organisation_nom", columnList = "nom"),
@Index(name = "idx_organisation_email", columnList = "email", unique = true),
@Index(name = "idx_organisation_statut", columnList = "statut"),
@Index(name = "idx_organisation_type", columnList = "type_organisation"),
@Index(name = "idx_organisation_ville", columnList = "ville"),
@Index(name = "idx_organisation_pays", columnList = "pays"),
@Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"),
@Index(
name = "idx_organisation_numero_enregistrement",
columnList = "numero_enregistrement",
unique = true)
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Organisation extends BaseEntity {
@NotBlank
@Column(name = "nom", nullable = false, length = 200)
private String nom;
@Column(name = "nom_court", length = 50)
private String nomCourt;
@NotBlank
@Column(name = "type_organisation", nullable = false, length = 50)
private String typeOrganisation;
@NotBlank
@Column(name = "statut", nullable = false, length = 50)
private String statut;
@Column(name = "description", length = 2000)
private String description;
@Column(name = "date_fondation")
private LocalDate dateFondation;
@Column(name = "numero_enregistrement", unique = true, length = 100)
private String numeroEnregistrement;
// Informations de contact
@Email
@NotBlank
@Column(name = "email", unique = true, nullable = false, length = 255)
private String email;
@Column(name = "telephone", length = 20)
private String telephone;
@Column(name = "telephone_secondaire", length = 20)
private String telephoneSecondaire;
@Email
@Column(name = "email_secondaire", length = 255)
private String emailSecondaire;
// Adresse
@Column(name = "adresse", length = 500)
private String adresse;
@Column(name = "ville", length = 100)
private String ville;
@Column(name = "code_postal", length = 20)
private String codePostal;
@Column(name = "region", length = 100)
private String region;
@Column(name = "pays", length = 100)
private String pays;
// Coordonnées géographiques
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6)
@Column(name = "latitude", precision = 9, scale = 6)
private BigDecimal latitude;
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6)
@Column(name = "longitude", precision = 9, scale = 6)
private BigDecimal longitude;
// Web et réseaux sociaux
@Column(name = "site_web", length = 500)
private String siteWeb;
@Column(name = "logo", length = 500)
private String logo;
@Column(name = "reseaux_sociaux", length = 1000)
private String reseauxSociaux;
// Hiérarchie
@Column(name = "organisation_parente_id")
private UUID organisationParenteId;
@Builder.Default
@Column(name = "niveau_hierarchique", nullable = false)
private Integer niveauHierarchique = 0;
// Statistiques
@Builder.Default
@Column(name = "nombre_membres", nullable = false)
private Integer nombreMembres = 0;
@Builder.Default
@Column(name = "nombre_administrateurs", nullable = false)
private Integer nombreAdministrateurs = 0;
// Finances
@DecimalMin(value = "0.0", message = "Le budget annuel doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "budget_annuel", precision = 14, scale = 2)
private BigDecimal budgetAnnuel;
@Builder.Default
@Column(name = "devise", length = 3)
private String devise = "XOF";
@Builder.Default
@Column(name = "cotisation_obligatoire", nullable = false)
private Boolean cotisationObligatoire = false;
@DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif")
@Digits(integer = 10, fraction = 2)
@Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2)
private BigDecimal montantCotisationAnnuelle;
// Informations complémentaires
@Column(name = "objectifs", length = 2000)
private String objectifs;
@Column(name = "activites_principales", length = 2000)
private String activitesPrincipales;
@Column(name = "certifications", length = 500)
private String certifications;
@Column(name = "partenaires", length = 1000)
private String partenaires;
@Column(name = "notes", length = 1000)
private String notes;
// Paramètres
@Builder.Default
@Column(name = "organisation_publique", nullable = false)
private Boolean organisationPublique = true;
@Builder.Default
@Column(name = "accepte_nouveaux_membres", nullable = false)
private Boolean accepteNouveauxMembres = true;
// Relations
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Membre> membres = new ArrayList<>();
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<CompteWave> comptesWave = new ArrayList<>();
/** Méthode métier pour obtenir le nom complet avec sigle */
public String getNomComplet() {
if (nomCourt != null && !nomCourt.isEmpty()) {
return nom + " (" + nomCourt + ")";
}
return nom;
}
/** Méthode métier pour calculer l'ancienneté en années */
public int getAncienneteAnnees() {
if (dateFondation == null) {
return 0;
}
return Period.between(dateFondation, LocalDate.now()).getYears();
}
/** Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans) */
public boolean isRecente() {
return getAncienneteAnnees() < 2;
}
/** Méthode métier pour vérifier si l'organisation est active */
public boolean isActive() {
return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif());
}
/** Méthode métier pour ajouter un membre */
public void ajouterMembre() {
if (nombreMembres == null) {
nombreMembres = 0;
}
nombreMembres++;
}
/** Méthode métier pour retirer un membre */
public void retirerMembre() {
if (nombreMembres != null && nombreMembres > 0) {
nombreMembres--;
}
}
/** Méthode métier pour activer l'organisation */
public void activer(String utilisateur) {
this.statut = "ACTIVE";
this.setActif(true);
marquerCommeModifie(utilisateur);
}
/** Méthode métier pour suspendre l'organisation */
public void suspendre(String utilisateur) {
this.statut = "SUSPENDUE";
this.accepteNouveauxMembres = false;
marquerCommeModifie(utilisateur);
}
/** Méthode métier pour dissoudre l'organisation */
public void dissoudre(String utilisateur) {
this.statut = "DISSOUTE";
this.setActif(false);
this.accepteNouveauxMembres = false;
marquerCommeModifie(utilisateur);
}
/** Marque l'entité comme modifiée */
public void marquerCommeModifie(String utilisateur) {
this.setDateModification(LocalDateTime.now());
this.setModifiePar(utilisateur);
if (this.getVersion() != null) {
this.setVersion(this.getVersion() + 1);
} else {
this.setVersion(1L);
}
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity
if (statut == null) {
statut = "ACTIVE";
}
if (typeOrganisation == null) {
typeOrganisation = "ASSOCIATION";
}
if (devise == null) {
devise = "XOF";
}
if (niveauHierarchique == null) {
niveauHierarchique = 0;
}
if (nombreMembres == null) {
nombreMembres = 0;
}
if (nombreAdministrateurs == null) {
nombreAdministrateurs = 0;
}
if (organisationPublique == null) {
organisationPublique = true;
}
if (accepteNouveauxMembres == null) {
accepteNouveauxMembres = true;
}
if (cotisationObligatoire == null) {
cotisationObligatoire = false;
}
}
}

View File

@@ -0,0 +1,169 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Paiement centralisée pour tous les types de paiements
* Réutilisable pour cotisations, adhésions, événements, aides
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "paiements",
indexes = {
@Index(name = "idx_paiement_numero_reference", columnList = "numero_reference", unique = true),
@Index(name = "idx_paiement_membre", columnList = "membre_id"),
@Index(name = "idx_paiement_statut", columnList = "statut_paiement"),
@Index(name = "idx_paiement_methode", columnList = "methode_paiement"),
@Index(name = "idx_paiement_date", columnList = "date_paiement")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Paiement extends BaseEntity {
/** Numéro de référence unique */
@NotBlank
@Column(name = "numero_reference", unique = true, nullable = false, length = 50)
private String numeroReference;
/** Montant du paiement */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant", nullable = false, precision = 14, scale = 2)
private BigDecimal montant;
/** Code devise (ISO 3 lettres) */
@NotBlank
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
@Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise;
/** Méthode de paiement */
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "methode_paiement", nullable = false, length = 50)
private MethodePaiement methodePaiement;
/** Statut du paiement */
@NotNull
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "statut_paiement", nullable = false, length = 30)
private StatutPaiement statutPaiement = StatutPaiement.EN_ATTENTE;
/** Date de paiement */
@Column(name = "date_paiement")
private LocalDateTime datePaiement;
/** Date de validation */
@Column(name = "date_validation")
private LocalDateTime dateValidation;
/** Validateur (email de l'administrateur) */
@Column(name = "validateur", length = 255)
private String validateur;
/** Référence externe (numéro transaction, URL preuve, etc.) */
@Column(name = "reference_externe", length = 500)
private String referenceExterne;
/** URL de preuve de paiement */
@Column(name = "url_preuve", length = 1000)
private String urlPreuve;
/** Commentaires et notes */
@Column(name = "commentaire", length = 1000)
private String commentaire;
/** Adresse IP de l'initiateur */
@Column(name = "ip_address", length = 45)
private String ipAddress;
/** User-Agent de l'initiateur */
@Column(name = "user_agent", length = 500)
private String userAgent;
/** Membre payeur */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
/** Relations avec les tables de liaison */
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<PaiementCotisation> paiementsCotisation = new ArrayList<>();
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<PaiementAdhesion> paiementsAdhesion = new ArrayList<>();
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<PaiementEvenement> paiementsEvenement = new ArrayList<>();
@OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<PaiementAide> paiementsAide = new ArrayList<>();
/** Relation avec TransactionWave (optionnelle) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transaction_wave_id")
private TransactionWave transactionWave;
/** Méthode métier pour générer un numéro de référence unique */
public static String genererNumeroReference() {
return "PAY-"
+ LocalDateTime.now().getYear()
+ "-"
+ String.format("%012d", System.currentTimeMillis() % 1000000000000L);
}
/** Méthode métier pour vérifier si le paiement est validé */
public boolean isValide() {
return StatutPaiement.VALIDE.equals(statutPaiement);
}
/** Méthode métier pour vérifier si le paiement peut être modifié */
public boolean peutEtreModifie() {
return !statutPaiement.isFinalise();
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (numeroReference == null || numeroReference.isEmpty()) {
numeroReference = genererNumeroReference();
}
if (codeDevise == null || codeDevise.isEmpty()) {
codeDevise = "XOF";
}
if (statutPaiement == null) {
statutPaiement = StatutPaiement.EN_ATTENTE;
}
if (datePaiement == null) {
datePaiement = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,75 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Paiement et Adhesion
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "paiements_adhesions",
indexes = {
@Index(name = "idx_paiement_adhesion_paiement", columnList = "paiement_id"),
@Index(name = "idx_paiement_adhesion_adhesion", columnList = "adhesion_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_paiement_adhesion",
columnNames = {"paiement_id", "adhesion_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class PaiementAdhesion extends BaseEntity {
/** Paiement */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id", nullable = false)
private Paiement paiement;
/** Adhésion */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "adhesion_id", nullable = false)
private Adhesion adhesion;
/** Montant appliqué à cette adhésion */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
private BigDecimal montantApplique;
/** Date d'application */
@Column(name = "date_application")
private LocalDateTime dateApplication;
/** Commentaire sur l'application */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateApplication == null) {
dateApplication = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,75 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Paiement et DemandeAide
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "paiements_aides",
indexes = {
@Index(name = "idx_paiement_aide_paiement", columnList = "paiement_id"),
@Index(name = "idx_paiement_aide_demande", columnList = "demande_aide_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_paiement_aide",
columnNames = {"paiement_id", "demande_aide_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class PaiementAide extends BaseEntity {
/** Paiement */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id", nullable = false)
private Paiement paiement;
/** Demande d'aide */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demande_aide_id", nullable = false)
private DemandeAide demandeAide;
/** Montant appliqué à cette demande d'aide */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
private BigDecimal montantApplique;
/** Date d'application */
@Column(name = "date_application")
private LocalDateTime dateApplication;
/** Commentaire sur l'application */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateApplication == null) {
dateApplication = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,76 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Paiement et Cotisation
* Permet à un paiement de couvrir plusieurs cotisations
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "paiements_cotisations",
indexes = {
@Index(name = "idx_paiement_cotisation_paiement", columnList = "paiement_id"),
@Index(name = "idx_paiement_cotisation_cotisation", columnList = "cotisation_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_paiement_cotisation",
columnNames = {"paiement_id", "cotisation_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class PaiementCotisation extends BaseEntity {
/** Paiement */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id", nullable = false)
private Paiement paiement;
/** Cotisation */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cotisation_id", nullable = false)
private Cotisation cotisation;
/** Montant appliqué à cette cotisation */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
private BigDecimal montantApplique;
/** Date d'application */
@Column(name = "date_application")
private LocalDateTime dateApplication;
/** Commentaire sur l'application */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateApplication == null) {
dateApplication = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,75 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Paiement et InscriptionEvenement
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "paiements_evenements",
indexes = {
@Index(name = "idx_paiement_evenement_paiement", columnList = "paiement_id"),
@Index(name = "idx_paiement_evenement_inscription", columnList = "inscription_evenement_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_paiement_evenement",
columnNames = {"paiement_id", "inscription_evenement_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class PaiementEvenement extends BaseEntity {
/** Paiement */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id", nullable = false)
private Paiement paiement;
/** Inscription à l'événement */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "inscription_evenement_id", nullable = false)
private InscriptionEvenement inscriptionEvenement;
/** Montant appliqué à cette inscription */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_applique", nullable = false, precision = 14, scale = 2)
private BigDecimal montantApplique;
/** Date d'application */
@Column(name = "date_application")
private LocalDateTime dateApplication;
/** Commentaire sur l'application */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateApplication == null) {
dateApplication = LocalDateTime.now();
}
}
}

View File

@@ -0,0 +1,90 @@
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é Permission pour la gestion des permissions granulaires
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "permissions",
indexes = {
@Index(name = "idx_permission_code", columnList = "code", unique = true),
@Index(name = "idx_permission_module", columnList = "module"),
@Index(name = "idx_permission_ressource", columnList = "ressource")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Permission extends BaseEntity {
/** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
@NotBlank
@Column(name = "code", unique = true, nullable = false, length = 100)
private String code;
/** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
@NotBlank
@Column(name = "module", nullable = false, length = 50)
private String module;
/** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
@NotBlank
@Column(name = "ressource", nullable = false, length = 50)
private String ressource;
/** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
@NotBlank
@Column(name = "action", nullable = false, length = 50)
private String action;
/** Libellé de la permission */
@Column(name = "libelle", length = 200)
private String libelle;
/** Description de la permission */
@Column(name = "description", length = 500)
private String description;
/** Rôles associés */
@OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<RolePermission> roles = new ArrayList<>();
/** Méthode métier pour générer le code à partir des composants */
public static String genererCode(String module, String ressource, String action) {
return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
}
/** Méthode métier pour vérifier si le code est valide */
public boolean isCodeValide() {
return code != null && code.contains(" > ") && code.split(" > ").length == 3;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
// Générer le code si non fourni
if (code == null || code.isEmpty()) {
if (module != null && ressource != null && action != null) {
code = genererCode(module, ressource, action);
}
}
}
}

View File

@@ -0,0 +1,103 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité PieceJointe pour l'association flexible de documents
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "pieces_jointes",
indexes = {
@Index(name = "idx_piece_jointe_document", columnList = "document_id"),
@Index(name = "idx_piece_jointe_membre", columnList = "membre_id"),
@Index(name = "idx_piece_jointe_organisation", columnList = "organisation_id"),
@Index(name = "idx_piece_jointe_cotisation", columnList = "cotisation_id"),
@Index(name = "idx_piece_jointe_adhesion", columnList = "adhesion_id"),
@Index(name = "idx_piece_jointe_demande_aide", columnList = "demande_aide_id"),
@Index(name = "idx_piece_jointe_transaction_wave", columnList = "transaction_wave_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class PieceJointe extends BaseEntity {
/** Ordre d'affichage */
@NotNull
@Min(value = 1, message = "L'ordre doit être positif")
@Column(name = "ordre", nullable = false)
private Integer ordre;
/** Libellé de la pièce jointe */
@Column(name = "libelle", length = 200)
private String libelle;
/** Commentaire */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Document associé */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "document_id", nullable = false)
private Document document;
// Relations flexibles (une seule doit être renseignée)
@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 = "cotisation_id")
private Cotisation cotisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "adhesion_id")
private Adhesion adhesion;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "demande_aide_id")
private DemandeAide demandeAide;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transaction_wave_id")
private TransactionWave transactionWave;
/** Méthode métier pour vérifier qu'une seule relation est renseignée */
public boolean isValide() {
int count = 0;
if (membre != null) count++;
if (organisation != null) count++;
if (cotisation != null) count++;
if (adhesion != null) count++;
if (demandeAide != null) count++;
if (transactionWave != null) count++;
return count == 1; // Exactement une relation doit être renseignée
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (ordre == null) {
ordre = 1;
}
}
}

View File

@@ -0,0 +1,105 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Role pour la gestion des rôles dans le système
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "roles",
indexes = {
@Index(name = "idx_role_code", columnList = "code", unique = true),
@Index(name = "idx_role_actif", columnList = "actif"),
@Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity {
/** Code unique du rôle */
@NotBlank
@Column(name = "code", unique = true, nullable = false, length = 50)
private String code;
/** Libellé du rôle */
@NotBlank
@Column(name = "libelle", nullable = false, length = 100)
private String libelle;
/** Description du rôle */
@Column(name = "description", length = 500)
private String description;
/** Niveau hiérarchique (plus bas = plus prioritaire) */
@NotNull
@Builder.Default
@Column(name = "niveau_hierarchique", nullable = false)
private Integer niveauHierarchique = 100;
/** Type de rôle */
@Enumerated(EnumType.STRING)
@Column(name = "type_role", nullable = false, length = 50)
private TypeRole typeRole;
/** Organisation propriétaire (null pour rôles système) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
/** Permissions associées */
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<RolePermission> permissions = new ArrayList<>();
/** Énumération des types de rôle */
public enum TypeRole {
SYSTEME("Rôle Système"),
ORGANISATION("Rôle Organisation"),
PERSONNALISE("Rôle Personnalisé");
private final String libelle;
TypeRole(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Méthode métier pour vérifier si c'est un rôle système */
public boolean isRoleSysteme() {
return TypeRole.SYSTEME.equals(typeRole);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (typeRole == null) {
typeRole = TypeRole.PERSONNALISE;
}
if (niveauHierarchique == null) {
niveauHierarchique = 100;
}
}
}

View File

@@ -0,0 +1,54 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Role et Permission
* Permet à un rôle d'avoir plusieurs permissions
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "roles_permissions",
indexes = {
@Index(name = "idx_role_permission_role", columnList = "role_id"),
@Index(name = "idx_role_permission_permission", columnList = "permission_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_role_permission",
columnNames = {"role_id", "permission_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class RolePermission extends BaseEntity {
/** Rôle */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private Role role;
/** Permission */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "permission_id", nullable = false)
private Permission permission;
/** Commentaire sur l'association */
@Column(name = "commentaire", length = 500)
private String commentaire;
}

View File

@@ -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";
}
}
}

View File

@@ -0,0 +1,161 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité TransactionWave pour le suivi des transactions Wave Mobile Money
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "transactions_wave",
indexes = {
@Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true),
@Index(name = "idx_transaction_wave_request_id", columnList = "wave_request_id"),
@Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"),
@Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"),
@Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class TransactionWave extends BaseEntity {
/** Identifiant Wave de la transaction (unique) */
@NotBlank
@Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100)
private String waveTransactionId;
/** Identifiant de requête Wave */
@Column(name = "wave_request_id", length = 100)
private String waveRequestId;
/** Référence Wave */
@Column(name = "wave_reference", length = 100)
private String waveReference;
/** Type de transaction */
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "type_transaction", nullable = false, length = 50)
private TypeTransactionWave typeTransaction;
/** Statut de la transaction */
@NotNull
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "statut_transaction", nullable = false, length = 30)
private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE;
/** Montant de la transaction */
@NotNull
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant", nullable = false, precision = 14, scale = 2)
private BigDecimal montant;
/** Frais de transaction */
@DecimalMin(value = "0.0")
@Digits(integer = 10, fraction = 2)
@Column(name = "frais", precision = 12, scale = 2)
private BigDecimal frais;
/** Montant net (montant - frais) */
@DecimalMin(value = "0.0")
@Digits(integer = 12, fraction = 2)
@Column(name = "montant_net", precision = 14, scale = 2)
private BigDecimal montantNet;
/** Code devise */
@NotBlank
@Pattern(regexp = "^[A-Z]{3}$")
@Column(name = "code_devise", nullable = false, length = 3)
private String codeDevise;
/** Numéro téléphone payeur */
@Column(name = "telephone_payeur", length = 13)
private String telephonePayeur;
/** Numéro téléphone bénéficiaire */
@Column(name = "telephone_beneficiaire", length = 13)
private String telephoneBeneficiaire;
/** Métadonnées JSON (réponse complète de Wave API) */
@Column(name = "metadonnees", columnDefinition = "TEXT")
private String metadonnees;
/** Réponse complète de Wave API (JSON) */
@Column(name = "reponse_wave_api", columnDefinition = "TEXT")
private String reponseWaveApi;
/** Nombre de tentatives */
@Builder.Default
@Column(name = "nombre_tentatives", nullable = false)
private Integer nombreTentatives = 0;
/** Date de dernière tentative */
@Column(name = "date_derniere_tentative")
private LocalDateTime dateDerniereTentative;
/** Message d'erreur (si échec) */
@Column(name = "message_erreur", length = 1000)
private String messageErreur;
// Relations
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compte_wave_id", nullable = false)
private CompteWave compteWave;
@OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<WebhookWave> webhooks = new ArrayList<>();
/** Méthode métier pour vérifier si la transaction est réussie */
public boolean isReussie() {
return StatutTransactionWave.REUSSIE.equals(statutTransaction);
}
/** Méthode métier pour vérifier si la transaction peut être retentée */
public boolean peutEtreRetentee() {
return (statutTransaction == StatutTransactionWave.ECHOUE
|| statutTransaction == StatutTransactionWave.EXPIRED)
&& (nombreTentatives == null || nombreTentatives < 5);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (statutTransaction == null) {
statutTransaction = StatutTransactionWave.INITIALISE;
}
if (codeDevise == null || codeDevise.isEmpty()) {
codeDevise = "XOF";
}
if (nombreTentatives == null) {
nombreTentatives = 0;
}
if (montantNet == null && montant != null && frais != null) {
montantNet = montant.subtract(frais);
}
}
}

View File

@@ -0,0 +1,73 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
/**
* Entité persistée représentant un type d'organisation.
*
* <p>Cette entité permet de gérer dynamiquement le catalogue des types d'organisations
* (codes, libellés, description, ordre d'affichage, activation/désactivation).
*
* <p>Le champ {@code code} doit rester synchronisé avec l'enum {@link
* dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation} pour les types
* standards fournis par la plateforme.
*/
@Entity
@Table(
name = "uf_type_organisation",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_type_organisation_code",
columnNames = {"code"})
})
public class TypeOrganisationEntity extends BaseEntity {
@Column(name = "code", length = 50, nullable = false, unique = true)
private String code;
@Column(name = "libelle", length = 150, nullable = false)
private String libelle;
@Column(name = "description", length = 500)
private String description;
@Column(name = "ordre_affichage")
private Integer ordreAffichage;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getOrdreAffichage() {
return ordreAffichage;
}
public void setOrdreAffichage(Integer ordreAffichage) {
this.ordreAffichage = ordreAffichage;
}
}

View File

@@ -0,0 +1,118 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité WebhookWave pour le traitement des événements Wave
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "webhooks_wave",
indexes = {
@Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true),
@Index(name = "idx_webhook_wave_statut", columnList = "statut_traitement"),
@Index(name = "idx_webhook_wave_type", columnList = "type_evenement"),
@Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"),
@Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class WebhookWave extends BaseEntity {
/** Identifiant unique de l'événement Wave */
@NotBlank
@Column(name = "wave_event_id", unique = true, nullable = false, length = 100)
private String waveEventId;
/** Type d'événement */
@Enumerated(EnumType.STRING)
@Column(name = "type_evenement", length = 50)
private TypeEvenementWebhook typeEvenement;
/** Statut de traitement */
@Enumerated(EnumType.STRING)
@Builder.Default
@Column(name = "statut_traitement", nullable = false, length = 30)
private StatutWebhook statutTraitement = StatutWebhook.EN_ATTENTE;
/** Payload JSON reçu */
@Column(name = "payload", columnDefinition = "TEXT")
private String payload;
/** Signature de validation */
@Column(name = "signature", length = 500)
private String signature;
/** Date de réception */
@Column(name = "date_reception")
private LocalDateTime dateReception;
/** Date de traitement */
@Column(name = "date_traitement")
private LocalDateTime dateTraitement;
/** Nombre de tentatives de traitement */
@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;
/** Commentaires */
@Column(name = "commentaire", length = 500)
private String commentaire;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transaction_wave_id")
private TransactionWave transactionWave;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "paiement_id")
private Paiement paiement;
/** Méthode métier pour vérifier si le webhook est traité */
public boolean isTraite() {
return StatutWebhook.TRAITE.equals(statutTraitement);
}
/** Méthode métier pour vérifier si le webhook peut être retenté */
public boolean peutEtreRetente() {
return (statutTraitement == StatutWebhook.ECHOUE || statutTraitement == StatutWebhook.EN_ATTENTE)
&& (nombreTentatives == null || nombreTentatives < 5);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (statutTraitement == null) {
statutTraitement = StatutWebhook.EN_ATTENTE;
}
if (dateReception == null) {
dateReception = LocalDateTime.now();
}
if (nombreTentatives == null) {
nombreTentatives = 0;
}
}
}