From f930ae734102f6a09c0f57a02a6dd9fb842b3597 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 30 Nov 2025 01:31:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20PHASE=201=20-=20Adresses=20et=20R=C3=B4?= =?UTF-8?q?les/Permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PHASE 1.1 - Entité Adresse: - Création entité Adresse avec types (SIEGE_SOCIAL, BUREAU, DOMICILE, AUTRE) - Relations flexibles: Organisation, Membre, Evenement - Enum TypeAdresse dans module API (DRY/WOU) - Repository et Service AdresseService - Relations bidirectionnelles mises à jour PHASE 1.2 - Système Rôles et Permissions: - Entité Role avec types (SYSTEME, ORGANISATION, PERSONNALISE) - Entité Permission avec structure MODULE > RESSOURCE > ACTION - Tables de liaison MembreRole et RolePermission - Repositories pour toutes les entités - Services RoleService et PermissionService - Relations bidirectionnelles dans Membre Respect strict DRY/WOU: - Enums dans module API réutilisables - Patterns de service cohérents - Relations JPA standardisées --- PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md | 310 ++++ union-flow.puml | 275 ++++ .../client/view/MembreInscriptionBean.java | 63 +- .../client/view/MembreListeBean.java | 20 +- .../pages/secure/membre/inscription.xhtml | 31 +- .../resources/pages/secure/membre/liste.xhtml | 2 +- .../server/api/dto/adresse/AdresseDTO.java | 82 ++ .../server/api/enums/adresse/TypeAdresse.java | 26 + .../unionflow/server/entity/Adresse.java | 154 ++ .../unionflow/server/entity/Evenement.java | 4 + .../lions/unionflow/server/entity/Membre.java | 10 + .../unionflow/server/entity/MembreRole.java | 88 ++ .../unionflow/server/entity/Organisation.java | 4 + .../unionflow/server/entity/Permission.java | 90 ++ .../lions/unionflow/server/entity/Role.java | 105 ++ .../server/entity/RolePermission.java | 54 + .../server/repository/AdresseRepository.java | 101 ++ .../repository/MembreRoleRepository.java | 66 + .../repository/PermissionRepository.java | 73 + .../repository/RolePermissionRepository.java | 50 + .../server/repository/RoleRepository.java | 75 + .../server/service/AdresseService.java | 358 +++++ .../server/service/PermissionService.java | 165 +++ .../unionflow/server/service/RoleService.java | 171 +++ unionflow.md | 1297 +++++++++++++++++ 25 files changed, 3583 insertions(+), 91 deletions(-) create mode 100644 PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md create mode 100644 union-flow.puml create mode 100644 unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/dto/adresse/AdresseDTO.java create mode 100644 unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/adresse/TypeAdresse.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Adresse.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Permission.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Role.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/AdresseService.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/PermissionService.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/RoleService.java create mode 100644 unionflow.md diff --git a/PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md b/PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md new file mode 100644 index 0000000..c9c0ba1 --- /dev/null +++ b/PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md @@ -0,0 +1,310 @@ +# Plan d'Implémentation - Architecture UnionFlow v3.0 + +**Date** : 2025-01-29 +**Objectif** : Aligner le code actuel avec l'architecture cible (union-flow.puml) + +--- + +## 📊 État Actuel vs Architecture Cible + +### ✅ Entités Existantes +- ✅ BaseEntity +- ✅ Organisation +- ✅ TypeOrganisationEntity +- ✅ Membre +- ✅ Cotisation +- ✅ Adhesion +- ✅ Evenement +- ✅ InscriptionEvenement +- ✅ DemandeAide +- ✅ AuditLog + +### ❌ Entités Manquantes +1. **Paiements** : Paiement, PaiementCotisation, PaiementAdhesion, PaiementEvenement, PaiementAide +2. **Wave** : CompteWave, TransactionWave, WebhookWave, ConfigurationWave +3. **Comptabilité** : CompteComptable, JournalComptable, EcritureComptable, LigneEcriture +4. **Documents** : Document, PieceJointe +5. **Notifications** : Notification, TemplateNotification +6. **Rôles/Permissions** : Role, Permission, MembreRole, RolePermission +7. **Adresses** : Adresse (séparée) + +--- + +## 🎯 Plan d'Implémentation par Étapes + +### **PHASE 1 : FONDATIONS - Adresses et Rôles** (Priorité HAUTE) +**Durée estimée** : 2-3 jours + +#### Étape 1.1 : Entité Adresse +- [ ] Créer `Adresse.java` (entité séparée) +- [ ] Types d'adresse : SIEGE_SOCIAL, BUREAU, DOMICILE, AUTRE +- [ ] Relations : Organisation ↔ Adresse (0..*), Membre ↔ Adresse (0..*) +- [ ] Migration : Extraire adresses de Organisation et Membre +- [ ] Repository : `AdresseRepository` +- [ ] Service : `AdresseService` +- [ ] DTO : `AdresseDTO` + +#### Étape 1.2 : Système de Rôles et Permissions +- [ ] Créer `Role.java` (entité) +- [ ] Créer `Permission.java` (entité) +- [ ] Créer `MembreRole.java` (table de liaison) +- [ ] Créer `RolePermission.java` (table de liaison) +- [ ] Enums : TypeRole, TypePermission +- [ ] Repository : `RoleRepository`, `PermissionRepository`, `MembreRoleRepository`, `RolePermissionRepository` +- [ ] Service : `RoleService`, `PermissionService` +- [ ] DTOs : `RoleDTO`, `PermissionDTO`, `MembreRoleDTO` + +--- + +### **PHASE 2 : SYSTÈME DE PAIEMENTS CENTRALISÉ** (Priorité CRITIQUE) +**Durée estimée** : 3-4 jours + +#### Étape 2.1 : Entité Paiement +- [ ] Créer `Paiement.java` (entité centrale) +- [ ] Enum : `MethodePaiement` (WAVE_MOBILE_MONEY, ORANGE_MONEY, MTN_MOBILE_MONEY, etc.) +- [ ] Enum : `StatutPaiement` (EN_ATTENTE, EN_COURS, VALIDE, ECHOUE, ANNULE, REMBOURSE) +- [ ] Champs : montant, devise, datePaiement, dateValidation, validateur, references externes +- [ ] Relation : Paiement → Membre (1-N) +- [ ] Repository : `PaiementRepository` +- [ ] Service : `PaiementService` +- [ ] DTO : `PaiementDTO` + +#### Étape 2.2 : Tables de Liaison Paiements +- [ ] Créer `PaiementCotisation.java` (table de liaison) +- [ ] Créer `PaiementAdhesion.java` (table de liaison) +- [ ] Créer `PaiementEvenement.java` (table de liaison) +- [ ] Créer `PaiementAide.java` (table de liaison) +- [ ] Champs communs : montantApplique, dateApplication +- [ ] Relations : Paiement ↔ Cotisation/Adhesion/Evenement/Aide +- [ ] Repositories : `PaiementCotisationRepository`, etc. +- [ ] Services : Logique d'application des paiements + +#### Étape 2.3 : Refactoring Cotisation et Adhesion +- [ ] Modifier `Cotisation.java` : Retirer montantPaye, utiliser PaiementCotisation +- [ ] Modifier `Adhesion.java` : Retirer montantPaye, utiliser PaiementAdhesion +- [ ] Mettre à jour `CotisationService` : Utiliser PaiementService +- [ ] Mettre à jour `AdhesionService` : Utiliser PaiementService +- [ ] Migration des données existantes + +--- + +### **PHASE 3 : INTÉGRATION WAVE MOBILE MONEY** (Priorité CRITIQUE) +**Durée estimée** : 4-5 jours + +#### Étape 3.1 : Entités Wave +- [ ] Créer `CompteWave.java` + - Numéro téléphone (+225XXXXXXXX) + - Statut : NON_VERIFIE, VERIFIE, SUSPENDU, BLOQUE + - Relations : Organisation (1-N), Membre (0..1) + - Identifiants API encryptés +- [ ] Créer `TransactionWave.java` + - Identifiants Wave (transactionId, requestId, reference) + - Type : DEPOT, RETRAIT, TRANSFERT, PAIEMENT, REMBOURSEMENT + - Statut : INITIALISE, EN_ATTENTE, EN_COURS, REUSSIE, ECHOUE, ANNULEE, EXPIRED + - Montant, frais, montant net + - Métadonnées JSON + - Relation : CompteWave (1-N), Paiement (0..1) +- [ ] Créer `WebhookWave.java` + - Wave event ID + - Type d'événement + - Statut traitement : EN_ATTENTE, EN_TRAITEMENT, TRAITE, ECHOUE, IGNORE + - Payload JSON + - Relation : TransactionWave (0..1), Paiement (0..1) +- [ ] Créer `ConfigurationWave.java` + - Clé-valeur pour configuration + - Support sandbox/production + +#### Étape 3.2 : Repositories et Services Wave +- [ ] Repositories : `CompteWaveRepository`, `TransactionWaveRepository`, `WebhookWaveRepository`, `ConfigurationWaveRepository` +- [ ] Service : `WaveService` (intégration API Wave) + - Méthodes : initierPaiement, verifierTransaction, traiterWebhook + - Gestion retry avec backoff exponentiel + - Validation de signature webhook + - Chiffrement des clés API + +#### Étape 3.3 : Intégration avec PaiementService +- [ ] Modifier `PaiementService` : Support Wave +- [ ] Workflow : Initiation → TransactionWave → Webhook → Validation +- [ ] Gestion des erreurs et retry + +--- + +### **PHASE 4 : COMPTABILITÉ** (Priorité MOYENNE) +**Durée estimée** : 3-4 jours + +#### Étape 4.1 : Plan Comptable +- [ ] Créer `CompteComptable.java` + - Numéro compte unique + - Type : ACTIF, PASSIF, CHARGES, PRODUITS, TRESORERIE, AUTRE + - Classe comptable (1-7) + - Solde initial, solde actuel +- [ ] Repository : `CompteComptableRepository` +- [ ] Service : `CompteComptableService` +- [ ] DTO : `CompteComptableDTO` + +#### Étape 4.2 : Journaux et Écritures +- [ ] Créer `JournalComptable.java` + - Code unique + - Type : ACHATS, VENTES, BANQUE, CAISSE, OD + - Période, statut +- [ ] Créer `EcritureComptable.java` + - Numéro pièce unique + - Date, libellé, référence + - Lettrage, pointage + - Relation : JournalComptable (1-N), Organisation (1-N), Paiement (0..1) +- [ ] Créer `LigneEcriture.java` + - Numéro ligne + - Compte débiteur/créditeur + - Montant débit/crédit + - Relation : EcritureComptable (1-N), CompteComptable (1-N) + - Validation : Débit = Crédit + +#### Étape 4.3 : Service Comptable +- [ ] Service : `ComptabiliteService` + - Génération automatique d'écritures pour paiements + - Rapprochement bancaire + - Pointage et lettrage +- [ ] Intégration avec PaiementService + +--- + +### **PHASE 5 : GESTION DOCUMENTAIRE** (Priorité MOYENNE) +**Durée estimée** : 2-3 jours + +#### Étape 5.1 : Entités Documents +- [ ] Créer `Document.java` + - Nom fichier, nom original + - Chemin stockage + - Type MIME, taille + - Hash MD5, SHA256 + - Type : IDENTITE, JUSTIFICATIF_DOMICILE, PHOTO, CONTRAT, FACTURE, RECU, RAPPORT, AUTRE +- [ ] Créer `PieceJointe.java` + - Ordre d'affichage + - Libellé, commentaire + - Relations flexibles : Membre, Organisation, Cotisation, Adhesion, DemandeAide, TransactionWave + +#### Étape 5.2 : Services Documents +- [ ] Repositories : `DocumentRepository`, `PieceJointeRepository` +- [ ] Service : `DocumentService` + - Upload sécurisé + - Vérification intégrité (hash) + - Contrôle d'accès + - Audit téléchargements + +--- + +### **PHASE 6 : SYSTÈME DE NOTIFICATIONS** (Priorité MOYENNE) +**Durée estimée** : 2-3 jours + +#### Étape 6.1 : Entités Notifications +- [ ] Créer `TemplateNotification.java` + - Code unique + - Sujet, corps (texte et HTML) + - Variables disponibles (JSON) + - Canaux supportés + - Support multi-langues +- [ ] Créer `Notification.java` + - Type : EMAIL, SMS, PUSH, IN_APP, SYSTEME + - Priorité : CRITIQUE, HAUTE, NORMALE, BASSE + - Statut : EN_ATTENTE, ENVOYEE, LUE, ECHOUE, ANNULEE + - Relations : Membre (1-N), Organisation (0..1), TemplateNotification (0..1) + +#### Étape 6.2 : Service Notifications +- [ ] Repositories : `NotificationRepository`, `TemplateNotificationRepository` +- [ ] Service : `NotificationService` + - Envoi multi-canaux + - Retry automatique + - Priorisation + - Templates réutilisables + +--- + +### **PHASE 7 : MISE À JOUR MEMBRE** (Priorité HAUTE) +**Durée estimée** : 1-2 jours + +#### Étape 7.1 : Ajout Champs Membre +- [ ] Ajouter `telephoneWave` (String, format +225XXXXXXXX) +- [ ] Ajouter `photoUrl` (String) +- [ ] Relation : Membre → CompteWave (0..1) +- [ ] Relation : Membre → Adresse (0..*) +- [ ] Relation : Membre → MembreRole (1-N) + +#### Étape 7.2 : Migration Données +- [ ] Script de migration pour extraire adresses +- [ ] Attribution rôles par défaut +- [ ] Validation format téléphone Wave + +--- + +### **PHASE 8 : MISE À JOUR ORGANISATION** (Priorité MOYENNE) +**Durée estimée** : 1 jour + +#### Étape 8.1 : Relations Organisation +- [ ] Relation : Organisation → CompteWave (1-N) +- [ ] Relation : Organisation → Adresse (0..*) +- [ ] Migration : Extraire adresses vers entité Adresse + +--- + +### **PHASE 9 : MISE À JOUR ÉVÉNEMENT** (Priorité MOYENNE) +**Durée estimée** : 1 jour + +#### Étape 9.1 : Relations Evenement +- [ ] Relation : Evenement → Adresse (0..1) +- [ ] Relation : Evenement → PaiementEvenement (0..*) +- [ ] Migration : Extraire adresse vers entité Adresse + +--- + +### **PHASE 10 : RESSOURCES REST ET DTOs** (Priorité HAUTE) +**Durée estimée** : 3-4 jours + +#### Étape 10.1 : DTOs API +- [ ] Créer tous les DTOs manquants dans `unionflow-server-api` +- [ ] Enums dans `unionflow-server-api` + +#### Étape 10.2 : Resources REST +- [ ] `PaiementResource` +- [ ] `WaveResource` (CompteWave, TransactionWave, WebhookWave) +- [ ] `ComptabiliteResource` (CompteComptable, JournalComptable, EcritureComptable) +- [ ] `DocumentResource` +- [ ] `NotificationResource` +- [ ] `RoleResource`, `PermissionResource` +- [ ] `AdresseResource` + +--- + +### **PHASE 11 : TESTS ET VALIDATION** (Priorité HAUTE) +**Durée estimée** : 2-3 jours + +#### Étape 11.1 : Tests Unitaires +- [ ] Tests pour toutes les nouvelles entités +- [ ] Tests pour tous les services +- [ ] Tests d'intégration Wave (mock) + +#### Étape 11.2 : Tests d'Intégration +- [ ] Tests de workflow complet paiement +- [ ] Tests webhooks Wave +- [ ] Tests génération écritures comptables + +--- + +## 📋 Ordre d'Implémentation Recommandé + +1. **PHASE 1** : Adresses et Rôles (fondations) +2. **PHASE 2** : Système de Paiements (critique) +3. **PHASE 7** : Mise à jour Membre (dépend de Phase 1) +4. **PHASE 3** : Intégration Wave (dépend de Phase 2) +5. **PHASE 4** : Comptabilité (dépend de Phase 2) +6. **PHASE 5** : Documents +7. **PHASE 6** : Notifications +8. **PHASE 8-9** : Mises à jour Organisation/Evenement +9. **PHASE 10** : Resources REST +10. **PHASE 11** : Tests + +--- + +## 🚀 Démarrage de l'Implémentation + +**Prochaine étape** : Commencer par la PHASE 1 - Étape 1.1 : Création de l'entité Adresse + diff --git a/union-flow.puml b/union-flow.puml new file mode 100644 index 0000000..7b55064 --- /dev/null +++ b/union-flow.puml @@ -0,0 +1,275 @@ +@startuml MCD_UnionFlow +!theme plain +skinparam linetype ortho +skinparam packageStyle rectangle +skinparam classAttributeIconSize 0 + +title UnionFlow - MCD avec Wave Mobile Money + +package Base { +abstract class BaseEntity { + + id : UUID + + dateCreation : LocalDateTime + + version : Long + + actif : Boolean +} +} + +package Orgs { +class Organisation { + + nom : String + + email : String + + statut : StatutOrg +} +class TypeOrganisation { + + code : String +} +class Adresse { + + ville : String + + pays : String +} +} + +package Membres { +class Membre { + + numeroMembre : String + + email : String + + telephoneWave : String +} +class Role { + + code : String +} +class Permission { + + code : String +} +class MembreRole { +} +class RolePermission { +} +} + +package Paiements { +class Paiement { + + montant : BigDecimal + + methodePaiement : MethodePmt + + statutPaiement : StatutPmt +} +class Cotisation { + + montantDu : BigDecimal + + statut : StatutCot +} +class PaiementCotisation { + + montantApplique : BigDecimal +} +class Adhesion { + + fraisAdhesion : BigDecimal + + statut : StatutAdh +} +class PaiementAdhesion { + + montantApplique : BigDecimal +} +} + +package Wave { +class CompteWave { + + numeroTelephone : String + + statutCompte : StatutWave +} +class TransactionWave { + + waveTransactionId : String + + montant : BigDecimal + + statutTransaction : StatutTxWave +} +class WebhookWave { + + waveEventId : String + + statutTraitement : StatutWebhook +} +class ConfigurationWave { + + cle : String + + valeur : String +} +} + +package Compta { +class CompteComptable { + + numeroCompte : String + + soldeActuel : BigDecimal +} +class JournalComptable { + + code : String +} +class EcritureComptable { + + montantDebit : BigDecimal + + montantCredit : BigDecimal +} +class LigneEcriture { + + montantDebit : BigDecimal + + montantCredit : BigDecimal +} +} + +package Evenements { +class Evenement { + + titre : String + + typeEvenement : TypeEvt + + statut : StatutEvt +} +class InscriptionEvenement { + + statut : StatutInsc +} +class PaiementEvenement { + + montantApplique : BigDecimal +} +} + +package Solidarite { +class DemandeAide { + + typeAide : TypeAide + + statut : StatutAide + + montantDemande : BigDecimal +} +class PaiementAide { + + montantApplique : BigDecimal +} +} + +package Docs { +class Document { + + nomFichier : String + + hashMd5 : String +} +class PieceJointe { + + ordre : Integer +} +} + +package Notifs { +class Notification { + + typeNotification : TypeNotif + + statut : StatutNotif +} +class TemplateNotification { + + code : String +} +} + +package Audit { +class AuditLog { + + typeAction : TypeAction + + severite : Severite +} +class ParametreSysteme { + + cle : String + + valeur : String +} +} + +BaseEntity <|-- Organisation +BaseEntity <|-- TypeOrganisation +BaseEntity <|-- Adresse +BaseEntity <|-- Membre +BaseEntity <|-- Role +BaseEntity <|-- Permission +BaseEntity <|-- MembreRole +BaseEntity <|-- RolePermission +BaseEntity <|-- Paiement +BaseEntity <|-- Cotisation +BaseEntity <|-- PaiementCotisation +BaseEntity <|-- Adhesion +BaseEntity <|-- PaiementAdhesion +BaseEntity <|-- CompteWave +BaseEntity <|-- TransactionWave +BaseEntity <|-- WebhookWave +BaseEntity <|-- ConfigurationWave +BaseEntity <|-- CompteComptable +BaseEntity <|-- JournalComptable +BaseEntity <|-- EcritureComptable +BaseEntity <|-- LigneEcriture +BaseEntity <|-- Evenement +BaseEntity <|-- InscriptionEvenement +BaseEntity <|-- PaiementEvenement +BaseEntity <|-- DemandeAide +BaseEntity <|-- PaiementAide +BaseEntity <|-- Document +BaseEntity <|-- PieceJointe +BaseEntity <|-- Notification +BaseEntity <|-- TemplateNotification +BaseEntity <|-- AuditLog +BaseEntity <|-- ParametreSysteme + +Organisation "1" *-- "0..*" Membre +Organisation "0..1" --o "0..*" Organisation +Organisation "1" *-- "1" TypeOrganisation +Organisation "0..1" --o "0..*" Adresse +Organisation "1" *-- "0..*" CompteWave + +Membre "1" *-- "0..*" MembreRole +MembreRole "1" *-- "1" Role +Membre "0..1" --o "0..*" Adresse +Membre "0..1" --o "0..*" CompteWave + +Role "1" *-- "0..*" RolePermission +RolePermission "1" *-- "1" Permission + +Paiement "1" *-- "0..*" PaiementCotisation +Paiement "1" *-- "0..*" PaiementAdhesion +Paiement "1" *-- "0..*" PaiementEvenement +Paiement "1" *-- "0..*" PaiementAide +Paiement "0..1" --o "1" TransactionWave +Paiement "1" *-- "1" Membre + +Cotisation "1" *-- "0..*" PaiementCotisation +PaiementCotisation "1" *-- "1" Paiement +Cotisation "1" *-- "1" Membre +Cotisation "1" *-- "1" Organisation + +Adhesion "1" *-- "0..*" PaiementAdhesion +PaiementAdhesion "1" *-- "1" Paiement +Adhesion "1" *-- "1" Membre +Adhesion "1" *-- "1" Organisation + +CompteWave "1" *-- "0..*" TransactionWave +TransactionWave "1" *-- "0..*" WebhookWave +WebhookWave "0..1" --o "1" TransactionWave +WebhookWave "0..1" --o "1" Paiement +CompteWave "1" *-- "1" Organisation +CompteWave "0..1" --o "1" Membre + +JournalComptable "1" *-- "0..*" EcritureComptable +EcritureComptable "1" *-- "1..*" LigneEcriture +LigneEcriture "1" *-- "1" CompteComptable +EcritureComptable "0..1" --o "1" Paiement +EcritureComptable "1" *-- "1" Organisation + +Evenement "1" *-- "0..*" InscriptionEvenement +InscriptionEvenement "1" *-- "1" Membre +InscriptionEvenement "1" *-- "1" Evenement +Evenement "1" *-- "1" Organisation +Evenement "0..1" --o "1" Adresse +Evenement "0..1" --o "0..*" PaiementEvenement +PaiementEvenement "1" *-- "1" Paiement +PaiementEvenement "1" *-- "1" InscriptionEvenement + +DemandeAide "1" *-- "0..*" PaiementAide +PaiementAide "1" *-- "1" Paiement +DemandeAide "1" *-- "1" Membre +DemandeAide "0..1" --o "1" Membre +DemandeAide "1" *-- "1" Organisation + +PieceJointe "1" *-- "1" Document +Document "0..1" --o "0..*" PieceJointe +PieceJointe "0..1" --o "1" Membre +PieceJointe "0..1" --o "1" Organisation +PieceJointe "0..1" --o "1" Cotisation +PieceJointe "0..1" --o "1" Adhesion +PieceJointe "0..1" --o "1" DemandeAide +PieceJointe "0..1" --o "1" TransactionWave + +Notification "0..1" --o "1" TemplateNotification +Notification "1" *-- "1" Membre +Notification "0..1" --o "1" Organisation + +AuditLog "0..1" --o "1" Membre +AuditLog "0..1" --o "1" Organisation + +@enduml diff --git a/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java b/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java index 4089a79..2de1930 100644 --- a/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java +++ b/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java @@ -42,8 +42,6 @@ public class MembreInscriptionBean implements Serializable { // Propriétés système private String numeroGenere; - private String membreIdString; // ID du membre en mode modification - private boolean modeModification = false; // Informations personnelles private String prenom; @@ -105,10 +103,8 @@ public class MembreInscriptionBean implements Serializable { @PostConstruct public void init() { - // Générer un numéro de membre automatiquement (seulement en mode création) - if (!modeModification) { - this.numeroGenere = "M" + System.currentTimeMillis(); - } + // Générer un numéro de membre automatiquement + this.numeroGenere = "M" + System.currentTimeMillis(); // Charger les organisations actives try { @@ -120,61 +116,6 @@ public class MembreInscriptionBean implements Serializable { } } - // Getters/Setters pour mode modification - public String getMembreIdString() { - return membreIdString; - } - - public void setMembreIdString(String membreIdString) { - this.membreIdString = membreIdString; - } - - public boolean isModeModification() { - return modeModification; - } - - // Méthode appelée par f:viewAction pour charger le membre en mode modification - public void chargerMembreSiModification() { - if (membreIdString != null && !membreIdString.isEmpty()) { - try { - java.util.UUID id = java.util.UUID.fromString(membreIdString); - MembreDTO membre = membreService.obtenirParId(id); - - if (membre != null) { - modeModification = true; - // Remplir tous les champs avec les données du membre - this.numeroGenere = membre.getNumeroMembre(); - this.prenom = membre.getPrenom(); - this.nom = membre.getNom(); - this.email = membre.getEmail(); - this.telephone = membre.getTelephone(); - this.dateNaissance = membre.getDateNaissance(); - this.adresse = membre.getAdresse(); - this.ville = membre.getVille(); - this.nationalite = membre.getNationalite(); - this.profession = membre.getProfession(); - this.situationMatrimoniale = membre.getStatutMatrimonial(); - this.organisationId = membre.getAssociationId() != null ? membre.getAssociationId().toString() : null; - // TODO: Charger les autres champs si disponibles - - LOGGER.info("Membre chargé pour modification: " + membre.getNomComplet()); - } else { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Membre introuvable")); - } - } catch (IllegalArgumentException e) { - LOGGER.severe("ID invalide: " + membreIdString); - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Identifiant de membre invalide")); - } catch (Exception e) { - LOGGER.severe("Erreur lors du chargement du membre: " + e.getMessage()); - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", - "Impossible de charger le membre: " + e.getMessage())); - } - } - } - // Actions public String inscrire() { try { diff --git a/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java b/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java index 5cd795d..d404bc0 100644 --- a/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java +++ b/unionflow-client-quarkus-primefaces-freya/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java @@ -196,15 +196,15 @@ public class MembreListeBean implements Serializable { } // Propriétés pour la page de modification - private String membreSelectionneIdString; + private UUID membreSelectionneId; private MembreDTO membreSelectionne; - public String getMembreSelectionneIdString() { - return membreSelectionneIdString; + public UUID getMembreSelectionneId() { + return membreSelectionneId; } - public void setMembreSelectionneIdString(String membreSelectionneIdString) { - this.membreSelectionneIdString = membreSelectionneIdString; + public void setMembreSelectionneId(UUID membreSelectionneId) { + this.membreSelectionneId = membreSelectionneId; } public MembreDTO getMembreSelectionne() { @@ -216,16 +216,10 @@ public class MembreListeBean implements Serializable { } public void chargerMembreSelectionne() { - if (membreSelectionneIdString != null && !membreSelectionneIdString.isEmpty()) { + if (membreSelectionneId != null) { try { - UUID id = UUID.fromString(membreSelectionneIdString); - membreSelectionne = membreService.obtenirParId(id); + membreSelectionne = membreService.obtenirParId(membreSelectionneId); LOGGER.info("Membre chargé pour modification: " + membreSelectionne.getNomComplet()); - } catch (IllegalArgumentException e) { - LOGGER.severe("ID invalide: " + membreSelectionneIdString); - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", - "Identifiant de membre invalide")); } catch (Exception e) { LOGGER.severe("Erreur lors du chargement du membre: " + e.getMessage()); FacesContext.getCurrentInstance().addMessage(null, diff --git a/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/inscription.xhtml b/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/inscription.xhtml index 8577b83..3fa2267 100644 --- a/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/inscription.xhtml +++ b/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/inscription.xhtml @@ -6,24 +6,18 @@ xmlns:p="http://primefaces.org/ui" template="/templates/main-template.xhtml"> - #{empty param.id ? 'Inscription' : 'Modification'} Membre - UnionFlow - - - - - - + Inscription Membre - UnionFlow - - - + + +
Numéro: #{membreInscriptionBean.numeroGenere}
- #{empty param.id ? 'Généré automatiquement' : 'Existant'} + Généré automatiquement
@@ -100,7 +94,7 @@
- @@ -113,7 +107,7 @@
- + @@ -148,6 +142,7 @@
@@ -155,12 +150,13 @@
- + @@ -221,6 +217,7 @@
@@ -228,7 +225,8 @@
- +
@@ -255,6 +253,7 @@
@@ -279,7 +278,7 @@
- + diff --git a/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/liste.xhtml b/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/liste.xhtml index 9141829..14680f7 100644 --- a/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/liste.xhtml +++ b/unionflow-client-quarkus-primefaces-freya/src/main/resources/META-INF/resources/pages/secure/membre/liste.xhtml @@ -197,7 +197,7 @@ iconOnly="true"/> diff --git a/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/dto/adresse/AdresseDTO.java b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/dto/adresse/AdresseDTO.java new file mode 100644 index 0000000..c7a3257 --- /dev/null +++ b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/dto/adresse/AdresseDTO.java @@ -0,0 +1,82 @@ +package dev.lions.unionflow.server.api.dto.adresse; + +import dev.lions.unionflow.server.api.dto.base.BaseDTO; +import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +/** + * DTO pour la gestion des adresses + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Getter +@Setter +public class AdresseDTO extends BaseDTO { + + private static final long serialVersionUID = 1L; + + /** Type d'adresse */ + @NotNull(message = "Le type d'adresse est obligatoire") + private TypeAdresse typeAdresse; + + /** Adresse complète */ + private String adresse; + + /** Complément d'adresse */ + private String complementAdresse; + + /** Code postal */ + private String codePostal; + + /** Ville */ + private String ville; + + /** Région */ + private String region; + + /** Pays */ + private String pays; + + /** 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) + private BigDecimal latitude; + + /** 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) + private BigDecimal longitude; + + /** Adresse principale */ + private Boolean principale; + + /** Libellé personnalisé */ + private String libelle; + + /** Notes et commentaires */ + private String notes; + + /** ID de l'organisation (si adresse d'organisation) */ + private UUID organisationId; + + /** ID du membre (si adresse de membre) */ + private UUID membreId; + + /** ID de l'événement (si adresse d'événement) */ + private UUID evenementId; + + /** Adresse complète formatée (calculée) */ + private String adresseComplete; +} + diff --git a/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/adresse/TypeAdresse.java b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/adresse/TypeAdresse.java new file mode 100644 index 0000000..d713d30 --- /dev/null +++ b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/adresse/TypeAdresse.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.enums.adresse; + +/** + * Énumération des types d'adresse dans UnionFlow + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +public enum TypeAdresse { + SIEGE_SOCIAL("Siège Social"), + BUREAU("Bureau"), + DOMICILE("Domicile"), + AUTRE("Autre"); + + private final String libelle; + + TypeAdresse(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Adresse.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Adresse.java new file mode 100644 index 0000000..710c249 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Adresse.java @@ -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 dev.lions.unionflow.server.api.enums.adresse.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; + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java index b8d1d10..5f1ccc1 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Evenement.java @@ -118,6 +118,10 @@ public class Evenement extends BaseEntity { @Builder.Default private List inscriptions = new ArrayList<>(); + @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List adresses = new ArrayList<>(); + /** Types d'événements */ public enum TypeEvenement { ASSEMBLEE_GENERALE("Assemblée Générale"), diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java index 12c46cd..96a56aa 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Membre.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; @@ -67,6 +69,14 @@ public class Membre extends BaseEntity { @JoinColumn(name = "organisation_id") private Organisation organisation; + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List adresses = new ArrayList<>(); + + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List roles = new ArrayList<>(); + /** Méthode métier pour obtenir le nom complet */ public String getNomComplet() { return prenom + " " + nom; diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java new file mode 100644 index 0000000..27f3025 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java @@ -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(); + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java index a230f11..287c696 100644 --- a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Organisation.java @@ -191,6 +191,10 @@ public class Organisation extends BaseEntity { @Builder.Default private List membres = new ArrayList<>(); + @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List adresses = new ArrayList<>(); + /** Méthode métier pour obtenir le nom complet avec sigle */ public String getNomComplet() { if (nomCourt != null && !nomCourt.isEmpty()) { diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Permission.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Permission.java new file mode 100644 index 0000000..8c5bf2c --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Permission.java @@ -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 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); + } + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Role.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Role.java new file mode 100644 index 0000000..7ddc3ab --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Role.java @@ -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 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; + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java new file mode 100644 index 0000000..6f07add --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java @@ -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; +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java new file mode 100644 index 0000000..1b523e7 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java @@ -0,0 +1,101 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Adresse; +import dev.lions.unionflow.server.entity.Adresse.TypeAdresse; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Adresse + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class AdresseRepository implements PanacheRepository { + + /** + * Trouve toutes les adresses d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des adresses + */ + public List findByOrganisationId(UUID organisationId) { + return find("organisation.id", organisationId).list(); + } + + /** + * Trouve l'adresse principale d'une organisation + * + * @param organisationId ID de l'organisation + * @return Adresse principale ou Optional.empty() + */ + public Optional findPrincipaleByOrganisationId(UUID organisationId) { + return find("organisation.id = ?1 AND principale = true", organisationId).firstResultOptional(); + } + + /** + * Trouve toutes les adresses d'un membre + * + * @param membreId ID du membre + * @return Liste des adresses + */ + public List findByMembreId(UUID membreId) { + return find("membre.id", membreId).list(); + } + + /** + * Trouve l'adresse principale d'un membre + * + * @param membreId ID du membre + * @return Adresse principale ou Optional.empty() + */ + public Optional findPrincipaleByMembreId(UUID membreId) { + return find("membre.id = ?1 AND principale = true", membreId).firstResultOptional(); + } + + /** + * Trouve l'adresse d'un événement + * + * @param evenementId ID de l'événement + * @return Adresse ou Optional.empty() + */ + public Optional findByEvenementId(UUID evenementId) { + return find("evenement.id", evenementId).firstResultOptional(); + } + + /** + * Trouve les adresses par type + * + * @param typeAdresse Type d'adresse + * @return Liste des adresses + */ + public List findByType(TypeAdresse typeAdresse) { + return find("typeAdresse", typeAdresse).list(); + } + + /** + * Trouve les adresses par ville + * + * @param ville Nom de la ville + * @return Liste des adresses + */ + public List findByVille(String ville) { + return find("LOWER(ville) = LOWER(?1)", ville).list(); + } + + /** + * Trouve les adresses par pays + * + * @param pays Nom du pays + * @return Liste des adresses + */ + public List findByPays(String pays) { + return find("LOWER(pays) = LOWER(?1)", pays).list(); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java new file mode 100644 index 0000000..6b88a52 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java @@ -0,0 +1,66 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.MembreRole; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +/** + * Repository pour l'entité MembreRole + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class MembreRoleRepository implements PanacheRepository { + + /** + * Trouve tous les rôles d'un membre + * + * @param membreId ID du membre + * @return Liste des attributions de rôles + */ + public List findByMembreId(UUID membreId) { + return find("membre.id = ?1 AND actif = true", membreId).list(); + } + + /** + * Trouve tous les rôles actifs d'un membre (dans la période valide) + * + * @param membreId ID du membre + * @return Liste des attributions de rôles actives + */ + public List findActifsByMembreId(UUID membreId) { + LocalDate aujourdhui = LocalDate.now(); + return find( + "membre.id = ?1 AND actif = true AND (dateDebut IS NULL OR dateDebut <= ?2) AND (dateFin IS NULL OR dateFin >= ?2)", + membreId, + aujourdhui) + .list(); + } + + /** + * Trouve tous les membres ayant un rôle spécifique + * + * @param roleId ID du rôle + * @return Liste des attributions de rôles + */ + public List findByRoleId(UUID roleId) { + return find("role.id = ?1 AND actif = true", roleId).list(); + } + + /** + * Trouve une attribution spécifique membre-role + * + * @param membreId ID du membre + * @param roleId ID du rôle + * @return Attribution ou null + */ + public MembreRole findByMembreAndRole(UUID membreId, UUID roleId) { + return find("membre.id = ?1 AND role.id = ?2", membreId, roleId).firstResult(); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java new file mode 100644 index 0000000..07ab24a --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Permission; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import java.util.Optional; + +/** + * Repository pour l'entité Permission + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class PermissionRepository implements PanacheRepository { + + /** + * Trouve une permission par son code + * + * @param code Code de la permission + * @return Permission ou Optional.empty() + */ + public Optional findByCode(String code) { + return find("code", code).firstResultOptional(); + } + + /** + * Trouve les permissions par module + * + * @param module Nom du module + * @return Liste des permissions + */ + public List findByModule(String module) { + return find("LOWER(module) = LOWER(?1) AND actif = true", module).list(); + } + + /** + * Trouve les permissions par ressource + * + * @param ressource Nom de la ressource + * @return Liste des permissions + */ + public List findByRessource(String ressource) { + return find("LOWER(ressource) = LOWER(?1) AND actif = true", ressource).list(); + } + + /** + * Trouve les permissions par module et ressource + * + * @param module Nom du module + * @param ressource Nom de la ressource + * @return Liste des permissions + */ + public List findByModuleAndRessource(String module, String ressource) { + return find( + "LOWER(module) = LOWER(?1) AND LOWER(ressource) = LOWER(?2) AND actif = true", + module, + ressource) + .list(); + } + + /** + * Trouve toutes les permissions actives + * + * @return Liste des permissions actives + */ + public List findAllActives() { + return find("actif = true").order("module ASC, ressource ASC, action ASC").list(); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java new file mode 100644 index 0000000..52d9e0e --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java @@ -0,0 +1,50 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.RolePermission; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import java.util.UUID; + +/** + * Repository pour l'entité RolePermission + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class RolePermissionRepository implements PanacheRepository { + + /** + * Trouve toutes les permissions d'un rôle + * + * @param roleId ID du rôle + * @return Liste des associations rôle-permission + */ + public List findByRoleId(UUID roleId) { + return find("role.id = ?1 AND actif = true", roleId).list(); + } + + /** + * Trouve tous les rôles ayant une permission spécifique + * + * @param permissionId ID de la permission + * @return Liste des associations rôle-permission + */ + public List findByPermissionId(UUID permissionId) { + return find("permission.id = ?1 AND actif = true", permissionId).list(); + } + + /** + * Trouve une association spécifique rôle-permission + * + * @param roleId ID du rôle + * @param permissionId ID de la permission + * @return Association ou null + */ + public RolePermission findByRoleAndPermission(UUID roleId, UUID permissionId) { + return find("role.id = ?1 AND permission.id = ?2", roleId, permissionId).firstResult(); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java new file mode 100644 index 0000000..d620b7b --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java @@ -0,0 +1,75 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Role; +import dev.lions.unionflow.server.entity.Role.TypeRole; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Role + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class RoleRepository implements PanacheRepository { + + /** + * Trouve un rôle par son code + * + * @param code Code du rôle + * @return Rôle ou Optional.empty() + */ + public Optional findByCode(String code) { + return find("code", code).firstResultOptional(); + } + + /** + * Trouve tous les rôles système + * + * @return Liste des rôles système + */ + public List findRolesSysteme() { + return find("typeRole = ?1 AND actif = true", TypeRole.SYSTEME) + .order("niveauHierarchique ASC") + .list(); + } + + /** + * Trouve tous les rôles d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des rôles + */ + public List findByOrganisationId(UUID organisationId) { + return find("organisation.id = ?1 AND actif = true", organisationId) + .order("niveauHierarchique ASC") + .list(); + } + + /** + * Trouve tous les rôles actifs + * + * @return Liste des rôles actifs + */ + public List findAllActifs() { + return find("actif = true").order("niveauHierarchique ASC").list(); + } + + /** + * Trouve les rôles par type + * + * @param typeRole Type de rôle + * @return Liste des rôles + */ + public List findByType(TypeRole typeRole) { + return find("typeRole = ?1 AND actif = true", typeRole) + .order("niveauHierarchique ASC") + .list(); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/AdresseService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/AdresseService.java new file mode 100644 index 0000000..9195740 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/AdresseService.java @@ -0,0 +1,358 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.adresse.AdresseDTO; +import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse; +import dev.lions.unionflow.server.entity.Adresse; +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.AdresseRepository; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.NotFoundException; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion des adresses + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class AdresseService { + + private static final Logger LOG = Logger.getLogger(AdresseService.class); + + @Inject AdresseRepository adresseRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject MembreRepository membreRepository; + + @Inject EvenementRepository evenementRepository; + + /** + * Crée une nouvelle adresse + * + * @param adresseDTO DTO de l'adresse à créer + * @return DTO de l'adresse créée + */ + @Transactional + public AdresseDTO creerAdresse(AdresseDTO adresseDTO) { + LOG.infof("Création d'une nouvelle adresse de type: %s", adresseDTO.getTypeAdresse()); + + Adresse adresse = convertToEntity(adresseDTO); + + // Gestion de l'adresse principale + if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) { + desactiverAutresPrincipales(adresseDTO); + } + + adresseRepository.persist(adresse); + LOG.infof("Adresse créée avec succès: ID=%s", adresse.getId()); + + return convertToDTO(adresse); + } + + /** + * Met à jour une adresse existante + * + * @param id ID de l'adresse + * @param adresseDTO DTO avec les nouvelles données + * @return DTO de l'adresse mise à jour + */ + @Transactional + public AdresseDTO mettreAJourAdresse(UUID id, AdresseDTO adresseDTO) { + LOG.infof("Mise à jour de l'adresse ID: %s", id); + + Adresse adresse = + adresseRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id)); + + // Mise à jour des champs + updateFromDTO(adresse, adresseDTO); + + // Gestion de l'adresse principale + if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) { + desactiverAutresPrincipales(adresseDTO); + } + + adresseRepository.persist(adresse); + LOG.infof("Adresse mise à jour avec succès: ID=%s", id); + + return convertToDTO(adresse); + } + + /** + * Supprime une adresse + * + * @param id ID de l'adresse + */ + @Transactional + public void supprimerAdresse(UUID id) { + LOG.infof("Suppression de l'adresse ID: %s", id); + + Adresse adresse = + adresseRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id)); + + adresseRepository.delete(adresse); + LOG.infof("Adresse supprimée avec succès: ID=%s", id); + } + + /** + * Trouve une adresse par son ID + * + * @param id ID de l'adresse + * @return DTO de l'adresse + */ + public AdresseDTO trouverParId(UUID id) { + return adresseRepository + .findByIdOptional(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id)); + } + + /** + * Trouve toutes les adresses d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des adresses + */ + public List trouverParOrganisation(UUID organisationId) { + return adresseRepository.findByOrganisationId(organisationId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Trouve toutes les adresses d'un membre + * + * @param membreId ID du membre + * @return Liste des adresses + */ + public List trouverParMembre(UUID membreId) { + return adresseRepository.findByMembreId(membreId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Trouve l'adresse d'un événement + * + * @param evenementId ID de l'événement + * @return DTO de l'adresse ou null + */ + public AdresseDTO trouverParEvenement(UUID evenementId) { + return adresseRepository + .findByEvenementId(evenementId) + .map(this::convertToDTO) + .orElse(null); + } + + /** + * Trouve l'adresse principale d'une organisation + * + * @param organisationId ID de l'organisation + * @return DTO de l'adresse principale ou null + */ + public AdresseDTO trouverPrincipaleParOrganisation(UUID organisationId) { + return adresseRepository + .findPrincipaleByOrganisationId(organisationId) + .map(this::convertToDTO) + .orElse(null); + } + + /** + * Trouve l'adresse principale d'un membre + * + * @param membreId ID du membre + * @return DTO de l'adresse principale ou null + */ + public AdresseDTO trouverPrincipaleParMembre(UUID membreId) { + return adresseRepository + .findPrincipaleByMembreId(membreId) + .map(this::convertToDTO) + .orElse(null); + } + + // ======================================== + // MÉTHODES PRIVÉES + // ======================================== + + /** Désactive les autres adresses principales pour la même entité */ + private void desactiverAutresPrincipales(AdresseDTO adresseDTO) { + List autresPrincipales; + + if (adresseDTO.getOrganisationId() != null) { + autresPrincipales = + adresseRepository + .find("organisation.id = ?1 AND principale = true", adresseDTO.getOrganisationId()) + .list(); + } else if (adresseDTO.getMembreId() != null) { + autresPrincipales = + adresseRepository + .find("membre.id = ?1 AND principale = true", adresseDTO.getMembreId()) + .list(); + } else { + return; // Pas d'entité associée + } + + autresPrincipales.forEach(adr -> adr.setPrincipale(false)); + } + + /** Convertit une entité en DTO */ + private AdresseDTO convertToDTO(Adresse adresse) { + if (adresse == null) { + return null; + } + + AdresseDTO dto = new AdresseDTO(); + dto.setId(adresse.getId()); + dto.setTypeAdresse(convertTypeAdresse(adresse.getTypeAdresse())); + dto.setAdresse(adresse.getAdresse()); + dto.setComplementAdresse(adresse.getComplementAdresse()); + dto.setCodePostal(adresse.getCodePostal()); + dto.setVille(adresse.getVille()); + dto.setRegion(adresse.getRegion()); + dto.setPays(adresse.getPays()); + dto.setLatitude(adresse.getLatitude()); + dto.setLongitude(adresse.getLongitude()); + dto.setPrincipale(adresse.getPrincipale()); + dto.setLibelle(adresse.getLibelle()); + dto.setNotes(adresse.getNotes()); + + if (adresse.getOrganisation() != null) { + dto.setOrganisationId(adresse.getOrganisation().getId()); + } + if (adresse.getMembre() != null) { + dto.setMembreId(adresse.getMembre().getId()); + } + if (adresse.getEvenement() != null) { + dto.setEvenementId(adresse.getEvenement().getId()); + } + + dto.setAdresseComplete(adresse.getAdresseComplete()); + dto.setDateCreation(adresse.getDateCreation()); + dto.setDateModification(adresse.getDateModification()); + dto.setActif(adresse.getActif()); + + return dto; + } + + /** Convertit un DTO en entité */ + private Adresse convertToEntity(AdresseDTO dto) { + if (dto == null) { + return null; + } + + Adresse adresse = new Adresse(); + adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse())); + adresse.setAdresse(dto.getAdresse()); + adresse.setComplementAdresse(dto.getComplementAdresse()); + adresse.setCodePostal(dto.getCodePostal()); + adresse.setVille(dto.getVille()); + adresse.setRegion(dto.getRegion()); + adresse.setPays(dto.getPays()); + adresse.setLatitude(dto.getLatitude()); + adresse.setLongitude(dto.getLongitude()); + adresse.setPrincipale(dto.getPrincipale() != null ? dto.getPrincipale() : false); + adresse.setLibelle(dto.getLibelle()); + adresse.setNotes(dto.getNotes()); + + // Relations + if (dto.getOrganisationId() != null) { + Organisation org = + organisationRepository + .findByIdOptional(dto.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + dto.getOrganisationId())); + adresse.setOrganisation(org); + } + + if (dto.getMembreId() != null) { + Membre membre = + membreRepository + .findByIdOptional(dto.getMembreId()) + .orElseThrow( + () -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId())); + adresse.setMembre(membre); + } + + if (dto.getEvenementId() != null) { + Evenement evenement = + evenementRepository + .findByIdOptional(dto.getEvenementId()) + .orElseThrow( + () -> + new NotFoundException( + "Événement non trouvé avec l'ID: " + dto.getEvenementId())); + adresse.setEvenement(evenement); + } + + return adresse; + } + + /** Met à jour une entité à partir d'un DTO */ + private void updateFromDTO(Adresse adresse, AdresseDTO dto) { + if (dto.getTypeAdresse() != null) { + adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse())); + } + if (dto.getAdresse() != null) { + adresse.setAdresse(dto.getAdresse()); + } + if (dto.getComplementAdresse() != null) { + adresse.setComplementAdresse(dto.getComplementAdresse()); + } + if (dto.getCodePostal() != null) { + adresse.setCodePostal(dto.getCodePostal()); + } + if (dto.getVille() != null) { + adresse.setVille(dto.getVille()); + } + if (dto.getRegion() != null) { + adresse.setRegion(dto.getRegion()); + } + if (dto.getPays() != null) { + adresse.setPays(dto.getPays()); + } + if (dto.getLatitude() != null) { + adresse.setLatitude(dto.getLatitude()); + } + if (dto.getLongitude() != null) { + adresse.setLongitude(dto.getLongitude()); + } + if (dto.getPrincipale() != null) { + adresse.setPrincipale(dto.getPrincipale()); + } + if (dto.getLibelle() != null) { + adresse.setLibelle(dto.getLibelle()); + } + if (dto.getNotes() != null) { + adresse.setNotes(dto.getNotes()); + } + } + + /** Convertit TypeAdresse (entité) vers TypeAdresse (DTO) - même enum, pas de conversion nécessaire */ + private TypeAdresse convertTypeAdresse(TypeAdresse type) { + return type; // Même enum, pas de conversion nécessaire + } + + /** Convertit TypeAdresse (DTO) vers TypeAdresse (entité) - même enum, pas de conversion nécessaire */ + private TypeAdresse convertTypeAdresse(TypeAdresse type) { + return type != null ? type : TypeAdresse.AUTRE; // Même enum, valeur par défaut si null + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/PermissionService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/PermissionService.java new file mode 100644 index 0000000..9bf7521 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/PermissionService.java @@ -0,0 +1,165 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.entity.Permission; +import dev.lions.unionflow.server.repository.PermissionRepository; +import dev.lions.unionflow.server.service.KeycloakService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.NotFoundException; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion des permissions + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class PermissionService { + + private static final Logger LOG = Logger.getLogger(PermissionService.class); + + @Inject PermissionRepository permissionRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée une nouvelle permission + * + * @param permission Permission à créer + * @return Permission créée + */ + @Transactional + public Permission creerPermission(Permission permission) { + LOG.infof("Création d'une nouvelle permission: %s", permission.getCode()); + + // Vérifier l'unicité du code + if (permissionRepository.findByCode(permission.getCode()).isPresent()) { + throw new IllegalArgumentException( + "Une permission avec ce code existe déjà: " + permission.getCode()); + } + + // Générer le code si non fourni + if (permission.getCode() == null || permission.getCode().isEmpty()) { + permission.setCode( + Permission.genererCode( + permission.getModule(), permission.getRessource(), permission.getAction())); + } + + // Métadonnées + permission.setCreePar(keycloakService.getCurrentUserEmail()); + + permissionRepository.persist(permission); + LOG.infof( + "Permission créée avec succès: ID=%s, Code=%s", + permission.getId(), permission.getCode()); + + return permission; + } + + /** + * Met à jour une permission existante + * + * @param id ID de la permission + * @param permissionModifiee Permission avec les modifications + * @return Permission mise à jour + */ + @Transactional + public Permission mettreAJourPermission(UUID id, Permission permissionModifiee) { + LOG.infof("Mise à jour de la permission ID: %s", id); + + Permission permission = + permissionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Permission non trouvée avec l'ID: " + id)); + + // Mise à jour + permission.setCode(permissionModifiee.getCode()); + permission.setModule(permissionModifiee.getModule()); + permission.setRessource(permissionModifiee.getRessource()); + permission.setAction(permissionModifiee.getAction()); + permission.setLibelle(permissionModifiee.getLibelle()); + permission.setDescription(permissionModifiee.getDescription()); + permission.setModifiePar(keycloakService.getCurrentUserEmail()); + + permissionRepository.persist(permission); + LOG.infof("Permission mise à jour avec succès: ID=%s", id); + + return permission; + } + + /** + * Trouve une permission par son ID + * + * @param id ID de la permission + * @return Permission ou null + */ + public Permission trouverParId(UUID id) { + return permissionRepository.findByIdOptional(id).orElse(null); + } + + /** + * Trouve une permission par son code + * + * @param code Code de la permission + * @return Permission ou null + */ + public Permission trouverParCode(String code) { + return permissionRepository.findByCode(code).orElse(null); + } + + /** + * Liste les permissions par module + * + * @param module Nom du module + * @return Liste des permissions + */ + public List listerParModule(String module) { + return permissionRepository.findByModule(module); + } + + /** + * Liste les permissions par ressource + * + * @param ressource Nom de la ressource + * @return Liste des permissions + */ + public List listerParRessource(String ressource) { + return permissionRepository.findByRessource(ressource); + } + + /** + * Liste toutes les permissions actives + * + * @return Liste des permissions actives + */ + public List listerToutesActives() { + return permissionRepository.findAllActives(); + } + + /** + * Supprime (désactive) une permission + * + * @param id ID de la permission + */ + @Transactional + public void supprimerPermission(UUID id) { + LOG.infof("Suppression de la permission ID: %s", id); + + Permission permission = + permissionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Permission non trouvée avec l'ID: " + id)); + + permission.setActif(false); + permission.setModifiePar(keycloakService.getCurrentUserEmail()); + + permissionRepository.persist(permission); + LOG.infof("Permission supprimée avec succès: ID=%s", id); + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/RoleService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/RoleService.java new file mode 100644 index 0000000..bf83fd2 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/RoleService.java @@ -0,0 +1,171 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.entity.Role; +import dev.lions.unionflow.server.entity.Role.TypeRole; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.repository.RoleRepository; +import dev.lions.unionflow.server.service.KeycloakService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.NotFoundException; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion des rôles + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class RoleService { + + private static final Logger LOG = Logger.getLogger(RoleService.class); + + @Inject RoleRepository roleRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouveau rôle + * + * @param role Rôle à créer + * @return Rôle créé + */ + @Transactional + public Role creerRole(Role role) { + LOG.infof("Création d'un nouveau rôle: %s", role.getCode()); + + // Vérifier l'unicité du code + if (roleRepository.findByCode(role.getCode()).isPresent()) { + throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + role.getCode()); + } + + // Métadonnées + role.setCreePar(keycloakService.getCurrentUserEmail()); + + roleRepository.persist(role); + LOG.infof("Rôle créé avec succès: ID=%s, Code=%s", role.getId(), role.getCode()); + + return role; + } + + /** + * Met à jour un rôle existant + * + * @param id ID du rôle + * @param roleModifie Rôle avec les modifications + * @return Rôle mis à jour + */ + @Transactional + public Role mettreAJourRole(UUID id, Role roleModifie) { + LOG.infof("Mise à jour du rôle ID: %s", id); + + Role role = + roleRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id)); + + // Vérifier l'unicité du code si modifié + if (!role.getCode().equals(roleModifie.getCode())) { + if (roleRepository.findByCode(roleModifie.getCode()).isPresent()) { + throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + roleModifie.getCode()); + } + } + + // Mise à jour + role.setCode(roleModifie.getCode()); + role.setLibelle(roleModifie.getLibelle()); + role.setDescription(roleModifie.getDescription()); + role.setNiveauHierarchique(roleModifie.getNiveauHierarchique()); + role.setTypeRole(roleModifie.getTypeRole()); + role.setOrganisation(roleModifie.getOrganisation()); + role.setModifiePar(keycloakService.getCurrentUserEmail()); + + roleRepository.persist(role); + LOG.infof("Rôle mis à jour avec succès: ID=%s", id); + + return role; + } + + /** + * Trouve un rôle par son ID + * + * @param id ID du rôle + * @return Rôle ou null + */ + public Role trouverParId(UUID id) { + return roleRepository.findByIdOptional(id).orElse(null); + } + + /** + * Trouve un rôle par son code + * + * @param code Code du rôle + * @return Rôle ou null + */ + public Role trouverParCode(String code) { + return roleRepository.findByCode(code).orElse(null); + } + + /** + * Liste tous les rôles système + * + * @return Liste des rôles système + */ + public List listerRolesSysteme() { + return roleRepository.findRolesSysteme(); + } + + /** + * Liste tous les rôles d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des rôles + */ + public List listerParOrganisation(UUID organisationId) { + return roleRepository.findByOrganisationId(organisationId); + } + + /** + * Liste tous les rôles actifs + * + * @return Liste des rôles actifs + */ + public List listerTousActifs() { + return roleRepository.findAllActifs(); + } + + /** + * Supprime (désactive) un rôle + * + * @param id ID du rôle + */ + @Transactional + public void supprimerRole(UUID id) { + LOG.infof("Suppression du rôle ID: %s", id); + + Role role = + roleRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id)); + + // Vérifier si c'est un rôle système + if (role.isRoleSysteme()) { + throw new IllegalStateException("Impossible de supprimer un rôle système"); + } + + role.setActif(false); + role.setModifiePar(keycloakService.getCurrentUserEmail()); + + roleRepository.persist(role); + LOG.infof("Rôle supprimé avec succès: ID=%s", id); + } +} + diff --git a/unionflow.md b/unionflow.md new file mode 100644 index 0000000..b14befe --- /dev/null +++ b/unionflow.md @@ -0,0 +1,1297 @@ +# Description Métier - UnionFlow + +**Version** : 3.0 (Optimisée avec intégration Wave Mobile Money) +**Date** : 2025-01-29 +**Domaine** : Gestion d'organisations associatives (Lions Clubs, Associations, Coopératives, etc.) + +--- + +## 🎯 Vision et Mission + +**UnionFlow** est une plateforme de gestion intégrée conçue pour les organisations (unions, associations, mutuelles et assimilées) de Côte d'Ivoire. Elle centralise et automatise la gestion administrative, financière et opérationnelle de ces organisations. + +### Mission +Faciliter la gestion quotidienne des organisations associatives en automatisant les processus administratifs, financiers et événementiels, tout en favorisant la solidarité entre membres. + +### Vision +Devenir la référence en matière de gestion numérique pour les organisations associatives en Afrique. + +--- + +## 🏢 Contexte Métier + +### Organisations Cibles +UnionFlow s'adresse à différents types d'organisations : +- **Associations** : Organisations à but non lucratif +- **Coopératives** : Groupements économiques +- **Fédérations** : Regroupements d'organisations +- **Mutuelles** : Organisations de solidarité +- **Syndicats** : Organisations professionnelles +- **Fondations** : Organisations philanthropiques +- **ONG** : Organisations non gouvernementales + +### Problématiques Résolues +1. **Gestion dispersée** : Informations éparpillées dans des fichiers Excel, carnets, etc. +2. **Suivi financier complexe** : Difficulté à suivre les cotisations, paiements, relances +3. **Communication inefficace** : Manque de centralisation pour les événements et annonces +4. **Solidarité non structurée** : Absence de processus formalisé pour les demandes d'aide +5. **Traçabilité limitée** : Pas d'historique complet des actions et décisions +6. **Paiements manuels** : Absence d'intégration avec les solutions de mobile money + +--- + +## 👥 Acteurs et Rôles + +### 1. **SUPER_ADMIN** +- **Rôle** : Administration système complète +- **Permissions** : + - Gestion de tous les utilisateurs et organisations + - Configuration du système + - Gestion du catalogue des types d'organisations + - Configuration Wave Mobile Money + - Accès à toutes les données et statistiques + - Gestion des rôles et permissions +- **Cas d'usage** : Configuration initiale, maintenance, support technique + +### 2. **ADMIN** (Administrateur d'Organisation) +- **Rôle** : Gestion complète d'une organisation +- **Permissions** : + - Gestion des membres de son organisation + - Gestion des cotisations + - Organisation d'événements + - Validation des adhésions + - Traitement des demandes d'aide + - Consultation des statistiques de son organisation + - Gestion du compte Wave de l'organisation + - Validation des paiements +- **Cas d'usage** : Gestion quotidienne d'un Lions Club ou d'une association + +### 3. **MEMBRE** +- **Rôle** : Membre actif d'une organisation +- **Permissions** : + - Consultation de son profil + - Consultation de ses cotisations + - Inscription aux événements + - Soumission de demandes d'aide + - Consultation des événements publics + - Paiement via Wave Mobile Money + - Gestion de son compte Wave personnel +- **Cas d'usage** : Participation active à la vie de l'organisation + +### 4. **ORGANISATEUR_EVENEMENT** +- **Rôle** : Organisation et gestion d'événements +- **Permissions** : + - Création et modification d'événements + - Gestion des inscriptions + - Suivi des participants +- **Cas d'usage** : Organisation d'assemblées générales, formations, manifestations + +### 5. **TRESORIER** +- **Rôle** : Gestion financière et comptable +- **Permissions** : + - Gestion des paiements + - Validation des transactions + - Gestion comptable + - Rapprochement bancaire + - Consultation des écritures comptables +- **Cas d'usage** : Suivi financier et comptabilité + +--- + +## 📋 Modules Fonctionnels + +### 1. 🏛️ Gestion des Organisations + +#### Description +Module central permettant de gérer toutes les informations relatives aux organisations (clubs, associations, etc.). + +#### Fonctionnalités Principales + +**Création et Configuration** +- Enregistrement d'une nouvelle organisation avec toutes ses informations : + - Identité : nom, nom court, type, statut + - Contact : email, téléphones, adresse complète, coordonnées GPS + - Web : site web, logo, réseaux sociaux + - Finances : budget annuel, devise, cotisation obligatoire, montant + - Métier : objectifs, activités principales, certifications, partenaires + - Paramètres : organisation publique, accepte nouveaux membres + +**Hiérarchie Organisationnelle** +- Structure hiérarchique (organisation parente) +- Niveaux hiérarchiques (0 = racine) +- Gestion des relations parent-enfant + +**Statuts Organisationnels** +- **ACTIVE** : Organisation opérationnelle +- **SUSPENDUE** : Temporairement suspendue (ne peut plus accepter de membres) +- **DISSOUTE** : Organisation dissoute (archivée) +- **EN_CREATION** : Organisation en cours de création + +**Gestion du Catalogue des Types** +- CRUD complet des types d'organisations +- Codes uniques (LIONS_CLUB, ASSOCIATION, etc.) +- Libellés et descriptions +- Ordre d'affichage +- Icônes pour l'interface +- Activation/désactivation + +**Gestion des Adresses** +- Entité dédiée pour les adresses +- Support de plusieurs adresses par organisation/membre +- Types d'adresses : siège social, bureau, domicile, autre +- Coordonnées GPS pour géolocalisation + +**Statistiques** +- Nombre de membres +- Nombre d'administrateurs +- Ancienneté (années depuis la fondation) +- Budget et finances +- Transactions Wave + +#### Règles Métier +- Unicité de l'email par organisation +- Unicité du numéro d'enregistrement +- Unicité du nom +- Date de fondation optionnelle mais utilisée pour calculer l'ancienneté +- Statut par défaut : ACTIVE +- Devise par défaut : XOF (Franc CFA) +- Une organisation peut avoir un compte Wave + +--- + +### 2. 👤 Gestion des Membres et Rôles + +#### Description +Gestion complète du cycle de vie des membres d'une organisation avec système de rôles et permissions granulaires. + +#### Fonctionnalités Principales + +**Inscription de Membres** +- Création d'un nouveau membre avec : + - Identité : prénom, nom, email (unique), téléphone + - Téléphone Wave : format +225XXXXXXXX + - Dates : naissance, adhésion + - Affiliation : organisation + - Photo de profil + - Adresses multiples + +**Génération Automatique** +- **Numéro de membre** : Format `UF{ANNEE}-{UUID}` (ex: `UF2025-A1B2C3D4`) + - Généré automatiquement si non fourni + - Unique dans tout le système +- **Date d'adhésion** : Automatiquement définie à `LocalDate.now()` si non fournie +- **Date de naissance** : Par défaut à 18 ans en arrière si non fournie + +**Statuts Membres** +- **ACTIF** : Membre actif et opérationnel +- **INACTIF** : Membre désactivé +- **SUSPENDU** : Membre temporairement suspendu +- **DEMISSIONNAIRE** : Membre ayant démissionné +- **EXCLU** : Membre exclu de l'organisation + +**Gestion des Rôles et Permissions** +- **Rôles** : Système de rôles hiérarchiques + - Rôles système (SUPER_ADMIN, ADMIN, MEMBRE, etc.) + - Rôles personnalisés par organisation + - Niveau hiérarchique pour ordre de priorité +- **Permissions** : Permissions granulaires + - Structure : Module > Ressource > Action + - Exemple : `ORGANISATION > MEMBRE > CREATE` +- **Attribution** : + - Un membre peut avoir plusieurs rôles + - Attribution avec dates de début/fin + - Traçabilité complète + +**Recherche et Filtrage** +- Recherche par nom, prénom, email, téléphone +- Filtrage par statut, organisation, date d'adhésion, rôles +- Recherche avancée avec critères multiples : + - Âge (min/max) + - Période d'adhésion + - Organisation(s) + - Rôles + - Compte Wave actif + +**Statistiques** +- Total de membres +- Membres actifs vs inactifs +- Nouveaux membres (30 derniers jours) +- Taux d'activité +- Répartition par rôles + +#### Règles Métier +- Email unique dans tout le système +- Numéro de membre unique +- Un membre appartient à une seule organisation +- Vérification de majorité (18 ans) pour certaines opérations +- Calcul automatique de l'âge à partir de la date de naissance +- Téléphone Wave au format +225XXXXXXXX +- Un membre peut avoir un compte Wave personnel + +--- + +### 3. 💰 Gestion des Paiements (Architecture Centralisée) + +#### Description +Système centralisé de gestion des paiements avec intégration stricte Wave Mobile Money et support multi-méthodes. + +#### Fonctionnalités Principales + +**Architecture Centralisée** +- **Entité Paiement** : Entité unique pour tous les types de paiements + - Réutilisable pour cotisations, adhésions, événements, aides + - Traçabilité complète + - Support multi-méthodes +- **Tables de liaison** : + - `PaiementCotisation` : Application d'un paiement à une cotisation + - `PaiementAdhesion` : Application d'un paiement à une adhésion + - `PaiementEvenement` : Application d'un paiement à un événement + - `PaiementAide` : Application d'un paiement à une aide + +**Méthodes de Paiement** +- **WAVE_MOBILE_MONEY** : Intégration stricte Wave (prioritaire) +- **ORANGE_MONEY** : Orange Money +- **MTN_MOBILE_MONEY** : MTN Mobile Money +- **MOOV_MONEY** : Moov Money +- **VIREMENT_BANCAIRE** : Virement bancaire +- **CARTE_BANCAIRE** : Carte bancaire +- **ESPECES** : Paiement en espèces +- **CHEQUE** : Chèque +- **AUTRE** : Autre méthode + +**Statuts de Paiement** +- **EN_ATTENTE** : Paiement initié, en attente +- **EN_COURS** : Paiement en cours de traitement +- **VALIDE** : Paiement validé et confirmé +- **ECHOUE** : Paiement échoué +- **ANNULE** : Paiement annulé +- **REMBOURSE** : Paiement remboursé + +**Gestion des Paiements** +- Numéro de référence unique +- Montant et devise +- Date de paiement +- Date de validation +- Validateur (optionnel) +- Références externes (numéro transaction, URL preuve) +- Commentaires et notes +- Traçabilité IP et User-Agent + +**Paiements Multiples** +- Un paiement peut couvrir plusieurs cotisations +- Un paiement peut être partiel +- Application automatique selon ordre de priorité +- Calcul automatique des montants restants + +#### Règles Métier +- Montant doit être positif +- Devise par défaut : XOF +- Validation optionnelle par administrateur +- Traçabilité complète (IP, User-Agent, dates) +- Support des paiements partiels +- Un paiement peut être lié à une transaction Wave + +--- + +### 4. 📱 Intégration Wave Mobile Money (Stricte) + +#### Description +Intégration complète et stricte de Wave Mobile Money pour tous les paiements de la plateforme. + +#### Fonctionnalités Principales + +**Gestion des Comptes Wave** +- **CompteWave** : Entité dédiée pour les comptes Wave + - Comptes organisation (pour recevoir les paiements) + - Comptes membres (pour payer) + - Numéro de téléphone au format +225XXXXXXXX + - Statut de vérification + - Identifiants Wave API (encryptés) + - Support sandbox et production +- **Vérification** : + - Vérification automatique ou manuelle + - Synchronisation avec Wave API + - Statuts : NON_VERIFIE, VERIFIE, SUSPENDU, BLOQUE + +**Transactions Wave** +- **TransactionWave** : Suivi complet des transactions + - Identifiants Wave uniques (transactionId, requestId, reference) + - Type de transaction : DÉPOT, RETRAIT, TRANSFERT, PAIEMENT, REMBOURSEMENT + - Statuts : INITIALISE, EN_ATTENTE, EN_COURS, REUSSIE, ECHOUE, ANNULEE, EXPIRED + - Montant, frais, montant net + - Informations payeur et bénéficiaire + - Métadonnées JSON + - Réponse complète de Wave API +- **Synchronisation** : + - Synchronisation bidirectionnelle avec Wave API + - Retry automatique en cas d'échec + - Suivi du nombre de tentatives + - Gestion des erreurs + +**Webhooks Wave** +- **WebhookWave** : Traitement des événements Wave + - Réception des événements en temps réel + - Types : TRANSACTION_CREATED, TRANSACTION_COMPLETED, TRANSACTION_FAILED, etc. + - Validation de signature + - Traitement asynchrone en queue + - Retry automatique + - Idempotence garantie + - Statuts : EN_ATTENTE, EN_TRAITEMENT, TRAITE, ECHOUE, IGNORE + +**Configuration Wave** +- **ConfigurationWave** : Paramètres d'intégration + - URL API Wave + - Timeout des requêtes + - Frais de transaction (pourcentage, fixe) + - Configuration webhook (retry, délai) + - Support sandbox et production + - Clés API encryptées + +**Processus de Paiement via Wave** + +1. **Initiation du Paiement** + - Membre sélectionne Wave comme méthode + - Vérification du compte Wave du membre + - Création de TransactionWave (statut INITIALISE) + +2. **Appel Wave API** + - Génération de la requête Wave + - Envoi à l'API Wave + - Enregistrement de la réponse + +3. **Traitement Webhook** + - Réception du webhook Wave + - Validation de la signature + - Mise à jour de TransactionWave + - Mise à jour du Paiement associé + - Notification au membre + +4. **Finalisation** + - Validation automatique ou manuelle + - Application aux cotisations/adhésions + - Génération des écritures comptables + - Notification de confirmation + +**Sécurité** +- Chiffrement des clés API +- Validation de signature des webhooks +- Audit complet des transactions +- Traçabilité IP et User-Agent +- Gestion des erreurs et retry sécurisé + +#### Règles Métier +- Format téléphone Wave strict : +225XXXXXXXX +- Vérification obligatoire avant utilisation +- Synchronisation automatique des statuts +- Retry automatique avec backoff exponentiel +- Idempotence des webhooks +- Validation de signature obligatoire +- Chiffrement des données sensibles + +--- + +### 5. 💼 Gestion Comptable + +#### Description +Système de comptabilité en partie double avec intégration automatique des paiements. + +#### Fonctionnalités Principales + +**Plan Comptable** +- **CompteComptable** : Comptes du plan comptable + - Numéro de compte unique + - Libellé et description + - Type : ACTIF, PASSIF, CHARGES, PRODUITS, TRESORERIE, AUTRE + - Classe comptable (1-7) + - Solde initial et solde actuel + - Devise + +**Journaux Comptables** +- **JournalComptable** : Journaux comptables + - Code unique + - Type : ACHATS, VENTES, BANQUE, CAISSE, OD + - Période (date début/fin) + - Statut : OUVERT, FERME, CLOTURE + +**Écritures Comptables** +- **EcritureComptable** : Écritures en partie double + - Numéro de pièce unique + - Date d'écriture + - Libellé et référence + - Lettrage et pointage + - Commentaires +- **LigneEcriture** : Lignes d'écriture + - Numéro de ligne + - Compte débiteur/créditeur + - Montant débit/crédit + - Équilibre automatique (Débit = Crédit) + +**Intégration Automatique** +- Génération automatique d'écritures pour : + - Paiements de cotisations + - Paiements d'adhésions + - Paiements d'événements + - Versements d'aides + - Transactions Wave (frais inclus) + +**Rapprochement Bancaire** +- Pointage des paiements +- Lettrage automatique +- Rapprochement avec comptes bancaires +- Écarts de rapprochement + +#### Règles Métier +- Équilibre obligatoire : Total Débit = Total Crédit +- Numéro de pièce unique +- Date d'écriture obligatoire +- Pointage et lettrage optionnels +- Intégration automatique avec paiements + +--- + +### 6. 💰 Gestion des Cotisations + +#### Description +Suivi complet des cotisations des membres : création, paiement via Wave, relances, statistiques. + +#### Fonctionnalités Principales + +**Types de Cotisations** +- **MENSUELLE** : Cotisation mensuelle récurrente +- **ANNUELLE** : Cotisation annuelle +- **ADHESION** : Frais d'adhésion initiale +- **EVENEMENT** : Participation à un événement payant +- **FORMATION** : Frais de formation +- **PROJET** : Contribution à un projet +- **SOLIDARITE** : Contribution au fonds de solidarité +- **EXTRAORDINAIRE** : Cotisation extraordinaire + +**Création de Cotisation** +- Association à un membre +- Montant dû (obligatoire, positif) +- Code devise (ISO 3 lettres, défaut : XOF) +- Date d'échéance +- Période (année, mois optionnel) +- Description et observations +- Type de cotisation +- Récurrente (oui/non) + +**Génération Automatique** +- **Numéro de référence** : Format `COT-{ANNEE}-{TIMESTAMP}` (ex: `COT-2025-12345678`) + - Généré automatiquement si non fourni + - Unique dans tout le système + +**Statuts de Cotisation** +- **EN_ATTENTE** : Créée mais non payée +- **PAYEE** : Intégralement payée +- **EN_RETARD** : Date d'échéance dépassée, non payée +- **PARTIELLEMENT_PAYEE** : Paiement partiel effectué +- **ANNULEE** : Cotisation annulée +- **REMBOURSEE** : Cotisation remboursée + +**Gestion des Paiements** +- **Paiements multiples** : Un paiement peut couvrir plusieurs cotisations +- **Paiements partiels** : Support des paiements partiels +- **Via Wave** : Paiement direct via Wave Mobile Money +- **Application automatique** : Calcul automatique du montant payé +- **Validation** : Validation optionnelle par administrateur + +**Relances Automatiques** +- Suivi du nombre de rappels +- Date du dernier rappel +- Détection automatique des cotisations en retard +- Notifications automatiques (email, SMS) + +**Recherche et Filtrage** +- Par membre +- Par statut +- Par type +- Par période (année, mois) +- Cotisations en retard +- Cotisations non payées + +**Statistiques** +- Total de cotisations +- Cotisations payées +- Cotisations en retard +- Taux de paiement +- Montants collectés par période +- Répartition par méthode de paiement + +#### Règles Métier +- Montant dû doit être positif +- Calcul automatique du montant restant : `montantDu - montantPaye` +- Détection automatique des cotisations en retard : `dateEcheance < aujourd'hui && !payeeIntegralement()` +- Une cotisation marquée "PAYEE" doit avoir tous les paiements validés +- Impossible de supprimer une cotisation déjà payée +- Paiement via Wave prioritaire si compte Wave disponible + +--- + +### 7. 📅 Gestion des Événements + +#### Description +Organisation complète d'événements : création, inscriptions, paiements via Wave, suivi, statistiques. + +#### Fonctionnalités Principales + +**Types d'Événements** +- **ASSEMBLEE_GENERALE** : Assemblée générale annuelle +- **REUNION** : Réunion régulière +- **FORMATION** : Session de formation +- **CONFERENCE** : Conférence ou séminaire +- **ATELIER** : Atelier pratique +- **SEMINAIRE** : Séminaire +- **EVENEMENT_SOCIAL** : Événement social (soirée, gala, etc.) +- **MANIFESTATION** : Manifestation publique +- **CELEBRATION** : Célébration (anniversaire, fête, etc.) +- **AUTRE** : Autre type d'événement + +**Création d'Événement** +- Titre (obligatoire) +- Description détaillée +- Dates : début (obligatoire), fin (optionnelle) +- Lieu et adresse complète (entité Adresse) +- Type d'événement +- Capacité maximale (optionnelle, pour gérer les inscriptions) +- Prix de participation (optionnel) +- Instructions particulières +- Contact organisateur +- Matériel requis +- Visibilité publique + +**Gestion des Inscriptions** +- Inscription requise (oui/non) +- Date limite d'inscription +- Capacité maximale +- Suivi des inscriptions : + - **CONFIRMEE** : Inscription validée + - **EN_ATTENTE** : En attente de validation + - **ANNULEE** : Inscription annulée + - **REFUSEE** : Inscription refusée + +**Paiements d'Événements** +- Paiement via Wave Mobile Money +- Paiement en espèces +- Paiement par virement +- Application automatique aux inscriptions +- Génération de reçus + +**Statuts d'Événement** +- **PLANIFIE** : Événement planifié (défaut) +- **CONFIRME** : Événement confirmé +- **EN_COURS** : Événement en cours +- **TERMINE** : Événement terminé +- **ANNULE** : Événement annulé +- **REPORTE** : Événement reporté + +**Règles d'Ouverture aux Inscriptions** +Un événement est ouvert aux inscriptions si : +- `inscriptionRequise = true` +- `actif = true` +- Date limite d'inscription non dépassée +- Date de début non dépassée +- Capacité non atteinte (si définie) +- Statut = PLANIFIE ou CONFIRME + +**Statistiques** +- Nombre total d'événements +- Événements actifs +- Événements à venir +- Événements en cours +- Événements passés +- Événements publics +- Taux de remplissage (inscrits / capacité) +- Taux d'activité +- Recettes par événement + +#### Règles Métier +- Titre obligatoire +- Date de début obligatoire et ne peut pas être dans le passé (sauf tolérance de 1 heure) +- Date de fin ne peut pas être antérieure à la date de début +- Capacité maximale doit être positive si définie +- Prix ne peut pas être négatif +- Impossible de supprimer un événement avec des inscriptions +- Impossible de changer le statut d'un événement terminé ou annulé +- Calcul automatique de la durée en heures +- Calcul automatique des places restantes +- Vérification si un membre est déjà inscrit + +--- + +### 8. 🤝 Gestion des Adhésions + +#### Description +Processus complet de demande, validation et paiement d'adhésion à une organisation avec support Wave Mobile Money. + +#### Fonctionnalités Principales + +**Création de Demande d'Adhésion** +- Membre demandeur +- Organisation cible +- Date de demande (automatique si non fournie) +- Frais d'adhésion (montant) +- Code devise (défaut : XOF) + +**Génération Automatique** +- **Numéro de référence** : Format `ADH-{TIMESTAMP}-{UUID}` (ex: `ADH-1706541234567-A1B2C3D4`) + - Généré automatiquement si non fourni + - Unique dans tout le système + +**Workflow d'Adhésion** + +1. **EN_ATTENTE** (Statut initial) + - Demande soumise + - En attente de validation par l'organisation + +2. **APPROUVEE** + - Demande approuvée par un administrateur + - Date d'approbation enregistrée + - Approuveur enregistré + - Passage automatique en attente de paiement + +3. **EN_PAIEMENT** + - Paiement partiel effectué + - Montant payé < frais d'adhésion + +4. **PAYEE** + - Paiement intégral effectué + - Montant payé >= frais d'adhésion + - Date de paiement enregistrée + - Membre officiellement admis + +5. **REJETEE** + - Demande rejetée par l'organisation + - Motif de rejet enregistré + +6. **ANNULEE** + - Demande annulée (par le demandeur ou l'organisation) + +**Gestion des Paiements** +- **Paiement via Wave** : Paiement direct via Wave Mobile Money +- **Paiements multiples** : Support des paiements partiels +- **Application automatique** : Calcul automatique du montant restant +- **Validation** : Validation optionnelle par administrateur + +**Actions Métier** +- **Approuver** : Valide une demande en attente +- **Rejeter** : Refuse une demande avec motif +- **Enregistrer paiement** : Enregistre un paiement (partiel ou complet) +- **Annuler** : Annule une demande (si non payée) + +**Recherche et Filtrage** +- Par membre +- Par organisation +- Par statut +- Adhésions en attente +- Adhésions approuvées non payées + +**Statistiques** +- Total d'adhésions +- Adhésions approuvées +- Adhésions en attente +- Adhésions payées +- Taux d'approbation +- Taux de paiement +- Délai moyen de traitement + +#### Règles Métier +- Frais d'adhésion doivent être positifs +- Calcul automatique du montant restant : `fraisAdhesion - montantPaye` +- Seules les adhésions EN_ATTENTE peuvent être approuvées ou rejetées +- Seules les adhésions APPROUVEE ou EN_PAIEMENT peuvent recevoir un paiement +- Impossible de supprimer une adhésion déjà payée +- Passage automatique en PAYEE si paiement intégral +- Passage automatique en EN_PAIEMENT si paiement partiel +- Paiement via Wave prioritaire si compte Wave disponible + +--- + +### 9. ❤️ Système de Solidarité (Demandes d'Aide) + +#### Description +Gestion complète du cycle de vie des demandes d'aide entre membres : soumission, évaluation, approbation, versement via Wave. + +#### Fonctionnalités Principales + +**Types d'Aide** +- **FINANCIERE** : Aide financière directe (versement via Wave) +- **MATERIELLE** : Fourniture de matériel +- **ALIMENTAIRE** : Aide alimentaire +- **MEDICALE** : Aide médicale +- **SCOLAIRE** : Aide scolaire (frais, fournitures) +- **LOGEMENT** : Aide au logement +- **EMPLOI** : Aide à l'emploi +- **FORMATION** : Aide à la formation +- **AUTRE** : Autre type d'aide + +**Création de Demande** +- Titre et description détaillée +- Type d'aide +- Montant demandé (pour aide financière) +- Justification +- Documents fournis (via entité Document) +- Urgence (oui/non) +- Membre demandeur +- Organisation traitante + +**Génération Automatique** +- **Numéro de référence** : Format `DA-{ANNEE}-{NUMERO}` (ex: `DA-2025-123456`) + - Généré automatiquement + - Unique dans tout le système + +**Workflow de Demande d'Aide** + +1. **BROUILLON** (Statut initial) + - Demande en cours de rédaction + - Modifiable par le demandeur + +2. **SOUMISE** + - Demande soumise à l'organisation + - Date de soumission enregistrée + +3. **EN_ATTENTE** + - En attente d'évaluation + +4. **EN_COURS_EVALUATION** + - Évaluation en cours par un évaluateur + - Évaluateur assigné + +5. **INFORMATIONS_REQUISES** + - Informations complémentaires demandées + - Retour au demandeur + +6. **APPROUVEE** + - Demande approuvée intégralement + - Montant approuvé = montant demandé + - Date d'approbation enregistrée + +7. **APPROUVEE_PARTIELLEMENT** + - Demande approuvée partiellement + - Montant approuvé < montant demandé + +8. **EN_COURS_TRAITEMENT** + - Traitement en cours (préparation de l'aide) + +9. **EN_COURS_VERSEMENT** + - Versement en cours (pour aide financière) + - Initiation du paiement via Wave + +10. **VERSEE** + - Aide versée (pour aide financière) + - Date de versement enregistrée + - Transaction Wave confirmée + +11. **LIVREE** + - Aide livrée (pour aide matérielle) + +12. **TERMINEE** + - Processus complètement terminé + +13. **REJETEE** + - Demande rejetée + - Commentaire d'évaluation avec motif + +14. **ANNULEE** + - Demande annulée + +15. **EXPIREE** + - Demande expirée (délai dépassé) + +16. **SUSPENDUE** + - Demande temporairement suspendue + +17. **EN_SUIVI** + - Demande en suivi post-versement + +18. **CLOTUREE** + - Demande clôturée définitivement + +**Versement d'Aide Financière** +- **Via Wave Mobile Money** : Versement direct sur le compte Wave du demandeur +- **Via Virement Bancaire** : Virement bancaire +- **En Espèces** : Versement en espèces +- Traçabilité complète du versement +- Génération automatique d'écriture comptable + +**Recherche et Filtrage** +- Par organisation +- Par type d'aide +- Par statut +- Par priorité +- Par demandeur +- Demandes urgentes +- Demandes en retard (délai dépassé) + +**Statistiques** +- Total de demandes +- Demandes par statut +- Demandes par type +- Montants demandés vs approuvés +- Taux d'approbation +- Délais moyens de traitement +- Montants versés via Wave + +#### Règles Métier +- Une demande ne peut être modifiée qu'en statut BROUILLON +- Transitions de statut validées (workflow strict) +- Calcul automatique du pourcentage d'approbation +- Détection automatique des demandes en retard +- Vérification de l'urgence pour priorisation +- Historique complet et immuable des changements +- Versement via Wave prioritaire si compte Wave disponible + +--- + +### 10. 📄 Gestion Documentaire + +#### Description +Système complet de gestion des documents avec stockage sécurisé et vérification d'intégrité. + +#### Fonctionnalités Principales + +**Stockage des Documents** +- **Document** : Entité centrale pour tous les documents + - Nom de fichier et nom original + - Chemin de stockage sécurisé + - Type MIME + - Taille + - Hash MD5 et SHA256 pour vérification d'intégrité + - Type de document + - Description + +**Types de Documents** +- **IDENTITE** : Pièce d'identité +- **JUSTIFICATIF_DOMICILE** : Justificatif de domicile +- **PHOTO** : Photo de profil +- **CONTRAT** : Contrat +- **FACTURE** : Facture +- **RECU** : Reçu de paiement +- **RAPPORT** : Rapport +- **AUTRE** : Autre type + +**Pièces Jointes** +- **PieceJointe** : Association flexible des documents + - Association à n'importe quelle entité (Membre, Organisation, Cotisation, Adhésion, DemandeAide, TransactionWave) + - Ordre d'affichage + - Libellé et commentaire + - Traçabilité complète + +**Sécurité** +- Stockage sécurisé (chiffrement optionnel) +- Vérification d'intégrité (hash) +- Contrôle d'accès par rôle +- Audit des téléchargements + +#### Règles Métier +- Hash MD5 et SHA256 obligatoires +- Vérification d'intégrité à chaque accès +- Association flexible via PieceJointe +- Taille maximale configurable + +--- + +### 11. 🔔 Système de Notifications + +#### Description +Système multi-canaux de notifications avec templates réutilisables. + +#### Fonctionnalités Principales + +**Types de Notifications** +- **EMAIL** : Notification par email +- **SMS** : Notification par SMS +- **PUSH** : Notification push (mobile) +- **IN_APP** : Notification dans l'application +- **SYSTEME** : Notification système + +**Templates de Notifications** +- **TemplateNotification** : Templates réutilisables + - Code unique + - Sujet et corps (texte et HTML) + - Variables disponibles (JSON) + - Canaux supportés + - Support multi-langues + +**Gestion des Notifications** +- **Notification** : Notifications individuelles + - Type et priorité + - Statut : EN_ATTENTE, ENVOYEE, LUE, ECHOUE, ANNULEE + - Titre et message + - Date d'envoi et de lecture + - Canaux d'envoi + - Retry automatique + - Métadonnées JSON + +**Priorités** +- **CRITIQUE** : Intervention immédiate +- **HAUTE** : Intervention rapide +- **NORMALE** : Traitement normal +- **BASSE** : Traitement différé + +**Notifications Automatiques** +- Nouvelle cotisation créée +- Cotisation en retard +- Paiement reçu (via Wave) +- Transaction Wave confirmée +- Adhésion approuvée +- Demande d'aide soumise +- Événement à venir + +#### Règles Métier +- Retry automatique en cas d'échec +- Priorisation selon la priorité +- Templates réutilisables +- Support multi-langues + +--- + +## 🔄 Processus Métier Principaux + +### Processus 1 : Inscription d'un Nouveau Membre + +1. **Saisie des informations** + - Nom, prénom, email, téléphone + - Téléphone Wave (optionnel) + - Date de naissance + - Organisation d'affiliation + - Adresse(s) + +2. **Validation automatique** + - Vérification unicité email + - Génération numéro de membre + - Définition date d'adhésion + - Vérification format téléphone Wave + +3. **Création du membre** + - Persistance en base + - Attribution statut ACTIF par défaut + - Attribution rôle MEMBRE par défaut + +4. **Mise à jour organisation** + - Incrémentation nombre de membres + +5. **Notification** + - Notification de bienvenue au membre + - Notification à l'administrateur + +### Processus 2 : Paiement de Cotisation via Wave + +1. **Sélection de la cotisation** + - Membre consulte ses cotisations + - Sélection d'une cotisation à payer + +2. **Initiation du paiement Wave** + - Vérification du compte Wave du membre + - Création de TransactionWave (statut INITIALISE) + - Création de Paiement (statut EN_ATTENTE) + - Appel Wave API + +3. **Traitement Webhook** + - Réception du webhook Wave + - Validation de la signature + - Mise à jour TransactionWave (statut REUSSIE) + - Mise à jour Paiement (statut VALIDE) + +4. **Application au paiement** + - Création de PaiementCotisation + - Mise à jour du montant payé de la cotisation + - Mise à jour du statut (PAYEE si intégral) + +5. **Génération comptable** + - Création d'écriture comptable + - Débit compte caisse Wave + - Crédit compte produits (cotisations) + +6. **Notifications** + - Notification de confirmation au membre + - Notification à l'administrateur + - Génération de reçu + +### Processus 3 : Organisation d'un Événement avec Paiement + +1. **Création de l'événement** + - Saisie des informations (titre, dates, lieu, etc.) + - Définition capacité et prix + - Configuration inscriptions + - Association d'une adresse + +2. **Ouverture des inscriptions** + - Vérification automatique des conditions + - Affichage public si visible + +3. **Inscription et paiement** + - Membre s'inscrit à l'événement + - Si payant, initiation du paiement via Wave + - Traitement du paiement + - Confirmation de l'inscription + +4. **Gestion des inscriptions** + - Validation/refus des inscriptions + - Suivi du nombre d'inscrits + - Gestion des places + +5. **Exécution de l'événement** + - Changement de statut (CONFIRME → EN_COURS → TERMINE) + - Suivi de la participation + +### Processus 4 : Demande d'Adhésion avec Paiement Wave + +1. **Soumission de la demande** + - Membre soumet une demande d'adhésion + - Statut initial : EN_ATTENTE + +2. **Évaluation** + - Administrateur examine la demande + - Décision : APPROUVEE ou REJETEE + +3. **Paiement via Wave** + - Si approuvée, membre paie via Wave + - Traitement du paiement + - Application à l'adhésion + +4. **Finalisation** + - Passage en statut PAYEE + - Membre officiellement admis + - Mise à jour de l'organisation + - Notification de confirmation + +### Processus 5 : Demande d'Aide avec Versement Wave + +1. **Création de la demande** + - Membre crée une demande (statut BROUILLON) + - Saisie des informations complètes + - Upload de documents justificatifs + +2. **Soumission** + - Passage en statut SOUMISE + - Assignation à l'organisation + +3. **Évaluation** + - Assignation d'un évaluateur + - Statut : EN_COURS_EVALUATION + - Analyse de la demande + +4. **Décision** + - APPROUVEE / APPROUVEE_PARTIELLEMENT / REJETEE + - Enregistrement du montant approuvé (si financière) + +5. **Versement via Wave** + - Si aide financière approuvée + - Initiation du versement via Wave + - Vérification du compte Wave du demandeur + - Traitement de la transaction + - Confirmation du versement + +6. **Suivi** + - Statut VERSEE + - Statut TERMINEE ou CLOTUREE + - Historique complet conservé + +--- + +## 📊 Indicateurs et Statistiques + +### Indicateurs Organisationnels +- Nombre total de membres +- Nombre de membres actifs +- Taux d'activité (%) +- Nouveaux membres (30 jours) +- Budget annuel +- Montant cotisations collectées +- Répartition par rôles + +### Indicateurs Financiers +- Total cotisations créées +- Cotisations payées +- Cotisations en retard +- Taux de paiement (%) +- Montants collectés par période +- Adhésions payées +- Répartition par méthode de paiement +- Transactions Wave (nombre, montant) +- Taux de succès Wave (%) + +### Indicateurs Wave Mobile Money +- Nombre de comptes Wave actifs +- Nombre de transactions Wave +- Montant total des transactions +- Taux de succès des transactions +- Temps moyen de traitement +- Frais de transaction totaux +- Transactions échouées + +### Indicateurs Événementiels +- Total événements +- Événements à venir +- Événements en cours +- Taux de remplissage moyen +- Participation moyenne +- Recettes par événement + +### Indicateurs Solidarité +- Total demandes d'aide +- Demandes urgentes +- Demandes approuvées +- Montants demandés vs approuvés +- Taux d'approbation +- Délais moyens de traitement +- Montants versés via Wave + +### Indicateurs Comptables +- Nombre d'écritures +- Solde des comptes +- Écritures non pointées +- Écritures non lettrées +- Écarts de rapprochement + +--- + +## 🔐 Sécurité et Contrôle d'Accès + +### Authentification +- **Keycloak OIDC** : Authentification centralisée +- Tokens JWT pour l'accès aux APIs +- Refresh automatique des tokens +- Support SSO + +### Autorisations par Rôle +- **Système de rôles hiérarchiques** : Rôles système et personnalisés +- **Permissions granulaires** : Structure Module > Ressource > Action +- **SUPER_ADMIN** : Accès total +- **ADMIN** : Gestion de son organisation +- **MEMBRE** : Consultation et actions limitées +- **ORGANISATEUR_EVENEMENT** : Gestion événements +- **TRESORIER** : Gestion financière et comptable + +### Audit et Traçabilité +- **AuditLog** : Enregistrement de toutes les actions importantes +- Champs d'audit sur toutes les entités : + - `creePar` : Créateur + - `modifiePar` : Dernier modificateur + - `dateCreation` : Date de création + - `dateModification` : Date de modification + - `version` : Version optimiste (gestion des conflits) +- Types d'actions auditées : + - CREATE, UPDATE, DELETE, READ + - LOGIN, LOGOUT + - EXPORT, IMPORT + - VALIDATION, REJECTION +- Sévérités : CRITIQUE, ELEVEE, MOYENNE, BASSE, INFO + +### Sécurité Wave +- Chiffrement des clés API +- Validation de signature des webhooks +- Audit complet des transactions +- Traçabilité IP et User-Agent +- Gestion des erreurs et retry sécurisé + +--- + +## 🎨 Principes de Conception + +### DRY (Don't Repeat Yourself) +- Composants réutilisables pour l'UI +- Services partagés +- DTOs standardisés +- Entité Paiement centralisée +- Entité Document centralisée + +### WOU (Write Once Use) +- Bibliothèque de composants JSF/PrimeFaces +- Fragments réutilisables +- Templates standardisés +- Templates de notifications + +### Séparation des Préoccupations +- **API** : Contrats et interfaces +- **Implémentation** : Logique métier et persistance +- **Client** : Interface utilisateur +- **Wave Service** : Service dédié Wave +- **Comptabilité Service** : Service dédié comptabilité + +### Normalisation +- Normalisation 3NF +- Séparation des adresses +- Tables de liaison pour relations many-to-many +- Entités dédiées pour chaque concept + +### Traçabilité Complète +- Historique des modifications +- Logs d'audit +- Versioning optimiste +- Audit des transactions Wave +- Vérification d'intégrité des documents + +--- + +## 🚀 Évolutions Futures + +### Court Terme (Implémenté) +- ✅ Intégration paiements mobiles (Wave Mobile Money) +- ✅ Notifications automatiques (email, SMS) +- ✅ Gestion documentaire (upload de documents) +- ✅ Système de rôles et permissions + +### Moyen Terme +- Application mobile native (Flutter) +- Tableau de bord analytique avancé +- Export de rapports (PDF, Excel) +- Intégration Orange Money et MTN Mobile Money +- API REST complète +- Webhooks personnalisés + +### Long Terme +- Intelligence artificielle pour recommandations +- Prédiction des cotisations en retard +- Optimisation automatique des événements +- Analyse prédictive des demandes d'aide +- Intégration avec systèmes bancaires +- Blockchain pour traçabilité + +--- + +## 📞 Support et Maintenance + +### Support Utilisateur +- Documentation complète +- Formation des administrateurs +- Support technique +- Guide d'utilisation Wave Mobile Money + +### Maintenance +- Sauvegardes régulières +- Mises à jour de sécurité +- Monitoring des performances +- Monitoring des transactions Wave +- Alertes en cas d'échec de transaction + +--- + +## 📝 Notes Techniques + +### Architecture Technique +- **Backend** : Java EE / Jakarta EE +- **Base de données** : PostgreSQL +- **Authentification** : Keycloak OIDC +- **API Wave** : Intégration REST +- **Webhooks** : Traitement asynchrone avec queue +- **Chiffrement** : AES-256 pour données sensibles + +### Performance +- Indexation optimale des clés étrangères +- Cache pour configurations fréquentes +- Traitement asynchrone des webhooks +- Pagination pour grandes listes + +### Conformité +- Comptabilité en partie double +- Traçabilité complète +- Audit des transactions +- Conformité RGPD (anonymisation) + +--- + +**Document généré le** : 2025-01-29 +**Version UnionFlow** : 3.0 (Optimisée avec intégration Wave Mobile Money) +**Auteur** : UnionFlow Team