feat: PHASE 1 - Adresses et Rôles/Permissions
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
This commit is contained in:
310
PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md
Normal file
310
PLAN_IMPLEMENTATION_ARCHITECTURE_V3.md
Normal file
@@ -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
|
||||
|
||||
275
union-flow.puml
Normal file
275
union-flow.puml
Normal file
@@ -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
|
||||
@@ -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) {
|
||||
// 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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,24 +6,18 @@
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">#{empty param.id ? 'Inscription' : 'Modification'} Membre - UnionFlow</ui:define>
|
||||
|
||||
<!-- Charger le membre si ID fourni (mode modification) -->
|
||||
<f:metadata>
|
||||
<f:viewParam name="id" value="#{membreInscriptionBean.membreIdString}" />
|
||||
<f:viewAction action="#{membreInscriptionBean.chargerMembreSiModification}" />
|
||||
</f:metadata>
|
||||
<ui:define name="title">Inscription Membre - UnionFlow</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="#{empty param.id ? 'pi pi-user-plus text-primary' : 'pi pi-pencil text-warning'}" />
|
||||
<ui:param name="title" value="#{empty param.id ? 'Inscription Nouveau Membre' : 'Modifier le Membre'}" />
|
||||
<ui:param name="description" value="#{empty param.id ? 'Formulaire complet d\'inscription avec photo et documents' : 'Modifiez les informations du membre'}" />
|
||||
<ui:param name="icon" value="pi pi-user-plus text-primary" />
|
||||
<ui:param name="title" value="Inscription Nouveau Membre" />
|
||||
<ui:param name="description" value="Formulaire complet d'inscription avec photo et documents" />
|
||||
<ui:define name="actions">
|
||||
<div>
|
||||
<div class="text-900 font-medium">Numéro: #{membreInscriptionBean.numeroGenere}</div>
|
||||
<small class="text-600">#{empty param.id ? 'Généré automatiquement' : 'Existant'}</small>
|
||||
<small class="text-600">Généré automatiquement</small>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
@@ -100,7 +94,7 @@
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateNaissance" value="Date de naissance" />
|
||||
<p:calendar id="dateNaissance" value="#{membreInscriptionBean.dateNaissance}"
|
||||
<p:calendar id="dateNaissance" value="#{membreInscriptionBean.dateNaissance}" required="true"
|
||||
pattern="dd/MM/yyyy" showIcon="true" yearNavigator="true" yearRange="1920:2030"
|
||||
monthNavigator="true" requiredMessage="La date de naissance est obligatoire"
|
||||
styleClass="w-full" />
|
||||
@@ -113,7 +107,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="sexe" value="Sexe" />
|
||||
<p:selectOneMenu id="sexe" value="#{membreInscriptionBean.sexe}" styleClass="w-full">
|
||||
<p:selectOneMenu id="sexe" value="#{membreInscriptionBean.sexe}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.sexeOptions}" />
|
||||
</p:selectOneMenu>
|
||||
@@ -148,6 +142,7 @@
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceNom" value="Nom complet" />
|
||||
<p:inputText id="contactUrgenceNom" value="#{membreInscriptionBean.contactUrgenceNom}"
|
||||
required="true" requiredMessage="Le nom du contact d'urgence est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="contactUrgenceNom" />
|
||||
</div>
|
||||
@@ -155,12 +150,13 @@
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceTelephone" value="Téléphone" />
|
||||
<p:inputText id="contactUrgenceTelephone" value="#{membreInscriptionBean.contactUrgenceTelephone}"
|
||||
required="true" requiredMessage="Le téléphone du contact d'urgence est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="contactUrgenceTelephone" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="contactUrgenceLien" value="Lien de parenté" />
|
||||
<p:selectOneMenu id="contactUrgenceLien" value="#{membreInscriptionBean.contactUrgenceLien}" styleClass="w-full">
|
||||
<p:selectOneMenu id="contactUrgenceLien" value="#{membreInscriptionBean.contactUrgenceLien}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.contactUrgenceLienOptions}" />
|
||||
</p:selectOneMenu>
|
||||
@@ -221,6 +217,7 @@
|
||||
<div class="field">
|
||||
<p:outputLabel for="adresse" value="Adresse complète" />
|
||||
<p:inputTextarea id="adresse" value="#{membreInscriptionBean.adresse}" rows="4"
|
||||
required="true" requiredMessage="L'adresse est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="adresse" />
|
||||
</div>
|
||||
@@ -228,7 +225,8 @@
|
||||
<div class="formgrid grid">
|
||||
<div class="field col">
|
||||
<p:outputLabel for="ville" value="Ville" />
|
||||
<p:inputText id="ville" value="#{membreInscriptionBean.ville}" styleClass="w-full" />
|
||||
<p:inputText id="ville" value="#{membreInscriptionBean.ville}" required="true"
|
||||
requiredMessage="La ville est obligatoire" styleClass="w-full" />
|
||||
<p:message for="ville" />
|
||||
</div>
|
||||
<div class="field col">
|
||||
@@ -255,6 +253,7 @@
|
||||
<div class="field col">
|
||||
<p:outputLabel for="telephoneMobile" value="Téléphone mobile" />
|
||||
<p:inputText id="telephoneMobile" value="#{membreInscriptionBean.telephoneMobile}"
|
||||
required="true" requiredMessage="Le téléphone mobile est obligatoire"
|
||||
styleClass="w-full" />
|
||||
<p:message for="telephoneMobile" />
|
||||
</div>
|
||||
@@ -279,7 +278,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="typeAdhesion" value="Type d'adhésion" />
|
||||
<p:selectOneMenu id="typeAdhesion" value="#{membreInscriptionBean.typeAdhesion}" styleClass="w-full">
|
||||
<p:selectOneMenu id="typeAdhesion" value="#{membreInscriptionBean.typeAdhesion}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{membreInscriptionBean.typeAdhesionOptions}" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
iconOnly="true"/>
|
||||
<!-- DRY/WOU: Composite Component action-button-edit-nav -->
|
||||
<uf:action-button-edit-nav itemId="#{membre.id}"
|
||||
editPage="/pages/secure/membre/inscription.xhtml"
|
||||
editPage="/pages/secure/membre/modifier.xhtml"
|
||||
iconOnly="true"/>
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-dollar" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,10 @@ public class Evenement extends BaseEntity {
|
||||
@Builder.Default
|
||||
private List<InscriptionEvenement> inscriptions = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
/** Types d'événements */
|
||||
public enum TypeEvenement {
|
||||
ASSEMBLEE_GENERALE("Assemblée Générale"),
|
||||
|
||||
@@ -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<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<MembreRole> roles = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour obtenir le nom complet */
|
||||
public String getNomComplet() {
|
||||
return prenom + " " + nom;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,10 @@ public class Organisation extends BaseEntity {
|
||||
@Builder.Default
|
||||
private List<Membre> membres = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<Adresse> adresses = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour obtenir le nom complet avec sigle */
|
||||
public String getNomComplet() {
|
||||
if (nomCourt != null && !nomCourt.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Permission pour la gestion des permissions granulaires
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "permissions",
|
||||
indexes = {
|
||||
@Index(name = "idx_permission_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_permission_module", columnList = "module"),
|
||||
@Index(name = "idx_permission_ressource", columnList = "ressource")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Permission extends BaseEntity {
|
||||
|
||||
/** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 100)
|
||||
private String code;
|
||||
|
||||
/** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
|
||||
@NotBlank
|
||||
@Column(name = "module", nullable = false, length = 50)
|
||||
private String module;
|
||||
|
||||
/** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
|
||||
@NotBlank
|
||||
@Column(name = "ressource", nullable = false, length = 50)
|
||||
private String ressource;
|
||||
|
||||
/** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
|
||||
@NotBlank
|
||||
@Column(name = "action", nullable = false, length = 50)
|
||||
private String action;
|
||||
|
||||
/** Libellé de la permission */
|
||||
@Column(name = "libelle", length = 200)
|
||||
private String libelle;
|
||||
|
||||
/** Description de la permission */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Rôles associés */
|
||||
@OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<RolePermission> roles = new ArrayList<>();
|
||||
|
||||
/** Méthode métier pour générer le code à partir des composants */
|
||||
public static String genererCode(String module, String ressource, String action) {
|
||||
return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le code est valide */
|
||||
public boolean isCodeValide() {
|
||||
return code != null && code.contains(" > ") && code.split(" > ").length == 3;
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
// Générer le code si non fourni
|
||||
if (code == null || code.isEmpty()) {
|
||||
if (module != null && ressource != null && action != null) {
|
||||
code = genererCode(module, ressource, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Role pour la gestion des rôles dans le système
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "roles",
|
||||
indexes = {
|
||||
@Index(name = "idx_role_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_role_actif", columnList = "actif"),
|
||||
@Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Role extends BaseEntity {
|
||||
|
||||
/** Code unique du rôle */
|
||||
@NotBlank
|
||||
@Column(name = "code", unique = true, nullable = false, length = 50)
|
||||
private String code;
|
||||
|
||||
/** Libellé du rôle */
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 100)
|
||||
private String libelle;
|
||||
|
||||
/** Description du rôle */
|
||||
@Column(name = "description", length = 500)
|
||||
private String description;
|
||||
|
||||
/** Niveau hiérarchique (plus bas = plus prioritaire) */
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@Column(name = "niveau_hierarchique", nullable = false)
|
||||
private Integer niveauHierarchique = 100;
|
||||
|
||||
/** Type de rôle */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_role", nullable = false, length = 50)
|
||||
private TypeRole typeRole;
|
||||
|
||||
/** Organisation propriétaire (null pour rôles système) */
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
/** Permissions associées */
|
||||
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<RolePermission> permissions = new ArrayList<>();
|
||||
|
||||
/** Énumération des types de rôle */
|
||||
public enum TypeRole {
|
||||
SYSTEME("Rôle Système"),
|
||||
ORGANISATION("Rôle Organisation"),
|
||||
PERSONNALISE("Rôle Personnalisé");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
TypeRole(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si c'est un rôle système */
|
||||
public boolean isRoleSysteme() {
|
||||
return TypeRole.SYSTEME.equals(typeRole);
|
||||
}
|
||||
|
||||
/** Callback JPA avant la persistance */
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (typeRole == null) {
|
||||
typeRole = TypeRole.PERSONNALISE;
|
||||
}
|
||||
if (niveauHierarchique == null) {
|
||||
niveauHierarchique = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Adresse> {
|
||||
|
||||
/**
|
||||
* Trouve toutes les adresses d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des adresses
|
||||
*/
|
||||
public List<Adresse> 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<Adresse> 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<Adresse> 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<Adresse> 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<Adresse> 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<Adresse> 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<Adresse> 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<Adresse> findByPays(String pays) {
|
||||
return find("LOWER(pays) = LOWER(?1)", pays).list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MembreRole> {
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles d'un membre
|
||||
*
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des attributions de rôles
|
||||
*/
|
||||
public List<MembreRole> 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<MembreRole> 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<MembreRole> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Permission> {
|
||||
|
||||
/**
|
||||
* Trouve une permission par son code
|
||||
*
|
||||
* @param code Code de la permission
|
||||
* @return Permission ou Optional.empty()
|
||||
*/
|
||||
public Optional<Permission> findByCode(String code) {
|
||||
return find("code", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les permissions par module
|
||||
*
|
||||
* @param module Nom du module
|
||||
* @return Liste des permissions
|
||||
*/
|
||||
public List<Permission> 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<Permission> 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<Permission> 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<Permission> findAllActives() {
|
||||
return find("actif = true").order("module ASC, ressource ASC, action ASC").list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RolePermission> {
|
||||
|
||||
/**
|
||||
* Trouve toutes les permissions d'un rôle
|
||||
*
|
||||
* @param roleId ID du rôle
|
||||
* @return Liste des associations rôle-permission
|
||||
*/
|
||||
public List<RolePermission> 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<RolePermission> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Role> {
|
||||
|
||||
/**
|
||||
* Trouve un rôle par son code
|
||||
*
|
||||
* @param code Code du rôle
|
||||
* @return Rôle ou Optional.empty()
|
||||
*/
|
||||
public Optional<Role> findByCode(String code) {
|
||||
return find("code", code).firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve tous les rôles système
|
||||
*
|
||||
* @return Liste des rôles système
|
||||
*/
|
||||
public List<Role> 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<Role> 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<Role> 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<Role> findByType(TypeRole typeRole) {
|
||||
return find("typeRole = ?1 AND actif = true", typeRole)
|
||||
.order("niveauHierarchique ASC")
|
||||
.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AdresseDTO> 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<AdresseDTO> 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<Adresse> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Permission> listerParModule(String module) {
|
||||
return permissionRepository.findByModule(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les permissions par ressource
|
||||
*
|
||||
* @param ressource Nom de la ressource
|
||||
* @return Liste des permissions
|
||||
*/
|
||||
public List<Permission> listerParRessource(String ressource) {
|
||||
return permissionRepository.findByRessource(ressource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les permissions actives
|
||||
*
|
||||
* @return Liste des permissions actives
|
||||
*/
|
||||
public List<Permission> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Role> 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<Role> listerParOrganisation(UUID organisationId) {
|
||||
return roleRepository.findByOrganisationId(organisationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les rôles actifs
|
||||
*
|
||||
* @return Liste des rôles actifs
|
||||
*/
|
||||
public List<Role> 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);
|
||||
}
|
||||
}
|
||||
|
||||
1297
unionflow.md
Normal file
1297
unionflow.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user