From 4a0c5f9d3307ce65e350a3056eb03a3e8ba50cab Mon Sep 17 00:00:00 2001 From: dahoud Date: Wed, 10 Dec 2025 01:08:17 +0000 Subject: [PATCH] Configure Maven repository for unionflow-server-api dependency --- Dockerfile.prod | 91 ++ [Help | 0 docker-compose.dev.yml | 43 + mvn | 0 pom.xml | 337 +++++++ setup-postgres.sql | 27 + .../server/auth/AuthCallbackResource.java | 136 +++ .../server/UnionFlowServerApplication.java | 35 + .../server/dto/EvenementMobileDTO.java | 143 +++ .../unionflow/server/entity/Adhesion.java | 132 +++ .../unionflow/server/entity/Adresse.java | 154 ++++ .../unionflow/server/entity/AuditLog.java | 81 ++ .../unionflow/server/entity/BaseEntity.java | 141 +++ .../server/entity/CompteComptable.java | 120 +++ .../unionflow/server/entity/CompteWave.java | 107 +++ .../server/entity/ConfigurationWave.java | 69 ++ .../unionflow/server/entity/Cotisation.java | 184 ++++ .../unionflow/server/entity/DemandeAide.java | 130 +++ .../unionflow/server/entity/Document.java | 128 +++ .../server/entity/EcritureComptable.java | 172 ++++ .../unionflow/server/entity/Evenement.java | 267 ++++++ .../server/entity/InscriptionEvenement.java | 156 ++++ .../server/entity/JournalComptable.java | 98 ++ .../server/entity/LigneEcriture.java | 100 +++ .../lions/unionflow/server/entity/Membre.java | 106 +++ .../unionflow/server/entity/MembreRole.java | 88 ++ .../unionflow/server/entity/Notification.java | 132 +++ .../unionflow/server/entity/Organisation.java | 308 +++++++ .../unionflow/server/entity/Paiement.java | 169 ++++ .../server/entity/PaiementAdhesion.java | 75 ++ .../unionflow/server/entity/PaiementAide.java | 75 ++ .../server/entity/PaiementCotisation.java | 76 ++ .../server/entity/PaiementEvenement.java | 75 ++ .../unionflow/server/entity/Permission.java | 90 ++ .../unionflow/server/entity/PieceJointe.java | 103 +++ .../lions/unionflow/server/entity/Role.java | 105 +++ .../server/entity/RolePermission.java | 54 ++ .../server/entity/TemplateNotification.java | 81 ++ .../server/entity/TransactionWave.java | 161 ++++ .../server/entity/TypeOrganisationEntity.java | 73 ++ .../unionflow/server/entity/WebhookWave.java | 118 +++ .../JsonProcessingExceptionMapper.java | 39 + .../server/repository/AdhesionRepository.java | 102 +++ .../server/repository/AdresseRepository.java | 111 +++ .../server/repository/AuditLogRepository.java | 26 + .../server/repository/BaseRepository.java | 148 +++ .../repository/CompteComptableRepository.java | 80 ++ .../repository/CompteWaveRepository.java | 98 ++ .../ConfigurationWaveRepository.java | 59 ++ .../repository/CotisationRepository.java | 392 ++++++++ .../repository/DemandeAideRepository.java | 275 ++++++ .../server/repository/DocumentRepository.java | 70 ++ .../EcritureComptableRepository.java | 109 +++ .../repository/EvenementRepository.java | 463 ++++++++++ .../JournalComptableRepository.java | 83 ++ .../repository/LigneEcritureRepository.java | 51 ++ .../server/repository/MembreRepository.java | 238 +++++ .../repository/MembreRoleRepository.java | 77 ++ .../repository/NotificationRepository.java | 127 +++ .../repository/OrganisationRepository.java | 424 +++++++++ .../server/repository/PaiementRepository.java | 110 +++ .../repository/PermissionRepository.java | 87 ++ .../repository/PieceJointeRepository.java | 100 +++ .../repository/RolePermissionRepository.java | 61 ++ .../server/repository/RoleRepository.java | 83 ++ .../TemplateNotificationRepository.java | 59 ++ .../repository/TransactionWaveRepository.java | 109 +++ .../TypeOrganisationRepository.java | 43 + .../repository/WebhookWaveRepository.java | 104 +++ .../server/resource/AdhesionResource.java | 705 +++++++++++++++ .../server/resource/AnalyticsResource.java | 345 +++++++ .../server/resource/AuditResource.java | 112 +++ .../server/resource/ComptabiliteResource.java | 278 ++++++ .../server/resource/CotisationResource.java | 674 ++++++++++++++ .../server/resource/DashboardResource.java | 251 ++++++ .../server/resource/DocumentResource.java | 158 ++++ .../server/resource/EvenementResource.java | 452 ++++++++++ .../server/resource/ExportResource.java | 119 +++ .../server/resource/HealthResource.java | 33 + .../server/resource/MembreResource.java | 643 +++++++++++++ .../server/resource/NotificationResource.java | 246 +++++ .../server/resource/OrganisationResource.java | 423 +++++++++ .../server/resource/PaiementResource.java | 213 +++++ .../server/resource/PreferencesResource.java | 75 ++ .../resource/TypeOrganisationResource.java | 165 ++++ .../server/resource/WaveResource.java | 269 ++++++ .../server/security/SecurityConfig.java | 214 +++++ .../server/service/AdhesionService.java | 559 ++++++++++++ .../server/service/AdresseService.java | 353 ++++++++ .../server/service/AnalyticsService.java | 478 ++++++++++ .../server/service/AuditService.java | 229 +++++ .../server/service/ComptabiliteService.java | 479 ++++++++++ .../server/service/CotisationService.java | 493 ++++++++++ .../server/service/DashboardServiceImpl.java | 254 ++++++ .../server/service/DemandeAideService.java | 400 +++++++++ .../server/service/DocumentService.java | 311 +++++++ .../server/service/EvenementService.java | 340 +++++++ .../server/service/ExportService.java | 237 +++++ .../server/service/KPICalculatorService.java | 363 ++++++++ .../server/service/KeycloakService.java | 311 +++++++ .../server/service/MatchingService.java | 428 +++++++++ .../service/MembreImportExportService.java | 842 ++++++++++++++++++ .../server/service/MembreService.java | 740 +++++++++++++++ .../service/NotificationHistoryService.java | 322 +++++++ .../server/service/NotificationService.java | 352 ++++++++ .../server/service/OrganisationService.java | 443 +++++++++ .../server/service/PaiementService.java | 309 +++++++ .../server/service/PermissionService.java | 165 ++++ .../PreferencesNotificationService.java | 140 +++ .../service/PropositionAideService.java | 442 +++++++++ .../unionflow/server/service/RoleService.java | 171 ++++ .../server/service/TrendAnalysisService.java | 412 +++++++++ .../service/TypeOrganisationService.java | 146 +++ .../unionflow/server/service/WaveService.java | 393 ++++++++ .../unionflow/server/util/IdConverter.java | 150 ++++ src/main/resources/META-INF/beans.xml | 8 + .../resources/application-minimal.properties | 56 ++ .../resources/application-prod.properties | 77 ++ .../resources/application-test.properties | 31 + src/main/resources/application.properties | 103 +++ .../V1.2__Create_Organisation_Table.sql | 143 +++ .../migration/V1.3__Convert_Ids_To_UUID.sql | 419 +++++++++ src/main/resources/import.sql | 10 + .../resources/keycloak/unionflow-realm.json | 307 +++++++ .../UnionFlowServerApplicationTest.java | 155 ++++ .../server/entity/MembreSimpleTest.java | 237 +++++ .../MembreRepositoryIntegrationTest.java | 184 ++++ .../repository/MembreRepositoryTest.java | 105 +++ .../server/resource/AideResourceTest.java | 394 ++++++++ .../resource/CotisationResourceTest.java | 325 +++++++ .../resource/EvenementResourceTest.java | 448 ++++++++++ .../server/resource/HealthResourceTest.java | 69 ++ ...MembreResourceCompleteIntegrationTest.java | 318 +++++++ .../MembreResourceSimpleIntegrationTest.java | 259 ++++++ .../server/resource/MembreResourceTest.java | 275 ++++++ .../resource/OrganisationResourceTest.java | 345 +++++++ .../server/service/AideServiceTest.java | 327 +++++++ .../server/service/EvenementServiceTest.java | 403 +++++++++ .../server/service/MembreServiceTest.java | 344 +++++++ .../service/OrganisationServiceTest.java | 356 ++++++++ .../resource/EvenementResourceTest.java | 400 +++++++++ .../MembreResourceAdvancedSearchTest.java | 334 +++++++ .../MembreResourceImportExportTest.java | 292 ++++++ .../resource/OrganisationResourceTest.java | 351 ++++++++ .../MembreImportExportServiceTest.java | 369 ++++++++ .../MembreServiceAdvancedSearchTest.java | 400 +++++++++ target/classes/META-INF/beans.xml | 8 + target/classes/application-minimal.properties | 56 ++ target/classes/application-prod.properties | 77 ++ target/classes/application-test.properties | 31 + target/classes/application.properties | 103 +++ .../V1.2__Create_Organisation_Table.sql | 143 +++ .../migration/V1.3__Convert_Ids_To_UUID.sql | 419 +++++++++ .../server/auth/AuthCallbackResource.class | Bin 0 -> 5120 bytes .../server/UnionFlowServerApplication.class | Bin 0 -> 1477 bytes ...tMobileDTO$EvenementMobileDTOBuilder.class | Bin 0 -> 7781 bytes .../server/dto/EvenementMobileDTO.class | Bin 0 -> 21856 bytes .../entity/Adhesion$AdhesionBuilder.class | Bin 0 -> 5429 bytes .../unionflow/server/entity/Adhesion.class | Bin 0 -> 14348 bytes .../entity/Adresse$AdresseBuilder.class | Bin 0 -> 5371 bytes .../unionflow/server/entity/Adresse.class | Bin 0 -> 13522 bytes .../unionflow/server/entity/AuditLog.class | Bin 0 -> 4965 bytes .../unionflow/server/entity/BaseEntity.class | Bin 0 -> 3658 bytes ...mpteComptable$CompteComptableBuilder.class | Bin 0 -> 5414 bytes .../server/entity/CompteComptable.class | Bin 0 -> 11543 bytes .../entity/CompteWave$CompteWaveBuilder.class | Bin 0 -> 4996 bytes .../unionflow/server/entity/CompteWave.class | Bin 0 -> 10925 bytes ...urationWave$ConfigurationWaveBuilder.class | Bin 0 -> 2354 bytes .../server/entity/ConfigurationWave.class | Bin 0 -> 5341 bytes .../entity/Cotisation$CotisationBuilder.class | Bin 0 -> 7104 bytes .../unionflow/server/entity/Cotisation.class | Bin 0 -> 19719 bytes .../DemandeAide$DemandeAideBuilder.class | Bin 0 -> 5968 bytes .../unionflow/server/entity/DemandeAide.class | Bin 0 -> 14442 bytes .../entity/Document$DocumentBuilder.class | Bin 0 -> 5107 bytes .../unionflow/server/entity/Document.class | Bin 0 -> 12466 bytes ...reComptable$EcritureComptableBuilder.class | Bin 0 -> 6117 bytes .../server/entity/EcritureComptable.class | Bin 0 -> 15702 bytes .../entity/Evenement$EvenementBuilder.class | Bin 0 -> 8586 bytes .../entity/Evenement$StatutEvenement.class | Bin 0 -> 1853 bytes .../entity/Evenement$TypeEvenement.class | Bin 0 -> 2194 bytes .../unionflow/server/entity/Evenement.class | Bin 0 -> 22924 bytes ...venement$InscriptionEvenementBuilder.class | Bin 0 -> 3898 bytes ...scriptionEvenement$StatutInscription.class | Bin 0 -> 1820 bytes .../server/entity/InscriptionEvenement.class | Bin 0 -> 7331 bytes ...nalComptable$JournalComptableBuilder.class | Bin 0 -> 4150 bytes .../server/entity/JournalComptable.class | Bin 0 -> 8907 bytes .../LigneEcriture$LigneEcritureBuilder.class | Bin 0 -> 3479 bytes .../server/entity/LigneEcriture.class | Bin 0 -> 8148 bytes .../server/entity/Membre$MembreBuilder.class | Bin 0 -> 5962 bytes .../unionflow/server/entity/Membre.class | Bin 0 -> 13679 bytes .../entity/MembreRole$MembreRoleBuilder.class | Bin 0 -> 2827 bytes .../unionflow/server/entity/MembreRole.class | Bin 0 -> 6267 bytes .../Notification$NotificationBuilder.class | Bin 0 -> 6586 bytes .../server/entity/Notification.class | Bin 0 -> 13450 bytes .../Organisation$OrganisationBuilder.class | Bin 0 -> 12085 bytes .../server/entity/Organisation.class | Bin 0 -> 34875 bytes .../entity/Paiement$PaiementBuilder.class | Bin 0 -> 8314 bytes .../unionflow/server/entity/Paiement.class | Bin 0 -> 19435 bytes ...mentAdhesion$PaiementAdhesionBuilder.class | Bin 0 -> 3134 bytes .../server/entity/PaiementAdhesion.class | Bin 0 -> 6499 bytes .../PaiementAide$PaiementAideBuilder.class | Bin 0 -> 3069 bytes .../server/entity/PaiementAide.class | Bin 0 -> 6483 bytes ...Cotisation$PaiementCotisationBuilder.class | Bin 0 -> 3184 bytes .../server/entity/PaiementCotisation.class | Bin 0 -> 6551 bytes ...ntEvenement$PaiementEvenementBuilder.class | Bin 0 -> 3214 bytes .../server/entity/PaiementEvenement.class | Bin 0 -> 6660 bytes .../entity/Permission$PermissionBuilder.class | Bin 0 -> 3289 bytes .../unionflow/server/entity/Permission.class | Bin 0 -> 7753 bytes .../PieceJointe$PieceJointeBuilder.class | Bin 0 -> 4790 bytes .../unionflow/server/entity/PieceJointe.class | Bin 0 -> 10165 bytes .../server/entity/Role$RoleBuilder.class | Bin 0 -> 4021 bytes .../server/entity/Role$TypeRole.class | Bin 0 -> 1587 bytes .../lions/unionflow/server/entity/Role.class | Bin 0 -> 8368 bytes ...RolePermission$RolePermissionBuilder.class | Bin 0 -> 2421 bytes .../server/entity/RolePermission.class | Bin 0 -> 4587 bytes ...fication$TemplateNotificationBuilder.class | Bin 0 -> 3891 bytes .../server/entity/TemplateNotification.class | Bin 0 -> 8568 bytes ...ansactionWave$TransactionWaveBuilder.class | Bin 0 -> 7211 bytes .../server/entity/TransactionWave.class | Bin 0 -> 17481 bytes .../entity/TypeOrganisationEntity.class | Bin 0 -> 1781 bytes .../WebhookWave$WebhookWaveBuilder.class | Bin 0 -> 5387 bytes .../unionflow/server/entity/WebhookWave.class | Bin 0 -> 11633 bytes .../JsonProcessingExceptionMapper.class | Bin 0 -> 3011 bytes .../repository/AdhesionRepository.class | Bin 0 -> 3269 bytes .../server/repository/AdresseRepository.class | Bin 0 -> 2881 bytes .../repository/AuditLogRepository.class | Bin 0 -> 680 bytes .../server/repository/BaseRepository.class | Bin 0 -> 4127 bytes .../CompteComptableRepository.class | Bin 0 -> 2821 bytes .../repository/CompteWaveRepository.class | Bin 0 -> 2786 bytes .../ConfigurationWaveRepository.class | Bin 0 -> 2061 bytes .../repository/CotisationRepository.class | Bin 0 -> 15637 bytes .../repository/DemandeAideRepository.class | Bin 0 -> 13008 bytes .../repository/DocumentRepository.class | Bin 0 -> 2320 bytes .../EcritureComptableRepository.class | Bin 0 -> 3385 bytes .../repository/EvenementRepository.class | Bin 0 -> 16094 bytes .../JournalComptableRepository.class | Bin 0 -> 2831 bytes .../repository/LigneEcritureRepository.class | Bin 0 -> 1729 bytes .../server/repository/MembreRepository.class | Bin 0 -> 10676 bytes .../repository/MembreRoleRepository.class | Bin 0 -> 2362 bytes .../repository/NotificationRepository.class | Bin 0 -> 4264 bytes .../repository/OrganisationRepository.class | Bin 0 -> 12986 bytes .../repository/PaiementRepository.class | Bin 0 -> 5433 bytes .../repository/PermissionRepository.class | Bin 0 -> 3038 bytes .../repository/PieceJointeRepository.class | Bin 0 -> 2523 bytes .../repository/RolePermissionRepository.class | Bin 0 -> 2032 bytes .../server/repository/RoleRepository.class | Bin 0 -> 3044 bytes .../TemplateNotificationRepository.class | Bin 0 -> 2088 bytes .../TransactionWaveRepository.class | Bin 0 -> 3614 bytes .../TypeOrganisationRepository.class | Bin 0 -> 2163 bytes .../repository/WebhookWaveRepository.class | Bin 0 -> 3458 bytes .../server/resource/AdhesionResource.class | Bin 0 -> 21078 bytes .../server/resource/AnalyticsResource.class | Bin 0 -> 12322 bytes .../server/resource/AuditResource.class | Bin 0 -> 6235 bytes .../ComptabiliteResource$ErrorResponse.class | Bin 0 -> 607 bytes .../resource/ComptabiliteResource.class | Bin 0 -> 8986 bytes .../server/resource/CotisationResource.class | Bin 0 -> 21779 bytes .../server/resource/DashboardResource.class | Bin 0 -> 8416 bytes .../DocumentResource$ErrorResponse.class | Bin 0 -> 591 bytes .../server/resource/DocumentResource.class | Bin 0 -> 5948 bytes .../server/resource/EvenementResource.class | Bin 0 -> 16787 bytes .../server/resource/ExportResource.class | Bin 0 -> 5786 bytes .../server/resource/HealthResource.class | Bin 0 -> 1701 bytes .../server/resource/MembreResource.class | Bin 0 -> 25949 bytes .../NotificationResource$ErrorResponse.class | Bin 0 -> 607 bytes ...nResource$NotificationGroupeeRequest.class | Bin 0 -> 765 bytes .../resource/NotificationResource.class | Bin 0 -> 8117 bytes .../resource/OrganisationResource.class | Bin 0 -> 15398 bytes .../PaiementResource$ErrorResponse.class | Bin 0 -> 591 bytes .../server/resource/PaiementResource.class | Bin 0 -> 6877 bytes .../server/resource/PreferencesResource.class | Bin 0 -> 4127 bytes .../resource/TypeOrganisationResource.class | Bin 0 -> 6805 bytes .../resource/WaveResource$ErrorResponse.class | Bin 0 -> 575 bytes .../server/resource/WaveResource.class | Bin 0 -> 8836 bytes .../security/SecurityConfig$Permissions.class | Bin 0 -> 1412 bytes .../security/SecurityConfig$Roles.class | Bin 0 -> 847 bytes .../server/security/SecurityConfig.class | Bin 0 -> 3163 bytes .../server/service/AdhesionService.class | Bin 0 -> 18808 bytes .../server/service/AdresseService.class | Bin 0 -> 12752 bytes .../server/service/AnalyticsService.class | Bin 0 -> 19263 bytes .../server/service/AuditService.class | Bin 0 -> 12333 bytes .../server/service/ComptabiliteService.class | Bin 0 -> 20125 bytes .../server/service/CotisationService.class | Bin 0 -> 20265 bytes .../server/service/DashboardServiceImpl.class | Bin 0 -> 18448 bytes .../server/service/DemandeAideService.class | Bin 0 -> 17976 bytes .../server/service/DocumentService.class | Bin 0 -> 14781 bytes .../server/service/EvenementService.class | Bin 0 -> 14106 bytes .../server/service/ExportService.class | Bin 0 -> 15236 bytes .../server/service/KPICalculatorService.class | Bin 0 -> 11923 bytes .../server/service/KeycloakService.class | Bin 0 -> 6454 bytes .../MatchingService$ResultatMatching.class | Bin 0 -> 745 bytes .../server/service/MatchingService.class | Bin 0 -> 17324 bytes ...reImportExportService$ResultatImport.class | Bin 0 -> 811 bytes .../service/MembreImportExportService.class | Bin 0 -> 31515 bytes .../server/service/MembreService.class | Bin 0 -> 33415 bytes ...ice$NotificationHistoryEntry$Builder.class | Bin 0 -> 2442 bytes ...toryService$NotificationHistoryEntry.class | Bin 0 -> 3207 bytes .../service/NotificationHistoryService.class | Bin 0 -> 11120 bytes .../server/service/NotificationService.class | Bin 0 -> 15397 bytes .../server/service/OrganisationService.class | Bin 0 -> 15772 bytes .../server/service/PaiementService.class | Bin 0 -> 11483 bytes .../server/service/PermissionService.class | Bin 0 -> 6084 bytes .../PreferencesNotificationService.class | Bin 0 -> 6023 bytes .../service/PropositionAideService.class | Bin 0 -> 18294 bytes .../server/service/RoleService.class | Bin 0 -> 6454 bytes ...TrendAnalysisService$StatistiquesDTO.class | Bin 0 -> 1170 bytes .../TrendAnalysisService$TendanceDTO.class | Bin 0 -> 690 bytes .../server/service/TrendAnalysisService.class | Bin 0 -> 23658 bytes .../service/TypeOrganisationService.class | Bin 0 -> 7994 bytes .../server/service/WaveService.class | Bin 0 -> 15654 bytes .../unionflow/server/util/IdConverter.class | Bin 0 -> 3395 bytes target/classes/import.sql | 10 + target/classes/keycloak/unionflow-realm.json | 307 +++++++ .../compile/default-compile/createdFiles.lst | 161 ++++ .../compile/default-compile/inputFiles.lst | 109 +++ .../resource/EvenementResourceTest.class | Bin 0 -> 14393 bytes .../MembreResourceAdvancedSearchTest.class | Bin 0 -> 10597 bytes .../MembreResourceImportExportTest.class | Bin 0 -> 13756 bytes .../resource/OrganisationResourceTest.class | Bin 0 -> 11981 bytes .../MembreImportExportServiceTest.class | Bin 0 -> 16907 bytes .../MembreServiceAdvancedSearchTest.class | Bin 0 -> 17211 bytes 320 files changed, 33373 insertions(+) create mode 100644 Dockerfile.prod create mode 100644 [Help create mode 100644 docker-compose.dev.yml create mode 100644 mvn create mode 100644 pom.xml create mode 100644 setup-postgres.sql create mode 100644 src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java create mode 100644 src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Adhesion.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Adresse.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/AuditLog.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/CompteWave.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Cotisation.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Document.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Evenement.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Membre.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/MembreRole.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Notification.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Organisation.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Paiement.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/PaiementAdhesion.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/PaiementAide.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/PaiementCotisation.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/PaiementEvenement.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Permission.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/Role.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/RolePermission.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/TypeOrganisationEntity.java create mode 100644 src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java create mode 100644 src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/CompteWaveRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/EcritureComptableRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/JournalComptableRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/LigneEcritureRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/NotificationRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/PaiementRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/PieceJointeRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/TemplateNotificationRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/TransactionWaveRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/TypeOrganisationRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/repository/WebhookWaveRepository.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/AdhesionResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/AuditResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/ExportResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/HealthResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/MembreResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/PaiementResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/PreferencesResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/resource/WaveResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/security/SecurityConfig.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/AdhesionService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/AdresseService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/AnalyticsService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/AuditService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/CotisationService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/DocumentService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/EvenementService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/ExportService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/KPICalculatorService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/KeycloakService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/MatchingService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/MembreService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/NotificationHistoryService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/NotificationService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/OrganisationService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/PaiementService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/PermissionService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/PreferencesNotificationService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/PropositionAideService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/RoleService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/TrendAnalysisService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/TypeOrganisationService.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/WaveService.java create mode 100644 src/main/java/dev/lions/unionflow/server/util/IdConverter.java create mode 100644 src/main/resources/META-INF/beans.xml create mode 100644 src/main/resources/application-minimal.properties create mode 100644 src/main/resources/application-prod.properties create mode 100644 src/main/resources/application-test.properties create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/db/migration/V1.2__Create_Organisation_Table.sql create mode 100644 src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql create mode 100644 src/main/resources/import.sql create mode 100644 src/main/resources/keycloak/unionflow-realm.json create mode 100644 src/test.bak/java/dev/lions/unionflow/server/UnionFlowServerApplicationTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/entity/MembreSimpleTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryIntegrationTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/AideResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/CotisationResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/HealthResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceCompleteIntegrationTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceSimpleIntegrationTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/service/AideServiceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/service/EvenementServiceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/service/MembreServiceTest.java create mode 100644 src/test.bak/java/dev/lions/unionflow/server/service/OrganisationServiceTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java create mode 100644 src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java create mode 100644 target/classes/META-INF/beans.xml create mode 100644 target/classes/application-minimal.properties create mode 100644 target/classes/application-prod.properties create mode 100644 target/classes/application-test.properties create mode 100644 target/classes/application.properties create mode 100644 target/classes/db/migration/V1.2__Create_Organisation_Table.sql create mode 100644 target/classes/db/migration/V1.3__Convert_Ids_To_UUID.sql create mode 100644 target/classes/de/lions/unionflow/server/auth/AuthCallbackResource.class create mode 100644 target/classes/dev/lions/unionflow/server/UnionFlowServerApplication.class create mode 100644 target/classes/dev/lions/unionflow/server/dto/EvenementMobileDTO$EvenementMobileDTOBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/dto/EvenementMobileDTO.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Adhesion$AdhesionBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Adhesion.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Adresse$AdresseBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Adresse.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/AuditLog.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/BaseEntity.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/CompteComptable$CompteComptableBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/CompteComptable.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/CompteWave$CompteWaveBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/CompteWave.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/ConfigurationWave$ConfigurationWaveBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/ConfigurationWave.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Cotisation$CotisationBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Cotisation.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/DemandeAide$DemandeAideBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/DemandeAide.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Document$DocumentBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Document.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/EcritureComptable$EcritureComptableBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/EcritureComptable.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Evenement$EvenementBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Evenement$StatutEvenement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Evenement$TypeEvenement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Evenement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$InscriptionEvenementBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$StatutInscription.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/JournalComptable$JournalComptableBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/JournalComptable.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/LigneEcriture$LigneEcritureBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/LigneEcriture.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Membre$MembreBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Membre.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/MembreRole$MembreRoleBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/MembreRole.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Notification$NotificationBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Notification.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Organisation$OrganisationBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Organisation.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Paiement$PaiementBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Paiement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion$PaiementAdhesionBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementAide$PaiementAideBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementAide.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementCotisation$PaiementCotisationBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementCotisation.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementEvenement$PaiementEvenementBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PaiementEvenement.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Permission$PermissionBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Permission.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PieceJointe$PieceJointeBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/PieceJointe.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Role$RoleBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Role$TypeRole.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/Role.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/RolePermission$RolePermissionBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/RolePermission.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/TemplateNotification$TemplateNotificationBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/TemplateNotification.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/TransactionWave$TransactionWaveBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/TransactionWave.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/TypeOrganisationEntity.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/WebhookWave$WebhookWaveBuilder.class create mode 100644 target/classes/dev/lions/unionflow/server/entity/WebhookWave.class create mode 100644 target/classes/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/AdhesionRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/AdresseRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/AuditLogRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/BaseRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/CompteComptableRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/CompteWaveRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/CotisationRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/DemandeAideRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/DocumentRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/EcritureComptableRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/EvenementRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/JournalComptableRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/LigneEcritureRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/MembreRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/MembreRoleRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/NotificationRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/OrganisationRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/PaiementRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/PermissionRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/PieceJointeRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/RolePermissionRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/RoleRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/TemplateNotificationRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/TransactionWaveRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/TypeOrganisationRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/repository/WebhookWaveRepository.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/AdhesionResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/AnalyticsResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/AuditResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/ComptabiliteResource$ErrorResponse.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/ComptabiliteResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/CotisationResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/DashboardResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/DocumentResource$ErrorResponse.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/DocumentResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/EvenementResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/ExportResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/HealthResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/MembreResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/NotificationResource$ErrorResponse.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/NotificationResource$NotificationGroupeeRequest.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/NotificationResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/OrganisationResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/PaiementResource$ErrorResponse.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/PaiementResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/PreferencesResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/TypeOrganisationResource.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/WaveResource$ErrorResponse.class create mode 100644 target/classes/dev/lions/unionflow/server/resource/WaveResource.class create mode 100644 target/classes/dev/lions/unionflow/server/security/SecurityConfig$Permissions.class create mode 100644 target/classes/dev/lions/unionflow/server/security/SecurityConfig$Roles.class create mode 100644 target/classes/dev/lions/unionflow/server/security/SecurityConfig.class create mode 100644 target/classes/dev/lions/unionflow/server/service/AdhesionService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/AdresseService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/AnalyticsService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/AuditService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/ComptabiliteService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/CotisationService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/DashboardServiceImpl.class create mode 100644 target/classes/dev/lions/unionflow/server/service/DemandeAideService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/DocumentService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/EvenementService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/ExportService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/KPICalculatorService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/KeycloakService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/MatchingService$ResultatMatching.class create mode 100644 target/classes/dev/lions/unionflow/server/service/MatchingService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/MembreImportExportService$ResultatImport.class create mode 100644 target/classes/dev/lions/unionflow/server/service/MembreImportExportService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/MembreService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/NotificationHistoryService$NotificationHistoryEntry$Builder.class create mode 100644 target/classes/dev/lions/unionflow/server/service/NotificationHistoryService$NotificationHistoryEntry.class create mode 100644 target/classes/dev/lions/unionflow/server/service/NotificationHistoryService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/NotificationService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/OrganisationService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/PaiementService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/PermissionService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/PreferencesNotificationService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/PropositionAideService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/RoleService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/TrendAnalysisService$StatistiquesDTO.class create mode 100644 target/classes/dev/lions/unionflow/server/service/TrendAnalysisService$TendanceDTO.class create mode 100644 target/classes/dev/lions/unionflow/server/service/TrendAnalysisService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/TypeOrganisationService.class create mode 100644 target/classes/dev/lions/unionflow/server/service/WaveService.class create mode 100644 target/classes/dev/lions/unionflow/server/util/IdConverter.class create mode 100644 target/classes/import.sql create mode 100644 target/classes/keycloak/unionflow-realm.json create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 target/test-classes/dev/lions/unionflow/server/resource/EvenementResourceTest.class create mode 100644 target/test-classes/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.class create mode 100644 target/test-classes/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.class create mode 100644 target/test-classes/dev/lions/unionflow/server/resource/OrganisationResourceTest.class create mode 100644 target/test-classes/dev/lions/unionflow/server/service/MembreImportExportServiceTest.class create mode 100644 target/test-classes/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.class diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..ccff9c8 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,91 @@ +#### +# Dockerfile de production pour UnionFlow Server (Backend) +# Multi-stage build optimisé avec sécurité renforcée +#### + +## Stage 1 : Build avec Maven +FROM maven:3.9.6-eclipse-temurin-17 AS builder + +WORKDIR /app + +# Copier les fichiers de configuration Maven +COPY pom.xml . +COPY ../unionflow-server-api/pom.xml ../unionflow-server-api/ + +# Télécharger les dépendances (cache Docker) +RUN mvn dependency:go-offline -B -pl unionflow-server-impl-quarkus -am + +# Copier le code source +COPY src ./src + +# Construire l'application avec profil production +RUN mvn clean package -DskipTests -B -Dquarkus.profile=prod -pl unionflow-server-impl-quarkus + +## Stage 2 : Image de production optimisée +FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 + +ENV LANGUAGE='en_US:en' + +# Configuration des variables d'environnement pour production +ENV QUARKUS_PROFILE=prod +ENV QUARKUS_HTTP_PORT=8085 +ENV QUARKUS_HTTP_HOST=0.0.0.0 + +# Configuration Base de données (à surcharger via variables d'environnement) +ENV DB_URL=jdbc:postgresql://postgresql:5432/unionflow +ENV DB_USERNAME=unionflow +ENV DB_PASSWORD=changeme + +# Configuration Keycloak/OIDC (production) +ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/unionflow +ENV QUARKUS_OIDC_CLIENT_ID=unionflow-server +ENV KEYCLOAK_CLIENT_SECRET=changeme +ENV QUARKUS_OIDC_TLS_VERIFICATION=required + +# Configuration CORS pour production +ENV CORS_ORIGINS=https://unionflow.lions.dev,https://security.lions.dev +ENV QUARKUS_HTTP_CORS_ORIGINS=${CORS_ORIGINS} + +# Configuration Wave Money (optionnel) +ENV WAVE_API_KEY= +ENV WAVE_API_SECRET= +ENV WAVE_API_BASE_URL=https://api.wave.com/v1 +ENV WAVE_ENVIRONMENT=production +ENV WAVE_WEBHOOK_SECRET= + +# Installer curl pour les health checks +USER root +RUN microdnf install curl -y && microdnf clean all +RUN mkdir -p /app/logs && chown -R 185:185 /app/logs +USER 185 + +# Copier l'application depuis le builder +COPY --from=builder --chown=185 /app/target/quarkus-app/lib/ /deployments/lib/ +COPY --from=builder --chown=185 /app/target/quarkus-app/*.jar /deployments/ +COPY --from=builder --chown=185 /app/target/quarkus-app/app/ /deployments/app/ +COPY --from=builder --chown=185 /app/target/quarkus-app/quarkus/ /deployments/quarkus/ + +# Exposer le port +EXPOSE 8085 + +# Variables JVM optimisées pour production avec sécurité +ENV JAVA_OPTS="-Xmx1g -Xms512m \ + -XX:+UseG1GC \ + -XX:MaxGCPauseMillis=200 \ + -XX:+UseStringDeduplication \ + -XX:+ParallelRefProcEnabled \ + -XX:+HeapDumpOnOutOfMemoryError \ + -XX:HeapDumpPath=/app/logs/heapdump.hprof \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Djava.util.logging.manager=org.jboss.logmanager.LogManager \ + -Dquarkus.profile=${QUARKUS_PROFILE}" + +# Point d'entrée avec profil production +ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"] + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8085/q/health/ready || exit 1 + diff --git a/[Help b/[Help new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..5b3c185 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + postgres-dev: + image: postgres:15-alpine + container_name: unionflow-postgres-dev + environment: + POSTGRES_DB: unionflow_dev + POSTGRES_USER: unionflow_dev + POSTGRES_PASSWORD: dev123 + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + ports: + - "5432:5432" + volumes: + - postgres_dev_data:/var/lib/postgresql/data + - ./src/main/resources/db/init:/docker-entrypoint-initdb.d + networks: + - unionflow-dev + healthcheck: + test: ["CMD-SHELL", "pg_isready -U unionflow_dev -d unionflow_dev"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + adminer: + image: adminer:4.8.1 + container_name: unionflow-adminer + ports: + - "8081:8080" + networks: + - unionflow-dev + depends_on: + - postgres-dev + restart: unless-stopped + +volumes: + postgres_dev_data: + driver: local + +networks: + unionflow-dev: + driver: bridge diff --git a/mvn b/mvn new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b044d05 --- /dev/null +++ b/pom.xml @@ -0,0 +1,337 @@ + + + 4.0.0 + + dev.lions.unionflow + unionflow-server-impl-quarkus + 1.0.0 + jar + + UnionFlow Server Implementation (Quarkus) + Implémentation Quarkus du serveur UnionFlow + + + 17 + 17 + UTF-8 + + 3.15.1 + io.quarkus.platform + quarkus-bom + + + 0.8.11 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + + dev.lions.unionflow + unionflow-server-api + 1.0.0 + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-rest + + + io.quarkus + quarkus-rest-jackson + + + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-jdbc-h2 + + + io.quarkus + quarkus-flyway + + + + + io.quarkus + quarkus-oidc + + + io.quarkus + quarkus-keycloak-authorization + + + + + io.quarkus + quarkus-config-yaml + + + io.quarkus + quarkus-smallrye-health + + + + + io.quarkus + quarkus-smallrye-openapi + + + + + io.quarkus + quarkus-hibernate-validator + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + org.apache.poi + poi + 5.2.5 + + + org.apache.poi + poi-ooxml + 5.2.5 + + + org.apache.poi + poi-ooxml-lite + 5.2.5 + + + org.apache.poi + poi-scratchpad + 5.2.5 + + + + + org.apache.commons + commons-csv + 1.10.0 + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-mockito + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-security + test + + + org.assertj + assertj-core + 3.24.2 + test + + + org.mockito + mockito-core + 5.7.0 + test + + + + + + gitea + Gitea Maven Repository + https://git.lions.dev/api/packages/lionsdev/maven + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + UTF-8 + + + org.projectlombok + lombok + 1.18.30 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + false + false + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + test + + report + + + + + **/*$*Builder*.class + **/Membre$MembreBuilder.class + + + + + check + + check + + + + + **/*$*Builder*.class + **/Membre$MembreBuilder.class + + + + BUNDLE + + + LINE + COVEREDRATIO + 1.00 + + + BRANCH + COVEREDRATIO + 1.00 + + + INSTRUCTION + COVEREDRATIO + 1.00 + + + METHOD + COVEREDRATIO + 1.00 + + + + + + + + + + + + + + native + + + native + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + + + + native-image + + + + + + + + + \ No newline at end of file diff --git a/setup-postgres.sql b/setup-postgres.sql new file mode 100644 index 0000000..44f7f2a --- /dev/null +++ b/setup-postgres.sql @@ -0,0 +1,27 @@ +-- Script de configuration PostgreSQL pour UnionFlow +-- Exécuter en tant que superuser (postgres) + +-- Créer l'utilisateur unionflow s'il n'existe pas +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = 'unionflow') THEN + CREATE USER unionflow WITH PASSWORD 'unionflow123'; + END IF; +END +$$; + +-- Créer la base de données unionflow si elle n'existe pas +SELECT 'CREATE DATABASE unionflow OWNER unionflow' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'unionflow')\gexec + +-- Donner tous les privilèges à l'utilisateur unionflow +GRANT ALL PRIVILEGES ON DATABASE unionflow TO unionflow; + +-- Se connecter à la base unionflow et donner les privilèges sur le schéma public +\c unionflow +GRANT ALL ON SCHEMA public TO unionflow; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO unionflow; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO unionflow; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO unionflow; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO unionflow; + diff --git a/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java new file mode 100644 index 0000000..e77af23 --- /dev/null +++ b/src/main/java/de/lions/unionflow/server/auth/AuthCallbackResource.java @@ -0,0 +1,136 @@ +package de.lions.unionflow.server.auth; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; +import org.jboss.logging.Logger; + +/** + * Resource temporaire pour gérer les callbacks d'authentification OAuth2/OIDC depuis l'application + * mobile. + */ +@Path("/auth") +public class AuthCallbackResource { + + private static final Logger log = Logger.getLogger(AuthCallbackResource.class); + + /** + * Endpoint de callback pour l'authentification OAuth2/OIDC. Redirige vers l'application mobile + * avec les paramètres reçus. + */ + @GET + @Path("/callback") + public Response handleCallback( + @QueryParam("code") String code, + @QueryParam("state") String state, + @QueryParam("session_state") String sessionState, + @QueryParam("error") String error, + @QueryParam("error_description") String errorDescription) { + + try { + // Log des paramètres reçus pour debug + log.infof("=== CALLBACK DEBUG === Code: %s, State: %s, Session State: %s, Error: %s, Error Description: %s", + code, state, sessionState, error, errorDescription); + + // URL de redirection simple vers l'application mobile + String redirectUrl = "dev.lions.unionflow-mobile://callback"; + + // Si nous avons un code d'autorisation, c'est un succès + if (code != null && !code.isEmpty()) { + redirectUrl += "?code=" + code; + if (state != null && !state.isEmpty()) { + redirectUrl += "&state=" + state; + } + } else if (error != null) { + redirectUrl += "?error=" + error; + if (errorDescription != null) { + redirectUrl += "&error_description=" + errorDescription; + } + } + + // Page HTML simple qui redirige automatiquement vers l'app mobile + String html = + """ + + + + Redirection vers UnionFlow + + + + + +
+

🔐 Authentification réussie

+
+

Redirection vers l'application UnionFlow...

+

Si la redirection ne fonctionne pas automatiquement, + cliquez ici

+
+ + + +""" + .formatted(redirectUrl, redirectUrl, redirectUrl); + + return Response.ok(html).type("text/html").build(); + + } catch (Exception e) { + // En cas d'erreur, retourner une page d'erreur simple + String errorHtml = + """ + + + Erreur d'authentification + +

❌ Erreur d'authentification

+

Une erreur s'est produite lors de la redirection.

+

Veuillez fermer cette page et réessayer.

+ + + """; + return Response.status(500).entity(errorHtml).type("text/html").build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java b/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java new file mode 100644 index 0000000..45d6000 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/UnionFlowServerApplication.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.server; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; +import jakarta.enterprise.context.ApplicationScoped; +import org.jboss.logging.Logger; + +/** + * Application principale UnionFlow Server + * + * @author Lions Dev Team + * @version 1.0.0 + */ +@QuarkusMain +@ApplicationScoped +public class UnionFlowServerApplication implements QuarkusApplication { + + private static final Logger LOG = Logger.getLogger(UnionFlowServerApplication.class); + + public static void main(String... args) { + Quarkus.run(UnionFlowServerApplication.class, args); + } + + @Override + public int run(String... args) throws Exception { + LOG.info("🚀 UnionFlow Server démarré avec succès!"); + LOG.info("📊 API disponible sur http://localhost:8080"); + LOG.info("📖 Documentation OpenAPI sur http://localhost:8080/q/swagger-ui"); + LOG.info("💚 Health check sur http://localhost:8080/health"); + + Quarkus.waitForExit(); + return 0; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java b/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java new file mode 100644 index 0000000..26b4157 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/dto/EvenementMobileDTO.java @@ -0,0 +1,143 @@ +package dev.lions.unionflow.server.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import dev.lions.unionflow.server.entity.Evenement; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * DTO pour l'API mobile - Mapping des champs de l'entité Evenement vers le format attendu par + * l'application mobile Flutter + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class EvenementMobileDTO { + + private UUID id; + private String titre; + private String description; + private LocalDateTime dateDebut; + private LocalDateTime dateFin; + private String lieu; + private String adresse; + private String ville; + private String codePostal; + + // Mapping: typeEvenement -> type + private String type; + + // Mapping: statut -> statut (OK) + private String statut; + + // Mapping: capaciteMax -> maxParticipants + private Integer maxParticipants; + + // Nombre de participants actuels (calculé depuis les inscriptions) + private Integer participantsActuels; + + // IDs et noms pour les relations + private UUID organisateurId; + private String organisateurNom; + private UUID organisationId; + private String organisationNom; + + // Priorité (à ajouter dans l'entité si nécessaire) + private String priorite; + + // Mapping: visiblePublic -> estPublic + private Boolean estPublic; + + // Mapping: inscriptionRequise -> inscriptionRequise (OK) + private Boolean inscriptionRequise; + + // Mapping: prix -> cout + private BigDecimal cout; + + // Devise + private String devise; + + // Tags (à implémenter si nécessaire) + private String[] tags; + + // URLs + private String imageUrl; + private String documentUrl; + + // Notes + private String notes; + + // Dates de création/modification + private LocalDateTime dateCreation; + private LocalDateTime dateModification; + + // Actif + private Boolean actif; + + /** + * Convertit une entité Evenement en DTO mobile + * + * @param evenement L'entité à convertir + * @return Le DTO mobile + */ + public static EvenementMobileDTO fromEntity(Evenement evenement) { + if (evenement == null) { + return null; + } + + return EvenementMobileDTO.builder() + .id(evenement.getId()) // Utilise getId() depuis BaseEntity + .titre(evenement.getTitre()) + .description(evenement.getDescription()) + .dateDebut(evenement.getDateDebut()) + .dateFin(evenement.getDateFin()) + .lieu(evenement.getLieu()) + .adresse(evenement.getAdresse()) + .ville(null) // Pas de champ ville dans l'entité + .codePostal(null) // Pas de champ codePostal dans l'entité + // Mapping des enums + .type(evenement.getTypeEvenement() != null ? evenement.getTypeEvenement().name() : null) + .statut(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE") + // Mapping des champs renommés + .maxParticipants(evenement.getCapaciteMax()) + .participantsActuels(evenement.getNombreInscrits()) + // Relations (gestion sécurisée des lazy loading) + .organisateurId(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getId() : null) + .organisateurNom(evenement.getOrganisateur() != null ? evenement.getOrganisateur().getNomComplet() : null) + .organisationId(evenement.getOrganisation() != null ? evenement.getOrganisation().getId() : null) + .organisationNom(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : null) + // Priorité (valeur par défaut) + .priorite("MOYENNE") + // Mapping booléens + .estPublic(evenement.getVisiblePublic()) + .inscriptionRequise(evenement.getInscriptionRequise()) + // Mapping prix -> cout + .cout(evenement.getPrix()) + .devise("XOF") + // Tags vides pour l'instant + .tags(new String[] {}) + // URLs (à implémenter si nécessaire) + .imageUrl(null) + .documentUrl(null) + // Notes + .notes(evenement.getInstructionsParticulieres()) + // Dates + .dateCreation(evenement.getDateCreation()) + .dateModification(evenement.getDateModification()) + // Actif + .actif(evenement.getActif()) + .build(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Adhesion.java b/src/main/java/dev/lions/unionflow/server/entity/Adhesion.java new file mode 100644 index 0000000..e5fbd8a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Adhesion.java @@ -0,0 +1,132 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Adhesion avec UUID + * Représente une demande d'adhésion d'un membre à une organisation + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@Entity +@Table( + name = "adhesions", + indexes = { + @Index(name = "idx_adhesion_membre", columnList = "membre_id"), + @Index(name = "idx_adhesion_organisation", columnList = "organisation_id"), + @Index(name = "idx_adhesion_reference", columnList = "numero_reference", unique = true), + @Index(name = "idx_adhesion_statut", columnList = "statut"), + @Index(name = "idx_adhesion_date_demande", columnList = "date_demande") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Adhesion extends BaseEntity { + + @NotBlank + @Column(name = "numero_reference", unique = true, nullable = false, length = 50) + private String numeroReference; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id", nullable = false) + private Membre membre; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id", nullable = false) + private Organisation organisation; + + @NotNull + @Column(name = "date_demande", nullable = false) + private LocalDate dateDemande; + + @NotNull + @DecimalMin(value = "0.0", message = "Le montant des frais d'adhésion doit être positif") + @Digits(integer = 10, fraction = 2) + @Column(name = "frais_adhesion", nullable = false, precision = 12, scale = 2) + private BigDecimal fraisAdhesion; + + @Builder.Default + @DecimalMin(value = "0.0", message = "Le montant payé doit être positif") + @Digits(integer = 10, fraction = 2) + @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2) + private BigDecimal montantPaye = BigDecimal.ZERO; + + @NotBlank + @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") + @Column(name = "code_devise", nullable = false, length = 3) + private String codeDevise; + + @NotBlank + @Pattern( + regexp = "^(EN_ATTENTE|APPROUVEE|REJETEE|ANNULEE|EN_PAIEMENT|PAYEE)$", + message = "Statut invalide") + @Column(name = "statut", nullable = false, length = 30) + private String statut; + + @Column(name = "date_approbation") + private LocalDate dateApprobation; + + @Column(name = "date_paiement") + private LocalDateTime datePaiement; + + @Size(max = 20) + @Column(name = "methode_paiement", length = 20) + private String methodePaiement; + + @Size(max = 100) + @Column(name = "reference_paiement", length = 100) + private String referencePaiement; + + @Size(max = 1000) + @Column(name = "motif_rejet", length = 1000) + private String motifRejet; + + @Size(max = 1000) + @Column(name = "observations", length = 1000) + private String observations; + + @Column(name = "approuve_par", length = 255) + private String approuvePar; + + @Column(name = "date_validation") + private LocalDate dateValidation; + + /** Méthode métier pour vérifier si l'adhésion est payée intégralement */ + public boolean isPayeeIntegralement() { + return montantPaye != null + && fraisAdhesion != null + && montantPaye.compareTo(fraisAdhesion) >= 0; + } + + /** Méthode métier pour vérifier si l'adhésion est en attente de paiement */ + public boolean isEnAttentePaiement() { + return "APPROUVEE".equals(statut) && !isPayeeIntegralement(); + } + + /** Méthode métier pour calculer le montant restant à payer */ + public BigDecimal getMontantRestant() { + if (fraisAdhesion == null) return BigDecimal.ZERO; + if (montantPaye == null) return fraisAdhesion; + BigDecimal restant = fraisAdhesion.subtract(montantPaye); + return restant.compareTo(BigDecimal.ZERO) > 0 ? restant : BigDecimal.ZERO; + } +} + + + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Adresse.java b/src/main/java/dev/lions/unionflow/server/entity/Adresse.java new file mode 100644 index 0000000..c2aa2d0 --- /dev/null +++ b/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 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/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java b/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java new file mode 100644 index 0000000..6ec2bee --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/AuditLog.java @@ -0,0 +1,81 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +/** + * Entité pour les logs d'audit + * Enregistre toutes les actions importantes du système + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@Entity +@Table(name = "audit_logs", indexes = { + @Index(name = "idx_audit_date_heure", columnList = "date_heure"), + @Index(name = "idx_audit_utilisateur", columnList = "utilisateur"), + @Index(name = "idx_audit_module", columnList = "module"), + @Index(name = "idx_audit_type_action", columnList = "type_action"), + @Index(name = "idx_audit_severite", columnList = "severite") +}) +@Getter +@Setter +public class AuditLog extends BaseEntity { + + @Column(name = "type_action", nullable = false, length = 50) + private String typeAction; + + @Column(name = "severite", nullable = false, length = 20) + private String severite; + + @Column(name = "utilisateur", length = 255) + private String utilisateur; + + @Column(name = "role", length = 50) + private String role; + + @Column(name = "module", length = 50) + private String module; + + @Column(name = "description", length = 500) + private String description; + + @Column(name = "details", columnDefinition = "TEXT") + private String details; + + @Column(name = "ip_address", length = 45) + private String ipAddress; + + @Column(name = "user_agent", length = 500) + private String userAgent; + + @Column(name = "session_id", length = 255) + private String sessionId; + + @Column(name = "date_heure", nullable = false) + private LocalDateTime dateHeure; + + @Column(name = "donnees_avant", columnDefinition = "TEXT") + private String donneesAvant; + + @Column(name = "donnees_apres", columnDefinition = "TEXT") + private String donneesApres; + + @Column(name = "entite_id", length = 255) + private String entiteId; + + @Column(name = "entite_type", length = 100) + private String entiteType; + + @PrePersist + protected void onCreate() { + if (dateHeure == null) { + dateHeure = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java b/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java new file mode 100644 index 0000000..5a1ef42 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/BaseEntity.java @@ -0,0 +1,141 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Classe de base pour les entités UnionFlow utilisant UUID comme identifiant + * + *

Remplace PanacheEntity pour utiliser UUID au lieu de Long comme ID. + * Fournit les fonctionnalités de base de Panache avec UUID. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(name = "date_creation", nullable = false, updatable = false) + protected LocalDateTime dateCreation; + + @Column(name = "date_modification") + protected LocalDateTime dateModification; + + @Column(name = "cree_par", length = 255) + protected String creePar; + + @Column(name = "modifie_par", length = 255) + protected String modifiePar; + + @Version + @Column(name = "version") + protected Long version; + + @Column(name = "actif", nullable = false) + protected Boolean actif = true; + + // Constructeur par défaut + public BaseEntity() { + this.dateCreation = LocalDateTime.now(); + this.actif = true; + this.version = 0L; + } + + // Getters et Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public LocalDateTime getDateCreation() { + return dateCreation; + } + + public void setDateCreation(LocalDateTime dateCreation) { + this.dateCreation = dateCreation; + } + + public LocalDateTime getDateModification() { + return dateModification; + } + + public void setDateModification(LocalDateTime dateModification) { + this.dateModification = dateModification; + } + + public String getCreePar() { + return creePar; + } + + public void setCreePar(String creePar) { + this.creePar = creePar; + } + + public String getModifiePar() { + return modifiePar; + } + + public void setModifiePar(String modifiePar) { + this.modifiePar = modifiePar; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Boolean getActif() { + return actif; + } + + public void setActif(Boolean actif) { + this.actif = actif; + } + + // Callbacks JPA + @PrePersist + protected void onCreate() { + if (this.dateCreation == null) { + this.dateCreation = LocalDateTime.now(); + } + if (this.actif == null) { + this.actif = true; + } + if (this.version == null) { + this.version = 0L; + } + } + + @PreUpdate + protected void onUpdate() { + this.dateModification = LocalDateTime.now(); + } + + // Méthodes utilitaires Panache-like + public void persist() { + // Cette méthode sera implémentée par les repositories ou services + // Pour l'instant, elle est là pour compatibilité avec le code existant + throw new UnsupportedOperationException( + "Utilisez le repository approprié pour persister cette entité"); + } + + public static T findById(UUID id) { + // Cette méthode sera implémentée par les repositories + throw new UnsupportedOperationException( + "Utilisez le repository approprié pour rechercher par ID"); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java b/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java new file mode 100644 index 0000000..6408807 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/CompteComptable.java @@ -0,0 +1,120 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité CompteComptable pour le plan comptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "comptes_comptables", + indexes = { + @Index(name = "idx_compte_numero", columnList = "numero_compte", unique = true), + @Index(name = "idx_compte_type", columnList = "type_compte"), + @Index(name = "idx_compte_classe", columnList = "classe_comptable") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class CompteComptable extends BaseEntity { + + /** Numéro de compte unique (ex: 411000, 512000) */ + @NotBlank + @Column(name = "numero_compte", unique = true, nullable = false, length = 10) + private String numeroCompte; + + /** Libellé du compte */ + @NotBlank + @Column(name = "libelle", nullable = false, length = 200) + private String libelle; + + /** Type de compte */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type_compte", nullable = false, length = 30) + private TypeCompteComptable typeCompte; + + /** Classe comptable (1-7) */ + @NotNull + @Min(value = 1, message = "La classe comptable doit être entre 1 et 7") + @Max(value = 7, message = "La classe comptable doit être entre 1 et 7") + @Column(name = "classe_comptable", nullable = false) + private Integer classeComptable; + + /** Solde initial */ + @Builder.Default + @DecimalMin(value = "0.0", message = "Le solde initial doit être positif ou nul") + @Digits(integer = 12, fraction = 2) + @Column(name = "solde_initial", precision = 14, scale = 2) + private BigDecimal soldeInitial = BigDecimal.ZERO; + + /** Solde actuel (calculé) */ + @Builder.Default + @Digits(integer = 12, fraction = 2) + @Column(name = "solde_actuel", precision = 14, scale = 2) + private BigDecimal soldeActuel = BigDecimal.ZERO; + + /** Compte collectif (regroupe plusieurs sous-comptes) */ + @Builder.Default + @Column(name = "compte_collectif", nullable = false) + private Boolean compteCollectif = false; + + /** Compte analytique */ + @Builder.Default + @Column(name = "compte_analytique", nullable = false) + private Boolean compteAnalytique = false; + + /** Description du compte */ + @Column(name = "description", length = 500) + private String description; + + /** Lignes d'écriture associées */ + @OneToMany(mappedBy = "compteComptable", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List lignesEcriture = new ArrayList<>(); + + /** Méthode métier pour obtenir le numéro formaté */ + public String getNumeroFormate() { + return String.format("%-10s", numeroCompte); + } + + /** Méthode métier pour vérifier si c'est un compte de trésorerie */ + public boolean isTresorerie() { + return TypeCompteComptable.TRESORERIE.equals(typeCompte); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (soldeInitial == null) { + soldeInitial = BigDecimal.ZERO; + } + if (soldeActuel == null) { + soldeActuel = soldeInitial; + } + if (compteCollectif == null) { + compteCollectif = false; + } + if (compteAnalytique == null) { + compteAnalytique = false; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java b/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java new file mode 100644 index 0000000..5aa1293 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/CompteWave.java @@ -0,0 +1,107 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité CompteWave pour la gestion des comptes Wave Mobile Money + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "comptes_wave", + indexes = { + @Index(name = "idx_compte_wave_telephone", columnList = "numero_telephone", unique = true), + @Index(name = "idx_compte_wave_statut", columnList = "statut_compte"), + @Index(name = "idx_compte_wave_organisation", columnList = "organisation_id"), + @Index(name = "idx_compte_wave_membre", columnList = "membre_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class CompteWave extends BaseEntity { + + /** Numéro de téléphone Wave (format +225XXXXXXXX) */ + @NotBlank + @Pattern( + regexp = "^\\+225[0-9]{8}$", + message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX") + @Column(name = "numero_telephone", unique = true, nullable = false, length = 13) + private String numeroTelephone; + + /** Statut du compte */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut_compte", nullable = false, length = 30) + private StatutCompteWave statutCompte = StatutCompteWave.NON_VERIFIE; + + /** Identifiant Wave API (encrypté) */ + @Column(name = "wave_account_id", length = 255) + private String waveAccountId; + + /** Clé API Wave (encryptée) */ + @Column(name = "wave_api_key", length = 500) + private String waveApiKey; + + /** Environnement (SANDBOX ou PRODUCTION) */ + @Column(name = "environnement", length = 20) + private String environnement; + + /** Date de dernière vérification */ + @Column(name = "date_derniere_verification") + private java.time.LocalDateTime dateDerniereVerification; + + /** Commentaires */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id") + private Membre membre; + + @OneToMany(mappedBy = "compteWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List transactions = new ArrayList<>(); + + /** Méthode métier pour vérifier si le compte est vérifié */ + public boolean isVerifie() { + return StatutCompteWave.VERIFIE.equals(statutCompte); + } + + /** Méthode métier pour vérifier si le compte peut être utilisé */ + public boolean peutEtreUtilise() { + return StatutCompteWave.VERIFIE.equals(statutCompte); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (statutCompte == null) { + statutCompte = StatutCompteWave.NON_VERIFIE; + } + if (environnement == null || environnement.isEmpty()) { + environnement = "SANDBOX"; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java b/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java new file mode 100644 index 0000000..6adca19 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/ConfigurationWave.java @@ -0,0 +1,69 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité ConfigurationWave pour la configuration de l'intégration Wave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "configurations_wave", + indexes = { + @Index(name = "idx_config_wave_cle", columnList = "cle", unique = true) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class ConfigurationWave extends BaseEntity { + + /** Clé de configuration */ + @NotBlank + @Column(name = "cle", unique = true, nullable = false, length = 100) + private String cle; + + /** Valeur de configuration (peut être encryptée) */ + @Column(name = "valeur", columnDefinition = "TEXT") + private String valeur; + + /** Description de la configuration */ + @Column(name = "description", length = 500) + private String description; + + /** Type de valeur (STRING, NUMBER, BOOLEAN, JSON, ENCRYPTED) */ + @Column(name = "type_valeur", length = 20) + private String typeValeur; + + /** Environnement (SANDBOX, PRODUCTION, COMMON) */ + @Column(name = "environnement", length = 20) + private String environnement; + + /** Méthode métier pour vérifier si la valeur est encryptée */ + public boolean isEncryptee() { + return "ENCRYPTED".equals(typeValeur); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (typeValeur == null || typeValeur.isEmpty()) { + typeValeur = "STRING"; + } + if (environnement == null || environnement.isEmpty()) { + environnement = "COMMON"; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java b/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java new file mode 100644 index 0000000..a157083 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Cotisation.java @@ -0,0 +1,184 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Cotisation avec UUID Représente une cotisation d'un membre à son organisation + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@Entity +@Table( + name = "cotisations", + indexes = { + @Index(name = "idx_cotisation_membre", columnList = "membre_id"), + @Index(name = "idx_cotisation_reference", columnList = "numero_reference", unique = true), + @Index(name = "idx_cotisation_statut", columnList = "statut"), + @Index(name = "idx_cotisation_echeance", columnList = "date_echeance"), + @Index(name = "idx_cotisation_type", columnList = "type_cotisation"), + @Index(name = "idx_cotisation_annee_mois", columnList = "annee, mois") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Cotisation extends BaseEntity { + + @NotBlank + @Column(name = "numero_reference", unique = true, nullable = false, length = 50) + private String numeroReference; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id", nullable = false) + private Membre membre; + + @NotBlank + @Column(name = "type_cotisation", nullable = false, length = 50) + private String typeCotisation; + + @NotNull + @DecimalMin(value = "0.0", message = "Le montant dû doit être positif") + @Digits(integer = 10, fraction = 2) + @Column(name = "montant_du", nullable = false, precision = 12, scale = 2) + private BigDecimal montantDu; + + @Builder.Default + @DecimalMin(value = "0.0", message = "Le montant payé doit être positif") + @Digits(integer = 10, fraction = 2) + @Column(name = "montant_paye", nullable = false, precision = 12, scale = 2) + private BigDecimal montantPaye = BigDecimal.ZERO; + + @NotBlank + @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") + @Column(name = "code_devise", nullable = false, length = 3) + private String codeDevise; + + @NotBlank + @Pattern(regexp = "^(EN_ATTENTE|PAYEE|EN_RETARD|PARTIELLEMENT_PAYEE|ANNULEE)$") + @Column(name = "statut", nullable = false, length = 30) + private String statut; + + @NotNull + @Column(name = "date_echeance", nullable = false) + private LocalDate dateEcheance; + + @Column(name = "date_paiement") + private LocalDateTime datePaiement; + + @Size(max = 500) + @Column(name = "description", length = 500) + private String description; + + @Size(max = 20) + @Column(name = "periode", length = 20) + private String periode; + + @NotNull + @Min(value = 2020, message = "L'année doit être supérieure à 2020") + @Max(value = 2100, message = "L'année doit être inférieure à 2100") + @Column(name = "annee", nullable = false) + private Integer annee; + + @Min(value = 1, message = "Le mois doit être entre 1 et 12") + @Max(value = 12, message = "Le mois doit être entre 1 et 12") + @Column(name = "mois") + private Integer mois; + + @Size(max = 1000) + @Column(name = "observations", length = 1000) + private String observations; + + @Builder.Default + @Column(name = "recurrente", nullable = false) + private Boolean recurrente = false; + + @Builder.Default + @Min(value = 0, message = "Le nombre de rappels doit être positif") + @Column(name = "nombre_rappels", nullable = false) + private Integer nombreRappels = 0; + + @Column(name = "date_dernier_rappel") + private LocalDateTime dateDernierRappel; + + @Column(name = "valide_par_id") + private UUID valideParId; + + @Size(max = 100) + @Column(name = "nom_validateur", length = 100) + private String nomValidateur; + + @Column(name = "date_validation") + private LocalDateTime dateValidation; + + @Size(max = 50) + @Column(name = "methode_paiement", length = 50) + private String methodePaiement; + + @Size(max = 100) + @Column(name = "reference_paiement", length = 100) + private String referencePaiement; + + /** Méthode métier pour calculer le montant restant à payer */ + public BigDecimal getMontantRestant() { + if (montantDu == null || montantPaye == null) { + return BigDecimal.ZERO; + } + return montantDu.subtract(montantPaye); + } + + /** Méthode métier pour vérifier si la cotisation est entièrement payée */ + public boolean isEntierementPayee() { + return getMontantRestant().compareTo(BigDecimal.ZERO) <= 0; + } + + /** Méthode métier pour vérifier si la cotisation est en retard */ + public boolean isEnRetard() { + return dateEcheance != null && dateEcheance.isBefore(LocalDate.now()) && !isEntierementPayee(); + } + + /** Méthode métier pour générer un numéro de référence unique */ + public static String genererNumeroReference() { + return "COT-" + + LocalDate.now().getYear() + + "-" + + String.format("%08d", System.currentTimeMillis() % 100000000); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); // Appelle le onCreate de BaseEntity + if (numeroReference == null || numeroReference.isEmpty()) { + numeroReference = genererNumeroReference(); + } + if (codeDevise == null) { + codeDevise = "XOF"; + } + if (statut == null) { + statut = "EN_ATTENTE"; + } + if (montantPaye == null) { + montantPaye = BigDecimal.ZERO; + } + if (nombreRappels == null) { + nombreRappels = 0; + } + if (recurrente == null) { + recurrente = false; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java b/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java new file mode 100644 index 0000000..5e6994c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/DemandeAide.java @@ -0,0 +1,130 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import jakarta.persistence.*; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** Entité représentant une demande d'aide dans le système de solidarité */ +@Entity +@Table(name = "demandes_aide") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class DemandeAide extends BaseEntity { + + @Column(name = "titre", nullable = false, length = 200) + private String titre; + + @Column(name = "description", nullable = false, columnDefinition = "TEXT") + private String description; + + @Enumerated(EnumType.STRING) + @Column(name = "type_aide", nullable = false) + private TypeAide typeAide; + + @Enumerated(EnumType.STRING) + @Column(name = "statut", nullable = false) + private StatutAide statut; + + @Column(name = "montant_demande", precision = 10, scale = 2) + private BigDecimal montantDemande; + + @Column(name = "montant_approuve", precision = 10, scale = 2) + private BigDecimal montantApprouve; + + @Column(name = "date_demande", nullable = false) + private LocalDateTime dateDemande; + + @Column(name = "date_evaluation") + private LocalDateTime dateEvaluation; + + @Column(name = "date_versement") + private LocalDateTime dateVersement; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "demandeur_id", nullable = false) + private Membre demandeur; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "evaluateur_id") + private Membre evaluateur; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id", nullable = false) + private Organisation organisation; + + @Column(name = "justification", columnDefinition = "TEXT") + private String justification; + + @Column(name = "commentaire_evaluation", columnDefinition = "TEXT") + private String commentaireEvaluation; + + @Column(name = "urgence", nullable = false) + @Builder.Default + private Boolean urgence = false; + + @Column(name = "documents_fournis") + private String documentsFournis; + + @PrePersist + protected void onCreate() { + super.onCreate(); // Appelle le onCreate de BaseEntity + if (dateDemande == null) { + dateDemande = LocalDateTime.now(); + } + if (statut == null) { + statut = StatutAide.EN_ATTENTE; + } + if (urgence == null) { + urgence = false; + } + } + + @PreUpdate + protected void onUpdate() { + // Méthode appelée avant mise à jour + } + + /** Vérifie si la demande est en attente */ + public boolean isEnAttente() { + return StatutAide.EN_ATTENTE.equals(statut); + } + + /** Vérifie si la demande est approuvée */ + public boolean isApprouvee() { + return StatutAide.APPROUVEE.equals(statut); + } + + /** Vérifie si la demande est rejetée */ + public boolean isRejetee() { + return StatutAide.REJETEE.equals(statut); + } + + /** Vérifie si la demande est urgente */ + public boolean isUrgente() { + return Boolean.TRUE.equals(urgence); + } + + /** Calcule le pourcentage d'approbation par rapport au montant demandé */ + public BigDecimal getPourcentageApprobation() { + if (montantDemande == null || montantDemande.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO; + } + if (montantApprouve == null) { + return BigDecimal.ZERO; + } + return montantApprouve + .divide(montantDemande, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/Document.java b/src/main/java/dev/lions/unionflow/server/entity/Document.java new file mode 100644 index 0000000..063c69e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Document.java @@ -0,0 +1,128 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.document.TypeDocument; +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Document pour la gestion documentaire sécurisée + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "documents", + indexes = { + @Index(name = "idx_document_nom_fichier", columnList = "nom_fichier"), + @Index(name = "idx_document_type", columnList = "type_document"), + @Index(name = "idx_document_hash_md5", columnList = "hash_md5"), + @Index(name = "idx_document_hash_sha256", columnList = "hash_sha256") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Document extends BaseEntity { + + /** Nom du fichier original */ + @NotBlank + @Column(name = "nom_fichier", nullable = false, length = 255) + private String nomFichier; + + /** Nom original du fichier (tel que téléchargé) */ + @Column(name = "nom_original", length = 255) + private String nomOriginal; + + /** Chemin de stockage */ + @NotBlank + @Column(name = "chemin_stockage", nullable = false, length = 1000) + private String cheminStockage; + + /** Type MIME */ + @Column(name = "type_mime", length = 100) + private String typeMime; + + /** Taille du fichier en octets */ + @NotNull + @Min(value = 0, message = "La taille doit être positive") + @Column(name = "taille_octets", nullable = false) + private Long tailleOctets; + + /** Type de document */ + @Enumerated(EnumType.STRING) + @Column(name = "type_document", length = 50) + private TypeDocument typeDocument; + + /** Hash MD5 pour vérification d'intégrité */ + @Column(name = "hash_md5", length = 32) + private String hashMd5; + + /** Hash SHA256 pour vérification d'intégrité */ + @Column(name = "hash_sha256", length = 64) + private String hashSha256; + + /** Description du document */ + @Column(name = "description", length = 1000) + private String description; + + /** Nombre de téléchargements */ + @Builder.Default + @Column(name = "nombre_telechargements", nullable = false) + private Integer nombreTelechargements = 0; + + /** Date de dernier téléchargement */ + @Column(name = "date_dernier_telechargement") + private java.time.LocalDateTime dateDernierTelechargement; + + /** Pièces jointes associées */ + @OneToMany(mappedBy = "document", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List piecesJointes = new ArrayList<>(); + + /** Méthode métier pour vérifier l'intégrité avec MD5 */ + public boolean verifierIntegriteMd5(String hashAttendu) { + return hashMd5 != null && hashMd5.equalsIgnoreCase(hashAttendu); + } + + /** Méthode métier pour vérifier l'intégrité avec SHA256 */ + public boolean verifierIntegriteSha256(String hashAttendu) { + return hashSha256 != null && hashSha256.equalsIgnoreCase(hashAttendu); + } + + /** Méthode métier pour obtenir la taille formatée */ + public String getTailleFormatee() { + if (tailleOctets == null) { + return "0 B"; + } + if (tailleOctets < 1024) { + return tailleOctets + " B"; + } else if (tailleOctets < 1024 * 1024) { + return String.format("%.2f KB", tailleOctets / 1024.0); + } else { + return String.format("%.2f MB", tailleOctets / (1024.0 * 1024.0)); + } + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (nombreTelechargements == null) { + nombreTelechargements = 0; + } + if (typeDocument == null) { + typeDocument = TypeDocument.AUTRE; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java b/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java new file mode 100644 index 0000000..940f6de --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/EcritureComptable.java @@ -0,0 +1,172 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité EcritureComptable pour les écritures comptables + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "ecritures_comptables", + indexes = { + @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true), + @Index(name = "idx_ecriture_date", columnList = "date_ecriture"), + @Index(name = "idx_ecriture_journal", columnList = "journal_id"), + @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"), + @Index(name = "idx_ecriture_paiement", columnList = "paiement_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class EcritureComptable extends BaseEntity { + + /** Numéro de pièce unique */ + @NotBlank + @Column(name = "numero_piece", unique = true, nullable = false, length = 50) + private String numeroPiece; + + /** Date de l'écriture */ + @NotNull + @Column(name = "date_ecriture", nullable = false) + private LocalDate dateEcriture; + + /** Libellé de l'écriture */ + @NotBlank + @Column(name = "libelle", nullable = false, length = 500) + private String libelle; + + /** Référence externe */ + @Column(name = "reference", length = 100) + private String reference; + + /** Lettrage (pour rapprochement) */ + @Column(name = "lettrage", length = 20) + private String lettrage; + + /** Pointage (pour rapprochement bancaire) */ + @Builder.Default + @Column(name = "pointe", nullable = false) + private Boolean pointe = false; + + /** Montant total débit (somme des lignes) */ + @Builder.Default + @DecimalMin(value = "0.0") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_debit", precision = 14, scale = 2) + private BigDecimal montantDebit = BigDecimal.ZERO; + + /** Montant total crédit (somme des lignes) */ + @Builder.Default + @DecimalMin(value = "0.0") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_credit", precision = 14, scale = 2) + private BigDecimal montantCredit = BigDecimal.ZERO; + + /** Commentaires */ + @Column(name = "commentaire", length = 1000) + private String commentaire; + + // Relations + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "journal_id", nullable = false) + private JournalComptable journal; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id") + private Paiement paiement; + + /** Lignes d'écriture */ + @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @Builder.Default + private List lignes = new ArrayList<>(); + + /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */ + public boolean isEquilibree() { + if (montantDebit == null || montantCredit == null) { + return false; + } + return montantDebit.compareTo(montantCredit) == 0; + } + + /** Méthode métier pour calculer les totaux à partir des lignes */ + public void calculerTotaux() { + if (lignes == null || lignes.isEmpty()) { + montantDebit = BigDecimal.ZERO; + montantCredit = BigDecimal.ZERO; + return; + } + + montantDebit = + lignes.stream() + .map(LigneEcriture::getMontantDebit) + .filter(amount -> amount != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + montantCredit = + lignes.stream() + .map(LigneEcriture::getMontantCredit) + .filter(amount -> amount != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + /** Méthode métier pour générer un numéro de pièce unique */ + public static String genererNumeroPiece(String prefixe, LocalDate date) { + return String.format( + "%s-%04d%02d%02d-%012d", + prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(), + System.currentTimeMillis() % 1000000000000L); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (numeroPiece == null || numeroPiece.isEmpty()) { + numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now()); + } + if (dateEcriture == null) { + dateEcriture = LocalDate.now(); + } + if (montantDebit == null) { + montantDebit = BigDecimal.ZERO; + } + if (montantCredit == null) { + montantCredit = BigDecimal.ZERO; + } + if (pointe == null) { + pointe = false; + } + // Calculer les totaux si les lignes sont déjà présentes + if (lignes != null && !lignes.isEmpty()) { + calculerTotaux(); + } + } + + /** Callback JPA avant la mise à jour */ + @PreUpdate + protected void onUpdate() { + calculerTotaux(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Evenement.java b/src/main/java/dev/lions/unionflow/server/entity/Evenement.java new file mode 100644 index 0000000..5f1ccc1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Evenement.java @@ -0,0 +1,267 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.*; + +/** + * Entité Événement pour la gestion des événements de l'union + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@Entity +@Table( + name = "evenements", + indexes = { + @Index(name = "idx_evenement_date_debut", columnList = "date_debut"), + @Index(name = "idx_evenement_statut", columnList = "statut"), + @Index(name = "idx_evenement_type", columnList = "type_evenement"), + @Index(name = "idx_evenement_organisation", columnList = "organisation_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Evenement extends BaseEntity { + + @NotBlank + @Size(min = 3, max = 200) + @Column(name = "titre", nullable = false, length = 200) + private String titre; + + @Size(max = 2000) + @Column(name = "description", length = 2000) + private String description; + + @NotNull + @Column(name = "date_debut", nullable = false) + private LocalDateTime dateDebut; + + @Column(name = "date_fin") + private LocalDateTime dateFin; + + @Size(max = 500) + @Column(name = "lieu", length = 500) + private String lieu; + + @Size(max = 1000) + @Column(name = "adresse", length = 1000) + private String adresse; + + @Enumerated(EnumType.STRING) + @Column(name = "type_evenement", length = 50) + private TypeEvenement typeEvenement; + + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut", nullable = false, length = 30) + private StatutEvenement statut = StatutEvenement.PLANIFIE; + + @Min(0) + @Column(name = "capacite_max") + private Integer capaciteMax; + + @DecimalMin("0.00") + @Digits(integer = 8, fraction = 2) + @Column(name = "prix", precision = 10, scale = 2) + private BigDecimal prix; + + @Builder.Default + @Column(name = "inscription_requise", nullable = false) + private Boolean inscriptionRequise = false; + + @Column(name = "date_limite_inscription") + private LocalDateTime dateLimiteInscription; + + @Size(max = 1000) + @Column(name = "instructions_particulieres", length = 1000) + private String instructionsParticulieres; + + @Size(max = 500) + @Column(name = "contact_organisateur", length = 500) + private String contactOrganisateur; + + @Size(max = 2000) + @Column(name = "materiel_requis", length = 2000) + private String materielRequis; + + @Builder.Default + @Column(name = "visible_public", nullable = false) + private Boolean visiblePublic = true; + + @Builder.Default + @Column(name = "actif", nullable = false) + private Boolean actif = true; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisateur_id") + private Membre organisateur; + + @OneToMany( + mappedBy = "evenement", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY) + @Builder.Default + private List 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"), + REUNION("Réunion"), + FORMATION("Formation"), + CONFERENCE("Conférence"), + ATELIER("Atelier"), + SEMINAIRE("Séminaire"), + EVENEMENT_SOCIAL("Événement Social"), + MANIFESTATION("Manifestation"), + CELEBRATION("Célébration"), + AUTRE("Autre"); + + private final String libelle; + + TypeEvenement(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } + } + + /** Statuts d'événement */ + public enum StatutEvenement { + PLANIFIE("Planifié"), + CONFIRME("Confirmé"), + EN_COURS("En cours"), + TERMINE("Terminé"), + ANNULE("Annulé"), + REPORTE("Reporté"); + + private final String libelle; + + StatutEvenement(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } + } + + // Méthodes métier + + /** Vérifie si l'événement est ouvert aux inscriptions */ + public boolean isOuvertAuxInscriptions() { + if (!inscriptionRequise || !actif) { + return false; + } + + LocalDateTime maintenant = LocalDateTime.now(); + + // Vérifier si la date limite d'inscription n'est pas dépassée + if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) { + return false; + } + + // Vérifier si l'événement n'a pas déjà commencé + if (maintenant.isAfter(dateDebut)) { + return false; + } + + // Vérifier la capacité + if (capaciteMax != null && getNombreInscrits() >= capaciteMax) { + return false; + } + + return statut == StatutEvenement.PLANIFIE || statut == StatutEvenement.CONFIRME; + } + + /** Obtient le nombre d'inscrits à l'événement */ + public int getNombreInscrits() { + return inscriptions != null + ? (int) + inscriptions.stream() + .filter( + inscription -> + inscription.getStatut() == InscriptionEvenement.StatutInscription.CONFIRMEE) + .count() + : 0; + } + + /** Vérifie si l'événement est complet */ + public boolean isComplet() { + return capaciteMax != null && getNombreInscrits() >= capaciteMax; + } + + /** Vérifie si l'événement est en cours */ + public boolean isEnCours() { + LocalDateTime maintenant = LocalDateTime.now(); + return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin)); + } + + /** Vérifie si l'événement est terminé */ + public boolean isTermine() { + if (statut == StatutEvenement.TERMINE) { + return true; + } + + LocalDateTime maintenant = LocalDateTime.now(); + return dateFin != null && maintenant.isAfter(dateFin); + } + + /** Calcule la durée de l'événement en heures */ + public Long getDureeEnHeures() { + if (dateFin == null) { + return null; + } + + return java.time.Duration.between(dateDebut, dateFin).toHours(); + } + + /** Obtient le nombre de places restantes */ + public Integer getPlacesRestantes() { + if (capaciteMax == null) { + return null; // Capacité illimitée + } + + return Math.max(0, capaciteMax - getNombreInscrits()); + } + + /** Vérifie si un membre est inscrit à l'événement */ + public boolean isMemberInscrit(UUID membreId) { + return inscriptions != null + && inscriptions.stream() + .anyMatch( + inscription -> + inscription.getMembre().getId().equals(membreId) + && inscription.getStatut() + == InscriptionEvenement.StatutInscription.CONFIRMEE); + } + + /** Obtient le taux de remplissage en pourcentage */ + public Double getTauxRemplissage() { + if (capaciteMax == null || capaciteMax == 0) { + return null; + } + + return (double) getNombreInscrits() / capaciteMax * 100; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java b/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java new file mode 100644 index 0000000..0cec9c7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/InscriptionEvenement.java @@ -0,0 +1,156 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.*; + +/** + * Entité InscriptionEvenement représentant l'inscription d'un membre à un événement + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@Entity +@Table( + name = "inscriptions_evenement", + indexes = { + @Index(name = "idx_inscription_membre", columnList = "membre_id"), + @Index(name = "idx_inscription_evenement", columnList = "evenement_id"), + @Index(name = "idx_inscription_date", columnList = "date_inscription") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class InscriptionEvenement extends BaseEntity { + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id", nullable = false) + private Membre membre; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "evenement_id", nullable = false) + private Evenement evenement; + + @Builder.Default + @Column(name = "date_inscription", nullable = false) + private LocalDateTime dateInscription = LocalDateTime.now(); + + @Enumerated(EnumType.STRING) + @Column(name = "statut", length = 20) + @Builder.Default + private StatutInscription statut = StatutInscription.CONFIRMEE; + + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Énumération des statuts d'inscription */ + public enum StatutInscription { + CONFIRMEE("Confirmée"), + EN_ATTENTE("En attente"), + ANNULEE("Annulée"), + REFUSEE("Refusée"); + + private final String libelle; + + StatutInscription(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } + } + + // Méthodes utilitaires + + /** + * Vérifie si l'inscription est confirmée + * + * @return true si l'inscription est confirmée + */ + public boolean isConfirmee() { + return StatutInscription.CONFIRMEE.equals(this.statut); + } + + /** + * Vérifie si l'inscription est en attente + * + * @return true si l'inscription est en attente + */ + public boolean isEnAttente() { + return StatutInscription.EN_ATTENTE.equals(this.statut); + } + + /** + * Vérifie si l'inscription est annulée + * + * @return true si l'inscription est annulée + */ + public boolean isAnnulee() { + return StatutInscription.ANNULEE.equals(this.statut); + } + + /** Confirme l'inscription */ + public void confirmer() { + this.statut = StatutInscription.CONFIRMEE; + this.dateModification = LocalDateTime.now(); + } + + /** + * Annule l'inscription + * + * @param commentaire le commentaire d'annulation + */ + public void annuler(String commentaire) { + this.statut = StatutInscription.ANNULEE; + this.commentaire = commentaire; + this.dateModification = LocalDateTime.now(); + } + + /** + * Met l'inscription en attente + * + * @param commentaire le commentaire de mise en attente + */ + public void mettreEnAttente(String commentaire) { + this.statut = StatutInscription.EN_ATTENTE; + this.commentaire = commentaire; + this.dateModification = LocalDateTime.now(); + } + + /** + * Refuse l'inscription + * + * @param commentaire le commentaire de refus + */ + public void refuser(String commentaire) { + this.statut = StatutInscription.REFUSEE; + this.commentaire = commentaire; + this.dateModification = LocalDateTime.now(); + } + + // Callbacks JPA + + @PreUpdate + public void preUpdate() { + super.onUpdate(); // Appelle le onUpdate de BaseEntity + this.dateModification = LocalDateTime.now(); + } + + @Override + public String toString() { + return String.format( + "InscriptionEvenement{id=%s, membre=%s, evenement=%s, statut=%s, dateInscription=%s}", + getId(), + membre != null ? membre.getEmail() : "null", + evenement != null ? evenement.getTitre() : "null", + statut, + dateInscription); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java b/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java new file mode 100644 index 0000000..cc4109c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/JournalComptable.java @@ -0,0 +1,98 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité JournalComptable pour la gestion des journaux + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "journaux_comptables", + indexes = { + @Index(name = "idx_journal_code", columnList = "code", unique = true), + @Index(name = "idx_journal_type", columnList = "type_journal"), + @Index(name = "idx_journal_periode", columnList = "date_debut, date_fin") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class JournalComptable extends BaseEntity { + + /** Code unique du journal */ + @NotBlank + @Column(name = "code", unique = true, nullable = false, length = 10) + private String code; + + /** Libellé du journal */ + @NotBlank + @Column(name = "libelle", nullable = false, length = 100) + private String libelle; + + /** Type de journal */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type_journal", nullable = false, length = 30) + private TypeJournalComptable typeJournal; + + /** Date de début de la période */ + @Column(name = "date_debut") + private LocalDate dateDebut; + + /** Date de fin de la période */ + @Column(name = "date_fin") + private LocalDate dateFin; + + /** Statut du journal (OUVERT, FERME, ARCHIVE) */ + @Builder.Default + @Column(name = "statut", length = 20) + private String statut = "OUVERT"; + + /** Description */ + @Column(name = "description", length = 500) + private String description; + + /** Écritures comptables associées */ + @OneToMany(mappedBy = "journal", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List ecritures = new ArrayList<>(); + + /** Méthode métier pour vérifier si le journal est ouvert */ + public boolean isOuvert() { + return "OUVERT".equals(statut); + } + + /** Méthode métier pour vérifier si une date est dans la période */ + public boolean estDansPeriode(LocalDate date) { + if (dateDebut == null || dateFin == null) { + return true; // Période illimitée + } + return !date.isBefore(dateDebut) && !date.isAfter(dateFin); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (statut == null || statut.isEmpty()) { + statut = "OUVERT"; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java b/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java new file mode 100644 index 0000000..08a170c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/LigneEcriture.java @@ -0,0 +1,100 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité LigneEcriture pour les lignes d'une écriture comptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "lignes_ecriture", + indexes = { + @Index(name = "idx_ligne_ecriture_ecriture", columnList = "ecriture_id"), + @Index(name = "idx_ligne_ecriture_compte", columnList = "compte_comptable_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class LigneEcriture extends BaseEntity { + + /** Numéro de ligne */ + @NotNull + @Min(value = 1, message = "Le numéro de ligne doit être positif") + @Column(name = "numero_ligne", nullable = false) + private Integer numeroLigne; + + /** Montant débit */ + @DecimalMin(value = "0.0", message = "Le montant débit doit être positif ou nul") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_debit", precision = 14, scale = 2) + private BigDecimal montantDebit; + + /** Montant crédit */ + @DecimalMin(value = "0.0", message = "Le montant crédit doit être positif ou nul") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_credit", precision = 14, scale = 2) + private BigDecimal montantCredit; + + /** Libellé de la ligne */ + @Column(name = "libelle", length = 500) + private String libelle; + + /** Référence */ + @Column(name = "reference", length = 100) + private String reference; + + // Relations + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ecriture_id", nullable = false) + private EcritureComptable ecriture; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "compte_comptable_id", nullable = false) + private CompteComptable compteComptable; + + /** Méthode métier pour vérifier que la ligne a soit un débit soit un crédit (pas les deux) */ + public boolean isValide() { + boolean aDebit = montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0; + boolean aCredit = montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0; + return aDebit != aCredit; // XOR : soit débit, soit crédit, pas les deux + } + + /** Méthode métier pour obtenir le montant (débit ou crédit) */ + public BigDecimal getMontant() { + if (montantDebit != null && montantDebit.compareTo(BigDecimal.ZERO) > 0) { + return montantDebit; + } + if (montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0) { + return montantCredit; + } + return BigDecimal.ZERO; + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (montantDebit == null) { + montantDebit = BigDecimal.ZERO; + } + if (montantCredit == null) { + montantCredit = BigDecimal.ZERO; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Membre.java b/src/main/java/dev/lions/unionflow/server/entity/Membre.java new file mode 100644 index 0000000..8f943a1 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Membre.java @@ -0,0 +1,106 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** Entité Membre avec UUID */ +@Entity +@Table( + name = "membres", + indexes = { + @Index(name = "idx_membre_email", columnList = "email", unique = true), + @Index(name = "idx_membre_numero", columnList = "numero_membre", unique = true), + @Index(name = "idx_membre_actif", columnList = "actif") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Membre extends BaseEntity { + + @NotBlank + @Column(name = "numero_membre", unique = true, nullable = false, length = 20) + private String numeroMembre; + + @NotBlank + @Column(name = "prenom", nullable = false, length = 100) + private String prenom; + + @NotBlank + @Column(name = "nom", nullable = false, length = 100) + private String nom; + + @Email + @NotBlank + @Column(name = "email", unique = true, nullable = false, length = 255) + private String email; + + @Column(name = "mot_de_passe", length = 255) + private String motDePasse; + + @Column(name = "telephone", length = 20) + private String telephone; + + @Pattern( + regexp = "^\\+225[0-9]{8}$", + message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX") + @Column(name = "telephone_wave", length = 13) + private String telephoneWave; + + @NotNull + @Column(name = "date_naissance", nullable = false) + private LocalDate dateNaissance; + + @NotNull + @Column(name = "date_adhesion", nullable = false) + private LocalDate dateAdhesion; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List adresses = new ArrayList<>(); + + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List roles = new ArrayList<>(); + + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List comptesWave = new ArrayList<>(); + + @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List paiements = new ArrayList<>(); + + /** Méthode métier pour obtenir le nom complet */ + public String getNomComplet() { + return prenom + " " + nom; + } + + /** Méthode métier pour vérifier si le membre est majeur */ + public boolean isMajeur() { + return dateNaissance.isBefore(LocalDate.now().minusYears(18)); + } + + /** Méthode métier pour calculer l'âge */ + public int getAge() { + return LocalDate.now().getYear() - dateNaissance.getYear(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java b/src/main/java/dev/lions/unionflow/server/entity/MembreRole.java new file mode 100644 index 0000000..27f3025 --- /dev/null +++ b/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/src/main/java/dev/lions/unionflow/server/entity/Notification.java b/src/main/java/dev/lions/unionflow/server/entity/Notification.java new file mode 100644 index 0000000..8170d4f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Notification.java @@ -0,0 +1,132 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification; +import dev.lions.unionflow.server.api.enums.notification.TypeNotification; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Notification pour la gestion des notifications + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "notifications", + indexes = { + @Index(name = "idx_notification_type", columnList = "type_notification"), + @Index(name = "idx_notification_statut", columnList = "statut"), + @Index(name = "idx_notification_priorite", columnList = "priorite"), + @Index(name = "idx_notification_membre", columnList = "membre_id"), + @Index(name = "idx_notification_organisation", columnList = "organisation_id"), + @Index(name = "idx_notification_date_envoi", columnList = "date_envoi") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Notification extends BaseEntity { + + /** Type de notification */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type_notification", nullable = false, length = 30) + private TypeNotification typeNotification; + + /** Priorité */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "priorite", length = 20) + private PrioriteNotification priorite = PrioriteNotification.NORMALE; + + /** Statut */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut", length = 30) + private dev.lions.unionflow.server.api.enums.notification.StatutNotification statut = + dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE; + + /** Sujet */ + @Column(name = "sujet", length = 500) + private String sujet; + + /** Corps du message */ + @Column(name = "corps", columnDefinition = "TEXT") + private String corps; + + /** Date d'envoi prévue */ + @Column(name = "date_envoi_prevue") + private LocalDateTime dateEnvoiPrevue; + + /** Date d'envoi réelle */ + @Column(name = "date_envoi") + private LocalDateTime dateEnvoi; + + /** Date de lecture */ + @Column(name = "date_lecture") + private LocalDateTime dateLecture; + + /** Nombre de tentatives d'envoi */ + @Builder.Default + @Column(name = "nombre_tentatives", nullable = false) + private Integer nombreTentatives = 0; + + /** Message d'erreur (si échec) */ + @Column(name = "message_erreur", length = 1000) + private String messageErreur; + + /** Données additionnelles (JSON) */ + @Column(name = "donnees_additionnelles", columnDefinition = "TEXT") + private String donneesAdditionnelles; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id") + private Membre membre; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "template_id") + private TemplateNotification template; + + /** Méthode métier pour vérifier si la notification est envoyée */ + public boolean isEnvoyee() { + return dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.equals(statut); + } + + /** Méthode métier pour vérifier si la notification est lue */ + public boolean isLue() { + return dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.equals(statut); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (priorite == null) { + priorite = PrioriteNotification.NORMALE; + } + if (statut == null) { + statut = dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE; + } + if (nombreTentatives == null) { + nombreTentatives = 0; + } + if (dateEnvoiPrevue == null) { + dateEnvoiPrevue = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Organisation.java b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java new file mode 100644 index 0000000..cd5eddd --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Organisation.java @@ -0,0 +1,308 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Period; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Organisation avec UUID Représente une organisation (Lions Club, Association, + * Coopérative, etc.) + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@Entity +@Table( + name = "organisations", + indexes = { + @Index(name = "idx_organisation_nom", columnList = "nom"), + @Index(name = "idx_organisation_email", columnList = "email", unique = true), + @Index(name = "idx_organisation_statut", columnList = "statut"), + @Index(name = "idx_organisation_type", columnList = "type_organisation"), + @Index(name = "idx_organisation_ville", columnList = "ville"), + @Index(name = "idx_organisation_pays", columnList = "pays"), + @Index(name = "idx_organisation_parente", columnList = "organisation_parente_id"), + @Index( + name = "idx_organisation_numero_enregistrement", + columnList = "numero_enregistrement", + unique = true) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Organisation extends BaseEntity { + + @NotBlank + @Column(name = "nom", nullable = false, length = 200) + private String nom; + + @Column(name = "nom_court", length = 50) + private String nomCourt; + + @NotBlank + @Column(name = "type_organisation", nullable = false, length = 50) + private String typeOrganisation; + + @NotBlank + @Column(name = "statut", nullable = false, length = 50) + private String statut; + + @Column(name = "description", length = 2000) + private String description; + + @Column(name = "date_fondation") + private LocalDate dateFondation; + + @Column(name = "numero_enregistrement", unique = true, length = 100) + private String numeroEnregistrement; + + // Informations de contact + @Email + @NotBlank + @Column(name = "email", unique = true, nullable = false, length = 255) + private String email; + + @Column(name = "telephone", length = 20) + private String telephone; + + @Column(name = "telephone_secondaire", length = 20) + private String telephoneSecondaire; + + @Email + @Column(name = "email_secondaire", length = 255) + private String emailSecondaire; + + // Adresse + @Column(name = "adresse", length = 500) + private String adresse; + + @Column(name = "ville", length = 100) + private String ville; + + @Column(name = "code_postal", length = 20) + private String codePostal; + + @Column(name = "region", length = 100) + private String region; + + @Column(name = "pays", length = 100) + private String pays; + + // Coordonnées géographiques + @DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90") + @DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90") + @Digits(integer = 3, fraction = 6) + @Column(name = "latitude", precision = 9, scale = 6) + private BigDecimal latitude; + + @DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180") + @DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180") + @Digits(integer = 3, fraction = 6) + @Column(name = "longitude", precision = 9, scale = 6) + private BigDecimal longitude; + + // Web et réseaux sociaux + @Column(name = "site_web", length = 500) + private String siteWeb; + + @Column(name = "logo", length = 500) + private String logo; + + @Column(name = "reseaux_sociaux", length = 1000) + private String reseauxSociaux; + + // Hiérarchie + @Column(name = "organisation_parente_id") + private UUID organisationParenteId; + + @Builder.Default + @Column(name = "niveau_hierarchique", nullable = false) + private Integer niveauHierarchique = 0; + + // Statistiques + @Builder.Default + @Column(name = "nombre_membres", nullable = false) + private Integer nombreMembres = 0; + + @Builder.Default + @Column(name = "nombre_administrateurs", nullable = false) + private Integer nombreAdministrateurs = 0; + + // Finances + @DecimalMin(value = "0.0", message = "Le budget annuel doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "budget_annuel", precision = 14, scale = 2) + private BigDecimal budgetAnnuel; + + @Builder.Default + @Column(name = "devise", length = 3) + private String devise = "XOF"; + + @Builder.Default + @Column(name = "cotisation_obligatoire", nullable = false) + private Boolean cotisationObligatoire = false; + + @DecimalMin(value = "0.0", message = "Le montant de cotisation doit être positif") + @Digits(integer = 10, fraction = 2) + @Column(name = "montant_cotisation_annuelle", precision = 12, scale = 2) + private BigDecimal montantCotisationAnnuelle; + + // Informations complémentaires + @Column(name = "objectifs", length = 2000) + private String objectifs; + + @Column(name = "activites_principales", length = 2000) + private String activitesPrincipales; + + @Column(name = "certifications", length = 500) + private String certifications; + + @Column(name = "partenaires", length = 1000) + private String partenaires; + + @Column(name = "notes", length = 1000) + private String notes; + + // Paramètres + @Builder.Default + @Column(name = "organisation_publique", nullable = false) + private Boolean organisationPublique = true; + + @Builder.Default + @Column(name = "accepte_nouveaux_membres", nullable = false) + private Boolean accepteNouveauxMembres = true; + + // Relations + @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List membres = new ArrayList<>(); + + @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List adresses = new ArrayList<>(); + + @OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List comptesWave = new ArrayList<>(); + + /** Méthode métier pour obtenir le nom complet avec sigle */ + public String getNomComplet() { + if (nomCourt != null && !nomCourt.isEmpty()) { + return nom + " (" + nomCourt + ")"; + } + return nom; + } + + /** Méthode métier pour calculer l'ancienneté en années */ + public int getAncienneteAnnees() { + if (dateFondation == null) { + return 0; + } + return Period.between(dateFondation, LocalDate.now()).getYears(); + } + + /** Méthode métier pour vérifier si l'organisation est récente (moins de 2 ans) */ + public boolean isRecente() { + return getAncienneteAnnees() < 2; + } + + /** Méthode métier pour vérifier si l'organisation est active */ + public boolean isActive() { + return "ACTIVE".equals(statut) && Boolean.TRUE.equals(getActif()); + } + + /** Méthode métier pour ajouter un membre */ + public void ajouterMembre() { + if (nombreMembres == null) { + nombreMembres = 0; + } + nombreMembres++; + } + + /** Méthode métier pour retirer un membre */ + public void retirerMembre() { + if (nombreMembres != null && nombreMembres > 0) { + nombreMembres--; + } + } + + /** Méthode métier pour activer l'organisation */ + public void activer(String utilisateur) { + this.statut = "ACTIVE"; + this.setActif(true); + marquerCommeModifie(utilisateur); + } + + /** Méthode métier pour suspendre l'organisation */ + public void suspendre(String utilisateur) { + this.statut = "SUSPENDUE"; + this.accepteNouveauxMembres = false; + marquerCommeModifie(utilisateur); + } + + /** Méthode métier pour dissoudre l'organisation */ + public void dissoudre(String utilisateur) { + this.statut = "DISSOUTE"; + this.setActif(false); + this.accepteNouveauxMembres = false; + marquerCommeModifie(utilisateur); + } + + /** Marque l'entité comme modifiée */ + public void marquerCommeModifie(String utilisateur) { + this.setDateModification(LocalDateTime.now()); + this.setModifiePar(utilisateur); + if (this.getVersion() != null) { + this.setVersion(this.getVersion() + 1); + } else { + this.setVersion(1L); + } + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); // Appelle le onCreate de BaseEntity + if (statut == null) { + statut = "ACTIVE"; + } + if (typeOrganisation == null) { + typeOrganisation = "ASSOCIATION"; + } + if (devise == null) { + devise = "XOF"; + } + if (niveauHierarchique == null) { + niveauHierarchique = 0; + } + if (nombreMembres == null) { + nombreMembres = 0; + } + if (nombreAdministrateurs == null) { + nombreAdministrateurs = 0; + } + if (organisationPublique == null) { + organisationPublique = true; + } + if (accepteNouveauxMembres == null) { + accepteNouveauxMembres = true; + } + if (cotisationObligatoire == null) { + cotisationObligatoire = false; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/entity/Paiement.java b/src/main/java/dev/lions/unionflow/server/entity/Paiement.java new file mode 100644 index 0000000..ff583be --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/Paiement.java @@ -0,0 +1,169 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement; +import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement; +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Paiement centralisée pour tous les types de paiements + * Réutilisable pour cotisations, adhésions, événements, aides + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "paiements", + indexes = { + @Index(name = "idx_paiement_numero_reference", columnList = "numero_reference", unique = true), + @Index(name = "idx_paiement_membre", columnList = "membre_id"), + @Index(name = "idx_paiement_statut", columnList = "statut_paiement"), + @Index(name = "idx_paiement_methode", columnList = "methode_paiement"), + @Index(name = "idx_paiement_date", columnList = "date_paiement") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Paiement extends BaseEntity { + + /** Numéro de référence unique */ + @NotBlank + @Column(name = "numero_reference", unique = true, nullable = false, length = 50) + private String numeroReference; + + /** Montant du paiement */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant", nullable = false, precision = 14, scale = 2) + private BigDecimal montant; + + /** Code devise (ISO 3 lettres) */ + @NotBlank + @Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres") + @Column(name = "code_devise", nullable = false, length = 3) + private String codeDevise; + + /** Méthode de paiement */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "methode_paiement", nullable = false, length = 50) + private MethodePaiement methodePaiement; + + /** Statut du paiement */ + @NotNull + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut_paiement", nullable = false, length = 30) + private StatutPaiement statutPaiement = StatutPaiement.EN_ATTENTE; + + /** Date de paiement */ + @Column(name = "date_paiement") + private LocalDateTime datePaiement; + + /** Date de validation */ + @Column(name = "date_validation") + private LocalDateTime dateValidation; + + /** Validateur (email de l'administrateur) */ + @Column(name = "validateur", length = 255) + private String validateur; + + /** Référence externe (numéro transaction, URL preuve, etc.) */ + @Column(name = "reference_externe", length = 500) + private String referenceExterne; + + /** URL de preuve de paiement */ + @Column(name = "url_preuve", length = 1000) + private String urlPreuve; + + /** Commentaires et notes */ + @Column(name = "commentaire", length = 1000) + private String commentaire; + + /** Adresse IP de l'initiateur */ + @Column(name = "ip_address", length = 45) + private String ipAddress; + + /** User-Agent de l'initiateur */ + @Column(name = "user_agent", length = 500) + private String userAgent; + + /** Membre payeur */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id", nullable = false) + private Membre membre; + + /** Relations avec les tables de liaison */ + @OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List paiementsCotisation = new ArrayList<>(); + + @OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List paiementsAdhesion = new ArrayList<>(); + + @OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List paiementsEvenement = new ArrayList<>(); + + @OneToMany(mappedBy = "paiement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List paiementsAide = new ArrayList<>(); + + /** Relation avec TransactionWave (optionnelle) */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "transaction_wave_id") + private TransactionWave transactionWave; + + /** Méthode métier pour générer un numéro de référence unique */ + public static String genererNumeroReference() { + return "PAY-" + + LocalDateTime.now().getYear() + + "-" + + String.format("%012d", System.currentTimeMillis() % 1000000000000L); + } + + /** Méthode métier pour vérifier si le paiement est validé */ + public boolean isValide() { + return StatutPaiement.VALIDE.equals(statutPaiement); + } + + /** Méthode métier pour vérifier si le paiement peut être modifié */ + public boolean peutEtreModifie() { + return !statutPaiement.isFinalise(); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (numeroReference == null || numeroReference.isEmpty()) { + numeroReference = genererNumeroReference(); + } + if (codeDevise == null || codeDevise.isEmpty()) { + codeDevise = "XOF"; + } + if (statutPaiement == null) { + statutPaiement = StatutPaiement.EN_ATTENTE; + } + if (datePaiement == null) { + datePaiement = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/PaiementAdhesion.java b/src/main/java/dev/lions/unionflow/server/entity/PaiementAdhesion.java new file mode 100644 index 0000000..628999b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/PaiementAdhesion.java @@ -0,0 +1,75 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Table de liaison entre Paiement et Adhesion + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "paiements_adhesions", + indexes = { + @Index(name = "idx_paiement_adhesion_paiement", columnList = "paiement_id"), + @Index(name = "idx_paiement_adhesion_adhesion", columnList = "adhesion_id") + }, + uniqueConstraints = { + @UniqueConstraint( + name = "uk_paiement_adhesion", + columnNames = {"paiement_id", "adhesion_id"}) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class PaiementAdhesion extends BaseEntity { + + /** Paiement */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id", nullable = false) + private Paiement paiement; + + /** Adhésion */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "adhesion_id", nullable = false) + private Adhesion adhesion; + + /** Montant appliqué à cette adhésion */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_applique", nullable = false, precision = 14, scale = 2) + private BigDecimal montantApplique; + + /** Date d'application */ + @Column(name = "date_application") + private LocalDateTime dateApplication; + + /** Commentaire sur l'application */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (dateApplication == null) { + dateApplication = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/PaiementAide.java b/src/main/java/dev/lions/unionflow/server/entity/PaiementAide.java new file mode 100644 index 0000000..4f9603f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/PaiementAide.java @@ -0,0 +1,75 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Table de liaison entre Paiement et DemandeAide + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "paiements_aides", + indexes = { + @Index(name = "idx_paiement_aide_paiement", columnList = "paiement_id"), + @Index(name = "idx_paiement_aide_demande", columnList = "demande_aide_id") + }, + uniqueConstraints = { + @UniqueConstraint( + name = "uk_paiement_aide", + columnNames = {"paiement_id", "demande_aide_id"}) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class PaiementAide extends BaseEntity { + + /** Paiement */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id", nullable = false) + private Paiement paiement; + + /** Demande d'aide */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "demande_aide_id", nullable = false) + private DemandeAide demandeAide; + + /** Montant appliqué à cette demande d'aide */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_applique", nullable = false, precision = 14, scale = 2) + private BigDecimal montantApplique; + + /** Date d'application */ + @Column(name = "date_application") + private LocalDateTime dateApplication; + + /** Commentaire sur l'application */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (dateApplication == null) { + dateApplication = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/PaiementCotisation.java b/src/main/java/dev/lions/unionflow/server/entity/PaiementCotisation.java new file mode 100644 index 0000000..6f4ca60 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/PaiementCotisation.java @@ -0,0 +1,76 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Table de liaison entre Paiement et Cotisation + * Permet à un paiement de couvrir plusieurs cotisations + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "paiements_cotisations", + indexes = { + @Index(name = "idx_paiement_cotisation_paiement", columnList = "paiement_id"), + @Index(name = "idx_paiement_cotisation_cotisation", columnList = "cotisation_id") + }, + uniqueConstraints = { + @UniqueConstraint( + name = "uk_paiement_cotisation", + columnNames = {"paiement_id", "cotisation_id"}) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class PaiementCotisation extends BaseEntity { + + /** Paiement */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id", nullable = false) + private Paiement paiement; + + /** Cotisation */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cotisation_id", nullable = false) + private Cotisation cotisation; + + /** Montant appliqué à cette cotisation */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_applique", nullable = false, precision = 14, scale = 2) + private BigDecimal montantApplique; + + /** Date d'application */ + @Column(name = "date_application") + private LocalDateTime dateApplication; + + /** Commentaire sur l'application */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (dateApplication == null) { + dateApplication = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/PaiementEvenement.java b/src/main/java/dev/lions/unionflow/server/entity/PaiementEvenement.java new file mode 100644 index 0000000..fb0a63b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/PaiementEvenement.java @@ -0,0 +1,75 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Table de liaison entre Paiement et InscriptionEvenement + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "paiements_evenements", + indexes = { + @Index(name = "idx_paiement_evenement_paiement", columnList = "paiement_id"), + @Index(name = "idx_paiement_evenement_inscription", columnList = "inscription_evenement_id") + }, + uniqueConstraints = { + @UniqueConstraint( + name = "uk_paiement_evenement", + columnNames = {"paiement_id", "inscription_evenement_id"}) + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class PaiementEvenement extends BaseEntity { + + /** Paiement */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id", nullable = false) + private Paiement paiement; + + /** Inscription à l'événement */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "inscription_evenement_id", nullable = false) + private InscriptionEvenement inscriptionEvenement; + + /** Montant appliqué à cette inscription */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant appliqué doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_applique", nullable = false, precision = 14, scale = 2) + private BigDecimal montantApplique; + + /** Date d'application */ + @Column(name = "date_application") + private LocalDateTime dateApplication; + + /** Commentaire sur l'application */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (dateApplication == null) { + dateApplication = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Permission.java b/src/main/java/dev/lions/unionflow/server/entity/Permission.java new file mode 100644 index 0000000..8c5bf2c --- /dev/null +++ b/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/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java b/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java new file mode 100644 index 0000000..6d3155b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/PieceJointe.java @@ -0,0 +1,103 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité PieceJointe pour l'association flexible de documents + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "pieces_jointes", + indexes = { + @Index(name = "idx_piece_jointe_document", columnList = "document_id"), + @Index(name = "idx_piece_jointe_membre", columnList = "membre_id"), + @Index(name = "idx_piece_jointe_organisation", columnList = "organisation_id"), + @Index(name = "idx_piece_jointe_cotisation", columnList = "cotisation_id"), + @Index(name = "idx_piece_jointe_adhesion", columnList = "adhesion_id"), + @Index(name = "idx_piece_jointe_demande_aide", columnList = "demande_aide_id"), + @Index(name = "idx_piece_jointe_transaction_wave", columnList = "transaction_wave_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class PieceJointe extends BaseEntity { + + /** Ordre d'affichage */ + @NotNull + @Min(value = 1, message = "L'ordre doit être positif") + @Column(name = "ordre", nullable = false) + private Integer ordre; + + /** Libellé de la pièce jointe */ + @Column(name = "libelle", length = 200) + private String libelle; + + /** Commentaire */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + /** Document associé */ + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "document_id", nullable = false) + private Document document; + + // Relations flexibles (une seule doit être renseignée) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id") + private Membre membre; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cotisation_id") + private Cotisation cotisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "adhesion_id") + private Adhesion adhesion; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "demande_aide_id") + private DemandeAide demandeAide; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "transaction_wave_id") + private TransactionWave transactionWave; + + /** Méthode métier pour vérifier qu'une seule relation est renseignée */ + public boolean isValide() { + int count = 0; + if (membre != null) count++; + if (organisation != null) count++; + if (cotisation != null) count++; + if (adhesion != null) count++; + if (demandeAide != null) count++; + if (transactionWave != null) count++; + return count == 1; // Exactement une relation doit être renseignée + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (ordre == null) { + ordre = 1; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/Role.java b/src/main/java/dev/lions/unionflow/server/entity/Role.java new file mode 100644 index 0000000..7ddc3ab --- /dev/null +++ b/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/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java b/src/main/java/dev/lions/unionflow/server/entity/RolePermission.java new file mode 100644 index 0000000..6f07add --- /dev/null +++ b/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/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java b/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java new file mode 100644 index 0000000..5adac3a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java @@ -0,0 +1,81 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité TemplateNotification pour les templates de notifications réutilisables + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "templates_notifications", + indexes = { + @Index(name = "idx_template_code", columnList = "code", unique = true), + @Index(name = "idx_template_actif", columnList = "actif") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class TemplateNotification extends BaseEntity { + + /** Code unique du template */ + @NotBlank + @Column(name = "code", unique = true, nullable = false, length = 100) + private String code; + + /** Sujet du template */ + @Column(name = "sujet", length = 500) + private String sujet; + + /** Corps du template (texte) */ + @Column(name = "corps_texte", columnDefinition = "TEXT") + private String corpsTexte; + + /** Corps du template (HTML) */ + @Column(name = "corps_html", columnDefinition = "TEXT") + private String corpsHtml; + + /** Variables disponibles (JSON) */ + @Column(name = "variables_disponibles", columnDefinition = "TEXT") + private String variablesDisponibles; + + /** Canaux supportés (JSON array) */ + @Column(name = "canaux_supportes", length = 500) + private String canauxSupportes; + + /** Langue du template */ + @Column(name = "langue", length = 10) + private String langue; + + /** Description */ + @Column(name = "description", length = 1000) + private String description; + + /** Notifications utilisant ce template */ + @OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List notifications = new ArrayList<>(); + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (langue == null || langue.isEmpty()) { + langue = "fr"; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java b/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java new file mode 100644 index 0000000..0c7573f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/TransactionWave.java @@ -0,0 +1,161 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave; +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité TransactionWave pour le suivi des transactions Wave Mobile Money + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "transactions_wave", + indexes = { + @Index(name = "idx_transaction_wave_id", columnList = "wave_transaction_id", unique = true), + @Index(name = "idx_transaction_wave_request_id", columnList = "wave_request_id"), + @Index(name = "idx_transaction_wave_reference", columnList = "wave_reference"), + @Index(name = "idx_transaction_wave_statut", columnList = "statut_transaction"), + @Index(name = "idx_transaction_wave_compte", columnList = "compte_wave_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class TransactionWave extends BaseEntity { + + /** Identifiant Wave de la transaction (unique) */ + @NotBlank + @Column(name = "wave_transaction_id", unique = true, nullable = false, length = 100) + private String waveTransactionId; + + /** Identifiant de requête Wave */ + @Column(name = "wave_request_id", length = 100) + private String waveRequestId; + + /** Référence Wave */ + @Column(name = "wave_reference", length = 100) + private String waveReference; + + /** Type de transaction */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type_transaction", nullable = false, length = 50) + private TypeTransactionWave typeTransaction; + + /** Statut de la transaction */ + @NotNull + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut_transaction", nullable = false, length = 30) + private StatutTransactionWave statutTransaction = StatutTransactionWave.INITIALISE; + + /** Montant de la transaction */ + @NotNull + @DecimalMin(value = "0.0", message = "Le montant doit être positif") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant", nullable = false, precision = 14, scale = 2) + private BigDecimal montant; + + /** Frais de transaction */ + @DecimalMin(value = "0.0") + @Digits(integer = 10, fraction = 2) + @Column(name = "frais", precision = 12, scale = 2) + private BigDecimal frais; + + /** Montant net (montant - frais) */ + @DecimalMin(value = "0.0") + @Digits(integer = 12, fraction = 2) + @Column(name = "montant_net", precision = 14, scale = 2) + private BigDecimal montantNet; + + /** Code devise */ + @NotBlank + @Pattern(regexp = "^[A-Z]{3}$") + @Column(name = "code_devise", nullable = false, length = 3) + private String codeDevise; + + /** Numéro téléphone payeur */ + @Column(name = "telephone_payeur", length = 13) + private String telephonePayeur; + + /** Numéro téléphone bénéficiaire */ + @Column(name = "telephone_beneficiaire", length = 13) + private String telephoneBeneficiaire; + + /** Métadonnées JSON (réponse complète de Wave API) */ + @Column(name = "metadonnees", columnDefinition = "TEXT") + private String metadonnees; + + /** Réponse complète de Wave API (JSON) */ + @Column(name = "reponse_wave_api", columnDefinition = "TEXT") + private String reponseWaveApi; + + /** Nombre de tentatives */ + @Builder.Default + @Column(name = "nombre_tentatives", nullable = false) + private Integer nombreTentatives = 0; + + /** Date de dernière tentative */ + @Column(name = "date_derniere_tentative") + private LocalDateTime dateDerniereTentative; + + /** Message d'erreur (si échec) */ + @Column(name = "message_erreur", length = 1000) + private String messageErreur; + + // Relations + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "compte_wave_id", nullable = false) + private CompteWave compteWave; + + @OneToMany(mappedBy = "transactionWave", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List webhooks = new ArrayList<>(); + + /** Méthode métier pour vérifier si la transaction est réussie */ + public boolean isReussie() { + return StatutTransactionWave.REUSSIE.equals(statutTransaction); + } + + /** Méthode métier pour vérifier si la transaction peut être retentée */ + public boolean peutEtreRetentee() { + return (statutTransaction == StatutTransactionWave.ECHOUE + || statutTransaction == StatutTransactionWave.EXPIRED) + && (nombreTentatives == null || nombreTentatives < 5); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (statutTransaction == null) { + statutTransaction = StatutTransactionWave.INITIALISE; + } + if (codeDevise == null || codeDevise.isEmpty()) { + codeDevise = "XOF"; + } + if (nombreTentatives == null) { + nombreTentatives = 0; + } + if (montantNet == null && montant != null && frais != null) { + montantNet = montant.subtract(frais); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/entity/TypeOrganisationEntity.java b/src/main/java/dev/lions/unionflow/server/entity/TypeOrganisationEntity.java new file mode 100644 index 0000000..988a9f4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/TypeOrganisationEntity.java @@ -0,0 +1,73 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; + +/** + * Entité persistée représentant un type d'organisation. + * + *

Cette entité permet de gérer dynamiquement le catalogue des types d'organisations + * (codes, libellés, description, ordre d'affichage, activation/désactivation). + * + *

Le champ {@code code} doit rester synchronisé avec l'enum {@link + * dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation} pour les types + * standards fournis par la plateforme. + */ +@Entity +@Table( + name = "uf_type_organisation", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_type_organisation_code", + columnNames = {"code"}) + }) +public class TypeOrganisationEntity extends BaseEntity { + + @Column(name = "code", length = 50, nullable = false, unique = true) + private String code; + + @Column(name = "libelle", length = 150, nullable = false) + private String libelle; + + @Column(name = "description", length = 500) + private String description; + + @Column(name = "ordre_affichage") + private Integer ordreAffichage; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getLibelle() { + return libelle; + } + + public void setLibelle(String libelle) { + this.libelle = libelle; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getOrdreAffichage() { + return ordreAffichage; + } + + public void setOrdreAffichage(Integer ordreAffichage) { + this.ordreAffichage = ordreAffichage; + } +} + + diff --git a/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java b/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java new file mode 100644 index 0000000..ec8c3e5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/WebhookWave.java @@ -0,0 +1,118 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.wave.StatutWebhook; +import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité WebhookWave pour le traitement des événements Wave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "webhooks_wave", + indexes = { + @Index(name = "idx_webhook_wave_event_id", columnList = "wave_event_id", unique = true), + @Index(name = "idx_webhook_wave_statut", columnList = "statut_traitement"), + @Index(name = "idx_webhook_wave_type", columnList = "type_evenement"), + @Index(name = "idx_webhook_wave_transaction", columnList = "transaction_wave_id"), + @Index(name = "idx_webhook_wave_paiement", columnList = "paiement_id") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class WebhookWave extends BaseEntity { + + /** Identifiant unique de l'événement Wave */ + @NotBlank + @Column(name = "wave_event_id", unique = true, nullable = false, length = 100) + private String waveEventId; + + /** Type d'événement */ + @Enumerated(EnumType.STRING) + @Column(name = "type_evenement", length = 50) + private TypeEvenementWebhook typeEvenement; + + /** Statut de traitement */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut_traitement", nullable = false, length = 30) + private StatutWebhook statutTraitement = StatutWebhook.EN_ATTENTE; + + /** Payload JSON reçu */ + @Column(name = "payload", columnDefinition = "TEXT") + private String payload; + + /** Signature de validation */ + @Column(name = "signature", length = 500) + private String signature; + + /** Date de réception */ + @Column(name = "date_reception") + private LocalDateTime dateReception; + + /** Date de traitement */ + @Column(name = "date_traitement") + private LocalDateTime dateTraitement; + + /** Nombre de tentatives de traitement */ + @Builder.Default + @Column(name = "nombre_tentatives", nullable = false) + private Integer nombreTentatives = 0; + + /** Message d'erreur (si échec) */ + @Column(name = "message_erreur", length = 1000) + private String messageErreur; + + /** Commentaires */ + @Column(name = "commentaire", length = 500) + private String commentaire; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "transaction_wave_id") + private TransactionWave transactionWave; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "paiement_id") + private Paiement paiement; + + /** Méthode métier pour vérifier si le webhook est traité */ + public boolean isTraite() { + return StatutWebhook.TRAITE.equals(statutTraitement); + } + + /** Méthode métier pour vérifier si le webhook peut être retenté */ + public boolean peutEtreRetente() { + return (statutTraitement == StatutWebhook.ECHOUE || statutTraitement == StatutWebhook.EN_ATTENTE) + && (nombreTentatives == null || nombreTentatives < 5); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (statutTraitement == null) { + statutTraitement = StatutWebhook.EN_ATTENTE; + } + if (dateReception == null) { + dateReception = LocalDateTime.now(); + } + if (nombreTentatives == null) { + nombreTentatives = 0; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java b/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java new file mode 100644 index 0000000..fa9b18e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.java @@ -0,0 +1,39 @@ +package dev.lions.unionflow.server.exception; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import java.util.Map; +import org.jboss.logging.Logger; + +/** + * Exception mapper pour gérer les erreurs de désérialisation JSON + * Retourne 400 (Bad Request) au lieu de 500 (Internal Server Error) + */ +@Provider +public class JsonProcessingExceptionMapper implements ExceptionMapper { + + private static final Logger LOG = Logger.getLogger(JsonProcessingExceptionMapper.class); + + @Override + public Response toResponse(com.fasterxml.jackson.core.JsonProcessingException exception) { + LOG.warnf("Erreur de désérialisation JSON: %s", exception.getMessage()); + + String message = "Erreur de format JSON"; + if (exception instanceof MismatchedInputException) { + message = "Format JSON invalide ou body manquant"; + } else if (exception instanceof InvalidFormatException) { + message = "Format de données invalide dans le JSON"; + } else if (exception instanceof JsonMappingException) { + message = "Erreur de mapping JSON: " + exception.getMessage(); + } + + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", message, "details", exception.getMessage())) + .build(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java new file mode 100644 index 0000000..0929487 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/AdhesionRepository.java @@ -0,0 +1,102 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Adhesion; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Adhesion + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@ApplicationScoped +public class AdhesionRepository extends BaseRepository { + + public AdhesionRepository() { + super(Adhesion.class); + } + + /** + * Trouve une adhésion par son numéro de référence + * + * @param numeroReference numéro de référence unique + * @return Optional contenant l'adhésion si trouvée + */ + public Optional findByNumeroReference(String numeroReference) { + TypedQuery query = + entityManager.createQuery( + "SELECT a FROM Adhesion a WHERE a.numeroReference = :numeroReference", Adhesion.class); + query.setParameter("numeroReference", numeroReference); + return query.getResultStream().findFirst(); + } + + /** + * Trouve toutes les adhésions d'un membre + * + * @param membreId identifiant du membre + * @return liste des adhésions du membre + */ + public List findByMembreId(UUID membreId) { + TypedQuery query = + entityManager.createQuery( + "SELECT a FROM Adhesion a WHERE a.membre.id = :membreId", Adhesion.class); + query.setParameter("membreId", membreId); + return query.getResultList(); + } + + /** + * Trouve toutes les adhésions d'une organisation + * + * @param organisationId identifiant de l'organisation + * @return liste des adhésions de l'organisation + */ + public List findByOrganisationId(UUID organisationId) { + TypedQuery query = + entityManager.createQuery( + "SELECT a FROM Adhesion a WHERE a.organisation.id = :organisationId", Adhesion.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** + * Trouve toutes les adhésions par statut + * + * @param statut statut de l'adhésion + * @return liste des adhésions avec le statut spécifié + */ + public List findByStatut(String statut) { + TypedQuery query = + entityManager.createQuery("SELECT a FROM Adhesion a WHERE a.statut = :statut", Adhesion.class); + query.setParameter("statut", statut); + return query.getResultList(); + } + + /** + * Trouve toutes les adhésions en attente + * + * @return liste des adhésions en attente + */ + public List findEnAttente() { + return findByStatut("EN_ATTENTE"); + } + + /** + * Trouve toutes les adhésions approuvées en attente de paiement + * + * @return liste des adhésions approuvées non payées + */ + public List findApprouveesEnAttentePaiement() { + TypedQuery query = + entityManager.createQuery( + "SELECT a FROM Adhesion a WHERE a.statut = :statut AND (a.montantPaye IS NULL OR a.montantPaye < a.fraisAdhesion)", + Adhesion.class); + query.setParameter("statut", "APPROUVEE"); + return query.getResultList(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java new file mode 100644 index 0000000..57aee11 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/AdresseRepository.java @@ -0,0 +1,111 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse; +import dev.lions.unionflow.server.entity.Adresse; +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 une adresse par son UUID + * + * @param id UUID de l'adresse + * @return Adresse ou Optional.empty() + */ + public Optional findAdresseById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * 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/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java b/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java new file mode 100644 index 0000000..bd78702 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/AuditLogRepository.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.AuditLog; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * Repository pour les logs d'audit + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@ApplicationScoped +public class AuditLogRepository extends BaseRepository { + + public AuditLogRepository() { + super(AuditLog.class); + } + + // Les méthodes de recherche spécifiques peuvent être ajoutées ici si nécessaire + // Pour l'instant, on utilise les méthodes de base et les requêtes dans le service +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java b/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java new file mode 100644 index 0000000..de2db0a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/BaseRepository.java @@ -0,0 +1,148 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.BaseEntity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository de base pour les entités utilisant UUID comme identifiant + * + *

Remplace PanacheRepository pour utiliser UUID au lieu de Long. + * Fournit les fonctionnalités de base de Panache avec UUID. + * + * @param Le type d'entité qui étend BaseEntity + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +public abstract class BaseRepository { + + @PersistenceContext + protected EntityManager entityManager; + + protected final Class entityClass; + + protected BaseRepository(Class entityClass) { + this.entityClass = entityClass; + } + + /** + * Trouve une entité par son UUID + * + * @param id L'UUID de l'entité + * @return L'entité trouvée ou null + */ + public T findById(UUID id) { + return entityManager.find(entityClass, id); + } + + /** + * Trouve une entité par son UUID (retourne Optional) + * + * @param id L'UUID de l'entité + * @return Optional contenant l'entité si trouvée + */ + public Optional findByIdOptional(UUID id) { + return Optional.ofNullable(findById(id)); + } + + /** + * Persiste une entité + * + * @param entity L'entité à persister + */ + @Transactional + public void persist(T entity) { + entityManager.persist(entity); + } + + /** + * Met à jour une entité + * + * @param entity L'entité à mettre à jour + * @return L'entité mise à jour + */ + @Transactional + public T update(T entity) { + return entityManager.merge(entity); + } + + /** + * Supprime une entité + * + * @param entity L'entité à supprimer + */ + @Transactional + public void delete(T entity) { + // Si l'entité n'est pas dans le contexte de persistance, la merger d'abord + if (!entityManager.contains(entity)) { + entity = entityManager.merge(entity); + } + entityManager.remove(entity); + } + + /** + * Supprime une entité par son UUID + * + * @param id L'UUID de l'entité à supprimer + */ + @Transactional + public boolean deleteById(UUID id) { + T entity = findById(id); + if (entity != null) { + // S'assurer que l'entité est dans le contexte de persistance + if (!entityManager.contains(entity)) { + entity = entityManager.merge(entity); + } + entityManager.remove(entity); + return true; + } + return false; + } + + /** + * Liste toutes les entités + * + * @return La liste de toutes les entités + */ + public List listAll() { + return entityManager.createQuery( + "SELECT e FROM " + entityClass.getSimpleName() + " e", entityClass) + .getResultList(); + } + + /** + * Compte toutes les entités + * + * @return Le nombre total d'entités + */ + public long count() { + return entityManager.createQuery( + "SELECT COUNT(e) FROM " + entityClass.getSimpleName() + " e", Long.class) + .getSingleResult(); + } + + /** + * Vérifie si une entité existe par son UUID + * + * @param id L'UUID de l'entité + * @return true si l'entité existe + */ + public boolean existsById(UUID id) { + return findById(id) != null; + } + + /** + * Obtient l'EntityManager (pour les requêtes avancées) + * + * @return L'EntityManager + */ + public EntityManager getEntityManager() { + return entityManager; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java new file mode 100644 index 0000000..99e3851 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/CompteComptableRepository.java @@ -0,0 +1,80 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable; +import dev.lions.unionflow.server.entity.CompteComptable; +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é CompteComptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class CompteComptableRepository implements PanacheRepository { + + /** + * Trouve un compte comptable par son UUID + * + * @param id UUID du compte comptable + * @return Compte comptable ou Optional.empty() + */ + public Optional findCompteComptableById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un compte par son numéro + * + * @param numeroCompte Numéro du compte + * @return Compte ou Optional.empty() + */ + public Optional findByNumeroCompte(String numeroCompte) { + return find("numeroCompte = ?1 AND actif = true", numeroCompte).firstResultOptional(); + } + + /** + * Trouve les comptes par type + * + * @param type Type de compte + * @return Liste des comptes + */ + public List findByType(TypeCompteComptable type) { + return find("typeCompte = ?1 AND actif = true ORDER BY numeroCompte ASC", type).list(); + } + + /** + * Trouve les comptes par classe comptable + * + * @param classe Classe comptable (1-7) + * @return Liste des comptes + */ + public List findByClasse(Integer classe) { + return find("classeComptable = ?1 AND actif = true ORDER BY numeroCompte ASC", classe).list(); + } + + /** + * Trouve tous les comptes actifs + * + * @return Liste des comptes actifs + */ + public List findAllActifs() { + return find("actif = true ORDER BY numeroCompte ASC").list(); + } + + /** + * Trouve les comptes de trésorerie + * + * @return Liste des comptes de trésorerie + */ + public List findComptesTresorerie() { + return find("typeCompte = ?1 AND actif = true ORDER BY numeroCompte ASC", TypeCompteComptable.TRESORERIE) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/CompteWaveRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CompteWaveRepository.java new file mode 100644 index 0000000..7a63944 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/CompteWaveRepository.java @@ -0,0 +1,98 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave; +import dev.lions.unionflow.server.entity.CompteWave; +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é CompteWave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class CompteWaveRepository implements PanacheRepository { + + /** + * Trouve un compte Wave par son UUID + * + * @param id UUID du compte Wave + * @return Compte Wave ou Optional.empty() + */ + public Optional findCompteWaveById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un compte Wave par numéro de téléphone + * + * @param numeroTelephone Numéro de téléphone + * @return Compte Wave ou Optional.empty() + */ + public Optional findByNumeroTelephone(String numeroTelephone) { + return find("numeroTelephone = ?1 AND actif = true", numeroTelephone).firstResultOptional(); + } + + /** + * Trouve tous les comptes Wave d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des comptes Wave + */ + public List findByOrganisationId(UUID organisationId) { + return find("organisation.id = ?1 AND actif = true", organisationId).list(); + } + + /** + * Trouve le compte Wave principal d'une organisation (premier vérifié) + * + * @param organisationId ID de l'organisation + * @return Compte Wave ou Optional.empty() + */ + public Optional findPrincipalByOrganisationId(UUID organisationId) { + return find( + "organisation.id = ?1 AND statutCompte = ?2 AND actif = true", + organisationId, + StatutCompteWave.VERIFIE) + .firstResultOptional(); + } + + /** + * Trouve tous les comptes Wave d'un membre + * + * @param membreId ID du membre + * @return Liste des comptes Wave + */ + public List findByMembreId(UUID membreId) { + return find("membre.id = ?1 AND actif = true", membreId).list(); + } + + /** + * Trouve le compte Wave principal d'un membre (premier vérifié) + * + * @param membreId ID du membre + * @return Compte Wave ou Optional.empty() + */ + public Optional findPrincipalByMembreId(UUID membreId) { + return find( + "membre.id = ?1 AND statutCompte = ?2 AND actif = true", + membreId, + StatutCompteWave.VERIFIE) + .firstResultOptional(); + } + + /** + * Trouve tous les comptes Wave vérifiés + * + * @return Liste des comptes Wave vérifiés + */ + public List findComptesVerifies() { + return find("statutCompte = ?1 AND actif = true", StatutCompteWave.VERIFIE).list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.java b/src/main/java/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.java new file mode 100644 index 0000000..0b8452a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.java @@ -0,0 +1,59 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.ConfigurationWave; +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é ConfigurationWave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class ConfigurationWaveRepository implements PanacheRepository { + + /** + * Trouve une configuration Wave par son UUID + * + * @param id UUID de la configuration + * @return Configuration ou Optional.empty() + */ + public Optional findConfigurationWaveById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve une configuration par sa clé + * + * @param cle Clé de configuration + * @return Configuration ou Optional.empty() + */ + public Optional findByCle(String cle) { + return find("cle = ?1 AND actif = true", cle).firstResultOptional(); + } + + /** + * Trouve toutes les configurations d'un environnement + * + * @param environnement Environnement (SANDBOX, PRODUCTION, COMMON) + * @return Liste des configurations + */ + public List findByEnvironnement(String environnement) { + return find("environnement = ?1 AND actif = true", environnement).list(); + } + + /** + * Trouve toutes les configurations actives + * + * @return Liste des configurations actives + */ + public List findAllActives() { + return find("actif = true ORDER BY cle ASC").list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java new file mode 100644 index 0000000..0c6863c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/CotisationRepository.java @@ -0,0 +1,392 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Cotisation; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour la gestion des cotisations avec UUID + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class CotisationRepository extends BaseRepository { + + public CotisationRepository() { + super(Cotisation.class); + } + + /** + * Trouve une cotisation par son numéro de référence + * + * @param numeroReference le numéro de référence unique + * @return Optional contenant la cotisation si trouvée + */ + public Optional findByNumeroReference(String numeroReference) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference", + Cotisation.class); + query.setParameter("numeroReference", numeroReference); + return query.getResultStream().findFirst(); + } + + /** + * Trouve toutes les cotisations d'un membre + * + * @param membreId l'UUID du membre + * @param page pagination + * @param sort tri + * @return liste paginée des cotisations + */ + public List findByMembreId(UUID membreId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy, + Cotisation.class); + query.setParameter("membreId", membreId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les cotisations par statut + * + * @param statut le statut recherché + * @param page pagination + * @return liste paginée des cotisations + */ + public List findByStatut(String statut, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les cotisations en retard + * + * @param dateReference date de référence (généralement aujourd'hui) + * @param page pagination + * @return liste des cotisations en retard + */ + public List findCotisationsEnRetard(LocalDate dateReference, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC", + Cotisation.class); + query.setParameter("dateReference", dateReference); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les cotisations par période (année/mois) + * + * @param annee l'année + * @param mois le mois (optionnel) + * @param page pagination + * @return liste des cotisations de la période + */ + public List findByPeriode(Integer annee, Integer mois, Page page) { + TypedQuery query; + if (mois != null) { + query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("annee", annee); + query.setParameter("mois", mois); + } else { + query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC", + Cotisation.class); + query.setParameter("annee", annee); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les cotisations par type + * + * @param typeCotisation le type de cotisation + * @param page pagination + * @return liste des cotisations du type spécifié + */ + public List findByType(String typeCotisation, Page page) { + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setParameter("typeCotisation", typeCotisation); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche avancée avec filtres multiples + * + * @param membreId UUID du membre (optionnel) + * @param statut statut (optionnel) + * @param typeCotisation type (optionnel) + * @param annee année (optionnel) + * @param mois mois (optionnel) + * @param page pagination + * @return liste filtrée des cotisations + */ + public List rechercheAvancee( + UUID membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) { + StringBuilder jpql = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1"); + Map params = new HashMap<>(); + + if (membreId != null) { + jpql.append(" AND c.membre.id = :membreId"); + params.put("membreId", membreId); + } + + if (statut != null && !statut.isEmpty()) { + jpql.append(" AND c.statut = :statut"); + params.put("statut", statut); + } + + if (typeCotisation != null && !typeCotisation.isEmpty()) { + jpql.append(" AND c.typeCotisation = :typeCotisation"); + params.put("typeCotisation", typeCotisation); + } + + if (annee != null) { + jpql.append(" AND c.annee = :annee"); + params.put("annee", annee); + } + + if (mois != null) { + jpql.append(" AND c.mois = :mois"); + params.put("mois", mois); + } + + jpql.append(" ORDER BY c.dateEcheance DESC"); + + TypedQuery query = entityManager.createQuery(jpql.toString(), Cotisation.class); + for (Map.Entry param : params.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Calcule le total des montants dus pour un membre + * + * @param membreId UUID du membre + * @return montant total dû + */ + public BigDecimal calculerTotalMontantDu(UUID membreId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId", + BigDecimal.class); + query.setParameter("membreId", membreId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** + * Calcule le total des montants payés pour un membre + * + * @param membreId UUID du membre + * @return montant total payé + */ + public BigDecimal calculerTotalMontantPaye(UUID membreId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId", + BigDecimal.class); + query.setParameter("membreId", membreId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** + * Compte les cotisations par statut + * + * @param statut le statut + * @return nombre de cotisations + */ + public long compterParStatut(String statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } + + /** + * Trouve les cotisations nécessitant un rappel + * + * @param joursAvantEcheance nombre de jours avant échéance + * @param nombreMaxRappels nombre maximum de rappels déjà envoyés + * @return liste des cotisations à rappeler + */ + public List findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) { + LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance); + TypedQuery query = entityManager.createQuery( + "SELECT c FROM Cotisation c WHERE c.dateEcheance <= :dateRappel AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' AND c.nombreRappels < :nombreMaxRappels ORDER BY c.dateEcheance ASC", + Cotisation.class); + query.setParameter("dateRappel", dateRappel); + query.setParameter("nombreMaxRappels", nombreMaxRappels); + return query.getResultList(); + } + + /** + * Met à jour le nombre de rappels pour une cotisation + * + * @param cotisationId UUID de la cotisation + * @return true si mise à jour réussie + */ + public boolean incrementerNombreRappels(UUID cotisationId) { + Cotisation cotisation = findByIdOptional(cotisationId).orElse(null); + if (cotisation != null) { + cotisation.setNombreRappels( + cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1); + cotisation.setDateDernierRappel(LocalDateTime.now()); + update(cotisation); + return true; + } + return false; + } + + /** + * Statistiques des cotisations par période + * + * @param annee l'année + * @param mois le mois (optionnel) + * @return map avec les statistiques + */ + public Map getStatistiquesPeriode(Integer annee, Integer mois) { + String baseQuery = mois != null + ? "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois" + : "SELECT c FROM Cotisation c WHERE c.annee = :annee"; + + TypedQuery countQuery; + TypedQuery montantTotalQuery; + TypedQuery montantPayeQuery; + TypedQuery payeesQuery; + + if (mois != null) { + countQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + Long.class); + montantTotalQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + BigDecimal.class); + montantPayeQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois", + BigDecimal.class); + payeesQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'", + Long.class); + + countQuery.setParameter("annee", annee); + countQuery.setParameter("mois", mois); + montantTotalQuery.setParameter("annee", annee); + montantTotalQuery.setParameter("mois", mois); + montantPayeQuery.setParameter("annee", annee); + montantPayeQuery.setParameter("mois", mois); + payeesQuery.setParameter("annee", annee); + payeesQuery.setParameter("mois", mois); + } else { + countQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee", Long.class); + montantTotalQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee", + BigDecimal.class); + montantPayeQuery = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee", + BigDecimal.class); + payeesQuery = entityManager.createQuery( + "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'", + Long.class); + + countQuery.setParameter("annee", annee); + montantTotalQuery.setParameter("annee", annee); + montantPayeQuery.setParameter("annee", annee); + payeesQuery.setParameter("annee", annee); + } + + Long totalCotisations = countQuery.getSingleResult(); + BigDecimal montantTotal = montantTotalQuery.getSingleResult(); + BigDecimal montantPaye = montantPayeQuery.getSingleResult(); + Long cotisationsPayees = payeesQuery.getSingleResult(); + + return Map.of( + "totalCotisations", totalCotisations != null ? totalCotisations : 0L, + "montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO, + "montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO, + "cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L, + "tauxPaiement", + totalCotisations != null && totalCotisations > 0 + ? (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations + : 0.0); + } + + /** Somme des montants payés dans une période */ + public BigDecimal sumMontantsPayes( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'PAYEE' AND c.datePaiement BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Somme des montants en attente dans une période */ + public BigDecimal sumMontantsEnAttente( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'EN_ATTENTE' AND c.dateCreation BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "c.dateEcheance DESC"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("c.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java b/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java new file mode 100644 index 0000000..d44bf34 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/DemandeAideRepository.java @@ -0,0 +1,275 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import dev.lions.unionflow.server.entity.DemandeAide; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Repository pour les demandes d'aide avec UUID */ +@ApplicationScoped +public class DemandeAideRepository extends BaseRepository { + + public DemandeAideRepository() { + super(DemandeAide.class); + } + + /** Trouve toutes les demandes d'aide par organisation */ + public List findByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide par organisation avec pagination */ + public List findByOrganisationId(UUID organisationId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY d.dateDemande DESC"; + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.organisation.id = :organisationId" + orderBy, + DemandeAide.class); + query.setParameter("organisationId", organisationId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide par demandeur */ + public List findByDemandeurId(UUID demandeurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.demandeur.id = :demandeurId", + DemandeAide.class); + query.setParameter("demandeurId", demandeurId); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide par statut */ + public List findByStatut(StatutAide statut) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut", + DemandeAide.class); + query.setParameter("statut", statut); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide par statut et organisation */ + public List findByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("statut", statut); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide par type */ + public List findByTypeAide(TypeAide typeAide) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.typeAide = :typeAide", + DemandeAide.class); + query.setParameter("typeAide", typeAide); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide urgentes */ + public List findUrgentes() { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.urgence = true", + DemandeAide.class); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide urgentes par organisation */ + public List findUrgentesByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.urgence = true AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide dans une période */ + public List findByPeriode(LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin", + DemandeAide.class); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getResultList(); + } + + /** Trouve toutes les demandes d'aide dans une période pour une organisation */ + public List findByPeriodeAndOrganisationId( + LocalDateTime debut, LocalDateTime fin, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :debut AND d.dateDemande <= :fin AND d.organisation.id = :organisationId", + DemandeAide.class); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** Compte le nombre de demandes par statut */ + public long countByStatut(StatutAide statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut", + Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } + + /** Compte le nombre de demandes par statut et organisation */ + public long countByStatutAndOrganisationId(StatutAide statut, UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.statut = :statut AND d.organisation.id = :organisationId", + Long.class); + query.setParameter("statut", statut); + query.setParameter("organisationId", organisationId); + return query.getSingleResult(); + } + + /** Calcule le montant total demandé par organisation */ + public Optional sumMontantDemandeByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantDemande), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + BigDecimal result = query.getSingleResult(); + return result != null && result.compareTo(BigDecimal.ZERO) > 0 + ? Optional.of(result) + : Optional.empty(); + } + + /** Calcule le montant total approuvé par organisation */ + public Optional sumMontantApprouveByOrganisationId(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + BigDecimal result = query.getSingleResult(); + return result != null && result.compareTo(BigDecimal.ZERO) > 0 + ? Optional.of(result) + : Optional.empty(); + } + + /** Trouve les demandes d'aide récentes (dernières 30 jours) */ + public List findRecentes() { + LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours ORDER BY d.dateDemande DESC", + DemandeAide.class); + query.setParameter("il30Jours", il30Jours); + return query.getResultList(); + } + + /** Trouve les demandes d'aide récentes par organisation */ + public List findRecentesByOrganisationId(UUID organisationId) { + LocalDateTime il30Jours = LocalDateTime.now().minusDays(30); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.dateDemande >= :il30Jours AND d.organisation.id = :organisationId ORDER BY d.dateDemande DESC", + DemandeAide.class); + query.setParameter("il30Jours", il30Jours); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** Trouve les demandes d'aide en attente depuis plus de X jours */ + public List findEnAttenteDepuis(int nombreJours) { + LocalDateTime dateLimit = LocalDateTime.now().minusDays(nombreJours); + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.statut = :statut AND d.dateDemande <= :dateLimit", + DemandeAide.class); + query.setParameter("statut", StatutAide.EN_ATTENTE); + query.setParameter("dateLimit", dateLimit); + return query.getResultList(); + } + + /** Trouve les demandes d'aide par évaluateur */ + public List findByEvaluateurId(UUID evaluateurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId", + DemandeAide.class); + query.setParameter("evaluateurId", evaluateurId); + return query.getResultList(); + } + + /** Trouve les demandes d'aide en cours d'évaluation par évaluateur */ + public List findEnCoursEvaluationByEvaluateurId(UUID evaluateurId) { + TypedQuery query = entityManager.createQuery( + "SELECT d FROM DemandeAide d WHERE d.evaluateur.id = :evaluateurId AND d.statut = :statut", + DemandeAide.class); + query.setParameter("evaluateurId", evaluateurId); + query.setParameter("statut", StatutAide.EN_COURS_EVALUATION); + return query.getResultList(); + } + + /** Compte les demandes approuvées dans une période */ + public long countDemandesApprouvees(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** Compte toutes les demandes dans une période */ + public long countDemandes(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(d) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.dateCreation BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** Somme des montants accordés dans une période */ + public BigDecimal sumMontantsAccordes( + UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(d.montantApprouve), 0) FROM DemandeAide d WHERE d.organisation.id = :organisationId AND d.statut = :statut AND d.dateCreation BETWEEN :debut AND :fin", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + query.setParameter("statut", StatutAide.APPROUVEE); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "d.dateDemande DESC"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("d.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java new file mode 100644 index 0000000..d97904a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/DocumentRepository.java @@ -0,0 +1,70 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.document.TypeDocument; +import dev.lions.unionflow.server.entity.Document; +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é Document + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class DocumentRepository implements PanacheRepository { + + /** + * Trouve un document par son UUID + * + * @param id UUID du document + * @return Document ou Optional.empty() + */ + public Optional findDocumentById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un document par son hash MD5 + * + * @param hashMd5 Hash MD5 + * @return Document ou Optional.empty() + */ + public Optional findByHashMd5(String hashMd5) { + return find("hashMd5 = ?1 AND actif = true", hashMd5).firstResultOptional(); + } + + /** + * Trouve un document par son hash SHA256 + * + * @param hashSha256 Hash SHA256 + * @return Document ou Optional.empty() + */ + public Optional findByHashSha256(String hashSha256) { + return find("hashSha256 = ?1 AND actif = true", hashSha256).firstResultOptional(); + } + + /** + * Trouve les documents par type + * + * @param type Type de document + * @return Liste des documents + */ + public List findByType(TypeDocument type) { + return find("typeDocument = ?1 AND actif = true ORDER BY dateCreation DESC", type).list(); + } + + /** + * Trouve tous les documents actifs + * + * @return Liste des documents actifs + */ + public List findAllActifs() { + return find("actif = true ORDER BY dateCreation DESC").list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/EcritureComptableRepository.java b/src/main/java/dev/lions/unionflow/server/repository/EcritureComptableRepository.java new file mode 100644 index 0000000..e72327a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/EcritureComptableRepository.java @@ -0,0 +1,109 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.EcritureComptable; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité EcritureComptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class EcritureComptableRepository implements PanacheRepository { + + /** + * Trouve une écriture comptable par son UUID + * + * @param id UUID de l'écriture comptable + * @return Écriture comptable ou Optional.empty() + */ + public Optional findEcritureComptableById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve une écriture par son numéro de pièce + * + * @param numeroPiece Numéro de pièce + * @return Écriture ou Optional.empty() + */ + public Optional findByNumeroPiece(String numeroPiece) { + return find("numeroPiece = ?1 AND actif = true", numeroPiece).firstResultOptional(); + } + + /** + * Trouve les écritures d'un journal + * + * @param journalId ID du journal + * @return Liste des écritures + */ + public List findByJournalId(UUID journalId) { + return find("journal.id = ?1 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC", journalId) + .list(); + } + + /** + * Trouve les écritures d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des écritures + */ + public List findByOrganisationId(UUID organisationId) { + return find( + "organisation.id = ?1 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC", + organisationId) + .list(); + } + + /** + * Trouve les écritures d'un paiement + * + * @param paiementId ID du paiement + * @return Liste des écritures + */ + public List findByPaiementId(UUID paiementId) { + return find("paiement.id = ?1 AND actif = true ORDER BY dateEcriture DESC", paiementId).list(); + } + + /** + * Trouve les écritures dans une période + * + * @param dateDebut Date de début + * @param dateFin Date de fin + * @return Liste des écritures + */ + public List findByPeriode(LocalDate dateDebut, LocalDate dateFin) { + return find( + "dateEcriture >= ?1 AND dateEcriture <= ?2 AND actif = true ORDER BY dateEcriture DESC, numeroPiece ASC", + dateDebut, + dateFin) + .list(); + } + + /** + * Trouve les écritures non pointées + * + * @return Liste des écritures non pointées + */ + public List findNonPointees() { + return find("pointe = false AND actif = true ORDER BY dateEcriture ASC").list(); + } + + /** + * Trouve les écritures avec un lettrage spécifique + * + * @param lettrage Lettrage + * @return Liste des écritures + */ + public List findByLettrage(String lettrage) { + return find("lettrage = ?1 AND actif = true ORDER BY dateEcriture DESC", lettrage).list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java b/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java new file mode 100644 index 0000000..62496b5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/EvenementRepository.java @@ -0,0 +1,463 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Événement avec UUID + * + *

Fournit les méthodes d'accès aux données pour la gestion des événements avec des + * fonctionnalités de recherche avancées et de filtrage. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class EvenementRepository extends BaseRepository { + + public EvenementRepository() { + super(Evenement.class); + } + + /** + * Trouve un événement par son titre (recherche exacte) + * + * @param titre le titre de l'événement + * @return l'événement trouvé ou Optional.empty() + */ + public Optional findByTitre(String titre) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.titre = :titre", Evenement.class); + query.setParameter("titre", titre); + return query.getResultStream().findFirst(); + } + + /** + * Trouve tous les événements actifs + * + * @return la liste des événements actifs + */ + public List findAllActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.actif = true", Evenement.class); + return query.getResultList(); + } + + /** + * Trouve tous les événements actifs avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements actifs + */ + public List findAllActifs(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.actif = true" + orderBy, Evenement.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte le nombre d'événements actifs + * + * @return le nombre d'événements actifs + */ + public long countActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class); + return query.getSingleResult(); + } + + /** + * Trouve les événements par statut + * + * @param statut le statut recherché + * @return la liste des événements avec ce statut + */ + public List findByStatut(StatutEvenement statut) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.statut = :statut", Evenement.class); + query.setParameter("statut", statut); + return query.getResultList(); + } + + /** + * Trouve les événements par statut avec pagination et tri + * + * @param statut le statut recherché + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements avec ce statut + */ + public List findByStatut(StatutEvenement statut, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.statut = :statut" + orderBy, Evenement.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les événements par type + * + * @param type le type d'événement recherché + * @return la liste des événements de ce type + */ + public List findByType(TypeEvenement type) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.typeEvenement = :type", Evenement.class); + query.setParameter("type", type); + return query.getResultList(); + } + + /** + * Trouve les événements par type avec pagination et tri + * + * @param type le type d'événement recherché + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements de ce type + */ + public List findByType(TypeEvenement type, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.typeEvenement = :type" + orderBy, Evenement.class); + query.setParameter("type", type); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les événements par organisation + * + * @param organisationId l'UUID de l'organisation + * @return la liste des événements de cette organisation + */ + public List findByOrganisation(UUID organisationId) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId", Evenement.class); + query.setParameter("organisationId", organisationId); + return query.getResultList(); + } + + /** + * Trouve les événements par organisation avec pagination et tri + * + * @param organisationId l'UUID de l'organisation + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements de cette organisation + */ + public List findByOrganisation(UUID organisationId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId" + orderBy, + Evenement.class); + query.setParameter("organisationId", organisationId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les événements à venir (date de début future) + * + * @return la liste des événements à venir + */ + public List findEvenementsAVenir() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true", + Evenement.class); + query.setParameter("maintenant", LocalDateTime.now()); + return query.getResultList(); + } + + /** + * Trouve les événements à venir avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements à venir + */ + public List findEvenementsAVenir(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true" + orderBy, + Evenement.class); + query.setParameter("maintenant", LocalDateTime.now()); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les événements visibles au public + * + * @return la liste des événements publics + */ + public List findEvenementsPublics() { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true", + Evenement.class); + return query.getResultList(); + } + + /** + * Trouve les événements visibles au public avec pagination et tri + * + * @param page la page demandée + * @param sort le tri à appliquer + * @return la liste paginée des événements publics + */ + public List findEvenementsPublics(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true" + orderBy, + Evenement.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche avancée d'événements avec filtres multiples + * + * @param recherche terme de recherche (titre, description) + * @param statut statut de l'événement (optionnel) + * @param type type d'événement (optionnel) + * @param organisationId UUID de l'organisation (optionnel) + * @param organisateurId UUID de l'organisateur (optionnel) + * @param dateDebutMin date de début minimum (optionnel) + * @param dateDebutMax date de début maximum (optionnel) + * @param visiblePublic visibilité publique (optionnel) + * @param inscriptionRequise inscription requise (optionnel) + * @param actif statut actif (optionnel) + * @param page pagination + * @param sort tri + * @return la liste paginée des événements correspondants aux critères + */ + public List rechercheAvancee( + String recherche, + StatutEvenement statut, + TypeEvenement type, + UUID organisationId, + UUID organisateurId, + LocalDateTime dateDebutMin, + LocalDateTime dateDebutMax, + Boolean visiblePublic, + Boolean inscriptionRequise, + Boolean actif, + Page page, + Sort sort) { + StringBuilder jpql = new StringBuilder("SELECT e FROM Evenement e WHERE 1=1"); + Map params = new HashMap<>(); + + if (recherche != null && !recherche.trim().isEmpty()) { + jpql.append( + " AND (LOWER(e.titre) LIKE LOWER(:recherche) OR LOWER(e.description) LIKE LOWER(:recherche) OR LOWER(e.lieu) LIKE LOWER(:recherche))"); + params.put("recherche", "%" + recherche.toLowerCase() + "%"); + } + + if (statut != null) { + jpql.append(" AND e.statut = :statut"); + params.put("statut", statut); + } + + if (type != null) { + jpql.append(" AND e.typeEvenement = :type"); + params.put("type", type); + } + + if (organisationId != null) { + jpql.append(" AND e.organisation.id = :organisationId"); + params.put("organisationId", organisationId); + } + + if (organisateurId != null) { + jpql.append(" AND e.organisateur.id = :organisateurId"); + params.put("organisateurId", organisateurId); + } + + if (dateDebutMin != null) { + jpql.append(" AND e.dateDebut >= :dateDebutMin"); + params.put("dateDebutMin", dateDebutMin); + } + + if (dateDebutMax != null) { + jpql.append(" AND e.dateDebut <= :dateDebutMax"); + params.put("dateDebutMax", dateDebutMax); + } + + if (visiblePublic != null) { + jpql.append(" AND e.visiblePublic = :visiblePublic"); + params.put("visiblePublic", visiblePublic); + } + + if (inscriptionRequise != null) { + jpql.append(" AND e.inscriptionRequise = :inscriptionRequise"); + params.put("inscriptionRequise", inscriptionRequise); + } + + if (actif != null) { + jpql.append(" AND e.actif = :actif"); + params.put("actif", actif); + } + + if (sort != null) { + jpql.append(" ORDER BY ").append(buildOrderBy(sort)); + } + + TypedQuery query = entityManager.createQuery(jpql.toString(), Evenement.class); + for (Map.Entry param : params.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Obtient les statistiques des événements + * + * @return une map contenant les statistiques + */ + public Map getStatistiques() { + Map stats = new HashMap<>(); + LocalDateTime maintenant = LocalDateTime.now(); + + TypedQuery totalQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e", Long.class); + stats.put("total", totalQuery.getSingleResult()); + + TypedQuery actifsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class); + stats.put("actifs", actifsQuery.getSingleResult()); + + TypedQuery inactifsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.actif = false", Long.class); + stats.put("inactifs", inactifsQuery.getSingleResult()); + + TypedQuery aVenirQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true", + Long.class); + aVenirQuery.setParameter("maintenant", maintenant); + stats.put("aVenir", aVenirQuery.getSingleResult()); + + TypedQuery enCoursQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut <= :maintenant AND (e.dateFin IS NULL OR e.dateFin >= :maintenant) AND e.actif = true", + Long.class); + enCoursQuery.setParameter("maintenant", maintenant); + stats.put("enCours", enCoursQuery.getSingleResult()); + + TypedQuery passesQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE (e.dateFin < :maintenant OR (e.dateFin IS NULL AND e.dateDebut < :maintenant)) AND e.actif = true", + Long.class); + passesQuery.setParameter("maintenant", maintenant); + stats.put("passes", passesQuery.getSingleResult()); + + TypedQuery publicsQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true", + Long.class); + stats.put("publics", publicsQuery.getSingleResult()); + + TypedQuery avecInscriptionQuery = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.inscriptionRequise = true AND e.actif = true", + Long.class); + stats.put("avecInscription", avecInscriptionQuery.getSingleResult()); + + return stats; + } + + /** + * Compte les événements dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return nombre d'événements + */ + public long countEvenements(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Calcule la moyenne de participants dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return moyenne de participants ou null + */ + public Double calculerMoyenneParticipants(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT AVG(e.nombreParticipants) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Double.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Compte le total des participations dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut date de début + * @param fin date de fin + * @return total des participations + */ + public Long countTotalParticipations(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COALESCE(SUM(e.nombreParticipants), 0) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + Long result = query.getSingleResult(); + return result != null ? result : 0L; + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "e.dateDebut"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("e.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/JournalComptableRepository.java b/src/main/java/dev/lions/unionflow/server/repository/JournalComptableRepository.java new file mode 100644 index 0000000..9972e23 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/JournalComptableRepository.java @@ -0,0 +1,83 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable; +import dev.lions.unionflow.server.entity.JournalComptable; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité JournalComptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class JournalComptableRepository implements PanacheRepository { + + /** + * Trouve un journal comptable par son UUID + * + * @param id UUID du journal comptable + * @return Journal comptable ou Optional.empty() + */ + public Optional findJournalComptableById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un journal par son code + * + * @param code Code du journal + * @return Journal ou Optional.empty() + */ + public Optional findByCode(String code) { + return find("code = ?1 AND actif = true", code).firstResultOptional(); + } + + /** + * Trouve les journaux par type + * + * @param type Type de journal + * @return Liste des journaux + */ + public List findByType(TypeJournalComptable type) { + return find("typeJournal = ?1 AND actif = true ORDER BY code ASC", type).list(); + } + + /** + * Trouve les journaux ouverts + * + * @return Liste des journaux ouverts + */ + public List findJournauxOuverts() { + return find("statut = ?1 AND actif = true ORDER BY code ASC", "OUVERT").list(); + } + + /** + * Trouve les journaux pour une date donnée + * + * @param date Date à vérifier + * @return Liste des journaux actifs pour cette date + */ + public List findJournauxPourDate(LocalDate date) { + return find( + "(dateDebut IS NULL OR dateDebut <= ?1) AND (dateFin IS NULL OR dateFin >= ?1) AND actif = true ORDER BY code ASC", + date) + .list(); + } + + /** + * Trouve tous les journaux actifs + * + * @return Liste des journaux actifs + */ + public List findAllActifs() { + return find("actif = true ORDER BY code ASC").list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/LigneEcritureRepository.java b/src/main/java/dev/lions/unionflow/server/repository/LigneEcritureRepository.java new file mode 100644 index 0000000..381ac39 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/LigneEcritureRepository.java @@ -0,0 +1,51 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.LigneEcriture; +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é LigneEcriture + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class LigneEcritureRepository implements PanacheRepository { + + /** + * Trouve une ligne d'écriture par son UUID + * + * @param id UUID de la ligne d'écriture + * @return Ligne d'écriture ou Optional.empty() + */ + public Optional findLigneEcritureById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * Trouve toutes les lignes d'une écriture + * + * @param ecritureId ID de l'écriture + * @return Liste des lignes + */ + public List findByEcritureId(UUID ecritureId) { + return find("ecriture.id = ?1 ORDER BY numeroLigne ASC", ecritureId).list(); + } + + /** + * Trouve toutes les lignes d'un compte comptable + * + * @param compteComptableId ID du compte comptable + * @return Liste des lignes + */ + public List findByCompteComptableId(UUID compteComptableId) { + return find("compteComptable.id = ?1 ORDER BY ecriture.dateEcriture DESC, numeroLigne ASC", compteComptableId) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java b/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java new file mode 100644 index 0000000..0161854 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/MembreRepository.java @@ -0,0 +1,238 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Membre; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** Repository pour l'entité Membre avec UUID */ +@ApplicationScoped +public class MembreRepository extends BaseRepository { + + public MembreRepository() { + super(Membre.class); + } + + /** Trouve un membre par son email */ + public Optional findByEmail(String email) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.email = :email", Membre.class); + query.setParameter("email", email); + return query.getResultStream().findFirst(); + } + + /** Trouve un membre par son numéro */ + public Optional findByNumeroMembre(String numeroMembre) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.numeroMembre = :numeroMembre", Membre.class); + query.setParameter("numeroMembre", numeroMembre); + return query.getResultStream().findFirst(); + } + + /** Trouve tous les membres actifs */ + public List findAllActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = true", Membre.class); + return query.getResultList(); + } + + /** Compte le nombre de membres actifs */ + public long countActifs() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.actif = true", Long.class); + return query.getSingleResult(); + } + + /** Trouve les membres par nom ou prénom (recherche partielle) */ + public List findByNomOrPrenom(String recherche) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)", + Membre.class); + query.setParameter("recherche", "%" + recherche + "%"); + return query.getResultList(); + } + + /** Trouve tous les membres actifs avec pagination et tri */ + public List findAllActifs(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = true" + orderBy, Membre.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Trouve les membres par nom ou prénom avec pagination et tri */ + public List findByNomOrPrenom(String recherche, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche)" + orderBy, + Membre.class); + query.setParameter("recherche", "%" + recherche + "%"); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Compte les nouveaux membres depuis une date donnée */ + public long countNouveauxMembres(LocalDate depuis) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.dateAdhesion >= :depuis", Long.class); + query.setParameter("depuis", depuis); + return query.getSingleResult(); + } + + /** Trouve les membres par statut avec pagination */ + public List findByStatut(boolean actif, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.actif = :actif" + orderBy, Membre.class); + query.setParameter("actif", actif); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Trouve les membres par tranche d'âge */ + public List findByTrancheAge(int ageMin, int ageMax, Page page, Sort sort) { + LocalDate dateNaissanceMax = LocalDate.now().minusYears(ageMin); + LocalDate dateNaissanceMin = LocalDate.now().minusYears(ageMax + 1); + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.dateNaissance BETWEEN :dateMin AND :dateMax" + orderBy, + Membre.class); + query.setParameter("dateMin", dateNaissanceMin); + query.setParameter("dateMax", dateNaissanceMax); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Recherche avancée de membres */ + public List rechercheAvancee( + String recherche, + Boolean actif, + LocalDate dateAdhesionMin, + LocalDate dateAdhesionMax, + Page page, + Sort sort) { + StringBuilder jpql = new StringBuilder("SELECT m FROM Membre m WHERE 1=1"); + + if (recherche != null && !recherche.isEmpty()) { + jpql.append(" AND (LOWER(m.nom) LIKE LOWER(:recherche) OR LOWER(m.prenom) LIKE LOWER(:recherche) OR LOWER(m.email) LIKE LOWER(:recherche))"); + } + if (actif != null) { + jpql.append(" AND m.actif = :actif"); + } + if (dateAdhesionMin != null) { + jpql.append(" AND m.dateAdhesion >= :dateAdhesionMin"); + } + if (dateAdhesionMax != null) { + jpql.append(" AND m.dateAdhesion <= :dateAdhesionMax"); + } + + if (sort != null) { + jpql.append(" ORDER BY ").append(buildOrderBy(sort)); + } + + TypedQuery query = entityManager.createQuery(jpql.toString(), Membre.class); + + if (recherche != null && !recherche.isEmpty()) { + query.setParameter("recherche", "%" + recherche + "%"); + } + if (actif != null) { + query.setParameter("actif", actif); + } + if (dateAdhesionMin != null) { + query.setParameter("dateAdhesionMin", dateAdhesionMin); + } + if (dateAdhesionMax != null) { + query.setParameter("dateAdhesionMax", dateAdhesionMax); + } + + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "m.id"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("m.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } + + /** + * Compte les membres actifs dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Nombre de membres actifs + */ + public Long countMembresActifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = true AND m.dateAdhesion BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Compte les membres inactifs dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Nombre de membres inactifs + */ + public Long countMembresInactifs(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(m) FROM Membre m WHERE m.organisation.id = :organisationId AND m.actif = false AND m.dateAdhesion BETWEEN :debut AND :fin", + Long.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } + + /** + * Calcule la moyenne d'âge des membres dans une période et organisation + * + * @param organisationId UUID de l'organisation + * @param debut Date de début + * @param fin Date de fin + * @return Moyenne d'âge ou null si aucun membre + */ + public Double calculerMoyenneAge(UUID organisationId, LocalDateTime debut, LocalDateTime fin) { + TypedQuery query = entityManager.createQuery( + "SELECT AVG(YEAR(CURRENT_DATE) - YEAR(m.dateNaissance)) FROM Membre m WHERE m.organisation.id = :organisationId AND m.dateAdhesion BETWEEN :debut AND :fin", + Double.class); + query.setParameter("organisationId", organisationId); + query.setParameter("debut", debut); + query.setParameter("fin", fin); + return query.getSingleResult(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java b/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java new file mode 100644 index 0000000..5a1ba7c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/MembreRoleRepository.java @@ -0,0 +1,77 @@ +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.Optional; +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 une attribution membre-role par son UUID + * + * @param id UUID de l'attribution + * @return Attribution ou Optional.empty() + */ + public Optional findMembreRoleById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * 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/src/main/java/dev/lions/unionflow/server/repository/NotificationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/NotificationRepository.java new file mode 100644 index 0000000..a8e22c9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/NotificationRepository.java @@ -0,0 +1,127 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification; +import dev.lions.unionflow.server.api.enums.notification.StatutNotification; +import dev.lions.unionflow.server.api.enums.notification.TypeNotification; +import dev.lions.unionflow.server.entity.Notification; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Notification + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class NotificationRepository implements PanacheRepository { + + /** + * Trouve une notification par son UUID + * + * @param id UUID de la notification + * @return Notification ou Optional.empty() + */ + public Optional findNotificationById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * Trouve toutes les notifications d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications + */ + public List findByMembreId(UUID membreId) { + return find("membre.id = ?1 ORDER BY dateEnvoiPrevue DESC, dateCreation DESC", membreId).list(); + } + + /** + * Trouve toutes les notifications non lues d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications non lues + */ + public List findNonLuesByMembreId(UUID membreId) { + return find( + "membre.id = ?1 AND statut = ?2 ORDER BY priorite ASC, dateEnvoiPrevue DESC", + membreId, + StatutNotification.NON_LUE) + .list(); + } + + /** + * Trouve toutes les notifications d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des notifications + */ + public List findByOrganisationId(UUID organisationId) { + return find("organisation.id = ?1 ORDER BY dateEnvoiPrevue DESC, dateCreation DESC", organisationId) + .list(); + } + + /** + * Trouve les notifications par type + * + * @param type Type de notification + * @return Liste des notifications + */ + public List findByType(TypeNotification type) { + return find("typeNotification = ?1 ORDER BY dateEnvoiPrevue DESC", type).list(); + } + + /** + * Trouve les notifications par statut + * + * @param statut Statut de la notification + * @return Liste des notifications + */ + public List findByStatut(StatutNotification statut) { + return find("statut = ?1 ORDER BY dateEnvoiPrevue DESC", statut).list(); + } + + /** + * Trouve les notifications par priorité + * + * @param priorite Priorité de la notification + * @return Liste des notifications + */ + public List findByPriorite(PrioriteNotification priorite) { + return find("priorite = ?1 ORDER BY dateEnvoiPrevue DESC", priorite).list(); + } + + /** + * Trouve les notifications en attente d'envoi + * + * @return Liste des notifications en attente + */ + public List findEnAttenteEnvoi() { + LocalDateTime maintenant = LocalDateTime.now(); + return find( + "statut IN (?1, ?2) AND dateEnvoiPrevue <= ?3 ORDER BY priorite DESC, dateEnvoiPrevue ASC", + StatutNotification.EN_ATTENTE, + StatutNotification.PROGRAMMEE, + maintenant) + .list(); + } + + /** + * Trouve les notifications échouées pouvant être retentées + * + * @return Liste des notifications échouées + */ + public List findEchoueesRetentables() { + return find( + "statut IN (?1, ?2) AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateEnvoiPrevue ASC", + StatutNotification.ECHEC_ENVOI, + StatutNotification.ERREUR_TECHNIQUE) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java new file mode 100644 index 0000000..e935553 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/OrganisationRepository.java @@ -0,0 +1,424 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Organisation; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Organisation avec UUID + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class OrganisationRepository extends BaseRepository { + + public OrganisationRepository() { + super(Organisation.class); + } + + /** + * Trouve une organisation par son email + * + * @param email l'email de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByEmail(String email) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.email = :email", Organisation.class); + query.setParameter("email", email); + return query.getResultStream().findFirst(); + } + + /** + * Trouve une organisation par son nom + * + * @param nom le nom de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByNom(String nom) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.nom = :nom", Organisation.class); + query.setParameter("nom", nom); + return query.getResultStream().findFirst(); + } + + /** + * Trouve une organisation par son numéro d'enregistrement + * + * @param numeroEnregistrement le numéro d'enregistrement officiel + * @return Optional contenant l'organisation si trouvée + */ + public Optional findByNumeroEnregistrement(String numeroEnregistrement) { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.numeroEnregistrement = :numeroEnregistrement", + Organisation.class); + query.setParameter("numeroEnregistrement", numeroEnregistrement); + return query.getResultStream().findFirst(); + } + + /** + * Trouve toutes les organisations actives + * + * @return liste des organisations actives + */ + public List findAllActives() { + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true", + Organisation.class); + return query.getResultList(); + } + + /** + * Trouve toutes les organisations actives avec pagination + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations actives + */ + public List findAllActives(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true" + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte le nombre d'organisations actives + * + * @return nombre d'organisations actives + */ + public long countActives() { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true", + Long.class); + return query.getSingleResult(); + } + + /** + * Trouve les organisations par statut + * + * @param statut le statut recherché + * @param page pagination + * @param sort tri + * @return liste paginée des organisations avec le statut spécifié + */ + public List findByStatut(String statut, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.statut = :statut" + orderBy, + Organisation.class); + query.setParameter("statut", statut); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations par type + * + * @param typeOrganisation le type d'organisation + * @param page pagination + * @param sort tri + * @return liste paginée des organisations du type spécifié + */ + public List findByType(String typeOrganisation, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation" + orderBy, + Organisation.class); + query.setParameter("typeOrganisation", typeOrganisation); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations par ville + * + * @param ville la ville + * @param page pagination + * @param sort tri + * @return liste paginée des organisations de la ville spécifiée + */ + public List findByVille(String ville, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.ville = :ville" + orderBy, + Organisation.class); + query.setParameter("ville", ville); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations par pays + * + * @param pays le pays + * @param page pagination + * @param sort tri + * @return liste paginée des organisations du pays spécifié + */ + public List findByPays(String pays, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.pays = :pays" + orderBy, + Organisation.class); + query.setParameter("pays", pays); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations par région + * + * @param region la région + * @param page pagination + * @param sort tri + * @return liste paginée des organisations de la région spécifiée + */ + public List findByRegion(String region, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.region = :region" + orderBy, + Organisation.class); + query.setParameter("region", region); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations filles d'une organisation parente + * + * @param organisationParenteId l'UUID de l'organisation parente + * @param page pagination + * @param sort tri + * @return liste paginée des organisations filles + */ + public List findByOrganisationParente( + UUID organisationParenteId, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationParenteId = :organisationParenteId" + + orderBy, + Organisation.class); + query.setParameter("organisationParenteId", organisationParenteId); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations racines (sans parent) + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations racines + */ + public List findOrganisationsRacines(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationParenteId IS NULL" + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche d'organisations par nom ou nom court + * + * @param recherche terme de recherche + * @param page pagination + * @param sort tri + * @return liste paginée des organisations correspondantes + */ + public List findByNomOrNomCourt(String recherche, Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE LOWER(o.nom) LIKE LOWER(:recherche) OR LOWER(o.nomCourt) LIKE LOWER(:recherche)" + + orderBy, + Organisation.class); + query.setParameter("recherche", "%" + recherche + "%"); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Recherche avancée d'organisations + * + * @param nom nom (optionnel) + * @param typeOrganisation type (optionnel) + * @param statut statut (optionnel) + * @param ville ville (optionnel) + * @param region région (optionnel) + * @param pays pays (optionnel) + * @param page pagination + * @return liste filtrée des organisations + */ + public List rechercheAvancee( + String nom, + String typeOrganisation, + String statut, + String ville, + String region, + String pays, + Page page) { + StringBuilder queryBuilder = new StringBuilder("SELECT o FROM Organisation o WHERE 1=1"); + Map parameters = new HashMap<>(); + + if (nom != null && !nom.isEmpty()) { + queryBuilder.append(" AND (LOWER(o.nom) LIKE LOWER(:nom) OR LOWER(o.nomCourt) LIKE LOWER(:nom))"); + parameters.put("nom", "%" + nom.toLowerCase() + "%"); + } + + if (typeOrganisation != null && !typeOrganisation.isEmpty()) { + queryBuilder.append(" AND o.typeOrganisation = :typeOrganisation"); + parameters.put("typeOrganisation", typeOrganisation); + } + + if (statut != null && !statut.isEmpty()) { + queryBuilder.append(" AND o.statut = :statut"); + parameters.put("statut", statut); + } + + if (ville != null && !ville.isEmpty()) { + queryBuilder.append(" AND LOWER(o.ville) LIKE LOWER(:ville)"); + parameters.put("ville", "%" + ville.toLowerCase() + "%"); + } + + if (region != null && !region.isEmpty()) { + queryBuilder.append(" AND LOWER(o.region) LIKE LOWER(:region)"); + parameters.put("region", "%" + region.toLowerCase() + "%"); + } + + if (pays != null && !pays.isEmpty()) { + queryBuilder.append(" AND LOWER(o.pays) LIKE LOWER(:pays)"); + parameters.put("pays", "%" + pays.toLowerCase() + "%"); + } + + queryBuilder.append(" ORDER BY o.nom ASC"); + + TypedQuery query = entityManager.createQuery( + queryBuilder.toString(), Organisation.class); + for (Map.Entry param : parameters.entrySet()) { + query.setParameter(param.getKey(), param.getValue()); + } + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte les nouvelles organisations depuis une date donnée + * + * @param depuis date de référence + * @return nombre de nouvelles organisations + */ + public long countNouvellesOrganisations(LocalDate depuis) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.dateCreation >= :depuis", Long.class); + query.setParameter("depuis", depuis.atStartOfDay()); + return query.getSingleResult(); + } + + /** + * Trouve les organisations publiques (visibles dans l'annuaire) + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations publiques + */ + public List findOrganisationsPubliques(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.organisationPublique = true AND o.statut = 'ACTIVE' AND o.actif = true" + + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Trouve les organisations acceptant de nouveaux membres + * + * @param page pagination + * @param sort tri + * @return liste paginée des organisations acceptant de nouveaux membres + */ + public List findOrganisationsOuvertes(Page page, Sort sort) { + String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : ""; + TypedQuery query = entityManager.createQuery( + "SELECT o FROM Organisation o WHERE o.accepteNouveauxMembres = true AND o.statut = 'ACTIVE' AND o.actif = true" + + orderBy, + Organisation.class); + query.setFirstResult(page.index * page.size); + query.setMaxResults(page.size); + return query.getResultList(); + } + + /** + * Compte les organisations par statut + * + * @param statut le statut + * @return nombre d'organisations avec ce statut + */ + public long countByStatut(String statut) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.statut = :statut", Long.class); + query.setParameter("statut", statut); + return query.getSingleResult(); + } + + /** + * Compte les organisations par type + * + * @param typeOrganisation le type d'organisation + * @return nombre d'organisations de ce type + */ + public long countByType(String typeOrganisation) { + TypedQuery query = entityManager.createQuery( + "SELECT COUNT(o) FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation", + Long.class); + query.setParameter("typeOrganisation", typeOrganisation); + return query.getSingleResult(); + } + + /** Construit la clause ORDER BY à partir d'un Sort */ + private String buildOrderBy(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "o.id"; + } + StringBuilder orderBy = new StringBuilder(); + for (int i = 0; i < sort.getColumns().size(); i++) { + if (i > 0) { + orderBy.append(", "); + } + Sort.Column column = sort.getColumns().get(i); + orderBy.append("o.").append(column.getName()); + if (column.getDirection() == Sort.Direction.Descending) { + orderBy.append(" DESC"); + } else { + orderBy.append(" ASC"); + } + } + return orderBy.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/PaiementRepository.java b/src/main/java/dev/lions/unionflow/server/repository/PaiementRepository.java new file mode 100644 index 0000000..a6f2af3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/PaiementRepository.java @@ -0,0 +1,110 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement; +import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement; +import dev.lions.unionflow.server.entity.Paiement; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Repository pour l'entité Paiement + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class PaiementRepository implements PanacheRepository { + + /** + * Trouve un paiement par son UUID + * + * @param id UUID du paiement + * @return Paiement ou Optional.empty() + */ + public Optional findPaiementById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un paiement par son numéro de référence + * + * @param numeroReference Numéro de référence + * @return Paiement ou Optional.empty() + */ + public Optional findByNumeroReference(String numeroReference) { + return find("numeroReference", numeroReference).firstResultOptional(); + } + + /** + * Trouve tous les paiements d'un membre + * + * @param membreId ID du membre + * @return Liste des paiements + */ + public List findByMembreId(UUID membreId) { + return find("membre.id = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), membreId) + .list(); + } + + /** + * Trouve les paiements par statut + * + * @param statut Statut du paiement + * @return Liste des paiements + */ + public List findByStatut(StatutPaiement statut) { + return find("statutPaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), statut) + .list(); + } + + /** + * Trouve les paiements par méthode + * + * @param methode Méthode de paiement + * @return Liste des paiements + */ + public List findByMethode(MethodePaiement methode) { + return find("methodePaiement = ?1 AND actif = true", Sort.by("datePaiement", Sort.Direction.Descending), methode) + .list(); + } + + /** + * Trouve les paiements validés dans une période + * + * @param dateDebut Date de début + * @param dateFin Date de fin + * @return Liste des paiements + */ + public List findValidesParPeriode(LocalDateTime dateDebut, LocalDateTime dateFin) { + return find( + "statutPaiement = ?1 AND dateValidation >= ?2 AND dateValidation <= ?3 AND actif = true", + Sort.by("dateValidation", Sort.Direction.Descending), + StatutPaiement.VALIDE, + dateDebut, + dateFin) + .list(); + } + + /** + * Calcule le montant total des paiements validés dans une période + * + * @param dateDebut Date de début + * @param dateFin Date de fin + * @return Montant total + */ + public BigDecimal calculerMontantTotalValides(LocalDateTime dateDebut, LocalDateTime dateFin) { + List paiements = findValidesParPeriode(dateDebut, dateFin); + return paiements.stream() + .map(Paiement::getMontant) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java b/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java new file mode 100644 index 0000000..bf7aaf7 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/PermissionRepository.java @@ -0,0 +1,87 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.Permission; +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * 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 UUID + * + * @param id UUID de la permission + * @return Permission ou Optional.empty() + */ + public Optional findPermissionById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * 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", Sort.by("module", Sort.Direction.Ascending) + .and("ressource", Sort.Direction.Ascending) + .and("action", Sort.Direction.Ascending)).list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/PieceJointeRepository.java b/src/main/java/dev/lions/unionflow/server/repository/PieceJointeRepository.java new file mode 100644 index 0000000..db165e0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/PieceJointeRepository.java @@ -0,0 +1,100 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.PieceJointe; +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é PieceJointe + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class PieceJointeRepository implements PanacheRepository { + + /** + * Trouve une pièce jointe par son UUID + * + * @param id UUID de la pièce jointe + * @return Pièce jointe ou Optional.empty() + */ + public Optional findPieceJointeById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * Trouve toutes les pièces jointes d'un document + * + * @param documentId ID du document + * @return Liste des pièces jointes + */ + public List findByDocumentId(UUID documentId) { + return find("document.id = ?1 ORDER BY ordre ASC", documentId).list(); + } + + /** + * Trouve toutes les pièces jointes d'un membre + * + * @param membreId ID du membre + * @return Liste des pièces jointes + */ + public List findByMembreId(UUID membreId) { + return find("membre.id = ?1 ORDER BY ordre ASC", membreId).list(); + } + + /** + * Trouve toutes les pièces jointes d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des pièces jointes + */ + public List findByOrganisationId(UUID organisationId) { + return find("organisation.id = ?1 ORDER BY ordre ASC", organisationId).list(); + } + + /** + * Trouve toutes les pièces jointes d'une cotisation + * + * @param cotisationId ID de la cotisation + * @return Liste des pièces jointes + */ + public List findByCotisationId(UUID cotisationId) { + return find("cotisation.id = ?1 ORDER BY ordre ASC", cotisationId).list(); + } + + /** + * Trouve toutes les pièces jointes d'une adhésion + * + * @param adhesionId ID de l'adhésion + * @return Liste des pièces jointes + */ + public List findByAdhesionId(UUID adhesionId) { + return find("adhesion.id = ?1 ORDER BY ordre ASC", adhesionId).list(); + } + + /** + * Trouve toutes les pièces jointes d'une demande d'aide + * + * @param demandeAideId ID de la demande d'aide + * @return Liste des pièces jointes + */ + public List findByDemandeAideId(UUID demandeAideId) { + return find("demandeAide.id = ?1 ORDER BY ordre ASC", demandeAideId).list(); + } + + /** + * Trouve toutes les pièces jointes d'une transaction Wave + * + * @param transactionWaveId ID de la transaction Wave + * @return Liste des pièces jointes + */ + public List findByTransactionWaveId(UUID transactionWaveId) { + return find("transactionWave.id = ?1 ORDER BY ordre ASC", transactionWaveId).list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java b/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java new file mode 100644 index 0000000..7780d2b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/RolePermissionRepository.java @@ -0,0 +1,61 @@ +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.Optional; +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 une association rôle-permission par son UUID + * + * @param id UUID de l'association + * @return Association ou Optional.empty() + */ + public Optional findRolePermissionById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * 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/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java b/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java new file mode 100644 index 0000000..2102a0c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/RoleRepository.java @@ -0,0 +1,83 @@ +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 io.quarkus.panache.common.Sort; +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 UUID + * + * @param id UUID du rôle + * @return Rôle ou Optional.empty() + */ + public Optional findRoleById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * 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", Sort.by("niveauHierarchique", Sort.Direction.Ascending), TypeRole.SYSTEME) + .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", Sort.by("niveauHierarchique", Sort.Direction.Ascending), organisationId) + .list(); + } + + /** + * Trouve tous les rôles actifs + * + * @return Liste des rôles actifs + */ + public List findAllActifs() { + return find("actif = true", Sort.by("niveauHierarchique", Sort.Direction.Ascending)).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", Sort.by("niveauHierarchique", Sort.Direction.Ascending), typeRole) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/TemplateNotificationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/TemplateNotificationRepository.java new file mode 100644 index 0000000..b98da10 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/TemplateNotificationRepository.java @@ -0,0 +1,59 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.TemplateNotification; +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é TemplateNotification + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class TemplateNotificationRepository implements PanacheRepository { + + /** + * Trouve un template par son UUID + * + * @param id UUID du template + * @return Template ou Optional.empty() + */ + public Optional findTemplateNotificationById(UUID id) { + return find("id = ?1 AND actif = true", id).firstResultOptional(); + } + + /** + * Trouve un template par son code + * + * @param code Code du template + * @return Template ou Optional.empty() + */ + public Optional findByCode(String code) { + return find("code = ?1 AND actif = true", code).firstResultOptional(); + } + + /** + * Trouve tous les templates actifs + * + * @return Liste des templates actifs + */ + public List findAllActifs() { + return find("actif = true ORDER BY code ASC").list(); + } + + /** + * Trouve les templates par langue + * + * @param langue Code langue (ex: fr, en) + * @return Liste des templates + */ + public List findByLangue(String langue) { + return find("langue = ?1 AND actif = true ORDER BY code ASC", langue).list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/TransactionWaveRepository.java b/src/main/java/dev/lions/unionflow/server/repository/TransactionWaveRepository.java new file mode 100644 index 0000000..1f5db53 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/TransactionWaveRepository.java @@ -0,0 +1,109 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave; +import dev.lions.unionflow.server.entity.TransactionWave; +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é TransactionWave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class TransactionWaveRepository implements PanacheRepository { + + /** + * Trouve une transaction par son UUID + * + * @param id UUID de la transaction + * @return Transaction ou Optional.empty() + */ + public Optional findTransactionWaveById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * Trouve une transaction par son identifiant Wave + * + * @param waveTransactionId Identifiant Wave + * @return Transaction ou Optional.empty() + */ + public Optional findByWaveTransactionId(String waveTransactionId) { + return find("waveTransactionId = ?1", waveTransactionId).firstResultOptional(); + } + + /** + * Trouve une transaction par son identifiant de requête + * + * @param waveRequestId Identifiant de requête + * @return Transaction ou Optional.empty() + */ + public Optional findByWaveRequestId(String waveRequestId) { + return find("waveRequestId = ?1", waveRequestId).firstResultOptional(); + } + + /** + * Trouve toutes les transactions d'un compte Wave + * + * @param compteWaveId ID du compte Wave + * @return Liste des transactions + */ + public List findByCompteWaveId(UUID compteWaveId) { + return find("compteWave.id = ?1 ORDER BY dateCreation DESC", compteWaveId).list(); + } + + /** + * Trouve les transactions par statut + * + * @param statut Statut de la transaction + * @return Liste des transactions + */ + public List findByStatut(StatutTransactionWave statut) { + return find("statutTransaction = ?1 ORDER BY dateCreation DESC", statut).list(); + } + + /** + * Trouve les transactions par type + * + * @param type Type de transaction + * @return Liste des transactions + */ + public List findByType(TypeTransactionWave type) { + return find("typeTransaction = ?1 ORDER BY dateCreation DESC", type).list(); + } + + /** + * Trouve les transactions réussies dans une période + * + * @param compteWaveId ID du compte Wave + * @return Liste des transactions réussies + */ + public List findReussiesByCompteWave(UUID compteWaveId) { + return find( + "compteWave.id = ?1 AND statutTransaction = ?2 ORDER BY dateCreation DESC", + compteWaveId, + StatutTransactionWave.REUSSIE) + .list(); + } + + /** + * Trouve les transactions échouées pouvant être retentées + * + * @return Liste des transactions échouées + */ + public List findEchoueesRetentables() { + return find( + "statutTransaction IN (?1, ?2) AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateCreation ASC", + StatutTransactionWave.ECHOUE, + StatutTransactionWave.EXPIRED) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/repository/TypeOrganisationRepository.java b/src/main/java/dev/lions/unionflow/server/repository/TypeOrganisationRepository.java new file mode 100644 index 0000000..9b842e9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/TypeOrganisationRepository.java @@ -0,0 +1,43 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.entity.TypeOrganisationEntity; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.TypedQuery; +import java.util.List; +import java.util.Optional; + +/** + * Repository pour l'entité {@link TypeOrganisationEntity}. + * + *

Permet de gérer le catalogue des types d'organisations. + */ +@ApplicationScoped +public class TypeOrganisationRepository extends BaseRepository { + + public TypeOrganisationRepository() { + super(TypeOrganisationEntity.class); + } + + /** Recherche un type par son code fonctionnel. */ + public Optional findByCode(String code) { + TypedQuery query = + entityManager.createQuery( + "SELECT t FROM TypeOrganisationEntity t WHERE UPPER(t.code) = UPPER(:code)", + TypeOrganisationEntity.class); + query.setParameter("code", code); + return query.getResultStream().findFirst(); + } + + /** Liste les types actifs, triés par ordreAffichage puis libellé. */ + public List listActifsOrdennes() { + return entityManager + .createQuery( + "SELECT t FROM TypeOrganisationEntity t " + + "WHERE t.actif = true " + + "ORDER BY COALESCE(t.ordreAffichage, 9999), t.libelle", + TypeOrganisationEntity.class) + .getResultList(); + } +} + + diff --git a/src/main/java/dev/lions/unionflow/server/repository/WebhookWaveRepository.java b/src/main/java/dev/lions/unionflow/server/repository/WebhookWaveRepository.java new file mode 100644 index 0000000..1164861 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/WebhookWaveRepository.java @@ -0,0 +1,104 @@ +package dev.lions.unionflow.server.repository; + +import dev.lions.unionflow.server.api.enums.wave.StatutWebhook; +import dev.lions.unionflow.server.api.enums.wave.TypeEvenementWebhook; +import dev.lions.unionflow.server.entity.WebhookWave; +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é WebhookWave + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class WebhookWaveRepository implements PanacheRepository { + + /** + * Trouve un webhook Wave par son UUID + * + * @param id UUID du webhook + * @return Webhook ou Optional.empty() + */ + public Optional findWebhookWaveById(UUID id) { + return find("id = ?1", id).firstResultOptional(); + } + + /** + * Trouve un webhook par son identifiant d'événement Wave + * + * @param waveEventId Identifiant d'événement + * @return Webhook ou Optional.empty() + */ + public Optional findByWaveEventId(String waveEventId) { + return find("waveEventId = ?1", waveEventId).firstResultOptional(); + } + + /** + * Trouve tous les webhooks d'une transaction + * + * @param transactionWaveId ID de la transaction + * @return Liste des webhooks + */ + public List findByTransactionWaveId(UUID transactionWaveId) { + return find("transactionWave.id = ?1 ORDER BY dateReception DESC", transactionWaveId).list(); + } + + /** + * Trouve tous les webhooks d'un paiement + * + * @param paiementId ID du paiement + * @return Liste des webhooks + */ + public List findByPaiementId(UUID paiementId) { + return find("paiement.id = ?1 ORDER BY dateReception DESC", paiementId).list(); + } + + /** + * Trouve les webhooks par statut + * + * @param statut Statut de traitement + * @return Liste des webhooks + */ + public List findByStatut(StatutWebhook statut) { + return find("statutTraitement = ?1 ORDER BY dateReception DESC", statut).list(); + } + + /** + * Trouve les webhooks par type d'événement + * + * @param type Type d'événement + * @return Liste des webhooks + */ + public List findByType(TypeEvenementWebhook type) { + return find("typeEvenement = ?1 ORDER BY dateReception DESC", type).list(); + } + + /** + * Trouve les webhooks en attente de traitement + * + * @return Liste des webhooks en attente + */ + public List findEnAttente() { + return find("statutTraitement = ?1 ORDER BY dateReception ASC", StatutWebhook.EN_ATTENTE) + .list(); + } + + /** + * Trouve les webhooks échoués pouvant être retentés + * + * @return Liste des webhooks échoués + */ + public List findEchouesRetentables() { + return find( + "statutTraitement = ?1 AND (nombreTentatives IS NULL OR nombreTentatives < 5) ORDER BY dateReception ASC", + StatutWebhook.ECHOUE) + .list(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/AdhesionResource.java b/src/main/java/dev/lions/unionflow/server/resource/AdhesionResource.java new file mode 100644 index 0000000..01cc320 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/AdhesionResource.java @@ -0,0 +1,705 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO; +import dev.lions.unionflow.server.service.AdhesionService; +import jakarta.inject.Inject; +import jakarta.annotation.security.RolesAllowed; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +/** + * Resource REST pour la gestion des adhésions + * Expose les endpoints API pour les opérations CRUD sur les adhésions + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@Path("/api/adhesions") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Adhésions", description = "Gestion des demandes d'adhésion des membres") +@Slf4j +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class AdhesionResource { + + @Inject AdhesionService adhesionService; + + /** Récupère toutes les adhésions avec pagination */ + @GET + @Operation( + summary = "Lister toutes les adhésions", + description = "Récupère la liste paginée de toutes les adhésions") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des adhésions récupérée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = AdhesionDTO.class))), + @APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAllAdhesions( + @Parameter(description = "Numéro de page (0-based)", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/adhesions - page: {}, size: {}", page, size); + + List adhesions = adhesionService.getAllAdhesions(page, size); + + log.info("Récupération réussie de {} adhésions", adhesions.size()); + return Response.ok(adhesions).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des adhésions", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération des adhésions", "message", e.getMessage())) + .build(); + } + } + + /** Récupère une adhésion par son ID */ + @GET + @Path("/{id}") + @Operation( + summary = "Récupérer une adhésion par ID", + description = "Récupère les détails d'une adhésion spécifique") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Adhésion trouvée", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = AdhesionDTO.class))), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionById( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id) { + + try { + log.info("GET /api/adhesions/{}", id); + + AdhesionDTO adhesion = adhesionService.getAdhesionById(id); + + log.info("Adhésion récupérée avec succès - ID: {}", id); + return Response.ok(adhesion).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération de l'adhésion - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Récupère une adhésion par son numéro de référence */ + @GET + @Path("/reference/{numeroReference}") + @Operation( + summary = "Récupérer une adhésion par référence", + description = "Récupère une adhésion par son numéro de référence unique") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Adhésion trouvée"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionByReference( + @Parameter(description = "Numéro de référence de l'adhésion", required = true) + @PathParam("numeroReference") + @NotNull + String numeroReference) { + + try { + log.info("GET /api/adhesions/reference/{}", numeroReference); + + AdhesionDTO adhesion = adhesionService.getAdhesionByReference(numeroReference); + + log.info("Adhésion récupérée avec succès - Référence: {}", numeroReference); + return Response.ok(adhesion).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée - Référence: {}", numeroReference); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "reference", numeroReference)) + .build(); + } catch (Exception e) { + log.error( + "Erreur lors de la récupération de l'adhésion - Référence: " + numeroReference, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Crée une nouvelle adhésion */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Operation( + summary = "Créer une nouvelle adhésion", + description = "Crée une nouvelle demande d'adhésion pour un membre") + @APIResponses({ + @APIResponse( + responseCode = "201", + description = "Adhésion créée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = AdhesionDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Membre ou organisation non trouvé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response createAdhesion( + @Parameter(description = "Données de l'adhésion à créer", required = true) @Valid + AdhesionDTO adhesionDTO) { + + try { + log.info( + "POST /api/adhesions - Création adhésion pour membre: {} et organisation: {}", + adhesionDTO.getMembreId(), + adhesionDTO.getOrganisationId()); + + AdhesionDTO nouvelleAdhesion = adhesionService.createAdhesion(adhesionDTO); + + log.info( + "Adhésion créée avec succès - ID: {}, Référence: {}", + nouvelleAdhesion.getId(), + nouvelleAdhesion.getNumeroReference()); + + return Response.status(Response.Status.CREATED).entity(nouvelleAdhesion).build(); + + } catch (NotFoundException e) { + log.warn("Membre ou organisation non trouvé lors de la création d'adhésion"); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Membre ou organisation non trouvé", "message", e.getMessage())) + .build(); + } catch (IllegalArgumentException e) { + log.warn("Données invalides pour la création d'adhésion: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Données invalides", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la création de l'adhésion", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la création de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Met à jour une adhésion existante */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}") + @Operation( + summary = "Mettre à jour une adhésion", + description = "Met à jour les données d'une adhésion existante") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Adhésion mise à jour avec succès"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response updateAdhesion( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id, + @Parameter(description = "Nouvelles données de l'adhésion", required = true) @Valid + AdhesionDTO adhesionDTO) { + + try { + log.info("PUT /api/adhesions/{}", id); + + AdhesionDTO adhesionMiseAJour = adhesionService.updateAdhesion(id, adhesionDTO); + + log.info("Adhésion mise à jour avec succès - ID: {}", id); + return Response.ok(adhesionMiseAJour).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée pour mise à jour - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (IllegalArgumentException e) { + log.warn( + "Données invalides pour la mise à jour d'adhésion - ID: {}, Erreur: {}", id, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Données invalides", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la mise à jour de l'adhésion - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la mise à jour de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Supprime une adhésion */ + @DELETE + @RolesAllowed({"ADMIN"}) + @Path("/{id}") + @Operation( + summary = "Supprimer une adhésion", + description = "Supprime (annule) une adhésion") + @APIResponses({ + @APIResponse(responseCode = "204", description = "Adhésion supprimée avec succès"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse( + responseCode = "409", + description = "Impossible de supprimer une adhésion payée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response deleteAdhesion( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id) { + + try { + log.info("DELETE /api/adhesions/{}", id); + + adhesionService.deleteAdhesion(id); + + log.info("Adhésion supprimée avec succès - ID: {}", id); + return Response.noContent().build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée pour suppression - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (IllegalStateException e) { + log.warn("Impossible de supprimer l'adhésion - ID: {}, Raison: {}", id, e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", "Impossible de supprimer l'adhésion", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la suppression de l'adhésion - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la suppression de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Approuve une adhésion */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/approuver") + @Operation( + summary = "Approuver une adhésion", + description = "Approuve une demande d'adhésion en attente") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Adhésion approuvée avec succès"), + @APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être approuvée"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response approuverAdhesion( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id, + @Parameter(description = "Nom de l'utilisateur qui approuve") + @QueryParam("approuvePar") + String approuvePar) { + + try { + log.info("POST /api/adhesions/{}/approuver", id); + + AdhesionDTO adhesion = adhesionService.approuverAdhesion(id, approuvePar); + + log.info("Adhésion approuvée avec succès - ID: {}", id); + return Response.ok(adhesion).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée pour approbation - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (IllegalStateException e) { + log.warn("Impossible d'approuver l'adhésion - ID: {}, Raison: {}", id, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Impossible d'approuver l'adhésion", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de l'approbation de l'adhésion - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de l'approbation de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Rejette une adhésion */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/rejeter") + @Operation( + summary = "Rejeter une adhésion", + description = "Rejette une demande d'adhésion en attente") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Adhésion rejetée avec succès"), + @APIResponse(responseCode = "400", description = "L'adhésion ne peut pas être rejetée"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response rejeterAdhesion( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id, + @Parameter(description = "Motif du rejet", required = true) @QueryParam("motifRejet") + @NotNull + String motifRejet) { + + try { + log.info("POST /api/adhesions/{}/rejeter", id); + + AdhesionDTO adhesion = adhesionService.rejeterAdhesion(id, motifRejet); + + log.info("Adhésion rejetée avec succès - ID: {}", id); + return Response.ok(adhesion).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée pour rejet - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (IllegalStateException e) { + log.warn("Impossible de rejeter l'adhésion - ID: {}, Raison: {}", id, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Impossible de rejeter l'adhésion", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors du rejet de l'adhésion - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors du rejet de l'adhésion", "message", e.getMessage())) + .build(); + } + } + + /** Enregistre un paiement pour une adhésion */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/paiement") + @Operation( + summary = "Enregistrer un paiement", + description = "Enregistre un paiement pour une adhésion approuvée") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Paiement enregistré avec succès"), + @APIResponse(responseCode = "400", description = "L'adhésion ne peut pas recevoir de paiement"), + @APIResponse(responseCode = "404", description = "Adhésion non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response enregistrerPaiement( + @Parameter(description = "Identifiant de l'adhésion", required = true) + @PathParam("id") + @NotNull + UUID id, + @Parameter(description = "Montant payé", required = true) @QueryParam("montantPaye") + @NotNull + BigDecimal montantPaye, + @Parameter(description = "Méthode de paiement") @QueryParam("methodePaiement") + String methodePaiement, + @Parameter(description = "Référence du paiement") @QueryParam("referencePaiement") + String referencePaiement) { + + try { + log.info("POST /api/adhesions/{}/paiement", id); + + AdhesionDTO adhesion = + adhesionService.enregistrerPaiement(id, montantPaye, methodePaiement, referencePaiement); + + log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id); + return Response.ok(adhesion).build(); + + } catch (NotFoundException e) { + log.warn("Adhésion non trouvée pour paiement - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Adhésion non trouvée", "id", id)) + .build(); + } catch (IllegalStateException e) { + log.warn("Impossible d'enregistrer le paiement - ID: {}, Raison: {}", id, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + Map.of("error", "Impossible d'enregistrer le paiement", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de l'enregistrement du paiement - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de l'enregistrement du paiement", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les adhésions d'un membre */ + @GET + @Path("/membre/{membreId}") + @Operation( + summary = "Lister les adhésions d'un membre", + description = "Récupère toutes les adhésions d'un membre spécifique") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Liste des adhésions du membre"), + @APIResponse(responseCode = "404", description = "Membre non trouvé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionsByMembre( + @Parameter(description = "Identifiant du membre", required = true) + @PathParam("membreId") + @NotNull + UUID membreId, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/adhesions/membre/{} - page: {}, size: {}", membreId, page, size); + + List adhesions = adhesionService.getAdhesionsByMembre(membreId, page, size); + + log.info( + "Récupération réussie de {} adhésions pour le membre {}", adhesions.size(), membreId); + return Response.ok(adhesions).build(); + + } catch (NotFoundException e) { + log.warn("Membre non trouvé - ID: {}", membreId); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Membre non trouvé", "membreId", membreId)) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération des adhésions du membre - ID: " + membreId, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les adhésions d'une organisation */ + @GET + @Path("/organisation/{organisationId}") + @Operation( + summary = "Lister les adhésions d'une organisation", + description = "Récupère toutes les adhésions d'une organisation spécifique") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Liste des adhésions de l'organisation"), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionsByOrganisation( + @Parameter(description = "Identifiant de l'organisation", required = true) + @PathParam("organisationId") + @NotNull + UUID organisationId, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info( + "GET /api/adhesions/organisation/{} - page: {}, size: {}", organisationId, page, size); + + List adhesions = + adhesionService.getAdhesionsByOrganisation(organisationId, page, size); + + log.info( + "Récupération réussie de {} adhésions pour l'organisation {}", + adhesions.size(), + organisationId); + return Response.ok(adhesions).build(); + + } catch (NotFoundException e) { + log.warn("Organisation non trouvée - ID: {}", organisationId); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Organisation non trouvée", "organisationId", organisationId)) + .build(); + } catch (Exception e) { + log.error( + "Erreur lors de la récupération des adhésions de l'organisation - ID: " + organisationId, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les adhésions par statut */ + @GET + @Path("/statut/{statut}") + @Operation( + summary = "Lister les adhésions par statut", + description = "Récupère toutes les adhésions ayant un statut spécifique") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des adhésions avec le statut spécifié"), + @APIResponse(responseCode = "400", description = "Statut invalide"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionsByStatut( + @Parameter(description = "Statut des adhésions", required = true, example = "EN_ATTENTE") + @PathParam("statut") + @NotNull + String statut, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/adhesions/statut/{} - page: {}, size: {}", statut, page, size); + + List adhesions = adhesionService.getAdhesionsByStatut(statut, page, size); + + log.info("Récupération réussie de {} adhésions avec statut {}", adhesions.size(), statut); + return Response.ok(adhesions).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des adhésions par statut - Statut: " + statut, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la récupération des adhésions", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les adhésions en attente */ + @GET + @Path("/en-attente") + @Operation( + summary = "Lister les adhésions en attente", + description = "Récupère toutes les adhésions en attente d'approbation") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Liste des adhésions en attente"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAdhesionsEnAttente( + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/adhesions/en-attente - page: {}, size: {}", page, size); + + List adhesions = adhesionService.getAdhesionsEnAttente(page, size); + + log.info("Récupération réussie de {} adhésions en attente", adhesions.size()); + return Response.ok(adhesions).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des adhésions en attente", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération des adhésions en attente", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les statistiques des adhésions */ + @GET + @Path("/stats") + @Operation( + summary = "Statistiques des adhésions", + description = "Récupère les statistiques globales des adhésions") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getStatistiquesAdhesions() { + try { + log.info("GET /api/adhesions/stats"); + + Map statistiques = adhesionService.getStatistiquesAdhesions(); + + log.info("Statistiques récupérées avec succès"); + return Response.ok(statistiques).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des statistiques", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération des statistiques", "message", e.getMessage())) + .build(); + } + } +} + + + diff --git a/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java new file mode 100644 index 0000000..84ae5f9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/AnalyticsResource.java @@ -0,0 +1,345 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO; +import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetDTO; +import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; +import dev.lions.unionflow.server.service.AnalyticsService; +import dev.lions.unionflow.server.service.KPICalculatorService; +import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** + * Ressource REST pour les analytics et métriques UnionFlow + * + *

Cette ressource expose les APIs pour accéder aux données analytics, KPI, tendances et widgets + * de tableau de bord. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@Path("/api/v1/analytics") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Authenticated +@Tag(name = "Analytics", description = "APIs pour les analytics et métriques") +public class AnalyticsResource { + + private static final Logger log = Logger.getLogger(AnalyticsResource.class); + + @Inject AnalyticsService analyticsService; + + @Inject KPICalculatorService kpiCalculatorService; + + /** Calcule une métrique analytics pour une période donnée */ + @GET + @Path("/metriques/{typeMetrique}") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Calculer une métrique analytics", + description = "Calcule une métrique spécifique pour une période et organisation données") + @APIResponse(responseCode = "200", description = "Métrique calculée avec succès") + @APIResponse(responseCode = "400", description = "Paramètres invalides") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response calculerMetrique( + @Parameter(description = "Type de métrique à calculer", required = true) + @PathParam("typeMetrique") + TypeMetrique typeMetrique, + @Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull + PeriodeAnalyse periodeAnalyse, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId) { + + try { + log.infof( + "Calcul de la métrique %s pour la période %s et l'organisation %s", + typeMetrique, periodeAnalyse, organisationId); + + AnalyticsDataDTO result = + analyticsService.calculerMetrique(typeMetrique, periodeAnalyse, organisationId); + + return Response.ok(result).build(); + + } catch (Exception e) { + log.errorf(e, "Erreur lors du calcul de la métrique %s: %s", typeMetrique, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors du calcul de la métrique", "message", e.getMessage())) + .build(); + } + } + + /** Calcule les tendances d'un KPI sur une période */ + @GET + @Path("/tendances/{typeMetrique}") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Calculer la tendance d'un KPI", + description = "Calcule l'évolution et les tendances d'un KPI sur une période donnée") + @APIResponse(responseCode = "200", description = "Tendance calculée avec succès") + @APIResponse(responseCode = "400", description = "Paramètres invalides") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response calculerTendanceKPI( + @Parameter(description = "Type de métrique pour la tendance", required = true) + @PathParam("typeMetrique") + TypeMetrique typeMetrique, + @Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull + PeriodeAnalyse periodeAnalyse, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId) { + + try { + log.infof( + "Calcul de la tendance KPI %s pour la période %s et l'organisation %s", + typeMetrique, periodeAnalyse, organisationId); + + KPITrendDTO result = + analyticsService.calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId); + + return Response.ok(result).build(); + + } catch (Exception e) { + log.errorf( + e, "Erreur lors du calcul de la tendance KPI %s: %s", typeMetrique, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors du calcul de la tendance", "message", e.getMessage())) + .build(); + } + } + + /** Obtient tous les KPI pour une organisation */ + @GET + @Path("/kpis") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Obtenir tous les KPI", + description = "Récupère tous les KPI calculés pour une organisation et période données") + @APIResponse(responseCode = "200", description = "KPI récupérés avec succès") + @APIResponse(responseCode = "400", description = "Paramètres invalides") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response obtenirTousLesKPI( + @Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull + PeriodeAnalyse periodeAnalyse, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId) { + + try { + log.infof( + "Récupération de tous les KPI pour la période %s et l'organisation %s", + periodeAnalyse, organisationId); + + Map kpis = + kpiCalculatorService.calculerTousLesKPI( + organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin()); + + return Response.ok(kpis).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des KPI: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of("error", "Erreur lors de la récupération des KPI", "message", e.getMessage())) + .build(); + } + } + + /** Calcule le KPI de performance globale */ + @GET + @Path("/performance-globale") + @RolesAllowed({"ADMIN", "MANAGER"}) + @Operation( + summary = "Calculer la performance globale", + description = "Calcule le score de performance globale de l'organisation") + @APIResponse(responseCode = "200", description = "Performance globale calculée avec succès") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response calculerPerformanceGlobale( + @Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull + PeriodeAnalyse periodeAnalyse, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId) { + + try { + log.infof( + "Calcul de la performance globale pour la période %s et l'organisation %s", + periodeAnalyse, organisationId); + + BigDecimal performanceGlobale = + kpiCalculatorService.calculerKPIPerformanceGlobale( + organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin()); + + return Response.ok( + Map.of( + "performanceGlobale", performanceGlobale, + "periode", periodeAnalyse, + "organisationId", organisationId, + "dateCalcul", java.time.LocalDateTime.now())) + .build(); + + } catch (Exception e) { + log.error("Erreur lors du calcul de la performance globale: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors du calcul de la performance globale", + "message", + e.getMessage())) + .build(); + } + } + + /** Obtient les évolutions des KPI par rapport à la période précédente */ + @GET + @Path("/evolutions") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Obtenir les évolutions des KPI", + description = "Récupère les évolutions des KPI par rapport à la période précédente") + @APIResponse(responseCode = "200", description = "Évolutions récupérées avec succès") + @APIResponse(responseCode = "400", description = "Paramètres invalides") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response obtenirEvolutionsKPI( + @Parameter(description = "Période d'analyse", required = true) @QueryParam("periode") @NotNull + PeriodeAnalyse periodeAnalyse, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId) { + + try { + log.infof( + "Récupération des évolutions KPI pour la période %s et l'organisation %s", + periodeAnalyse, organisationId); + + Map evolutions = + kpiCalculatorService.calculerEvolutionsKPI( + organisationId, periodeAnalyse.getDateDebut(), periodeAnalyse.getDateFin()); + + return Response.ok(evolutions).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des évolutions KPI: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des évolutions", + "message", + e.getMessage())) + .build(); + } + } + + /** Obtient les widgets du tableau de bord pour un utilisateur */ + @GET + @Path("/dashboard/widgets") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Obtenir les widgets du tableau de bord", + description = "Récupère tous les widgets configurés pour le tableau de bord de l'utilisateur") + @APIResponse(responseCode = "200", description = "Widgets récupérés avec succès") + @APIResponse(responseCode = "403", description = "Accès non autorisé") + public Response obtenirWidgetsTableauBord( + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("organisationId") + UUID organisationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("utilisateurId") + @NotNull + UUID utilisateurId) { + + try { + log.infof( + "Récupération des widgets du tableau de bord pour l'organisation %s et l'utilisateur %s", + organisationId, utilisateurId); + + List widgets = + analyticsService.obtenirMetriquesTableauBord(organisationId, utilisateurId); + + return Response.ok(widgets).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des widgets: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la récupération des widgets", "message", e.getMessage())) + .build(); + } + } + + /** Obtient les types de métriques disponibles */ + @GET + @Path("/types-metriques") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Obtenir les types de métriques disponibles", + description = "Récupère la liste de tous les types de métriques disponibles") + @APIResponse(responseCode = "200", description = "Types de métriques récupérés avec succès") + public Response obtenirTypesMetriques() { + try { + log.info("Récupération des types de métriques disponibles"); + + TypeMetrique[] typesMetriques = TypeMetrique.values(); + + return Response.ok(Map.of("typesMetriques", typesMetriques, "total", typesMetriques.length)) + .build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des types de métriques: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des types de métriques", + "message", + e.getMessage())) + .build(); + } + } + + /** Obtient les périodes d'analyse disponibles */ + @GET + @Path("/periodes-analyse") + @RolesAllowed({"ADMIN", "MANAGER", "MEMBER"}) + @Operation( + summary = "Obtenir les périodes d'analyse disponibles", + description = "Récupère la liste de toutes les périodes d'analyse disponibles") + @APIResponse(responseCode = "200", description = "Périodes d'analyse récupérées avec succès") + public Response obtenirPeriodesAnalyse() { + try { + log.info("Récupération des périodes d'analyse disponibles"); + + PeriodeAnalyse[] periodesAnalyse = PeriodeAnalyse.values(); + + return Response.ok( + Map.of("periodesAnalyse", periodesAnalyse, "total", periodesAnalyse.length)) + .build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des périodes d'analyse: {}", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des périodes d'analyse", + "message", + e.getMessage())) + .build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java b/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java new file mode 100644 index 0000000..aa792b0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/AuditResource.java @@ -0,0 +1,112 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO; +import dev.lions.unionflow.server.service.AuditService; +import jakarta.inject.Inject; +import jakarta.annotation.security.RolesAllowed; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.time.LocalDateTime; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +/** + * Resource REST pour la gestion des logs d'audit + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@Path("/api/audit") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Audit", description = "Gestion des logs d'audit") +@Slf4j +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class AuditResource { + + @Inject + AuditService auditService; + + @GET + @Operation(summary = "Liste tous les logs d'audit", description = "Récupère tous les logs avec pagination") + public Response listerTous( + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("50") int size, + @QueryParam("sortBy") @DefaultValue("dateHeure") String sortBy, + @QueryParam("sortOrder") @DefaultValue("desc") String sortOrder) { + + try { + Map result = auditService.listerTous(page, size, sortBy, sortOrder); + return Response.ok(result).build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération des logs d'audit", e); + return Response.serverError() + .entity(Map.of("error", "Erreur lors de la récupération des logs: " + e.getMessage())) + .build(); + } + } + + @POST + @Path("/rechercher") + @Operation(summary = "Recherche des logs avec filtres", description = "Recherche avancée avec filtres multiples") + public Response rechercher( + @QueryParam("dateDebut") String dateDebutStr, + @QueryParam("dateFin") String dateFinStr, + @QueryParam("typeAction") String typeAction, + @QueryParam("severite") String severite, + @QueryParam("utilisateur") String utilisateur, + @QueryParam("module") String module, + @QueryParam("ipAddress") String ipAddress, + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("50") int size) { + + try { + LocalDateTime dateDebut = dateDebutStr != null ? LocalDateTime.parse(dateDebutStr) : null; + LocalDateTime dateFin = dateFinStr != null ? LocalDateTime.parse(dateFinStr) : null; + + Map result = auditService.rechercher( + dateDebut, dateFin, typeAction, severite, utilisateur, module, ipAddress, page, size); + return Response.ok(result).build(); + } catch (Exception e) { + log.error("Erreur lors de la recherche des logs d'audit", e); + return Response.serverError() + .entity(Map.of("error", "Erreur lors de la recherche: " + e.getMessage())) + .build(); + } + } + + @POST + @Operation(summary = "Enregistre un nouveau log d'audit", description = "Crée une nouvelle entrée dans le journal d'audit") + public Response enregistrerLog(@Valid AuditLogDTO dto) { + try { + AuditLogDTO result = auditService.enregistrerLog(dto); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + log.error("Erreur lors de l'enregistrement du log d'audit", e); + return Response.serverError() + .entity(Map.of("error", "Erreur lors de l'enregistrement: " + e.getMessage())) + .build(); + } + } + + @GET + @Path("/statistiques") + @Operation(summary = "Récupère les statistiques d'audit", description = "Retourne les statistiques globales des logs") + public Response getStatistiques() { + try { + Map stats = auditService.getStatistiques(); + return Response.ok(stats).build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération des statistiques", e); + return Response.serverError() + .entity(Map.of("error", "Erreur lors de la récupération des statistiques: " + e.getMessage())) + .build(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java new file mode 100644 index 0000000..512267e --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java @@ -0,0 +1,278 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.comptabilite.*; +import dev.lions.unionflow.server.service.ComptabiliteService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion comptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/comptabilite") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class ComptabiliteResource { + + private static final Logger LOG = Logger.getLogger(ComptabiliteResource.class); + + @Inject ComptabiliteService comptabiliteService; + + // ======================================== + // COMPTES COMPTABLES + // ======================================== + + /** + * Crée un nouveau compte comptable + * + * @param compteDTO DTO du compte à créer + * @return Compte créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/comptes") + public Response creerCompteComptable(@Valid CompteComptableDTO compteDTO) { + try { + CompteComptableDTO result = comptabiliteService.creerCompteComptable(compteDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du compte comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du compte comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un compte comptable par son ID + * + * @param id ID du compte + * @return Compte comptable + */ + @GET + @Path("/comptes/{id}") + public Response trouverCompteParId(@PathParam("id") UUID id) { + try { + CompteComptableDTO result = comptabiliteService.trouverCompteParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Compte comptable non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du compte comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du compte comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Liste tous les comptes comptables actifs + * + * @return Liste des comptes + */ + @GET + @Path("/comptes") + public Response listerTousLesComptes() { + try { + List result = comptabiliteService.listerTousLesComptes(); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des comptes comptables"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des comptes comptables: " + e.getMessage())) + .build(); + } + } + + // ======================================== + // JOURNAUX COMPTABLES + // ======================================== + + /** + * Crée un nouveau journal comptable + * + * @param journalDTO DTO du journal à créer + * @return Journal créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/journaux") + public Response creerJournalComptable(@Valid JournalComptableDTO journalDTO) { + try { + JournalComptableDTO result = comptabiliteService.creerJournalComptable(journalDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du journal comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du journal comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un journal comptable par son ID + * + * @param id ID du journal + * @return Journal comptable + */ + @GET + @Path("/journaux/{id}") + public Response trouverJournalParId(@PathParam("id") UUID id) { + try { + JournalComptableDTO result = comptabiliteService.trouverJournalParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Journal comptable non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du journal comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du journal comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Liste tous les journaux comptables actifs + * + * @return Liste des journaux + */ + @GET + @Path("/journaux") + public Response listerTousLesJournaux() { + try { + List result = comptabiliteService.listerTousLesJournaux(); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des journaux comptables"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des journaux comptables: " + e.getMessage())) + .build(); + } + } + + // ======================================== + // ÉCRITURES COMPTABLES + // ======================================== + + /** + * Crée une nouvelle écriture comptable + * + * @param ecritureDTO DTO de l'écriture à créer + * @return Écriture créée + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/ecritures") + public Response creerEcritureComptable(@Valid EcritureComptableDTO ecritureDTO) { + try { + EcritureComptableDTO result = comptabiliteService.creerEcritureComptable(ecritureDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création de l'écriture comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création de l'écriture comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve une écriture comptable par son ID + * + * @param id ID de l'écriture + * @return Écriture comptable + */ + @GET + @Path("/ecritures/{id}") + public Response trouverEcritureParId(@PathParam("id") UUID id) { + try { + EcritureComptableDTO result = comptabiliteService.trouverEcritureParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Écriture comptable non trouvée")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche de l'écriture comptable"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche de l'écriture comptable: " + e.getMessage())) + .build(); + } + } + + /** + * Liste les écritures d'un journal + * + * @param journalId ID du journal + * @return Liste des écritures + */ + @GET + @Path("/ecritures/journal/{journalId}") + public Response listerEcrituresParJournal(@PathParam("journalId") UUID journalId) { + try { + List result = comptabiliteService.listerEcrituresParJournal(journalId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des écritures"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage())) + .build(); + } + } + + /** + * Liste les écritures d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des écritures + */ + @GET + @Path("/ecritures/organisation/{organisationId}") + public Response listerEcrituresParOrganisation(@PathParam("organisationId") UUID organisationId) { + try { + List result = comptabiliteService.listerEcrituresParOrganisation(organisationId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des écritures"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des écritures: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java new file mode 100644 index 0000000..0ec502d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/CotisationResource.java @@ -0,0 +1,674 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.finance.CotisationDTO; +import dev.lions.unionflow.server.service.CotisationService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +/** + * Resource REST pour la gestion des cotisations Expose les endpoints API pour les opérations CRUD + * sur les cotisations + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@Path("/api/cotisations") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Cotisations", description = "Gestion des cotisations des membres") +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +@Slf4j +public class CotisationResource { + + @Inject CotisationService cotisationService; + + /** Endpoint public pour les cotisations (test) */ + @GET + @Path("/public") + @Operation( + summary = "Cotisations publiques", + description = "Liste des cotisations sans authentification") + @APIResponse(responseCode = "200", description = "Liste des cotisations") + public Response getCotisationsPublic( + @QueryParam("page") @DefaultValue("0") @Min(0) int page, + @QueryParam("size") @DefaultValue("20") @Min(1) int size) { + + try { + log.info("GET /api/cotisations/public - page: {}, size: {}", page, size); + + // Récupérer les cotisations depuis la base de données + List cotisationsDTO = cotisationService.getAllCotisations(page, size); + + // Convertir en format pour l'application mobile + List> cotisations = cotisationsDTO.stream() + .map(c -> { + Map map = new java.util.HashMap<>(); + map.put("id", c.getId() != null ? c.getId().toString() : ""); + map.put("nom", c.getDescription() != null ? c.getDescription() : "Cotisation"); + map.put("description", c.getDescription() != null ? c.getDescription() : ""); + map.put("montant", c.getMontantDu() != null ? c.getMontantDu().doubleValue() : 0.0); + map.put("devise", c.getCodeDevise() != null ? c.getCodeDevise() : "XOF"); + map.put("dateEcheance", c.getDateEcheance() != null ? c.getDateEcheance().toString() : ""); + map.put("statut", c.getStatut() != null ? c.getStatut() : "EN_ATTENTE"); + map.put("type", c.getTypeCotisation() != null ? c.getTypeCotisation() : "MENSUELLE"); + return map; + }) + .collect(Collectors.toList()); + + long totalElements = cotisationService.getStatistiquesCotisations().get("totalCotisations") != null + ? ((Number) cotisationService.getStatistiquesCotisations().get("totalCotisations")).longValue() + : cotisations.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + + Map response = + Map.of( + "content", cotisations, + "totalElements", totalElements, + "totalPages", totalPages, + "size", size, + "number", page); + + return Response.ok(response).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des cotisations publiques", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération des cotisations")) + .build(); + } + } + + /** Récupère toutes les cotisations avec pagination */ + @GET + @Operation( + summary = "Lister toutes les cotisations", + description = "Récupère la liste paginée de toutes les cotisations") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des cotisations récupérée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CotisationDTO.class))), + @APIResponse(responseCode = "400", description = "Paramètres de pagination invalides"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getAllCotisations( + @Parameter(description = "Numéro de page (0-based)", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/cotisations - page: {}, size: {}", page, size); + + List cotisations = cotisationService.getAllCotisations(page, size); + + log.info("Récupération réussie de {} cotisations", cotisations.size()); + return Response.ok(cotisations).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des cotisations", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des cotisations", + "message", + e.getMessage())) + .build(); + } + } + + /** Récupère une cotisation par son ID */ + @GET + @Path("/{id}") + @Operation( + summary = "Récupérer une cotisation par ID", + description = "Récupère les détails d'une cotisation spécifique") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Cotisation trouvée", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CotisationDTO.class))), + @APIResponse(responseCode = "404", description = "Cotisation non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCotisationById( + @Parameter(description = "Identifiant de la cotisation", required = true) + @PathParam("id") + @NotNull + UUID id) { + + try { + log.info("GET /api/cotisations/{}", id); + + CotisationDTO cotisation = cotisationService.getCotisationById(id); + + log.info("Cotisation récupérée avec succès - ID: {}", id); + return Response.ok(cotisation).build(); + + } catch (NotFoundException e) { + log.warn("Cotisation non trouvée - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Cotisation non trouvée", "id", id)) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération de la cotisation - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération de la cotisation", + "message", + e.getMessage())) + .build(); + } + } + + /** Récupère une cotisation par son numéro de référence */ + @GET + @Path("/reference/{numeroReference}") + @Operation( + summary = "Récupérer une cotisation par référence", + description = "Récupère une cotisation par son numéro de référence unique") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Cotisation trouvée"), + @APIResponse(responseCode = "404", description = "Cotisation non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCotisationByReference( + @Parameter(description = "Numéro de référence de la cotisation", required = true) + @PathParam("numeroReference") + @NotNull + String numeroReference) { + + try { + log.info("GET /api/cotisations/reference/{}", numeroReference); + + CotisationDTO cotisation = cotisationService.getCotisationByReference(numeroReference); + + log.info("Cotisation récupérée avec succès - Référence: {}", numeroReference); + return Response.ok(cotisation).build(); + + } catch (NotFoundException e) { + log.warn("Cotisation non trouvée - Référence: {}", numeroReference); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Cotisation non trouvée", "reference", numeroReference)) + .build(); + } catch (Exception e) { + log.error( + "Erreur lors de la récupération de la cotisation - Référence: " + numeroReference, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération de la cotisation", + "message", + e.getMessage())) + .build(); + } + } + + /** Crée une nouvelle cotisation */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Operation( + summary = "Créer une nouvelle cotisation", + description = "Crée une nouvelle cotisation pour un membre") + @APIResponses({ + @APIResponse( + responseCode = "201", + description = "Cotisation créée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CotisationDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Membre non trouvé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response createCotisation( + @Parameter(description = "Données de la cotisation à créer", required = true) @Valid + CotisationDTO cotisationDTO) { + + try { + log.info( + "POST /api/cotisations - Création cotisation pour membre: {}", + cotisationDTO.getMembreId()); + + CotisationDTO nouvelleCotisation = cotisationService.createCotisation(cotisationDTO); + + log.info( + "Cotisation créée avec succès - ID: {}, Référence: {}", + nouvelleCotisation.getId(), + nouvelleCotisation.getNumeroReference()); + + return Response.status(Response.Status.CREATED).entity(nouvelleCotisation).build(); + + } catch (NotFoundException e) { + log.warn( + "Membre non trouvé lors de la création de cotisation: {}", cotisationDTO.getMembreId()); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Membre non trouvé", "membreId", cotisationDTO.getMembreId())) + .build(); + } catch (IllegalArgumentException e) { + log.warn("Données invalides pour la création de cotisation: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Données invalides", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la création de la cotisation", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la création de la cotisation", + "message", + e.getMessage())) + .build(); + } + } + + /** Met à jour une cotisation existante */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}") + @Operation( + summary = "Mettre à jour une cotisation", + description = "Met à jour les données d'une cotisation existante") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Cotisation mise à jour avec succès"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Cotisation non trouvée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response updateCotisation( + @Parameter(description = "Identifiant de la cotisation", required = true) + @PathParam("id") + @NotNull + UUID id, + @Parameter(description = "Nouvelles données de la cotisation", required = true) @Valid + CotisationDTO cotisationDTO) { + + try { + log.info("PUT /api/cotisations/{}", id); + + CotisationDTO cotisationMiseAJour = cotisationService.updateCotisation(id, cotisationDTO); + + log.info("Cotisation mise à jour avec succès - ID: {}", id); + return Response.ok(cotisationMiseAJour).build(); + + } catch (NotFoundException e) { + log.warn("Cotisation non trouvée pour mise à jour - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Cotisation non trouvée", "id", id)) + .build(); + } catch (IllegalArgumentException e) { + log.warn( + "Données invalides pour la mise à jour de cotisation - ID: {}, Erreur: {}", + id, + e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Données invalides", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la mise à jour de la cotisation - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la mise à jour de la cotisation", + "message", + e.getMessage())) + .build(); + } + } + + /** Supprime une cotisation */ + @DELETE + @RolesAllowed({"ADMIN"}) + @Path("/{id}") + @Operation( + summary = "Supprimer une cotisation", + description = "Supprime (désactive) une cotisation") + @APIResponses({ + @APIResponse(responseCode = "204", description = "Cotisation supprimée avec succès"), + @APIResponse(responseCode = "404", description = "Cotisation non trouvée"), + @APIResponse( + responseCode = "409", + description = "Impossible de supprimer une cotisation payée"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response deleteCotisation( + @Parameter(description = "Identifiant de la cotisation", required = true) + @PathParam("id") + @NotNull + UUID id) { + + try { + log.info("DELETE /api/cotisations/{}", id); + + cotisationService.deleteCotisation(id); + + log.info("Cotisation supprimée avec succès - ID: {}", id); + return Response.noContent().build(); + + } catch (NotFoundException e) { + log.warn("Cotisation non trouvée pour suppression - ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Cotisation non trouvée", "id", id)) + .build(); + } catch (IllegalStateException e) { + log.warn("Impossible de supprimer la cotisation - ID: {}, Raison: {}", id, e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity( + Map.of("error", "Impossible de supprimer la cotisation", "message", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la suppression de la cotisation - ID: " + id, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la suppression de la cotisation", + "message", + e.getMessage())) + .build(); + } + } + + /** Récupère les cotisations d'un membre */ + @GET + @Path("/membre/{membreId}") + @Operation( + summary = "Lister les cotisations d'un membre", + description = "Récupère toutes les cotisations d'un membre spécifique") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Liste des cotisations du membre"), + @APIResponse(responseCode = "404", description = "Membre non trouvé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCotisationsByMembre( + @Parameter(description = "Identifiant du membre", required = true) + @PathParam("membreId") + @NotNull + UUID membreId, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/cotisations/membre/{} - page: {}, size: {}", membreId, page, size); + + List cotisations = + cotisationService.getCotisationsByMembre(membreId, page, size); + + log.info( + "Récupération réussie de {} cotisations pour le membre {}", cotisations.size(), membreId); + return Response.ok(cotisations).build(); + + } catch (NotFoundException e) { + log.warn("Membre non trouvé - ID: {}", membreId); + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Membre non trouvé", "membreId", membreId)) + .build(); + } catch (Exception e) { + log.error("Erreur lors de la récupération des cotisations du membre - ID: " + membreId, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des cotisations", + "message", + e.getMessage())) + .build(); + } + } + + /** Récupère les cotisations par statut */ + @GET + @Path("/statut/{statut}") + @Operation( + summary = "Lister les cotisations par statut", + description = "Récupère toutes les cotisations ayant un statut spécifique") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des cotisations avec le statut spécifié"), + @APIResponse(responseCode = "400", description = "Statut invalide"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCotisationsByStatut( + @Parameter(description = "Statut des cotisations", required = true, example = "EN_ATTENTE") + @PathParam("statut") + @NotNull + String statut, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/cotisations/statut/{} - page: {}, size: {}", statut, page, size); + + List cotisations = + cotisationService.getCotisationsByStatut(statut, page, size); + + log.info("Récupération réussie de {} cotisations avec statut {}", cotisations.size(), statut); + return Response.ok(cotisations).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des cotisations par statut - Statut: " + statut, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des cotisations", + "message", + e.getMessage())) + .build(); + } + } + + /** Récupère les cotisations en retard */ + @GET + @Path("/en-retard") + @Operation( + summary = "Lister les cotisations en retard", + description = "Récupère toutes les cotisations dont la date d'échéance est dépassée") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Liste des cotisations en retard"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCotisationsEnRetard( + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info("GET /api/cotisations/en-retard - page: {}, size: {}", page, size); + + List cotisations = cotisationService.getCotisationsEnRetard(page, size); + + log.info("Récupération réussie de {} cotisations en retard", cotisations.size()); + return Response.ok(cotisations).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des cotisations en retard", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des cotisations en retard", + "message", + e.getMessage())) + .build(); + } + } + + /** Recherche avancée de cotisations */ + @GET + @Path("/recherche") + @Operation( + summary = "Recherche avancée de cotisations", + description = "Recherche de cotisations avec filtres multiples") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Résultats de la recherche"), + @APIResponse(responseCode = "400", description = "Paramètres de recherche invalides"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response rechercherCotisations( + @Parameter(description = "Identifiant du membre") @QueryParam("membreId") UUID membreId, + @Parameter(description = "Statut de la cotisation") @QueryParam("statut") String statut, + @Parameter(description = "Type de cotisation") @QueryParam("typeCotisation") + String typeCotisation, + @Parameter(description = "Année") @QueryParam("annee") Integer annee, + @Parameter(description = "Mois") @QueryParam("mois") Integer mois, + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size) { + + try { + log.info( + "GET /api/cotisations/recherche - Filtres: membreId={}, statut={}, type={}, annee={}," + + " mois={}", + membreId, + statut, + typeCotisation, + annee, + mois); + + List cotisations = + cotisationService.rechercherCotisations( + membreId, statut, typeCotisation, annee, mois, page, size); + + log.info("Recherche réussie - {} cotisations trouvées", cotisations.size()); + return Response.ok(cotisations).build(); + + } catch (Exception e) { + log.error("Erreur lors de la recherche de cotisations", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", "Erreur lors de la recherche de cotisations", "message", e.getMessage())) + .build(); + } + } + + /** Récupère les statistiques des cotisations */ + @GET + @Path("/stats") + @Operation( + summary = "Statistiques des cotisations", + description = "Récupère les statistiques globales des cotisations") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getStatistiquesCotisations() { + try { + log.info("GET /api/cotisations/stats"); + + Map statistiques = cotisationService.getStatistiquesCotisations(); + + log.info("Statistiques récupérées avec succès"); + return Response.ok(statistiques).build(); + + } catch (Exception e) { + log.error("Erreur lors de la récupération des statistiques", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity( + Map.of( + "error", + "Erreur lors de la récupération des statistiques", + "message", + e.getMessage())) + .build(); + } + } + + /** + * Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY) + * + * @param membreIds Liste des IDs des membres destinataires + * @return Nombre de rappels envoyés + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/rappels/groupes") + @Consumes(MediaType.APPLICATION_JSON) + @Operation(summary = "Envoyer des rappels de cotisations groupés") + @APIResponse(responseCode = "200", description = "Rappels envoyés avec succès") + public Response envoyerRappelsGroupes(List membreIds) { + try { + int rappelsEnvoyes = cotisationService.envoyerRappelsCotisationsGroupes(membreIds); + return Response.ok(Map.of("rappelsEnvoyes", rappelsEnvoyes)).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + log.error("Erreur lors de l'envoi des rappels groupés", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de l'envoi des rappels: " + e.getMessage())) + .build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java b/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java new file mode 100644 index 0000000..6668d17 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/DashboardResource.java @@ -0,0 +1,251 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO; +import dev.lions.unionflow.server.api.service.dashboard.DashboardService; +import jakarta.inject.Inject; +import jakarta.annotation.security.RolesAllowed; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Resource REST pour les APIs du dashboard + * + *

Cette ressource expose les endpoints pour récupérer les données du dashboard, + * incluant les statistiques, activités récentes et événements à venir. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@Path("/api/v1/dashboard") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Dashboard", description = "APIs pour la gestion du dashboard") +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class DashboardResource { + + private static final Logger LOG = Logger.getLogger(DashboardResource.class); + + @Inject + DashboardService dashboardService; + + /** + * Récupère toutes les données du dashboard + */ + @GET + @Path("/data") + @Operation( + summary = "Récupérer toutes les données du dashboard", + description = "Retourne les statistiques, activités récentes et événements à venir" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Données récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getDashboardData( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("GET /api/v1/dashboard/data - org: %s, user: %s", organizationId, userId); + + try { + DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId); + return Response.ok(dashboardData).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des données dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère uniquement les statistiques du dashboard + */ + @GET + @Path("/stats") + @Operation( + summary = "Récupérer les statistiques du dashboard", + description = "Retourne uniquement les statistiques principales" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getDashboardStats( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("GET /api/v1/dashboard/stats - org: %s, user: %s", organizationId, userId); + + try { + DashboardStatsDTO stats = dashboardService.getDashboardStats(organizationId, userId); + return Response.ok(stats).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des statistiques dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère les activités récentes + */ + @GET + @Path("/activities") + @Operation( + summary = "Récupérer les activités récentes", + description = "Retourne la liste des activités récentes avec pagination" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Activités récupérées avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getRecentActivities( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId, + @Parameter(description = "Nombre maximum d'activités à retourner", required = false) + @QueryParam("limit") @DefaultValue("10") int limit) { + + LOG.infof("GET /api/v1/dashboard/activities - org: %s, user: %s, limit: %d", + organizationId, userId, limit); + + try { + List activities = dashboardService.getRecentActivities( + organizationId, userId, limit); + + Map response = new HashMap<>(); + response.put("activities", activities); + response.put("total", activities.size()); + response.put("limit", limit); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des activités récentes"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Récupère les événements à venir + */ + @GET + @Path("/events/upcoming") + @Operation( + summary = "Récupérer les événements à venir", + description = "Retourne la liste des événements à venir avec pagination" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Événements récupérés avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response getUpcomingEvents( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId, + @Parameter(description = "Nombre maximum d'événements à retourner", required = false) + @QueryParam("limit") @DefaultValue("5") int limit) { + + LOG.infof("GET /api/v1/dashboard/events/upcoming - org: %s, user: %s, limit: %d", + organizationId, userId, limit); + + try { + List events = dashboardService.getUpcomingEvents( + organizationId, userId, limit); + + Map response = new HashMap<>(); + response.put("events", events); + response.put("total", events.size()); + response.put("limit", limit); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des événements à venir"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } + + /** + * Endpoint de santé pour vérifier le statut du dashboard + */ + @GET + @Path("/health") + @Operation( + summary = "Vérifier la santé du service dashboard", + description = "Retourne le statut de santé du service dashboard" + ) + @APIResponse(responseCode = "200", description = "Service en bonne santé") + public Response healthCheck() { + LOG.debug("GET /api/v1/dashboard/health"); + + Map health = new HashMap<>(); + health.put("status", "UP"); + health.put("service", "dashboard"); + health.put("timestamp", System.currentTimeMillis()); + health.put("version", "1.0.0"); + + return Response.ok(health).build(); + } + + /** + * Endpoint pour rafraîchir les données du dashboard + */ + @POST + @Path("/refresh") + @Operation( + summary = "Rafraîchir les données du dashboard", + description = "Force la mise à jour des données du dashboard" + ) + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Données rafraîchies avec succès"), + @APIResponse(responseCode = "400", description = "Paramètres invalides"), + @APIResponse(responseCode = "500", description = "Erreur serveur") + }) + public Response refreshDashboard( + @Parameter(description = "ID de l'organisation", required = true) + @QueryParam("organizationId") @NotNull String organizationId, + @Parameter(description = "ID de l'utilisateur", required = true) + @QueryParam("userId") @NotNull String userId) { + + LOG.infof("POST /api/v1/dashboard/refresh - org: %s, user: %s", organizationId, userId); + + try { + // Simuler un rafraîchissement (dans un vrai système, cela pourrait vider le cache) + DashboardDataDTO dashboardData = dashboardService.getDashboardData(organizationId, userId); + + Map response = new HashMap<>(); + response.put("status", "refreshed"); + response.put("timestamp", System.currentTimeMillis()); + response.put("data", dashboardData); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du rafraîchissement du dashboard"); + return Response.serverError().entity(Map.of("error", "Erreur serveur")).build(); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java b/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java new file mode 100644 index 0000000..67df12a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java @@ -0,0 +1,158 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.document.DocumentDTO; +import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO; +import dev.lions.unionflow.server.service.DocumentService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion documentaire + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/documents") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class DocumentResource { + + private static final Logger LOG = Logger.getLogger(DocumentResource.class); + + @Inject DocumentService documentService; + + /** + * Crée un nouveau document + * + * @param documentDTO DTO du document à créer + * @return Document créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + public Response creerDocument(@Valid DocumentDTO documentDTO) { + try { + DocumentDTO result = documentService.creerDocument(documentDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du document"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un document par son ID + * + * @param id ID du document + * @return Document + */ + @GET + @Path("/{id}") + public Response trouverParId(@PathParam("id") UUID id) { + try { + DocumentDTO result = documentService.trouverParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Document non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du document"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage())) + .build(); + } + } + + /** + * Enregistre un téléchargement de document + * + * @param id ID du document + * @return Succès + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/telechargement") + public Response enregistrerTelechargement(@PathParam("id") UUID id) { + try { + documentService.enregistrerTelechargement(id); + return Response.ok().build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Document non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de l'enregistrement du téléchargement: " + e.getMessage())) + .build(); + } + } + + /** + * Crée une pièce jointe + * + * @param pieceJointeDTO DTO de la pièce jointe à créer + * @return Pièce jointe créée + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/pieces-jointes") + public Response creerPieceJointe(@Valid PieceJointeDTO pieceJointeDTO) { + try { + PieceJointeDTO result = documentService.creerPieceJointe(pieceJointeDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création de la pièce jointe"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage())) + .build(); + } + } + + /** + * Liste toutes les pièces jointes d'un document + * + * @param documentId ID du document + * @return Liste des pièces jointes + */ + @GET + @Path("/{documentId}/pieces-jointes") + public Response listerPiecesJointesParDocument(@PathParam("documentId") UUID documentId) { + try { + List result = documentService.listerPiecesJointesParDocument(documentId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des pièces jointes"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java b/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java new file mode 100644 index 0000000..81d9a8b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java @@ -0,0 +1,452 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.dto.EvenementMobileDTO; +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import dev.lions.unionflow.server.service.EvenementService; +import java.util.stream.Collectors; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion des événements + * + *

Fournit les endpoints API pour les opérations CRUD sur les événements, optimisé pour + * l'intégration avec l'application mobile UnionFlow. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@Path("/api/evenements") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Événements", description = "Gestion des événements de l'union") +public class EvenementResource { + + private static final Logger LOG = Logger.getLogger(EvenementResource.class); + + @Inject EvenementService evenementService; + + /** Endpoint de test public pour vérifier la connectivité */ + @GET + @Path("/test") + @Operation( + summary = "Test de connectivité", + description = "Endpoint public pour tester la connectivité") + @APIResponse(responseCode = "200", description = "Test réussi") + public Response testConnectivity() { + LOG.info("Test de connectivité appelé depuis l'application mobile"); + return Response.ok( + Map.of( + "status", "success", + "message", "Serveur UnionFlow opérationnel", + "timestamp", System.currentTimeMillis(), + "version", "1.0.0")) + .build(); + } + + /** Endpoint de debug pour vérifier le chargement des données */ + @GET + @Path("/count") + @Operation(summary = "Compter les événements", description = "Compte le nombre d'événements dans la base") + @APIResponse(responseCode = "200", description = "Nombre d'événements") + public Response countEvenements() { + try { + long count = evenementService.countEvenements(); + return Response.ok(Map.of("count", count, "status", "success")).build(); + } catch (Exception e) { + LOG.errorf("Erreur count: %s", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + /** Liste tous les événements actifs avec pagination */ + @GET + @Operation( + summary = "Lister tous les événements actifs", + description = "Récupère la liste paginée des événements actifs") + @APIResponse(responseCode = "200", description = "Liste des événements actifs") + @APIResponse(responseCode = "401", description = "Non authentifié") + // @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) // Temporairement désactivé + public Response listerEvenements( + @Parameter(description = "Numéro de page (0-based)", example = "0") + @QueryParam("page") + @DefaultValue("0") + @Min(0) + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + @Min(1) + int size, + @Parameter(description = "Champ de tri", example = "dateDebut") + @QueryParam("sort") + @DefaultValue("dateDebut") + String sortField, + @Parameter(description = "Direction du tri (asc/desc)", example = "asc") + @QueryParam("direction") + @DefaultValue("asc") + String sortDirection) { + + try { + LOG.infof("GET /api/evenements - page: %d, size: %d", page, size); + + Sort sort = + sortDirection.equalsIgnoreCase("desc") + ? Sort.by(sortField).descending() + : Sort.by(sortField).ascending(); + + List evenements = + evenementService.listerEvenementsActifs(Page.of(page, size), sort); + + LOG.infof("Nombre d'événements récupérés: %d", evenements.size()); + + // Convertir en DTO mobile + List evenementsDTOs = new ArrayList<>(); + for (Evenement evenement : evenements) { + try { + EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(evenement); + evenementsDTOs.add(dto); + } catch (Exception e) { + LOG.errorf("Erreur lors de la conversion de l'événement %s: %s", evenement.getId(), e.getMessage()); + // Continuer avec les autres événements + } + } + + LOG.infof("Nombre de DTOs créés: %d", evenementsDTOs.size()); + + // Compter le total d'événements actifs + long total = evenementService.countEvenementsActifs(); + int totalPages = total > 0 ? (int) Math.ceil((double) total / size) : 0; + + // Retourner la structure paginée attendue par le mobile + Map response = new HashMap<>(); + response.put("data", evenementsDTOs); + response.put("total", total); + response.put("page", page); + response.put("size", size); + response.put("totalPages", totalPages); + + LOG.infof("Réponse prête: %d événements, total=%d, pages=%d", evenementsDTOs.size(), total, totalPages); + + return Response.ok(response) + .header("Content-Type", "application/json;charset=UTF-8") + .build(); + + } catch (Exception e) { + LOG.errorf("Erreur lors de la récupération des événements: %s", e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération des événements: " + e.getMessage())) + .build(); + } + } + + /** Récupère un événement par son ID */ + @GET + @Path("/{id}") + @Operation(summary = "Récupérer un événement par ID") + @APIResponse(responseCode = "200", description = "Événement trouvé") + @APIResponse(responseCode = "404", description = "Événement non trouvé") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) + public Response obtenirEvenement( + @Parameter(description = "UUID de l'événement", required = true) @PathParam("id") UUID id) { + + try { + LOG.infof("GET /api/evenements/%s", id); + + Optional evenement = evenementService.trouverParId(id); + + if (evenement.isPresent()) { + return Response.ok(evenement.get()).build(); + } else { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Événement non trouvé")) + .build(); + } + + } catch (Exception e) { + LOG.errorf("Erreur lors de la récupération de l'événement %d: %s", id, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération de l'événement")) + .build(); + } + } + + /** Crée un nouvel événement */ + @POST + @Operation(summary = "Créer un nouvel événement") + @APIResponse(responseCode = "201", description = "Événement créé avec succès") + @APIResponse(responseCode = "400", description = "Données invalides") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"}) + public Response creerEvenement( + @Parameter(description = "Données de l'événement à créer", required = true) @Valid + Evenement evenement) { + + try { + LOG.infof("POST /api/evenements - Création événement: %s", evenement.getTitre()); + + Evenement evenementCree = evenementService.creerEvenement(evenement); + + return Response.status(Response.Status.CREATED).entity(evenementCree).build(); + + } catch (IllegalArgumentException e) { + LOG.warnf("Données invalides: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (SecurityException e) { + LOG.warnf("Permissions insuffisantes: %s", e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf("Erreur lors de la création: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la création de l'événement")) + .build(); + } + } + + /** Met à jour un événement existant */ + @PUT + @Path("/{id}") + @Operation(summary = "Mettre à jour un événement") + @APIResponse(responseCode = "200", description = "Événement mis à jour avec succès") + @APIResponse(responseCode = "404", description = "Événement non trouvé") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"}) + public Response mettreAJourEvenement(@PathParam("id") UUID id, @Valid Evenement evenement) { + + try { + LOG.infof("PUT /api/evenements/%s", id); + + Evenement evenementMisAJour = evenementService.mettreAJourEvenement(id, evenement); + + return Response.ok(evenementMisAJour).build(); + + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (SecurityException e) { + return Response.status(Response.Status.FORBIDDEN) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf("Erreur lors de la mise à jour: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la mise à jour")) + .build(); + } + } + + /** Supprime un événement */ + @DELETE + @Path("/{id}") + @Operation(summary = "Supprimer un événement") + @APIResponse(responseCode = "204", description = "Événement supprimé avec succès") + @RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"}) + public Response supprimerEvenement(@PathParam("id") UUID id) { + + try { + LOG.infof("DELETE /api/evenements/%s", id); + + evenementService.supprimerEvenement(id); + + return Response.noContent().build(); + + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (SecurityException e) { + return Response.status(Response.Status.FORBIDDEN) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf("Erreur lors de la suppression: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la suppression")) + .build(); + } + } + + /** Endpoints spécialisés pour l'application mobile */ + + /** Liste les événements à venir */ + @GET + @Path("/a-venir") + @Operation(summary = "Événements à venir") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) + public Response evenementsAVenir( + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("10") int size) { + + try { + List evenements = + evenementService.listerEvenementsAVenir( + Page.of(page, size), Sort.by("dateDebut").ascending()); + + return Response.ok(evenements).build(); + } catch (Exception e) { + LOG.errorf("Erreur événements à venir: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération")) + .build(); + } + } + + /** Liste les événements publics */ + @GET + @Path("/publics") + @Operation(summary = "Événements publics") + public Response evenementsPublics( + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("20") int size) { + + try { + List evenements = + evenementService.listerEvenementsPublics( + Page.of(page, size), Sort.by("dateDebut").ascending()); + + return Response.ok(evenements).build(); + } catch (Exception e) { + LOG.errorf("Erreur événements publics: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération")) + .build(); + } + } + + /** Recherche d'événements */ + @GET + @Path("/recherche") + @Operation(summary = "Rechercher des événements") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) + public Response rechercherEvenements( + @QueryParam("q") String recherche, + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("20") int size) { + + try { + if (recherche == null || recherche.trim().isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Le terme de recherche est obligatoire")) + .build(); + } + + List evenements = + evenementService.rechercherEvenements( + recherche, Page.of(page, size), Sort.by("dateDebut").ascending()); + + return Response.ok(evenements).build(); + } catch (Exception e) { + LOG.errorf("Erreur recherche: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la recherche")) + .build(); + } + } + + /** Événements par type */ + @GET + @Path("/type/{type}") + @Operation(summary = "Événements par type") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE"}) + public Response evenementsParType( + @PathParam("type") TypeEvenement type, + @QueryParam("page") @DefaultValue("0") int page, + @QueryParam("size") @DefaultValue("20") int size) { + + try { + List evenements = + evenementService.listerParType( + type, Page.of(page, size), Sort.by("dateDebut").ascending()); + + return Response.ok(evenements).build(); + } catch (Exception e) { + LOG.errorf("Erreur événements par type: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la récupération")) + .build(); + } + } + + /** Change le statut d'un événement */ + @PATCH + @Path("/{id}/statut") + @Operation(summary = "Changer le statut d'un événement") + @RolesAllowed({"ADMIN", "PRESIDENT", "ORGANISATEUR_EVENEMENT"}) + public Response changerStatut( + @PathParam("id") UUID id, @QueryParam("statut") StatutEvenement nouveauStatut) { + + try { + if (nouveauStatut == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Le nouveau statut est obligatoire")) + .build(); + } + + Evenement evenement = evenementService.changerStatut(id, nouveauStatut); + + return Response.ok(evenement).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (SecurityException e) { + return Response.status(Response.Status.FORBIDDEN) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf("Erreur changement statut: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors du changement de statut")) + .build(); + } + } + + /** Statistiques des événements */ + @GET + @Path("/statistiques") + @Operation(summary = "Statistiques des événements") + @RolesAllowed({"ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT"}) + public Response obtenirStatistiques() { + + try { + Map statistiques = evenementService.obtenirStatistiques(); + + return Response.ok(statistiques).build(); + } catch (Exception e) { + LOG.errorf("Erreur statistiques: %s", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors du calcul des statistiques")) + .build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/ExportResource.java b/src/main/java/dev/lions/unionflow/server/resource/ExportResource.java new file mode 100644 index 0000000..452f278 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/ExportResource.java @@ -0,0 +1,119 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.service.ExportService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.annotation.security.RolesAllowed; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** Resource REST pour l'export des données */ +@Path("/api/export") +@ApplicationScoped +@Tag(name = "Export", description = "API d'export des données") +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class ExportResource { + + private static final Logger LOG = Logger.getLogger(ExportResource.class); + + @Inject ExportService exportService; + + @GET + @Path("/cotisations/csv") + @Produces("text/csv") + @Operation(summary = "Exporter les cotisations en CSV") + @APIResponse(responseCode = "200", description = "Fichier CSV généré") + public Response exporterCotisationsCSV( + @QueryParam("statut") String statut, + @QueryParam("type") String type, + @QueryParam("associationId") UUID associationId) { + LOG.info("Export CSV des cotisations"); + + byte[] csv = exportService.exporterToutesCotisationsCSV(statut, type, associationId); + + return Response.ok(csv) + .header("Content-Disposition", "attachment; filename=\"cotisations.csv\"") + .header("Content-Type", "text/csv; charset=UTF-8") + .build(); + } + + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/cotisations/csv") + @Consumes(MediaType.APPLICATION_JSON) + @Produces("text/csv") + @Operation(summary = "Exporter des cotisations spécifiques en CSV") + @APIResponse(responseCode = "200", description = "Fichier CSV généré") + public Response exporterCotisationsSelectionneesCSV(List cotisationIds) { + LOG.infof("Export CSV de %d cotisations", cotisationIds.size()); + + byte[] csv = exportService.exporterCotisationsCSV(cotisationIds); + + return Response.ok(csv) + .header("Content-Disposition", "attachment; filename=\"cotisations.csv\"") + .header("Content-Type", "text/csv; charset=UTF-8") + .build(); + } + + @GET + @Path("/cotisations/{cotisationId}/recu") + @Produces("text/plain") + @Operation(summary = "Générer un reçu de paiement") + @APIResponse(responseCode = "200", description = "Reçu généré") + public Response genererRecu(@PathParam("cotisationId") UUID cotisationId) { + LOG.infof("Génération reçu pour: %s", cotisationId); + + byte[] recu = exportService.genererRecuPaiement(cotisationId); + + return Response.ok(recu) + .header("Content-Disposition", "attachment; filename=\"recu-" + cotisationId + ".txt\"") + .header("Content-Type", "text/plain; charset=UTF-8") + .build(); + } + + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/cotisations/recus") + @Consumes(MediaType.APPLICATION_JSON) + @Produces("text/plain") + @Operation(summary = "Générer des reçus groupés") + @APIResponse(responseCode = "200", description = "Reçus générés") + public Response genererRecusGroupes(List cotisationIds) { + LOG.infof("Génération de %d reçus", cotisationIds.size()); + + byte[] recus = exportService.genererRecusGroupes(cotisationIds); + + return Response.ok(recus) + .header("Content-Disposition", "attachment; filename=\"recus-groupes.txt\"") + .header("Content-Type", "text/plain; charset=UTF-8") + .build(); + } + + @GET + @Path("/rapport/mensuel") + @Produces("text/plain") + @Operation(summary = "Générer un rapport mensuel") + @APIResponse(responseCode = "200", description = "Rapport généré") + public Response genererRapportMensuel( + @QueryParam("annee") int annee, + @QueryParam("mois") int mois, + @QueryParam("associationId") UUID associationId) { + LOG.infof("Génération rapport mensuel: %d/%d", mois, annee); + + byte[] rapport = exportService.genererRapportMensuel(annee, mois, associationId); + + return Response.ok(rapport) + .header("Content-Disposition", + "attachment; filename=\"rapport-" + annee + "-" + String.format("%02d", mois) + ".txt\"") + .header("Content-Type", "text/plain; charset=UTF-8") + .build(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/HealthResource.java b/src/main/java/dev/lions/unionflow/server/resource/HealthResource.java new file mode 100644 index 0000000..85536a4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/HealthResource.java @@ -0,0 +1,33 @@ +package dev.lions.unionflow.server.resource; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.time.LocalDateTime; +import java.util.Map; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +/** Resource de santé pour UnionFlow Server */ +@Path("/api/status") +@Produces(MediaType.APPLICATION_JSON) +@ApplicationScoped +@Tag(name = "Status", description = "API de statut du serveur") +public class HealthResource { + + @GET + @Operation(summary = "Vérifier le statut du serveur") + public Response getStatus() { + return Response.ok( + Map.of( + "status", "UP", + "service", "UnionFlow Server", + "version", "1.0.0", + "timestamp", LocalDateTime.now().toString(), + "message", "Serveur opérationnel")) + .build(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java new file mode 100644 index 0000000..785ae61 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java @@ -0,0 +1,643 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.membre.MembreDTO; +import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; +import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.service.MembreService; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.annotation.security.PermitAll; +import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** Resource REST pour la gestion des membres */ +@Path("/api/membres") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@ApplicationScoped +@Tag(name = "Membres", description = "API de gestion des membres") +public class MembreResource { + + private static final Logger LOG = Logger.getLogger(MembreResource.class); + + @Inject MembreService membreService; + + @GET + @Operation(summary = "Lister tous les membres actifs") + @APIResponse(responseCode = "200", description = "Liste des membres actifs") + public Response listerMembres( + @Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") + int page, + @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") + int size, + @Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") + String sortField, + @Parameter(description = "Direction du tri (asc/desc)") + @QueryParam("direction") + @DefaultValue("asc") + String sortDirection) { + + LOG.infof("Récupération de la liste des membres actifs - page: %d, size: %d", page, size); + + Sort sort = + "desc".equalsIgnoreCase(sortDirection) + ? Sort.by(sortField).descending() + : Sort.by(sortField).ascending(); + + List membres = membreService.listerMembresActifs(Page.of(page, size), sort); + List membresDTO = membreService.convertToDTOList(membres); + + return Response.ok(membresDTO).build(); + } + + @GET + @Path("/{id}") + @Operation(summary = "Récupérer un membre par son ID") + @APIResponse(responseCode = "200", description = "Membre trouvé") + @APIResponse(responseCode = "404", description = "Membre non trouvé") + public Response obtenirMembre(@Parameter(description = "UUID du membre") @PathParam("id") UUID id) { + LOG.infof("Récupération du membre ID: %s", id); + return membreService + .trouverParId(id) + .map( + membre -> { + MembreDTO membreDTO = membreService.convertToDTO(membre); + return Response.ok(membreDTO).build(); + }) + .orElse( + Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("message", "Membre non trouvé")) + .build()); + } + + @POST + @PermitAll + @Operation(summary = "Créer un nouveau membre") + @APIResponse(responseCode = "201", description = "Membre créé avec succès") + @APIResponse(responseCode = "400", description = "Données invalides") + public Response creerMembre(@Valid MembreDTO membreDTO) { + LOG.infof("Création d'un nouveau membre: %s", membreDTO.getEmail()); + try { + // Conversion DTO vers entité + Membre membre = membreService.convertFromDTO(membreDTO); + + // Création du membre + Membre nouveauMembre = membreService.creerMembre(membre); + + // Conversion de retour vers DTO + MembreDTO nouveauMembreDTO = membreService.convertToDTO(nouveauMembre); + + return Response.status(Response.Status.CREATED).entity(nouveauMembreDTO).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", e.getMessage())) + .build(); + } + } + + @PUT + @Path("/{id}") + @Operation(summary = "Mettre à jour un membre existant") + @APIResponse(responseCode = "200", description = "Membre mis à jour avec succès") + @APIResponse(responseCode = "404", description = "Membre non trouvé") + @APIResponse(responseCode = "400", description = "Données invalides") + public Response mettreAJourMembre( + @Parameter(description = "UUID du membre") @PathParam("id") UUID id, + @Valid MembreDTO membreDTO) { + LOG.infof("Mise à jour du membre ID: %s", id); + try { + // Conversion DTO vers entité + Membre membre = membreService.convertFromDTO(membreDTO); + + // Mise à jour du membre + Membre membreMisAJour = membreService.mettreAJourMembre(id, membre); + + // Conversion de retour vers DTO + MembreDTO membreMisAJourDTO = membreService.convertToDTO(membreMisAJour); + + return Response.ok(membreMisAJourDTO).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", e.getMessage())) + .build(); + } + } + + @DELETE + @Path("/{id}") + @Operation(summary = "Désactiver un membre") + @APIResponse(responseCode = "204", description = "Membre désactivé avec succès") + @APIResponse(responseCode = "404", description = "Membre non trouvé") + public Response desactiverMembre( + @Parameter(description = "UUID du membre") @PathParam("id") UUID id) { + LOG.infof("Désactivation du membre ID: %s", id); + try { + membreService.desactiverMembre(id); + return Response.noContent().build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("message", e.getMessage())) + .build(); + } + } + + @GET + @Path("/recherche") + @Operation(summary = "Rechercher des membres par nom ou prénom") + @APIResponse(responseCode = "200", description = "Résultats de la recherche") + public Response rechercherMembres( + @Parameter(description = "Terme de recherche") @QueryParam("q") String recherche, + @Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") + int page, + @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") + int size, + @Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") + String sortField, + @Parameter(description = "Direction du tri (asc/desc)") + @QueryParam("direction") + @DefaultValue("asc") + String sortDirection) { + + LOG.infof("Recherche de membres avec le terme: %s", recherche); + if (recherche == null || recherche.trim().isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Le terme de recherche est requis")) + .build(); + } + + Sort sort = + "desc".equalsIgnoreCase(sortDirection) + ? Sort.by(sortField).descending() + : Sort.by(sortField).ascending(); + + List membres = + membreService.rechercherMembres(recherche.trim(), Page.of(page, size), sort); + List membresDTO = membreService.convertToDTOList(membres); + + return Response.ok(membresDTO).build(); + } + + @GET + @Path("/stats") + @Operation(summary = "Obtenir les statistiques avancées des membres") + @APIResponse(responseCode = "200", description = "Statistiques complètes des membres") + public Response obtenirStatistiques() { + LOG.info("Récupération des statistiques avancées des membres"); + Map statistiques = membreService.obtenirStatistiquesAvancees(); + return Response.ok(statistiques).build(); + } + + @GET + @Path("/autocomplete/villes") + @Operation(summary = "Obtenir la liste des villes pour autocomplétion") + @APIResponse(responseCode = "200", description = "Liste des villes distinctes") + public Response obtenirVilles( + @Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) { + LOG.infof("Récupération des villes pour autocomplétion - query: %s", query); + List villes = membreService.obtenirVillesDistinctes(query); + return Response.ok(villes).build(); + } + + @GET + @Path("/autocomplete/professions") + @Operation(summary = "Obtenir la liste des professions pour autocomplétion") + @APIResponse(responseCode = "200", description = "Liste des professions distinctes") + public Response obtenirProfessions( + @Parameter(description = "Terme de recherche (optionnel)") @QueryParam("query") String query) { + LOG.infof("Récupération des professions pour autocomplétion - query: %s", query); + List professions = membreService.obtenirProfessionsDistinctes(query); + return Response.ok(professions).build(); + } + + @GET + @Path("/recherche-avancee") + @Operation(summary = "Recherche avancée de membres avec filtres multiples (DEPRECATED)") + @APIResponse(responseCode = "200", description = "Résultats de la recherche avancée") + @Deprecated + public Response rechercheAvancee( + @Parameter(description = "Terme de recherche") @QueryParam("q") String recherche, + @Parameter(description = "Statut actif (true/false)") @QueryParam("actif") Boolean actif, + @Parameter(description = "Date d'adhésion minimum (YYYY-MM-DD)") + @QueryParam("dateAdhesionMin") + String dateAdhesionMin, + @Parameter(description = "Date d'adhésion maximum (YYYY-MM-DD)") + @QueryParam("dateAdhesionMax") + String dateAdhesionMax, + @Parameter(description = "Numéro de page (0-based)") @QueryParam("page") @DefaultValue("0") + int page, + @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") + int size, + @Parameter(description = "Champ de tri") @QueryParam("sort") @DefaultValue("nom") + String sortField, + @Parameter(description = "Direction du tri (asc/desc)") + @QueryParam("direction") + @DefaultValue("asc") + String sortDirection) { + + LOG.infof( + "Recherche avancée de membres (DEPRECATED) - recherche: %s, actif: %s", recherche, actif); + + try { + Sort sort = + "desc".equalsIgnoreCase(sortDirection) + ? Sort.by(sortField).descending() + : Sort.by(sortField).ascending(); + + // Conversion des dates si fournies + java.time.LocalDate dateMin = + dateAdhesionMin != null ? java.time.LocalDate.parse(dateAdhesionMin) : null; + java.time.LocalDate dateMax = + dateAdhesionMax != null ? java.time.LocalDate.parse(dateAdhesionMax) : null; + + List membres = + membreService.rechercheAvancee( + recherche, actif, dateMin, dateMax, Page.of(page, size), sort); + List membresDTO = membreService.convertToDTOList(membres); + + return Response.ok(membresDTO).build(); + } catch (Exception e) { + LOG.errorf("Erreur lors de la recherche avancée: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Erreur dans les paramètres de recherche: " + e.getMessage())) + .build(); + } + } + + /** + * Nouvelle recherche avancée avec critères complets et résultats enrichis Réservée aux super + * administrateurs pour des recherches sophistiquées + */ + @POST + @Path("/search/advanced") + @RolesAllowed({"SUPER_ADMIN", "ADMIN"}) + @Operation( + summary = "Recherche avancée de membres avec critères multiples", + description = + """ + Recherche sophistiquée de membres avec de nombreux critères de filtrage : + - Recherche textuelle dans nom, prénom, email + - Filtres par organisation, rôles, statut + - Filtres par âge, région, profession + - Filtres par dates d'adhésion + - Résultats paginés avec statistiques + + Réservée aux super administrateurs et administrateurs. + """) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Recherche effectuée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = MembreSearchResultDTO.class), + examples = + @ExampleObject( + name = "Exemple de résultats", + value = + """ + { + "membres": [...], + "totalElements": 247, + "totalPages": 13, + "currentPage": 0, + "pageSize": 20, + "hasNext": true, + "hasPrevious": false, + "executionTimeMs": 45, + "statistics": { + "membresActifs": 230, + "membresInactifs": 17, + "ageMoyen": 34.5, + "nombreOrganisations": 12 + } + } + """))), + @APIResponse( + responseCode = "400", + description = "Critères de recherche invalides", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + examples = + @ExampleObject( + value = + """ +{ + "message": "Critères de recherche invalides", + "details": "La date minimum ne peut pas être postérieure à la date maximum" +} +"""))), + @APIResponse( + responseCode = "403", + description = "Accès non autorisé - Rôle SUPER_ADMIN ou ADMIN requis"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + @SecurityRequirement(name = "keycloak") + public Response searchMembresAdvanced( + @RequestBody( + description = "Critères de recherche avancée", + required = false, + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = MembreSearchCriteria.class), + examples = + @ExampleObject( + name = "Exemple de critères", + value = + """ + { + "query": "marie", + "statut": "ACTIF", + "ageMin": 25, + "ageMax": 45, + "region": "Dakar", + "roles": ["PRESIDENT", "SECRETAIRE"], + "dateAdhesionMin": "2020-01-01", + "includeInactifs": false + } + """))) + MembreSearchCriteria criteria, + @Parameter(description = "Numéro de page (0-based)", example = "0") + @QueryParam("page") + @DefaultValue("0") + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + int size, + @Parameter(description = "Champ de tri", example = "nom") + @QueryParam("sort") + @DefaultValue("nom") + String sortField, + @Parameter(description = "Direction du tri (asc/desc)", example = "asc") + @QueryParam("direction") + @DefaultValue("asc") + String sortDirection) { + + long startTime = System.currentTimeMillis(); + + try { + // Validation des critères + if (criteria == null) { + LOG.warn("Recherche avancée de membres - critères null rejetés"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Les critères de recherche sont requis")) + .build(); + } + + LOG.infof( + "Recherche avancée de membres - critères: %s, page: %d, size: %d", + criteria.getDescription(), page, size); + + // Nettoyage et validation des critères + criteria.sanitize(); + + if (!criteria.hasAnyCriteria()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Au moins un critère de recherche doit être spécifié")) + .build(); + } + + if (!criteria.isValid()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity( + Map.of( + "message", "Critères de recherche invalides", + "details", "Vérifiez la cohérence des dates et des âges")) + .build(); + } + + // Construction du tri + Sort sort = + "desc".equalsIgnoreCase(sortDirection) + ? Sort.by(sortField).descending() + : Sort.by(sortField).ascending(); + + // Exécution de la recherche + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(page, size), sort); + + // Calcul du temps d'exécution + long executionTime = System.currentTimeMillis() - startTime; + result.setExecutionTimeMs(executionTime); + + LOG.infof( + "Recherche avancée terminée - %d résultats trouvés en %d ms", + result.getTotalElements(), executionTime); + + return Response.ok(result).build(); + + } catch (jakarta.validation.ConstraintViolationException e) { + LOG.warnf("Erreur de validation Jakarta dans la recherche avancée: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Critères de recherche invalides", "details", e.getMessage())) + .build(); + } catch (IllegalArgumentException e) { + LOG.warnf("Erreur de validation dans la recherche avancée: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("message", "Paramètres de recherche invalides", "details", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche avancée de membres"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("message", "Erreur interne lors de la recherche", "error", e.getMessage())) + .build(); + } + } + + @POST + @Path("/export/selection") + @Consumes(MediaType.APPLICATION_JSON) + @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + @Operation(summary = "Exporter une sélection de membres en Excel") + @APIResponse(responseCode = "200", description = "Fichier Excel généré") + public Response exporterSelectionMembres( + @Parameter(description = "Liste des IDs des membres à exporter") List membreIds, + @Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format) { + LOG.infof("Export de %d membres sélectionnés", membreIds.size()); + try { + byte[] excelData = membreService.exporterMembresSelectionnes(membreIds, format); + return Response.ok(excelData) + .header("Content-Disposition", "attachment; filename=\"membres_selection_" + + java.time.LocalDate.now() + "." + (format != null ? format.toLowerCase() : "xlsx") + "\"") + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'export de la sélection"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage())) + .build(); + } + } + + @POST + @Path("/import") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Importer des membres depuis un fichier Excel ou CSV") + @APIResponse(responseCode = "200", description = "Import terminé") + public Response importerMembres( + @Parameter(description = "Contenu du fichier à importer") @FormParam("file") byte[] fileContent, + @Parameter(description = "Nom du fichier") @FormParam("fileName") String fileName, + @Parameter(description = "ID de l'organisation (optionnel)") @FormParam("organisationId") UUID organisationId, + @Parameter(description = "Type de membre par défaut") @FormParam("typeMembreDefaut") String typeMembreDefaut, + @Parameter(description = "Mettre à jour les membres existants") @FormParam("mettreAJourExistants") boolean mettreAJourExistants, + @Parameter(description = "Ignorer les erreurs") @FormParam("ignorerErreurs") boolean ignorerErreurs) { + + try { + if (fileContent == null || fileContent.length == 0) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Aucun fichier fourni")) + .build(); + } + + if (fileName == null || fileName.isEmpty()) { + fileName = "import.xlsx"; + } + + if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) { + typeMembreDefaut = "ACTIF"; + } + + InputStream fileInputStream = new java.io.ByteArrayInputStream(fileContent); + dev.lions.unionflow.server.service.MembreImportExportService.ResultatImport resultat = membreService.importerMembres( + fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs); + + Map response = new HashMap<>(); + response.put("totalLignes", resultat.totalLignes); + response.put("lignesTraitees", resultat.lignesTraitees); + response.put("lignesErreur", resultat.lignesErreur); + response.put("erreurs", resultat.erreurs); + response.put("membresImportes", resultat.membresImportes); + + return Response.ok(response).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'import"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de l'import: " + e.getMessage())) + .build(); + } + } + + @GET + @Path("/export") + @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + @Operation(summary = "Exporter des membres en Excel, CSV ou PDF") + @APIResponse(responseCode = "200", description = "Fichier exporté") + public Response exporterMembres( + @Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format, + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId, + @Parameter(description = "Statut des membres") @QueryParam("statut") String statut, + @Parameter(description = "Type de membre") @QueryParam("type") String type, + @Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut, + @Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin, + @Parameter(description = "Colonnes à exporter") @QueryParam("colonnes") List colonnesExportList, + @Parameter(description = "Inclure les en-têtes") @QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders, + @Parameter(description = "Formater les dates") @QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates, + @Parameter(description = "Inclure un onglet statistiques (Excel uniquement)") @QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques, + @Parameter(description = "Mot de passe pour chiffrer le fichier (optionnel)") @QueryParam("motDePasse") String motDePasse) { + + try { + // Récupérer les membres selon les filtres + List membres = membreService.listerMembresPourExport( + associationId, statut, type, dateAdhesionDebut, dateAdhesionFin); + + byte[] exportData; + String contentType; + String extension; + + List colonnesExport = colonnesExportList != null ? colonnesExportList : new ArrayList<>(); + + if ("CSV".equalsIgnoreCase(format)) { + exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates); + contentType = "text/csv"; + extension = "csv"; + } else { + // Pour Excel, inclure les statistiques uniquement si demandé et si format Excel + boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format); + exportData = membreService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, stats, motDePasse); + contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + extension = "xlsx"; + } + + return Response.ok(exportData) + .type(contentType) + .header("Content-Disposition", "attachment; filename=\"membres_export_" + + java.time.LocalDate.now() + "." + extension + "\"") + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'export"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage())) + .build(); + } + } + + @GET + @Path("/import/modele") + @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + @Operation(summary = "Télécharger le modèle Excel pour l'import") + @APIResponse(responseCode = "200", description = "Modèle Excel généré") + public Response telechargerModeleImport() { + try { + byte[] modele = membreService.genererModeleImport(); + return Response.ok(modele) + .header("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"") + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la génération du modèle"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors de la génération du modèle: " + e.getMessage())) + .build(); + } + } + + @GET + @Path("/export/count") + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Compter les membres selon les filtres pour l'export") + @APIResponse(responseCode = "200", description = "Nombre de membres correspondant aux critères") + public Response compterMembresPourExport( + @Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId, + @Parameter(description = "Statut des membres") @QueryParam("statut") String statut, + @Parameter(description = "Type de membre") @QueryParam("type") String type, + @Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut, + @Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin) { + + try { + List membres = membreService.listerMembresPourExport( + associationId, statut, type, dateAdhesionDebut, dateAdhesionFin); + + return Response.ok(Map.of("count", membres.size())).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du comptage des membres"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur lors du comptage: " + e.getMessage())) + .build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java b/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java new file mode 100644 index 0000000..a0158bc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/NotificationResource.java @@ -0,0 +1,246 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.notification.NotificationDTO; +import dev.lions.unionflow.server.api.dto.notification.TemplateNotificationDTO; +import dev.lions.unionflow.server.service.NotificationService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion des notifications + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/notifications") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class NotificationResource { + + private static final Logger LOG = Logger.getLogger(NotificationResource.class); + + @Inject NotificationService notificationService; + + // ======================================== + // TEMPLATES + // ======================================== + + /** + * Crée un nouveau template de notification + * + * @param templateDTO DTO du template à créer + * @return Template créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/templates") + public Response creerTemplate(@Valid TemplateNotificationDTO templateDTO) { + try { + TemplateNotificationDTO result = notificationService.creerTemplate(templateDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du template"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du template: " + e.getMessage())) + .build(); + } + } + + // ======================================== + // NOTIFICATIONS + // ======================================== + + /** + * Crée une nouvelle notification + * + * @param notificationDTO DTO de la notification à créer + * @return Notification créée + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + public Response creerNotification(@Valid NotificationDTO notificationDTO) { + try { + NotificationDTO result = notificationService.creerNotification(notificationDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création de la notification"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création de la notification: " + e.getMessage())) + .build(); + } + } + + /** + * Marque une notification comme lue + * + * @param id ID de la notification + * @return Notification mise à jour + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/marquer-lue") + public Response marquerCommeLue(@PathParam("id") UUID id) { + try { + NotificationDTO result = notificationService.marquerCommeLue(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Notification non trouvée")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du marquage de la notification"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors du marquage de la notification: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve une notification par son ID + * + * @param id ID de la notification + * @return Notification + */ + @GET + @Path("/{id}") + public Response trouverNotificationParId(@PathParam("id") UUID id) { + try { + NotificationDTO result = notificationService.trouverNotificationParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Notification non trouvée")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche de la notification"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche de la notification: " + e.getMessage())) + .build(); + } + } + + /** + * Liste toutes les notifications d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications + */ + @GET + @Path("/membre/{membreId}") + public Response listerNotificationsParMembre(@PathParam("membreId") UUID membreId) { + try { + List result = notificationService.listerNotificationsParMembre(membreId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des notifications"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des notifications: " + e.getMessage())) + .build(); + } + } + + /** + * Liste les notifications non lues d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications non lues + */ + @GET + @Path("/membre/{membreId}/non-lues") + public Response listerNotificationsNonLuesParMembre(@PathParam("membreId") UUID membreId) { + try { + List result = notificationService.listerNotificationsNonLuesParMembre(membreId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des notifications non lues"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de la liste des notifications non lues: " + e.getMessage())) + .build(); + } + } + + /** + * Liste les notifications en attente d'envoi + * + * @return Liste des notifications en attente + */ + @GET + @Path("/en-attente-envoi") + public Response listerNotificationsEnAttenteEnvoi() { + try { + List result = notificationService.listerNotificationsEnAttenteEnvoi(); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des notifications en attente"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de la liste des notifications en attente: " + e.getMessage())) + .build(); + } + } + + /** + * Envoie des notifications groupées à plusieurs membres (WOU/DRY) + * + * @param request DTO contenant les IDs des membres, sujet, corps et canaux + * @return Nombre de notifications créées + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/groupees") + public Response envoyerNotificationsGroupees(NotificationGroupeeRequest request) { + try { + int notificationsCreees = + notificationService.envoyerNotificationsGroupees( + request.membreIds, request.sujet, request.corps, request.canaux); + return Response.ok(Map.of("notificationsCreees", notificationsCreees)).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'envoi des notifications groupées"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de l'envoi des notifications groupées: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } + + /** Classe interne pour les requêtes de notifications groupées (WOU/DRY) */ + public static class NotificationGroupeeRequest { + public List membreIds; + public String sujet; + public String corps; + public List canaux; + + public NotificationGroupeeRequest() {} + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java new file mode 100644 index 0000000..b3126ab --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java @@ -0,0 +1,423 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.service.KeycloakService; +import dev.lions.unionflow.server.service.OrganisationService; +import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion des organisations + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@Path("/api/organisations") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Organisations", description = "Gestion des organisations") +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class OrganisationResource { + + private static final Logger LOG = Logger.getLogger(OrganisationResource.class); + + @Inject OrganisationService organisationService; + + @Inject KeycloakService keycloakService; + + /** Crée une nouvelle organisation */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + + @Operation( + summary = "Créer une nouvelle organisation", + description = "Crée une nouvelle organisation dans le système") + @APIResponses({ + @APIResponse( + responseCode = "201", + description = "Organisation créée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = OrganisationDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "409", description = "Organisation déjà existante"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response creerOrganisation(@Valid OrganisationDTO organisationDTO) { + LOG.infof("Création d'une nouvelle organisation: %s", organisationDTO.getNom()); + + try { + Organisation organisation = organisationService.convertFromDTO(organisationDTO); + Organisation organisationCreee = organisationService.creerOrganisation(organisation); + OrganisationDTO dto = organisationService.convertToDTO(organisationCreee); + + return Response.created(URI.create("/api/organisations/" + organisationCreee.getId())) + .entity(dto) + .build(); + } catch (IllegalArgumentException e) { + LOG.warnf("Erreur lors de la création de l'organisation: %s", e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la création de l'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Récupère toutes les organisations actives */ + @GET + @jakarta.annotation.security.PermitAll // ✅ Accès public pour inscription + @Operation( + summary = "Lister les organisations", + description = "Récupère la liste des organisations actives avec pagination") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des organisations récupérée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response listerOrganisations( + @Parameter(description = "Numéro de page (commence à 0)", example = "0") + @QueryParam("page") + @DefaultValue("0") + int page, + @Parameter(description = "Taille de la page", example = "20") + @QueryParam("size") + @DefaultValue("20") + int size, + @Parameter(description = "Terme de recherche (nom ou nom court)") @QueryParam("recherche") + String recherche) { + + LOG.infof( + "Récupération des organisations - page: %d, size: %d, recherche: %s", + page, size, recherche); + + try { + List organisations; + + if (recherche != null && !recherche.trim().isEmpty()) { + organisations = organisationService.rechercherOrganisations(recherche.trim(), page, size); + } else { + organisations = organisationService.listerOrganisationsActives(page, size); + } + + List dtos = + organisations.stream() + .map(organisationService::convertToDTO) + .collect(Collectors.toList()); + + return Response.ok(dtos).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des organisations"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Récupère une organisation par son ID */ + @GET + @Path("/{id}") + + @Operation( + summary = "Récupérer une organisation", + description = "Récupère une organisation par son ID") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Organisation trouvée", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = OrganisationDTO.class))), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response obtenirOrganisation( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { + + LOG.infof("Récupération de l'organisation ID: %d", id); + + return organisationService + .trouverParId(id) + .map( + organisation -> { + OrganisationDTO dto = organisationService.convertToDTO(organisation); + return Response.ok(dto).build(); + }) + .orElse( + Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", "Organisation non trouvée")) + .build()); + } + + /** Met à jour une organisation */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}") + + @Operation( + summary = "Mettre à jour une organisation", + description = "Met à jour les informations d'une organisation") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Organisation mise à jour avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = OrganisationDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "409", description = "Conflit de données"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response mettreAJourOrganisation( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id, + @Valid OrganisationDTO organisationDTO) { + + LOG.infof("Mise à jour de l'organisation ID: %s", id); + + try { + Organisation organisationMiseAJour = organisationService.convertFromDTO(organisationDTO); + Organisation organisation = + organisationService.mettreAJourOrganisation(id, organisationMiseAJour, "system"); + OrganisationDTO dto = organisationService.convertToDTO(organisation); + + return Response.ok(dto).build(); + } catch (NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (IllegalArgumentException e) { + LOG.warnf("Erreur lors de la mise à jour de l'organisation: %s", e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la mise à jour de l'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Supprime une organisation */ + @DELETE + @RolesAllowed({"ADMIN"}) + @Path("/{id}") + + @Operation( + summary = "Supprimer une organisation", + description = "Supprime une organisation (soft delete)") + @APIResponses({ + @APIResponse(responseCode = "204", description = "Organisation supprimée avec succès"), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "409", description = "Impossible de supprimer l'organisation"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response supprimerOrganisation( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { + + LOG.infof("Suppression de l'organisation ID: %d", id); + + try { + organisationService.supprimerOrganisation(id, "system"); + return Response.noContent().build(); + } catch (NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (IllegalStateException e) { + LOG.warnf("Erreur lors de la suppression de l'organisation: %s", e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la suppression de l'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Recherche avancée d'organisations */ + @GET + @Path("/recherche") + + @Operation( + summary = "Recherche avancée", + description = "Recherche d'organisations avec critères multiples") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Résultats de recherche", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(type = SchemaType.ARRAY, implementation = OrganisationDTO.class))), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response rechercheAvancee( + @Parameter(description = "Nom de l'organisation") @QueryParam("nom") String nom, + @Parameter(description = "Type d'organisation") @QueryParam("type") String typeOrganisation, + @Parameter(description = "Statut") @QueryParam("statut") String statut, + @Parameter(description = "Ville") @QueryParam("ville") String ville, + @Parameter(description = "Région") @QueryParam("region") String region, + @Parameter(description = "Pays") @QueryParam("pays") String pays, + @Parameter(description = "Numéro de page") @QueryParam("page") @DefaultValue("0") int page, + @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") + int size) { + + LOG.infof("Recherche avancée d'organisations avec critères multiples"); + + try { + List organisations = + organisationService.rechercheAvancee( + nom, typeOrganisation, statut, ville, region, pays, page, size); + + List dtos = + organisations.stream() + .map(organisationService::convertToDTO) + .collect(Collectors.toList()); + + return Response.ok(dtos).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche avancée"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Active une organisation */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/activer") + + @Operation( + summary = "Activer une organisation", + description = "Active une organisation suspendue") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Organisation activée avec succès"), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response activerOrganisation( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { + + LOG.infof("Activation de l'organisation ID: %d", id); + + try { + Organisation organisation = organisationService.activerOrganisation(id, "system"); + OrganisationDTO dto = organisationService.convertToDTO(organisation); + return Response.ok(dto).build(); + } catch (NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'activation de l'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Suspend une organisation */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/suspendre") + + @Operation( + summary = "Suspendre une organisation", + description = "Suspend une organisation active") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Organisation suspendue avec succès"), + @APIResponse(responseCode = "404", description = "Organisation non trouvée"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response suspendreOrganisation( + @Parameter(description = "UUID de l'organisation", required = true) @PathParam("id") UUID id) { + + LOG.infof("Suspension de l'organisation ID: %d", id); + + try { + Organisation organisation = organisationService.suspendreOrganisation(id, "system"); + OrganisationDTO dto = organisationService.convertToDTO(organisation); + return Response.ok(dto).build(); + } catch (NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la suspension de l'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Obtient les statistiques des organisations */ + @GET + @Path("/statistiques") + + @Operation( + summary = "Statistiques des organisations", + description = "Récupère les statistiques globales des organisations") + @APIResponses({ + @APIResponse(responseCode = "200", description = "Statistiques récupérées avec succès"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response obtenirStatistiques() { + LOG.info("Récupération des statistiques des organisations"); + + try { + Map statistiques = organisationService.obtenirStatistiques(); + return Response.ok(statistiques).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la récupération des statistiques"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/PaiementResource.java b/src/main/java/dev/lions/unionflow/server/resource/PaiementResource.java new file mode 100644 index 0000000..c732483 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/PaiementResource.java @@ -0,0 +1,213 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO; +import dev.lions.unionflow.server.service.PaiementService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion des paiements + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/paiements") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class PaiementResource { + + private static final Logger LOG = Logger.getLogger(PaiementResource.class); + + @Inject PaiementService paiementService; + + /** + * Crée un nouveau paiement + * + * @param paiementDTO DTO du paiement à créer + * @return Paiement créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + public Response creerPaiement(@Valid PaiementDTO paiementDTO) { + try { + PaiementDTO result = paiementService.creerPaiement(paiementDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Met à jour un paiement + * + * @param id ID du paiement + * @param paiementDTO DTO avec les modifications + * @return Paiement mis à jour + */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}") + public Response mettreAJourPaiement(@PathParam("id") UUID id, @Valid PaiementDTO paiementDTO) { + try { + PaiementDTO result = paiementService.mettreAJourPaiement(id, paiementDTO); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Paiement non trouvé")) + .build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la mise à jour du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la mise à jour du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Valide un paiement + * + * @param id ID du paiement + * @return Paiement validé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/valider") + public Response validerPaiement(@PathParam("id") UUID id) { + try { + PaiementDTO result = paiementService.validerPaiement(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Paiement non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la validation du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la validation du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Annule un paiement + * + * @param id ID du paiement + * @return Paiement annulé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}/annuler") + public Response annulerPaiement(@PathParam("id") UUID id) { + try { + PaiementDTO result = paiementService.annulerPaiement(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Paiement non trouvé")) + .build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'annulation du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de l'annulation du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un paiement par son ID + * + * @param id ID du paiement + * @return Paiement + */ + @GET + @Path("/{id}") + public Response trouverParId(@PathParam("id") UUID id) { + try { + PaiementDTO result = paiementService.trouverParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Paiement non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un paiement par son numéro de référence + * + * @param numeroReference Numéro de référence + * @return Paiement + */ + @GET + @Path("/reference/{numeroReference}") + public Response trouverParNumeroReference(@PathParam("numeroReference") String numeroReference) { + try { + PaiementDTO result = paiementService.trouverParNumeroReference(numeroReference); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Paiement non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du paiement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage())) + .build(); + } + } + + /** + * Liste tous les paiements d'un membre + * + * @param membreId ID du membre + * @return Liste des paiements + */ + @GET + @Path("/membre/{membreId}") + public Response listerParMembre(@PathParam("membreId") UUID membreId) { + try { + List result = paiementService.listerParMembre(membreId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des paiements"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des paiements: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/PreferencesResource.java b/src/main/java/dev/lions/unionflow/server/resource/PreferencesResource.java new file mode 100644 index 0000000..eaada05 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/PreferencesResource.java @@ -0,0 +1,75 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.service.PreferencesNotificationService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Map; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** Resource REST pour la gestion des préférences utilisateur */ +@Path("/api/preferences") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@ApplicationScoped +@Tag(name = "Préférences", description = "API de gestion des préférences utilisateur") +public class PreferencesResource { + + private static final Logger LOG = Logger.getLogger(PreferencesResource.class); + + @Inject PreferencesNotificationService preferencesService; + + @GET + @Path("/{utilisateurId}") + @RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"}) + @Operation(summary = "Obtenir les préférences d'un utilisateur") + @APIResponse(responseCode = "200", description = "Préférences récupérées avec succès") + public Response obtenirPreferences( + @PathParam("utilisateurId") UUID utilisateurId) { + LOG.infof("Récupération des préférences pour l'utilisateur %s", utilisateurId); + Map preferences = preferencesService.obtenirPreferences(utilisateurId); + return Response.ok(preferences).build(); + } + + @PUT + @Path("/{utilisateurId}") + @RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"}) + @Operation(summary = "Mettre à jour les préférences d'un utilisateur") + @APIResponse(responseCode = "204", description = "Préférences mises à jour avec succès") + public Response mettreAJourPreferences( + @PathParam("utilisateurId") UUID utilisateurId, Map preferences) { + LOG.infof("Mise à jour des préférences pour l'utilisateur %s", utilisateurId); + preferencesService.mettreAJourPreferences(utilisateurId, preferences); + return Response.noContent().build(); + } + + @POST + @Path("/{utilisateurId}/reinitialiser") + @RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"}) + @Operation(summary = "Réinitialiser les préférences d'un utilisateur") + @APIResponse(responseCode = "204", description = "Préférences réinitialisées avec succès") + public Response reinitialiserPreferences(@PathParam("utilisateurId") UUID utilisateurId) { + LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId); + preferencesService.reinitialiserPreferences(utilisateurId); + return Response.noContent().build(); + } + + @GET + @Path("/{utilisateurId}/export") + @RolesAllowed({"USER", "ADMIN", "SUPER_ADMIN"}) + @Operation(summary = "Exporter les préférences d'un utilisateur") + @APIResponse(responseCode = "200", description = "Préférences exportées avec succès") + public Response exporterPreferences(@PathParam("utilisateurId") UUID utilisateurId) { + LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId); + Map export = preferencesService.exporterPreferences(utilisateurId); + return Response.ok(export).build(); + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationResource.java new file mode 100644 index 0000000..66f0703 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/TypeOrganisationResource.java @@ -0,0 +1,165 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.organisation.TypeOrganisationDTO; +import dev.lions.unionflow.server.service.TypeOrganisationService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.jboss.logging.Logger; + +/** + * Ressource REST pour la gestion du catalogue des types d'organisation. + */ +@Path("/api/types-organisations") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Types d'organisation", description = "Catalogue des types d'organisation") +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class TypeOrganisationResource { + + private static final Logger LOG = Logger.getLogger(TypeOrganisationResource.class); + + @Inject TypeOrganisationService service; + + /** Liste les types d'organisation. */ + @GET + @Operation( + summary = "Lister les types d'organisation", + description = "Récupère la liste des types d'organisation, optionnellement seulement actifs") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste des types récupérée avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = TypeOrganisationDTO.class))), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response listTypes( + @Parameter(description = "Limiter aux types actifs", example = "true") + @QueryParam("onlyActifs") + @DefaultValue("true") + String onlyActifs) { + // Parsing manuel pour éviter toute erreur de conversion JAX-RS (qui peut renvoyer une 400) + boolean actifsSeulement = !"false".equalsIgnoreCase(onlyActifs); + List types = service.listAll(actifsSeulement); + return Response.ok(types).build(); + } + + /** Crée un nouveau type d'organisation (réservé à l'administration). */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Operation( + summary = "Créer un type d'organisation", + description = "Crée un nouveau type dans le catalogue (code doit exister dans l'enum)") + @APIResponses({ + @APIResponse( + responseCode = "201", + description = "Type créé avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = TypeOrganisationDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response create(TypeOrganisationDTO dto) { + try { + TypeOrganisationDTO created = service.create(dto); + return Response.status(Response.Status.CREATED).entity(created).build(); + } catch (IllegalArgumentException e) { + LOG.warnf("Erreur lors de la création du type d'organisation: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la création du type d'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Met à jour un type. */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/{id}") + @Operation( + summary = "Mettre à jour un type d'organisation", + description = "Met à jour un type existant (libellé, description, ordre, actif, code)") + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Type mis à jour avec succès", + content = + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = TypeOrganisationDTO.class))), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "404", description = "Type non trouvé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response update(@PathParam("id") UUID id, TypeOrganisationDTO dto) { + try { + TypeOrganisationDTO updated = service.update(id, dto); + return Response.ok(updated).build(); + } catch (IllegalArgumentException e) { + LOG.warnf("Erreur lors de la mise à jour du type d'organisation: %s", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la mise à jour du type d'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } + + /** Désactive un type (soft delete). */ + @DELETE + @RolesAllowed({"ADMIN"}) + @Path("/{id}") + @Operation( + summary = "Désactiver un type d'organisation", + description = "Désactive un type dans le catalogue (soft delete)") + @APIResponses({ + @APIResponse(responseCode = "204", description = "Type désactivé avec succès"), + @APIResponse(responseCode = "404", description = "Type non trouvé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Non autorisé") + }) + public Response disable(@PathParam("id") UUID id) { + try { + service.disable(id); + return Response.noContent().build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(Map.of("error", e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur inattendue lors de la désactivation du type d'organisation"); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", "Erreur interne du serveur")) + .build(); + } + } +} + + diff --git a/src/main/java/dev/lions/unionflow/server/resource/WaveResource.java b/src/main/java/dev/lions/unionflow/server/resource/WaveResource.java new file mode 100644 index 0000000..63b9b4c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/WaveResource.java @@ -0,0 +1,269 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO; +import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO; +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import dev.lions.unionflow.server.service.WaveService; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour l'intégration Wave Mobile Money + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/wave") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RolesAllowed({"ADMIN", "MEMBRE", "USER"}) +public class WaveResource { + + private static final Logger LOG = Logger.getLogger(WaveResource.class); + + @Inject WaveService waveService; + + // ======================================== + // COMPTES WAVE + // ======================================== + + /** + * Crée un nouveau compte Wave + * + * @param compteWaveDTO DTO du compte à créer + * @return Compte créé + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/comptes") + public Response creerCompteWave(@Valid CompteWaveDTO compteWaveDTO) { + try { + CompteWaveDTO result = waveService.creerCompteWave(compteWaveDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du compte Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du compte Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Met à jour un compte Wave + * + * @param id ID du compte + * @param compteWaveDTO DTO avec les modifications + * @return Compte mis à jour + */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/comptes/{id}") + public Response mettreAJourCompteWave(@PathParam("id") UUID id, @Valid CompteWaveDTO compteWaveDTO) { + try { + CompteWaveDTO result = waveService.mettreAJourCompteWave(id, compteWaveDTO); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Compte Wave non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la mise à jour du compte Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la mise à jour du compte Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Vérifie un compte Wave + * + * @param id ID du compte + * @return Compte vérifié + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/comptes/{id}/verifier") + public Response verifierCompteWave(@PathParam("id") UUID id) { + try { + CompteWaveDTO result = waveService.verifierCompteWave(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Compte Wave non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la vérification du compte Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la vérification du compte Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un compte Wave par son ID + * + * @param id ID du compte + * @return Compte Wave + */ + @GET + @Path("/comptes/{id}") + public Response trouverCompteWaveParId(@PathParam("id") UUID id) { + try { + CompteWaveDTO result = waveService.trouverCompteWaveParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Compte Wave non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du compte Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un compte Wave par numéro de téléphone + * + * @param numeroTelephone Numéro de téléphone + * @return Compte Wave ou null + */ + @GET + @Path("/comptes/telephone/{numeroTelephone}") + public Response trouverCompteWaveParTelephone(@PathParam("numeroTelephone") String numeroTelephone) { + try { + CompteWaveDTO result = waveService.trouverCompteWaveParTelephone(numeroTelephone); + if (result == null) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Compte Wave non trouvé")) + .build(); + } + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du compte Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Liste tous les comptes Wave d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des comptes Wave + */ + @GET + @Path("/comptes/organisation/{organisationId}") + public Response listerComptesWaveParOrganisation(@PathParam("organisationId") UUID organisationId) { + try { + List result = waveService.listerComptesWaveParOrganisation(organisationId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des comptes Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des comptes Wave: " + e.getMessage())) + .build(); + } + } + + // ======================================== + // TRANSACTIONS WAVE + // ======================================== + + /** + * Crée une nouvelle transaction Wave + * + * @param transactionWaveDTO DTO de la transaction à créer + * @return Transaction créée + */ + @POST + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/transactions") + public Response creerTransactionWave(@Valid TransactionWaveDTO transactionWaveDTO) { + try { + TransactionWaveDTO result = waveService.creerTransactionWave(transactionWaveDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création de la transaction Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création de la transaction Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Met à jour le statut d'une transaction Wave + * + * @param waveTransactionId Identifiant Wave de la transaction + * @param statut Nouveau statut + * @return Transaction mise à jour + */ + @PUT + @RolesAllowed({"ADMIN", "MEMBRE"}) + @Path("/transactions/{waveTransactionId}/statut") + public Response mettreAJourStatutTransaction( + @PathParam("waveTransactionId") String waveTransactionId, StatutTransactionWave statut) { + try { + TransactionWaveDTO result = waveService.mettreAJourStatutTransaction(waveTransactionId, statut); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Transaction Wave non trouvée")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la mise à jour du statut de la transaction Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve une transaction Wave par son identifiant Wave + * + * @param waveTransactionId Identifiant Wave + * @return Transaction Wave + */ + @GET + @Path("/transactions/{waveTransactionId}") + public Response trouverTransactionWaveParId(@PathParam("waveTransactionId") String waveTransactionId) { + try { + TransactionWaveDTO result = waveService.trouverTransactionWaveParId(waveTransactionId); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Transaction Wave non trouvée")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche de la transaction Wave"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche de la transaction Wave: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/security/SecurityConfig.java b/src/main/java/dev/lions/unionflow/server/security/SecurityConfig.java new file mode 100644 index 0000000..bea4aa8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/security/SecurityConfig.java @@ -0,0 +1,214 @@ +package dev.lions.unionflow.server.security; + +import dev.lions.unionflow.server.service.KeycloakService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.Set; +import org.jboss.logging.Logger; + +/** + * Configuration et utilitaires de sécurité avec Keycloak + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@ApplicationScoped +public class SecurityConfig { + + private static final Logger LOG = Logger.getLogger(SecurityConfig.class); + + @Inject KeycloakService keycloakService; + + /** Rôles disponibles dans l'application */ + public static class Roles { + public static final String ADMIN = "ADMIN"; + public static final String GESTIONNAIRE_MEMBRE = "GESTIONNAIRE_MEMBRE"; + public static final String TRESORIER = "TRESORIER"; + public static final String SECRETAIRE = "SECRETAIRE"; + public static final String MEMBRE = "MEMBRE"; + public static final String PRESIDENT = "PRESIDENT"; + public static final String VICE_PRESIDENT = "VICE_PRESIDENT"; + public static final String ORGANISATEUR_EVENEMENT = "ORGANISATEUR_EVENEMENT"; + public static final String GESTIONNAIRE_SOLIDARITE = "GESTIONNAIRE_SOLIDARITE"; + public static final String AUDITEUR = "AUDITEUR"; + } + + /** Permissions disponibles dans l'application */ + public static class Permissions { + // Permissions membres + public static final String CREATE_MEMBRE = "CREATE_MEMBRE"; + public static final String READ_MEMBRE = "READ_MEMBRE"; + public static final String UPDATE_MEMBRE = "UPDATE_MEMBRE"; + public static final String DELETE_MEMBRE = "DELETE_MEMBRE"; + + // Permissions organisations + public static final String CREATE_ORGANISATION = "CREATE_ORGANISATION"; + public static final String READ_ORGANISATION = "READ_ORGANISATION"; + public static final String UPDATE_ORGANISATION = "UPDATE_ORGANISATION"; + public static final String DELETE_ORGANISATION = "DELETE_ORGANISATION"; + + // Permissions événements + public static final String CREATE_EVENEMENT = "CREATE_EVENEMENT"; + public static final String READ_EVENEMENT = "READ_EVENEMENT"; + public static final String UPDATE_EVENEMENT = "UPDATE_EVENEMENT"; + public static final String DELETE_EVENEMENT = "DELETE_EVENEMENT"; + + // Permissions finances + public static final String CREATE_COTISATION = "CREATE_COTISATION"; + public static final String READ_COTISATION = "READ_COTISATION"; + public static final String UPDATE_COTISATION = "UPDATE_COTISATION"; + public static final String DELETE_COTISATION = "DELETE_COTISATION"; + + // Permissions solidarité + public static final String CREATE_SOLIDARITE = "CREATE_SOLIDARITE"; + public static final String READ_SOLIDARITE = "READ_SOLIDARITE"; + public static final String UPDATE_SOLIDARITE = "UPDATE_SOLIDARITE"; + public static final String DELETE_SOLIDARITE = "DELETE_SOLIDARITE"; + + // Permissions administration + public static final String ADMIN_USERS = "ADMIN_USERS"; + public static final String ADMIN_SYSTEM = "ADMIN_SYSTEM"; + public static final String VIEW_REPORTS = "VIEW_REPORTS"; + public static final String EXPORT_DATA = "EXPORT_DATA"; + } + + /** + * Vérifie si l'utilisateur actuel a un rôle spécifique + * + * @param role le rôle à vérifier + * @return true si l'utilisateur a le rôle + */ + public boolean hasRole(String role) { + return keycloakService.hasRole(role); + } + + /** + * Vérifie si l'utilisateur actuel a au moins un des rôles spécifiés + * + * @param roles les rôles à vérifier + * @return true si l'utilisateur a au moins un des rôles + */ + public boolean hasAnyRole(String... roles) { + return keycloakService.hasAnyRole(roles); + } + + /** + * Vérifie si l'utilisateur actuel a tous les rôles spécifiés + * + * @param roles les rôles à vérifier + * @return true si l'utilisateur a tous les rôles + */ + public boolean hasAllRoles(String... roles) { + return keycloakService.hasAllRoles(roles); + } + + /** + * Obtient l'ID de l'utilisateur actuel + * + * @return l'ID de l'utilisateur ou null si non authentifié + */ + public String getCurrentUserId() { + return keycloakService.getCurrentUserId(); + } + + /** + * Obtient l'email de l'utilisateur actuel + * + * @return l'email de l'utilisateur ou null si non authentifié + */ + public String getCurrentUserEmail() { + return keycloakService.getCurrentUserEmail(); + } + + /** + * Obtient tous les rôles de l'utilisateur actuel + * + * @return les rôles de l'utilisateur + */ + public Set getCurrentUserRoles() { + return keycloakService.getCurrentUserRoles(); + } + + /** + * Vérifie si l'utilisateur actuel est authentifié + * + * @return true si l'utilisateur est authentifié + */ + public boolean isAuthenticated() { + return keycloakService.isAuthenticated(); + } + + /** + * Vérifie si l'utilisateur actuel est un administrateur + * + * @return true si l'utilisateur est administrateur + */ + public boolean isAdmin() { + return hasRole(Roles.ADMIN); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les membres + * + * @return true si l'utilisateur peut gérer les membres + */ + public boolean canManageMembers() { + return hasAnyRole(Roles.ADMIN, Roles.GESTIONNAIRE_MEMBRE, Roles.PRESIDENT, Roles.SECRETAIRE); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les finances + * + * @return true si l'utilisateur peut gérer les finances + */ + public boolean canManageFinances() { + return hasAnyRole(Roles.ADMIN, Roles.TRESORIER, Roles.PRESIDENT); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les événements + * + * @return true si l'utilisateur peut gérer les événements + */ + public boolean canManageEvents() { + return hasAnyRole(Roles.ADMIN, Roles.ORGANISATEUR_EVENEMENT, Roles.PRESIDENT, Roles.SECRETAIRE); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les organisations + * + * @return true si l'utilisateur peut gérer les organisations + */ + public boolean canManageOrganizations() { + return hasAnyRole(Roles.ADMIN, Roles.PRESIDENT); + } + + /** + * Vérifie si l'utilisateur actuel peut accéder aux données d'un membre spécifique + * + * @param membreId l'ID du membre + * @return true si l'utilisateur peut accéder aux données + */ + public boolean canAccessMemberData(String membreId) { + // Un utilisateur peut toujours accéder à ses propres données + if (membreId.equals(getCurrentUserId())) { + return true; + } + + // Les gestionnaires peuvent accéder aux données de tous les membres + return canManageMembers(); + } + + /** Log les informations de sécurité pour debug */ + public void logSecurityInfo() { + if (LOG.isDebugEnabled()) { + if (isAuthenticated()) { + LOG.debugf( + "Utilisateur authentifié: %s, Rôles: %s", getCurrentUserEmail(), getCurrentUserRoles()); + } else { + LOG.debug("Utilisateur non authentifié"); + } + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/AdhesionService.java b/src/main/java/dev/lions/unionflow/server/service/AdhesionService.java new file mode 100644 index 0000000..55aa855 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/AdhesionService.java @@ -0,0 +1,559 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.finance.AdhesionDTO; +import dev.lions.unionflow.server.entity.Adhesion; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.AdhesionRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.NotFoundException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +/** + * Service métier pour la gestion des adhésions + * Contient la logique métier et les règles de validation + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@ApplicationScoped +@Slf4j +public class AdhesionService { + + @Inject AdhesionRepository adhesionRepository; + + @Inject MembreRepository membreRepository; + + @Inject OrganisationRepository organisationRepository; + + /** + * Récupère toutes les adhésions avec pagination + * + * @param page numéro de page (0-based) + * @param size taille de la page + * @return liste des adhésions converties en DTO + */ + public List getAllAdhesions(int page, int size) { + log.debug("Récupération des adhésions - page: {}, size: {}", page, size); + + jakarta.persistence.TypedQuery query = + adhesionRepository + .getEntityManager() + .createQuery( + "SELECT a FROM Adhesion a ORDER BY a.dateDemande DESC", Adhesion.class); + query.setFirstResult(page * size); + query.setMaxResults(size); + List adhesions = query.getResultList(); + + return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère une adhésion par son ID + * + * @param id identifiant UUID de l'adhésion + * @return DTO de l'adhésion + * @throws NotFoundException si l'adhésion n'existe pas + */ + public AdhesionDTO getAdhesionById(@NotNull UUID id) { + log.debug("Récupération de l'adhésion avec ID: {}", id); + + Adhesion adhesion = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + return convertToDTO(adhesion); + } + + /** + * Récupère une adhésion par son numéro de référence + * + * @param numeroReference numéro de référence unique + * @return DTO de l'adhésion + * @throws NotFoundException si l'adhésion n'existe pas + */ + public AdhesionDTO getAdhesionByReference(@NotNull String numeroReference) { + log.debug("Récupération de l'adhésion avec référence: {}", numeroReference); + + Adhesion adhesion = + adhesionRepository + .findByNumeroReference(numeroReference) + .orElseThrow( + () -> + new NotFoundException( + "Adhésion non trouvée avec la référence: " + numeroReference)); + + return convertToDTO(adhesion); + } + + /** + * Crée une nouvelle adhésion + * + * @param adhesionDTO données de l'adhésion à créer + * @return DTO de l'adhésion créée + */ + @Transactional + public AdhesionDTO createAdhesion(@Valid AdhesionDTO adhesionDTO) { + log.info( + "Création d'une nouvelle adhésion pour le membre: {} et l'organisation: {}", + adhesionDTO.getMembreId(), + adhesionDTO.getOrganisationId()); + + // Validation du membre + Membre membre = + membreRepository + .findByIdOptional(adhesionDTO.getMembreId()) + .orElseThrow( + () -> + new NotFoundException( + "Membre non trouvé avec l'ID: " + adhesionDTO.getMembreId())); + + // Validation de l'organisation + Organisation organisation = + organisationRepository + .findByIdOptional(adhesionDTO.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + adhesionDTO.getOrganisationId())); + + // Conversion DTO vers entité + Adhesion adhesion = convertToEntity(adhesionDTO); + adhesion.setMembre(membre); + adhesion.setOrganisation(organisation); + + // Génération automatique du numéro de référence si absent + if (adhesion.getNumeroReference() == null || adhesion.getNumeroReference().isEmpty()) { + adhesion.setNumeroReference(genererNumeroReference()); + } + + // Initialisation par défaut + if (adhesion.getDateDemande() == null) { + adhesion.setDateDemande(LocalDate.now()); + } + if (adhesion.getStatut() == null || adhesion.getStatut().isEmpty()) { + adhesion.setStatut("EN_ATTENTE"); + } + if (adhesion.getMontantPaye() == null) { + adhesion.setMontantPaye(BigDecimal.ZERO); + } + if (adhesion.getCodeDevise() == null || adhesion.getCodeDevise().isEmpty()) { + adhesion.setCodeDevise("XOF"); + } + + // Persistance + adhesionRepository.persist(adhesion); + + log.info( + "Adhésion créée avec succès - ID: {}, Référence: {}", + adhesion.getId(), + adhesion.getNumeroReference()); + + return convertToDTO(adhesion); + } + + /** + * Met à jour une adhésion existante + * + * @param id identifiant UUID de l'adhésion + * @param adhesionDTO nouvelles données + * @return DTO de l'adhésion mise à jour + */ + @Transactional + public AdhesionDTO updateAdhesion(@NotNull UUID id, @Valid AdhesionDTO adhesionDTO) { + log.info("Mise à jour de l'adhésion avec ID: {}", id); + + Adhesion adhesionExistante = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + // Mise à jour des champs modifiables + updateAdhesionFields(adhesionExistante, adhesionDTO); + + log.info("Adhésion mise à jour avec succès - ID: {}", id); + + return convertToDTO(adhesionExistante); + } + + /** + * Supprime (désactive) une adhésion + * + * @param id identifiant UUID de l'adhésion + */ + @Transactional + public void deleteAdhesion(@NotNull UUID id) { + log.info("Suppression de l'adhésion avec ID: {}", id); + + Adhesion adhesion = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + // Vérification si l'adhésion peut être supprimée + if ("PAYEE".equals(adhesion.getStatut())) { + throw new IllegalStateException("Impossible de supprimer une adhésion déjà payée"); + } + + adhesion.setStatut("ANNULEE"); + + log.info("Adhésion supprimée avec succès - ID: {}", id); + } + + /** + * Approuve une adhésion + * + * @param id identifiant UUID de l'adhésion + * @param approuvePar nom de l'utilisateur qui approuve + * @return DTO de l'adhésion approuvée + */ + @Transactional + public AdhesionDTO approuverAdhesion(@NotNull UUID id, String approuvePar) { + log.info("Approbation de l'adhésion avec ID: {}", id); + + Adhesion adhesion = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + if (!"EN_ATTENTE".equals(adhesion.getStatut())) { + throw new IllegalStateException( + "Seules les adhésions en attente peuvent être approuvées"); + } + + adhesion.setStatut("APPROUVEE"); + adhesion.setDateApprobation(LocalDate.now()); + adhesion.setApprouvePar(approuvePar); + adhesion.setDateValidation(LocalDate.now()); + + log.info("Adhésion approuvée avec succès - ID: {}", id); + + return convertToDTO(adhesion); + } + + /** + * Rejette une adhésion + * + * @param id identifiant UUID de l'adhésion + * @param motifRejet motif du rejet + * @return DTO de l'adhésion rejetée + */ + @Transactional + public AdhesionDTO rejeterAdhesion(@NotNull UUID id, String motifRejet) { + log.info("Rejet de l'adhésion avec ID: {}", id); + + Adhesion adhesion = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + if (!"EN_ATTENTE".equals(adhesion.getStatut())) { + throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées"); + } + + adhesion.setStatut("REJETEE"); + adhesion.setMotifRejet(motifRejet); + + log.info("Adhésion rejetée avec succès - ID: {}", id); + + return convertToDTO(adhesion); + } + + /** + * Enregistre un paiement pour une adhésion + * + * @param id identifiant UUID de l'adhésion + * @param montantPaye montant payé + * @param methodePaiement méthode de paiement + * @param referencePaiement référence du paiement + * @return DTO de l'adhésion mise à jour + */ + @Transactional + public AdhesionDTO enregistrerPaiement( + @NotNull UUID id, + BigDecimal montantPaye, + String methodePaiement, + String referencePaiement) { + log.info("Enregistrement du paiement pour l'adhésion avec ID: {}", id); + + Adhesion adhesion = + adhesionRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id)); + + if (!"APPROUVEE".equals(adhesion.getStatut()) && !"EN_PAIEMENT".equals(adhesion.getStatut())) { + throw new IllegalStateException( + "Seules les adhésions approuvées peuvent recevoir un paiement"); + } + + BigDecimal nouveauMontantPaye = + adhesion.getMontantPaye() != null + ? adhesion.getMontantPaye().add(montantPaye) + : montantPaye; + + adhesion.setMontantPaye(nouveauMontantPaye); + adhesion.setMethodePaiement(methodePaiement); + adhesion.setReferencePaiement(referencePaiement); + adhesion.setDatePaiement(java.time.LocalDateTime.now()); + + // Mise à jour du statut si payée intégralement + if (adhesion.isPayeeIntegralement()) { + adhesion.setStatut("PAYEE"); + } else { + adhesion.setStatut("EN_PAIEMENT"); + } + + log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id); + + return convertToDTO(adhesion); + } + + /** + * Récupère les adhésions d'un membre + * + * @param membreId identifiant UUID du membre + * @param page numéro de page + * @param size taille de la page + * @return liste des adhésions du membre + */ + public List getAdhesionsByMembre(@NotNull UUID membreId, int page, int size) { + log.debug("Récupération des adhésions du membre: {}", membreId); + + if (!membreRepository.findByIdOptional(membreId).isPresent()) { + throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId); + } + + List adhesions = + adhesionRepository.findByMembreId(membreId).stream() + .skip(page * size) + .limit(size) + .collect(Collectors.toList()); + + return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les adhésions d'une organisation + * + * @param organisationId identifiant UUID de l'organisation + * @param page numéro de page + * @param size taille de la page + * @return liste des adhésions de l'organisation + */ + public List getAdhesionsByOrganisation( + @NotNull UUID organisationId, int page, int size) { + log.debug("Récupération des adhésions de l'organisation: {}", organisationId); + + if (!organisationRepository.findByIdOptional(organisationId).isPresent()) { + throw new NotFoundException("Organisation non trouvée avec l'ID: " + organisationId); + } + + List adhesions = + adhesionRepository.findByOrganisationId(organisationId).stream() + .skip(page * size) + .limit(size) + .collect(Collectors.toList()); + + return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les adhésions par statut + * + * @param statut statut recherché + * @param page numéro de page + * @param size taille de la page + * @return liste des adhésions avec le statut spécifié + */ + public List getAdhesionsByStatut(@NotNull String statut, int page, int size) { + log.debug("Récupération des adhésions avec statut: {}", statut); + + List adhesions = + adhesionRepository.findByStatut(statut).stream() + .skip(page * size) + .limit(size) + .collect(Collectors.toList()); + + return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les adhésions en attente + * + * @param page numéro de page + * @param size taille de la page + * @return liste des adhésions en attente + */ + public List getAdhesionsEnAttente(int page, int size) { + log.debug("Récupération des adhésions en attente"); + + List adhesions = + adhesionRepository.findEnAttente().stream() + .skip(page * size) + .limit(size) + .collect(Collectors.toList()); + + return adhesions.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les statistiques des adhésions + * + * @return map contenant les statistiques + */ + public Map getStatistiquesAdhesions() { + log.debug("Calcul des statistiques des adhésions"); + + long totalAdhesions = adhesionRepository.count(); + long adhesionsApprouvees = adhesionRepository.findByStatut("APPROUVEE").size(); + long adhesionsEnAttente = adhesionRepository.findEnAttente().size(); + long adhesionsPayees = adhesionRepository.findByStatut("PAYEE").size(); + + return Map.of( + "totalAdhesions", totalAdhesions, + "adhesionsApprouvees", adhesionsApprouvees, + "adhesionsEnAttente", adhesionsEnAttente, + "adhesionsPayees", adhesionsPayees, + "tauxApprobation", + totalAdhesions > 0 ? (adhesionsApprouvees * 100.0 / totalAdhesions) : 0.0, + "tauxPaiement", + adhesionsApprouvees > 0 + ? (adhesionsPayees * 100.0 / adhesionsApprouvees) + : 0.0); + } + + /** Génère un numéro de référence unique pour une adhésion */ + private String genererNumeroReference() { + return "ADH-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } + + /** Convertit une entité Adhesion en DTO */ + private AdhesionDTO convertToDTO(Adhesion adhesion) { + if (adhesion == null) { + return null; + } + + AdhesionDTO dto = new AdhesionDTO(); + + dto.setId(adhesion.getId()); + dto.setNumeroReference(adhesion.getNumeroReference()); + + // Conversion du membre associé + if (adhesion.getMembre() != null) { + dto.setMembreId(adhesion.getMembre().getId()); + dto.setNomMembre(adhesion.getMembre().getNomComplet()); + dto.setNumeroMembre(adhesion.getMembre().getNumeroMembre()); + dto.setEmailMembre(adhesion.getMembre().getEmail()); + } + + // Conversion de l'organisation + if (adhesion.getOrganisation() != null) { + dto.setOrganisationId(adhesion.getOrganisation().getId()); + dto.setNomOrganisation(adhesion.getOrganisation().getNom()); + } + + // Propriétés de l'adhésion + dto.setDateDemande(adhesion.getDateDemande()); + dto.setFraisAdhesion(adhesion.getFraisAdhesion()); + dto.setMontantPaye(adhesion.getMontantPaye()); + dto.setCodeDevise(adhesion.getCodeDevise()); + dto.setStatut(adhesion.getStatut()); + dto.setDateApprobation(adhesion.getDateApprobation()); + dto.setDatePaiement(adhesion.getDatePaiement()); + dto.setMethodePaiement(adhesion.getMethodePaiement()); + dto.setReferencePaiement(adhesion.getReferencePaiement()); + dto.setMotifRejet(adhesion.getMotifRejet()); + dto.setObservations(adhesion.getObservations()); + dto.setApprouvePar(adhesion.getApprouvePar()); + dto.setDateValidation(adhesion.getDateValidation()); + + // Métadonnées de BaseEntity + dto.setDateCreation(adhesion.getDateCreation()); + dto.setDateModification(adhesion.getDateModification()); + dto.setCreePar(adhesion.getCreePar()); + dto.setModifiePar(adhesion.getModifiePar()); + dto.setActif(adhesion.getActif()); + + return dto; + } + + /** Convertit un DTO en entité Adhesion */ + private Adhesion convertToEntity(AdhesionDTO dto) { + if (dto == null) { + return null; + } + + Adhesion adhesion = new Adhesion(); + + adhesion.setNumeroReference(dto.getNumeroReference()); + adhesion.setDateDemande(dto.getDateDemande()); + adhesion.setFraisAdhesion(dto.getFraisAdhesion()); + adhesion.setMontantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO); + adhesion.setCodeDevise(dto.getCodeDevise()); + adhesion.setStatut(dto.getStatut()); + adhesion.setDateApprobation(dto.getDateApprobation()); + adhesion.setDatePaiement(dto.getDatePaiement()); + adhesion.setMethodePaiement(dto.getMethodePaiement()); + adhesion.setReferencePaiement(dto.getReferencePaiement()); + adhesion.setMotifRejet(dto.getMotifRejet()); + adhesion.setObservations(dto.getObservations()); + adhesion.setApprouvePar(dto.getApprouvePar()); + adhesion.setDateValidation(dto.getDateValidation()); + + return adhesion; + } + + /** Met à jour les champs modifiables d'une adhésion existante */ + private void updateAdhesionFields(Adhesion adhesion, AdhesionDTO dto) { + if (dto.getFraisAdhesion() != null) { + adhesion.setFraisAdhesion(dto.getFraisAdhesion()); + } + if (dto.getMontantPaye() != null) { + adhesion.setMontantPaye(dto.getMontantPaye()); + } + if (dto.getStatut() != null) { + adhesion.setStatut(dto.getStatut()); + } + if (dto.getDateApprobation() != null) { + adhesion.setDateApprobation(dto.getDateApprobation()); + } + if (dto.getDatePaiement() != null) { + adhesion.setDatePaiement(dto.getDatePaiement()); + } + if (dto.getMethodePaiement() != null) { + adhesion.setMethodePaiement(dto.getMethodePaiement()); + } + if (dto.getReferencePaiement() != null) { + adhesion.setReferencePaiement(dto.getReferencePaiement()); + } + if (dto.getMotifRejet() != null) { + adhesion.setMotifRejet(dto.getMotifRejet()); + } + if (dto.getObservations() != null) { + adhesion.setObservations(dto.getObservations()); + } + if (dto.getApprouvePar() != null) { + adhesion.setApprouvePar(dto.getApprouvePar()); + } + if (dto.getDateValidation() != null) { + adhesion.setDateValidation(dto.getDateValidation()); + } + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/AdresseService.java b/src/main/java/dev/lions/unionflow/server/service/AdresseService.java new file mode 100644 index 0000000..ddcacc9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/AdresseService.java @@ -0,0 +1,353 @@ +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 + .findAdresseById(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 + .findAdresseById(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 + .findAdresseById(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 != null ? type : TypeAdresse.AUTRE; // Même enum, valeur par défaut si null + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/AnalyticsService.java b/src/main/java/dev/lions/unionflow/server/service/AnalyticsService.java new file mode 100644 index 0000000..3535da0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/AnalyticsService.java @@ -0,0 +1,478 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataDTO; +import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetDTO; +import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +// import dev.lions.unionflow.server.entity.DemandeAide; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; +import dev.lions.unionflow.server.repository.CotisationRepository; +import dev.lions.unionflow.server.repository.DemandeAideRepository; +import dev.lions.unionflow.server.repository.EvenementRepository; +// import dev.lions.unionflow.server.repository.DemandeAideRepository; +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 java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** + * Service principal pour les analytics et métriques UnionFlow + * + *

Ce service calcule et fournit toutes les métriques analytics pour les tableaux de bord, + * rapports et widgets. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +@Slf4j +public class AnalyticsService { + + @Inject OrganisationRepository organisationRepository; + + @Inject MembreRepository membreRepository; + + @Inject CotisationRepository cotisationRepository; + + @Inject DemandeAideRepository demandeAideRepository; + + @Inject EvenementRepository evenementRepository; + + // @Inject + // DemandeAideRepository demandeAideRepository; + + @Inject KPICalculatorService kpiCalculatorService; + + @Inject TrendAnalysisService trendAnalysisService; + + /** + * Calcule une métrique analytics pour une période donnée + * + * @param typeMetrique Le type de métrique à calculer + * @param periodeAnalyse La période d'analyse + * @param organisationId L'ID de l'organisation (optionnel) + * @return Les données analytics calculées + */ + @Transactional + public AnalyticsDataDTO calculerMetrique( + TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) { + log.info( + "Calcul de la métrique {} pour la période {} et l'organisation {}", + typeMetrique, + periodeAnalyse, + organisationId); + + LocalDateTime dateDebut = periodeAnalyse.getDateDebut(); + LocalDateTime dateFin = periodeAnalyse.getDateFin(); + + BigDecimal valeur = + switch (typeMetrique) { + // Métriques membres + case NOMBRE_MEMBRES_ACTIFS -> + calculerNombreMembresActifs(organisationId, dateDebut, dateFin); + case NOMBRE_MEMBRES_INACTIFS -> + calculerNombreMembresInactifs(organisationId, dateDebut, dateFin); + case TAUX_CROISSANCE_MEMBRES -> + calculerTauxCroissanceMembres(organisationId, dateDebut, dateFin); + case MOYENNE_AGE_MEMBRES -> calculerMoyenneAgeMembres(organisationId, dateDebut, dateFin); + + // Métriques financières + case TOTAL_COTISATIONS_COLLECTEES -> + calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin); + case COTISATIONS_EN_ATTENTE -> + calculerCotisationsEnAttente(organisationId, dateDebut, dateFin); + case TAUX_RECOUVREMENT_COTISATIONS -> + calculerTauxRecouvrementCotisations(organisationId, dateDebut, dateFin); + case MOYENNE_COTISATION_MEMBRE -> + calculerMoyenneCotisationMembre(organisationId, dateDebut, dateFin); + + // Métriques événements + case NOMBRE_EVENEMENTS_ORGANISES -> + calculerNombreEvenementsOrganises(organisationId, dateDebut, dateFin); + case TAUX_PARTICIPATION_EVENEMENTS -> + calculerTauxParticipationEvenements(organisationId, dateDebut, dateFin); + case MOYENNE_PARTICIPANTS_EVENEMENT -> + calculerMoyenneParticipantsEvenement(organisationId, dateDebut, dateFin); + + // Métriques solidarité + case NOMBRE_DEMANDES_AIDE -> + calculerNombreDemandesAide(organisationId, dateDebut, dateFin); + case MONTANT_AIDES_ACCORDEES -> + calculerMontantAidesAccordees(organisationId, dateDebut, dateFin); + case TAUX_APPROBATION_AIDES -> + calculerTauxApprobationAides(organisationId, dateDebut, dateFin); + + default -> BigDecimal.ZERO; + }; + + // Calcul de la valeur précédente pour comparaison + BigDecimal valeurPrecedente = + calculerValeurPrecedente(typeMetrique, periodeAnalyse, organisationId); + BigDecimal pourcentageEvolution = calculerPourcentageEvolution(valeur, valeurPrecedente); + + return AnalyticsDataDTO.builder() + .typeMetrique(typeMetrique) + .periodeAnalyse(periodeAnalyse) + .valeur(valeur) + .valeurPrecedente(valeurPrecedente) + .pourcentageEvolution(pourcentageEvolution) + .dateDebut(dateDebut) + .dateFin(dateFin) + .dateCalcul(LocalDateTime.now()) + .organisationId(organisationId) + .nomOrganisation(obtenirNomOrganisation(organisationId)) + .indicateurFiabilite(new BigDecimal("95.0")) + .niveauPriorite(3) + .tempsReel(false) + .necessiteMiseAJour(false) + .build(); + } + + /** + * Calcule les tendances d'un KPI sur une période + * + * @param typeMetrique Le type de métrique + * @param periodeAnalyse La période d'analyse + * @param organisationId L'ID de l'organisation (optionnel) + * @return Les données de tendance du KPI + */ + @Transactional + public KPITrendDTO calculerTendanceKPI( + TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) { + log.info( + "Calcul de la tendance KPI {} pour la période {} et l'organisation {}", + typeMetrique, + periodeAnalyse, + organisationId); + + return trendAnalysisService.calculerTendance(typeMetrique, periodeAnalyse, organisationId); + } + + /** + * Obtient les métriques pour un tableau de bord + * + * @param organisationId L'ID de l'organisation + * @param utilisateurId L'ID de l'utilisateur + * @return La liste des widgets du tableau de bord + */ + @Transactional + public List obtenirMetriquesTableauBord( + UUID organisationId, UUID utilisateurId) { + log.info( + "Obtention des métriques du tableau de bord pour l'organisation {} et l'utilisateur {}", + organisationId, + utilisateurId); + + List widgets = new ArrayList<>(); + + // Widget KPI Membres Actifs + widgets.add( + creerWidgetKPI( + TypeMetrique.NOMBRE_MEMBRES_ACTIFS, + PeriodeAnalyse.CE_MOIS, + organisationId, + utilisateurId, + 0, + 0, + 3, + 2)); + + // Widget KPI Cotisations + widgets.add( + creerWidgetKPI( + TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, + PeriodeAnalyse.CE_MOIS, + organisationId, + utilisateurId, + 3, + 0, + 3, + 2)); + + // Widget KPI Événements + widgets.add( + creerWidgetKPI( + TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES, + PeriodeAnalyse.CE_MOIS, + organisationId, + utilisateurId, + 6, + 0, + 3, + 2)); + + // Widget KPI Solidarité + widgets.add( + creerWidgetKPI( + TypeMetrique.NOMBRE_DEMANDES_AIDE, + PeriodeAnalyse.CE_MOIS, + organisationId, + utilisateurId, + 9, + 0, + 3, + 2)); + + // Widget Graphique Évolution Membres + widgets.add( + creerWidgetGraphique( + TypeMetrique.NOMBRE_MEMBRES_ACTIFS, + PeriodeAnalyse.SIX_DERNIERS_MOIS, + organisationId, + utilisateurId, + 0, + 2, + 6, + 4, + "line")); + + // Widget Graphique Évolution Financière + widgets.add( + creerWidgetGraphique( + TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, + PeriodeAnalyse.SIX_DERNIERS_MOIS, + organisationId, + utilisateurId, + 6, + 2, + 6, + 4, + "area")); + + return widgets; + } + + // === MÉTHODES PRIVÉES DE CALCUL === + + private BigDecimal calculerNombreMembresActifs( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerNombreMembresInactifs( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = membreRepository.countMembresInactifs(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerTauxCroissanceMembres( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long membresActuels = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + Long membresPrecedents = + membreRepository.countMembresActifs( + organisationId, dateDebut.minusMonths(1), dateFin.minusMonths(1)); + + if (membresPrecedents == 0) return BigDecimal.ZERO; + + BigDecimal croissance = + new BigDecimal(membresActuels - membresPrecedents) + .divide(new BigDecimal(membresPrecedents), 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + + return croissance; + } + + private BigDecimal calculerMoyenneAgeMembres( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Double moyenneAge = membreRepository.calculerMoyenneAge(organisationId, dateDebut, dateFin); + return moyenneAge != null + ? new BigDecimal(moyenneAge).setScale(1, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + } + + private BigDecimal calculerTotalCotisationsCollectees( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = cotisationRepository.sumMontantsPayes(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerCotisationsEnAttente( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = + cotisationRepository.sumMontantsEnAttente(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerTauxRecouvrementCotisations( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal collectees = calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin); + BigDecimal enAttente = calculerCotisationsEnAttente(organisationId, dateDebut, dateFin); + BigDecimal total = collectees.add(enAttente); + + if (total.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; + + return collectees.divide(total, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100")); + } + + private BigDecimal calculerMoyenneCotisationMembre( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = calculerTotalCotisationsCollectees(organisationId, dateDebut, dateFin); + Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + + if (nombreMembres == 0) return BigDecimal.ZERO; + + return total.divide(new BigDecimal(nombreMembres), 2, RoundingMode.HALF_UP); + } + + private BigDecimal calculerNombreEvenementsOrganises( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = evenementRepository.countEvenements(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerTauxParticipationEvenements( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + // Implémentation simplifiée - à enrichir selon les besoins + return new BigDecimal("75.5"); // Valeur par défaut + } + + private BigDecimal calculerMoyenneParticipantsEvenement( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Double moyenne = + evenementRepository.calculerMoyenneParticipants(organisationId, dateDebut, dateFin); + return moyenne != null + ? new BigDecimal(moyenne).setScale(1, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + } + + private BigDecimal calculerNombreDemandesAide( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerMontantAidesAccordees( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = + demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerTauxApprobationAides( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long totalDemandes = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin); + Long demandesApprouvees = + demandeAideRepository.countDemandesApprouvees(organisationId, dateDebut, dateFin); + + if (totalDemandes == 0) return BigDecimal.ZERO; + + return new BigDecimal(demandesApprouvees) + .divide(new BigDecimal(totalDemandes), 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + private BigDecimal calculerValeurPrecedente( + TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) { + // Calcul de la période précédente + LocalDateTime dateDebutPrecedente = + periodeAnalyse.getDateDebut().minus(periodeAnalyse.getDuree(), periodeAnalyse.getUnite()); + LocalDateTime dateFinPrecedente = + periodeAnalyse.getDateFin().minus(periodeAnalyse.getDuree(), periodeAnalyse.getUnite()); + + return switch (typeMetrique) { + case NOMBRE_MEMBRES_ACTIFS -> + calculerNombreMembresActifs(organisationId, dateDebutPrecedente, dateFinPrecedente); + case TOTAL_COTISATIONS_COLLECTEES -> + calculerTotalCotisationsCollectees( + organisationId, dateDebutPrecedente, dateFinPrecedente); + case NOMBRE_EVENEMENTS_ORGANISES -> + calculerNombreEvenementsOrganises(organisationId, dateDebutPrecedente, dateFinPrecedente); + case NOMBRE_DEMANDES_AIDE -> + calculerNombreDemandesAide(organisationId, dateDebutPrecedente, dateFinPrecedente); + default -> BigDecimal.ZERO; + }; + } + + private BigDecimal calculerPourcentageEvolution( + BigDecimal valeurActuelle, BigDecimal valeurPrecedente) { + if (valeurPrecedente == null || valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO; + } + + return valeurActuelle + .subtract(valeurPrecedente) + .divide(valeurPrecedente, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + private String obtenirNomOrganisation(UUID organisationId) { + // Temporairement désactivé pour éviter les erreurs de compilation + return "Organisation " + + (organisationId != null ? organisationId.toString().substring(0, 8) : "inconnue"); + } + + private DashboardWidgetDTO creerWidgetKPI( + TypeMetrique typeMetrique, + PeriodeAnalyse periodeAnalyse, + UUID organisationId, + UUID utilisateurId, + int positionX, + int positionY, + int largeur, + int hauteur) { + AnalyticsDataDTO data = calculerMetrique(typeMetrique, periodeAnalyse, organisationId); + + return DashboardWidgetDTO.builder() + .titre(typeMetrique.getLibelle()) + .typeWidget("kpi") + .typeMetrique(typeMetrique) + .periodeAnalyse(periodeAnalyse) + .organisationId(organisationId) + .utilisateurProprietaireId(utilisateurId) + .positionX(positionX) + .positionY(positionY) + .largeur(largeur) + .hauteur(hauteur) + .couleurPrincipale(typeMetrique.getCouleur()) + .icone(typeMetrique.getIcone()) + .donneesWidget(convertirEnJSON(data)) + .dateDerniereMiseAJour(LocalDateTime.now()) + .build(); + } + + private DashboardWidgetDTO creerWidgetGraphique( + TypeMetrique typeMetrique, + PeriodeAnalyse periodeAnalyse, + UUID organisationId, + UUID utilisateurId, + int positionX, + int positionY, + int largeur, + int hauteur, + String typeGraphique) { + KPITrendDTO trend = calculerTendanceKPI(typeMetrique, periodeAnalyse, organisationId); + + return DashboardWidgetDTO.builder() + .titre("Évolution " + typeMetrique.getLibelle()) + .typeWidget("chart") + .typeMetrique(typeMetrique) + .periodeAnalyse(periodeAnalyse) + .organisationId(organisationId) + .utilisateurProprietaireId(utilisateurId) + .positionX(positionX) + .positionY(positionY) + .largeur(largeur) + .hauteur(hauteur) + .couleurPrincipale(typeMetrique.getCouleur()) + .icone(typeMetrique.getIcone()) + .donneesWidget(convertirEnJSON(trend)) + .configurationVisuelle("{\"type\":\"" + typeGraphique + "\",\"responsive\":true}") + .dateDerniereMiseAJour(LocalDateTime.now()) + .build(); + } + + private String convertirEnJSON(Object data) { + // Implémentation simplifiée - utiliser Jackson en production + return "{}"; // À implémenter avec ObjectMapper + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/AuditService.java b/src/main/java/dev/lions/unionflow/server/service/AuditService.java new file mode 100644 index 0000000..a2fb126 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/AuditService.java @@ -0,0 +1,229 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO; +import dev.lions.unionflow.server.entity.AuditLog; +import dev.lions.unionflow.server.repository.AuditLogRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +/** + * Service pour la gestion des logs d'audit + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-17 + */ +@ApplicationScoped +@Slf4j +public class AuditService { + + @Inject + AuditLogRepository auditLogRepository; + + /** + * Enregistre un nouveau log d'audit + */ + @Transactional + public AuditLogDTO enregistrerLog(AuditLogDTO dto) { + log.debug("Enregistrement d'un log d'audit: {}", dto.getTypeAction()); + + AuditLog auditLog = convertToEntity(dto); + auditLogRepository.persist(auditLog); + + return convertToDTO(auditLog); + } + + /** + * Récupère tous les logs avec pagination + */ + public Map listerTous(int page, int size, String sortBy, String sortOrder) { + log.debug("Récupération des logs d'audit - page: {}, size: {}", page, size); + + String orderBy = sortBy != null ? sortBy : "dateHeure"; + String order = "desc".equalsIgnoreCase(sortOrder) ? "DESC" : "ASC"; + + var entityManager = auditLogRepository.getEntityManager(); + + // Compter le total + long total = auditLogRepository.count(); + + // Récupérer les logs avec pagination + var query = entityManager.createQuery( + "SELECT a FROM AuditLog a ORDER BY a." + orderBy + " " + order, AuditLog.class); + query.setFirstResult(page * size); + query.setMaxResults(size); + + List logs = query.getResultList(); + List dtos = logs.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + + return Map.of( + "data", dtos, + "total", total, + "page", page, + "size", size, + "totalPages", (int) Math.ceil((double) total / size) + ); + } + + /** + * Recherche les logs avec filtres + */ + public Map rechercher( + LocalDateTime dateDebut, LocalDateTime dateFin, + String typeAction, String severite, String utilisateur, + String module, String ipAddress, + int page, int size) { + + log.debug("Recherche de logs d'audit avec filtres"); + + // Construire la requête dynamique avec Criteria API + var entityManager = auditLogRepository.getEntityManager(); + var cb = entityManager.getCriteriaBuilder(); + var query = cb.createQuery(AuditLog.class); + var root = query.from(AuditLog.class); + + var predicates = new ArrayList(); + + if (dateDebut != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("dateHeure"), dateDebut)); + } + if (dateFin != null) { + predicates.add(cb.lessThanOrEqualTo(root.get("dateHeure"), dateFin)); + } + if (typeAction != null && !typeAction.isEmpty()) { + predicates.add(cb.equal(root.get("typeAction"), typeAction)); + } + if (severite != null && !severite.isEmpty()) { + predicates.add(cb.equal(root.get("severite"), severite)); + } + if (utilisateur != null && !utilisateur.isEmpty()) { + predicates.add(cb.like(cb.lower(root.get("utilisateur")), + "%" + utilisateur.toLowerCase() + "%")); + } + if (module != null && !module.isEmpty()) { + predicates.add(cb.equal(root.get("module"), module)); + } + if (ipAddress != null && !ipAddress.isEmpty()) { + predicates.add(cb.like(root.get("ipAddress"), "%" + ipAddress + "%")); + } + + query.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0])); + query.orderBy(cb.desc(root.get("dateHeure"))); + + // Compter le total + var countQuery = cb.createQuery(Long.class); + countQuery.select(cb.count(countQuery.from(AuditLog.class))); + countQuery.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0])); + long total = entityManager.createQuery(countQuery).getSingleResult(); + + // Récupérer les résultats avec pagination + var typedQuery = entityManager.createQuery(query); + typedQuery.setFirstResult(page * size); + typedQuery.setMaxResults(size); + + List logs = typedQuery.getResultList(); + List dtos = logs.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + + return Map.of( + "data", dtos, + "total", total, + "page", page, + "size", size, + "totalPages", (int) Math.ceil((double) total / size) + ); + } + + /** + * Récupère les statistiques d'audit + */ + public Map getStatistiques() { + long total = auditLogRepository.count(); + + var entityManager = auditLogRepository.getEntityManager(); + + long success = entityManager.createQuery( + "SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class) + .setParameter("severite", "SUCCESS") + .getSingleResult(); + + long errors = entityManager.createQuery( + "SELECT COUNT(a) FROM AuditLog a WHERE a.severite IN :severites", Long.class) + .setParameter("severites", List.of("ERROR", "CRITICAL")) + .getSingleResult(); + + long warnings = entityManager.createQuery( + "SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class) + .setParameter("severite", "WARNING") + .getSingleResult(); + + return Map.of( + "total", total, + "success", success, + "errors", errors, + "warnings", warnings + ); + } + + /** + * Convertit une entité en DTO + */ + private AuditLogDTO convertToDTO(AuditLog auditLog) { + AuditLogDTO dto = new AuditLogDTO(); + dto.setId(auditLog.getId()); + dto.setTypeAction(auditLog.getTypeAction()); + dto.setSeverite(auditLog.getSeverite()); + dto.setUtilisateur(auditLog.getUtilisateur()); + dto.setRole(auditLog.getRole()); + dto.setModule(auditLog.getModule()); + dto.setDescription(auditLog.getDescription()); + dto.setDetails(auditLog.getDetails()); + dto.setIpAddress(auditLog.getIpAddress()); + dto.setUserAgent(auditLog.getUserAgent()); + dto.setSessionId(auditLog.getSessionId()); + dto.setDateHeure(auditLog.getDateHeure()); + dto.setDonneesAvant(auditLog.getDonneesAvant()); + dto.setDonneesApres(auditLog.getDonneesApres()); + dto.setEntiteId(auditLog.getEntiteId()); + dto.setEntiteType(auditLog.getEntiteType()); + return dto; + } + + /** + * Convertit un DTO en entité + */ + private AuditLog convertToEntity(AuditLogDTO dto) { + AuditLog auditLog = new AuditLog(); + if (dto.getId() != null) { + auditLog.setId(dto.getId()); + } + auditLog.setTypeAction(dto.getTypeAction()); + auditLog.setSeverite(dto.getSeverite()); + auditLog.setUtilisateur(dto.getUtilisateur()); + auditLog.setRole(dto.getRole()); + auditLog.setModule(dto.getModule()); + auditLog.setDescription(dto.getDescription()); + auditLog.setDetails(dto.getDetails()); + auditLog.setIpAddress(dto.getIpAddress()); + auditLog.setUserAgent(dto.getUserAgent()); + auditLog.setSessionId(dto.getSessionId()); + auditLog.setDateHeure(dto.getDateHeure() != null ? dto.getDateHeure() : LocalDateTime.now()); + auditLog.setDonneesAvant(dto.getDonneesAvant()); + auditLog.setDonneesApres(dto.getDonneesApres()); + auditLog.setEntiteId(dto.getEntiteId()); + auditLog.setEntiteType(dto.getEntiteType()); + return auditLog; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java b/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java new file mode 100644 index 0000000..8a68cc0 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java @@ -0,0 +1,479 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.comptabilite.*; +import dev.lions.unionflow.server.entity.*; +import dev.lions.unionflow.server.repository.*; +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.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion comptable + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class ComptabiliteService { + + private static final Logger LOG = Logger.getLogger(ComptabiliteService.class); + + @Inject CompteComptableRepository compteComptableRepository; + + @Inject JournalComptableRepository journalComptableRepository; + + @Inject EcritureComptableRepository ecritureComptableRepository; + + @Inject LigneEcritureRepository ligneEcritureRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject PaiementRepository paiementRepository; + + @Inject KeycloakService keycloakService; + + // ======================================== + // COMPTES COMPTABLES + // ======================================== + + /** + * Crée un nouveau compte comptable + * + * @param compteDTO DTO du compte à créer + * @return DTO du compte créé + */ + @Transactional + public CompteComptableDTO creerCompteComptable(CompteComptableDTO compteDTO) { + LOG.infof("Création d'un nouveau compte comptable: %s", compteDTO.getNumeroCompte()); + + // Vérifier l'unicité du numéro + if (compteComptableRepository.findByNumeroCompte(compteDTO.getNumeroCompte()).isPresent()) { + throw new IllegalArgumentException("Un compte avec ce numéro existe déjà: " + compteDTO.getNumeroCompte()); + } + + CompteComptable compte = convertToEntity(compteDTO); + compte.setCreePar(keycloakService.getCurrentUserEmail()); + + compteComptableRepository.persist(compte); + LOG.infof("Compte comptable créé avec succès: ID=%s, Numéro=%s", compte.getId(), compte.getNumeroCompte()); + + return convertToDTO(compte); + } + + /** + * Trouve un compte comptable par son ID + * + * @param id ID du compte + * @return DTO du compte + */ + public CompteComptableDTO trouverCompteParId(UUID id) { + return compteComptableRepository + .findCompteComptableById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Compte comptable non trouvé avec l'ID: " + id)); + } + + /** + * Liste tous les comptes comptables actifs + * + * @return Liste des comptes + */ + public List listerTousLesComptes() { + return compteComptableRepository.findAllActifs().stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + // ======================================== + // JOURNAUX COMPTABLES + // ======================================== + + /** + * Crée un nouveau journal comptable + * + * @param journalDTO DTO du journal à créer + * @return DTO du journal créé + */ + @Transactional + public JournalComptableDTO creerJournalComptable(JournalComptableDTO journalDTO) { + LOG.infof("Création d'un nouveau journal comptable: %s", journalDTO.getCode()); + + // Vérifier l'unicité du code + if (journalComptableRepository.findByCode(journalDTO.getCode()).isPresent()) { + throw new IllegalArgumentException("Un journal avec ce code existe déjà: " + journalDTO.getCode()); + } + + JournalComptable journal = convertToEntity(journalDTO); + journal.setCreePar(keycloakService.getCurrentUserEmail()); + + journalComptableRepository.persist(journal); + LOG.infof("Journal comptable créé avec succès: ID=%s, Code=%s", journal.getId(), journal.getCode()); + + return convertToDTO(journal); + } + + /** + * Trouve un journal comptable par son ID + * + * @param id ID du journal + * @return DTO du journal + */ + public JournalComptableDTO trouverJournalParId(UUID id) { + return journalComptableRepository + .findJournalComptableById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + id)); + } + + /** + * Liste tous les journaux comptables actifs + * + * @return Liste des journaux + */ + public List listerTousLesJournaux() { + return journalComptableRepository.findAllActifs().stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + // ======================================== + // ÉCRITURES COMPTABLES + // ======================================== + + /** + * Crée une nouvelle écriture comptable avec validation de l'équilibre + * + * @param ecritureDTO DTO de l'écriture à créer + * @return DTO de l'écriture créée + */ + @Transactional + public EcritureComptableDTO creerEcritureComptable(EcritureComptableDTO ecritureDTO) { + LOG.infof("Création d'une nouvelle écriture comptable: %s", ecritureDTO.getNumeroPiece()); + + // Vérifier l'équilibre + if (!isEcritureEquilibree(ecritureDTO)) { + throw new IllegalArgumentException("L'écriture n'est pas équilibrée (Débit ≠ Crédit)"); + } + + EcritureComptable ecriture = convertToEntity(ecritureDTO); + ecriture.setCreePar(keycloakService.getCurrentUserEmail()); + + // Calculer les totaux + ecriture.calculerTotaux(); + + ecritureComptableRepository.persist(ecriture); + LOG.infof("Écriture comptable créée avec succès: ID=%s, Numéro=%s", ecriture.getId(), ecriture.getNumeroPiece()); + + return convertToDTO(ecriture); + } + + /** + * Trouve une écriture comptable par son ID + * + * @param id ID de l'écriture + * @return DTO de l'écriture + */ + public EcritureComptableDTO trouverEcritureParId(UUID id) { + return ecritureComptableRepository + .findEcritureComptableById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Écriture comptable non trouvée avec l'ID: " + id)); + } + + /** + * Liste les écritures d'un journal + * + * @param journalId ID du journal + * @return Liste des écritures + */ + public List listerEcrituresParJournal(UUID journalId) { + return ecritureComptableRepository.findByJournalId(journalId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Liste les écritures d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des écritures + */ + public List listerEcrituresParOrganisation(UUID organisationId) { + return ecritureComptableRepository.findByOrganisationId(organisationId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + // ======================================== + // MÉTHODES PRIVÉES - CONVERSIONS + // ======================================== + + /** Vérifie si une écriture est équilibrée */ + private boolean isEcritureEquilibree(EcritureComptableDTO ecritureDTO) { + if (ecritureDTO.getLignes() == null || ecritureDTO.getLignes().isEmpty()) { + return false; + } + + BigDecimal totalDebit = + ecritureDTO.getLignes().stream() + .map(LigneEcritureDTO::getMontantDebit) + .filter(amount -> amount != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal totalCredit = + ecritureDTO.getLignes().stream() + .map(LigneEcritureDTO::getMontantCredit) + .filter(amount -> amount != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + return totalDebit.compareTo(totalCredit) == 0; + } + + /** Convertit une entité CompteComptable en DTO */ + private CompteComptableDTO convertToDTO(CompteComptable compte) { + if (compte == null) { + return null; + } + + CompteComptableDTO dto = new CompteComptableDTO(); + dto.setId(compte.getId()); + dto.setNumeroCompte(compte.getNumeroCompte()); + dto.setLibelle(compte.getLibelle()); + dto.setTypeCompte(compte.getTypeCompte()); + dto.setClasseComptable(compte.getClasseComptable()); + dto.setSoldeInitial(compte.getSoldeInitial()); + dto.setSoldeActuel(compte.getSoldeActuel()); + dto.setCompteCollectif(compte.getCompteCollectif()); + dto.setCompteAnalytique(compte.getCompteAnalytique()); + dto.setDescription(compte.getDescription()); + dto.setDateCreation(compte.getDateCreation()); + dto.setDateModification(compte.getDateModification()); + dto.setActif(compte.getActif()); + + return dto; + } + + /** Convertit un DTO en entité CompteComptable */ + private CompteComptable convertToEntity(CompteComptableDTO dto) { + if (dto == null) { + return null; + } + + CompteComptable compte = new CompteComptable(); + compte.setNumeroCompte(dto.getNumeroCompte()); + compte.setLibelle(dto.getLibelle()); + compte.setTypeCompte(dto.getTypeCompte()); + compte.setClasseComptable(dto.getClasseComptable()); + compte.setSoldeInitial(dto.getSoldeInitial() != null ? dto.getSoldeInitial() : BigDecimal.ZERO); + compte.setSoldeActuel(dto.getSoldeActuel() != null ? dto.getSoldeActuel() : dto.getSoldeInitial()); + compte.setCompteCollectif(dto.getCompteCollectif() != null ? dto.getCompteCollectif() : false); + compte.setCompteAnalytique(dto.getCompteAnalytique() != null ? dto.getCompteAnalytique() : false); + compte.setDescription(dto.getDescription()); + + return compte; + } + + /** Convertit une entité JournalComptable en DTO */ + private JournalComptableDTO convertToDTO(JournalComptable journal) { + if (journal == null) { + return null; + } + + JournalComptableDTO dto = new JournalComptableDTO(); + dto.setId(journal.getId()); + dto.setCode(journal.getCode()); + dto.setLibelle(journal.getLibelle()); + dto.setTypeJournal(journal.getTypeJournal()); + dto.setDateDebut(journal.getDateDebut()); + dto.setDateFin(journal.getDateFin()); + dto.setStatut(journal.getStatut()); + dto.setDescription(journal.getDescription()); + dto.setDateCreation(journal.getDateCreation()); + dto.setDateModification(journal.getDateModification()); + dto.setActif(journal.getActif()); + + return dto; + } + + /** Convertit un DTO en entité JournalComptable */ + private JournalComptable convertToEntity(JournalComptableDTO dto) { + if (dto == null) { + return null; + } + + JournalComptable journal = new JournalComptable(); + journal.setCode(dto.getCode()); + journal.setLibelle(dto.getLibelle()); + journal.setTypeJournal(dto.getTypeJournal()); + journal.setDateDebut(dto.getDateDebut()); + journal.setDateFin(dto.getDateFin()); + journal.setStatut(dto.getStatut() != null ? dto.getStatut() : "OUVERT"); + journal.setDescription(dto.getDescription()); + + return journal; + } + + /** Convertit une entité EcritureComptable en DTO */ + private EcritureComptableDTO convertToDTO(EcritureComptable ecriture) { + if (ecriture == null) { + return null; + } + + EcritureComptableDTO dto = new EcritureComptableDTO(); + dto.setId(ecriture.getId()); + dto.setNumeroPiece(ecriture.getNumeroPiece()); + dto.setDateEcriture(ecriture.getDateEcriture()); + dto.setLibelle(ecriture.getLibelle()); + dto.setReference(ecriture.getReference()); + dto.setLettrage(ecriture.getLettrage()); + dto.setPointe(ecriture.getPointe()); + dto.setMontantDebit(ecriture.getMontantDebit()); + dto.setMontantCredit(ecriture.getMontantCredit()); + dto.setCommentaire(ecriture.getCommentaire()); + + if (ecriture.getJournal() != null) { + dto.setJournalId(ecriture.getJournal().getId()); + } + if (ecriture.getOrganisation() != null) { + dto.setOrganisationId(ecriture.getOrganisation().getId()); + } + if (ecriture.getPaiement() != null) { + dto.setPaiementId(ecriture.getPaiement().getId()); + } + + // Convertir les lignes + if (ecriture.getLignes() != null) { + dto.setLignes( + ecriture.getLignes().stream().map(this::convertToDTO).collect(Collectors.toList())); + } + + dto.setDateCreation(ecriture.getDateCreation()); + dto.setDateModification(ecriture.getDateModification()); + dto.setActif(ecriture.getActif()); + + return dto; + } + + /** Convertit un DTO en entité EcritureComptable */ + private EcritureComptable convertToEntity(EcritureComptableDTO dto) { + if (dto == null) { + return null; + } + + EcritureComptable ecriture = new EcritureComptable(); + ecriture.setNumeroPiece(dto.getNumeroPiece()); + ecriture.setDateEcriture(dto.getDateEcriture() != null ? dto.getDateEcriture() : LocalDate.now()); + ecriture.setLibelle(dto.getLibelle()); + ecriture.setReference(dto.getReference()); + ecriture.setLettrage(dto.getLettrage()); + ecriture.setPointe(dto.getPointe() != null ? dto.getPointe() : false); + ecriture.setCommentaire(dto.getCommentaire()); + + // Relations + if (dto.getJournalId() != null) { + JournalComptable journal = + journalComptableRepository + .findJournalComptableById(dto.getJournalId()) + .orElseThrow( + () -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + dto.getJournalId())); + ecriture.setJournal(journal); + } + + if (dto.getOrganisationId() != null) { + Organisation org = + organisationRepository + .findByIdOptional(dto.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + dto.getOrganisationId())); + ecriture.setOrganisation(org); + } + + if (dto.getPaiementId() != null) { + Paiement paiement = + paiementRepository + .findPaiementById(dto.getPaiementId()) + .orElseThrow( + () -> new NotFoundException("Paiement non trouvé avec l'ID: " + dto.getPaiementId())); + ecriture.setPaiement(paiement); + } + + // Convertir les lignes + if (dto.getLignes() != null) { + for (LigneEcritureDTO ligneDTO : dto.getLignes()) { + LigneEcriture ligne = convertToEntity(ligneDTO); + ligne.setEcriture(ecriture); + ecriture.getLignes().add(ligne); + } + } + + return ecriture; + } + + /** Convertit une entité LigneEcriture en DTO */ + private LigneEcritureDTO convertToDTO(LigneEcriture ligne) { + if (ligne == null) { + return null; + } + + LigneEcritureDTO dto = new LigneEcritureDTO(); + dto.setId(ligne.getId()); + dto.setNumeroLigne(ligne.getNumeroLigne()); + dto.setMontantDebit(ligne.getMontantDebit()); + dto.setMontantCredit(ligne.getMontantCredit()); + dto.setLibelle(ligne.getLibelle()); + dto.setReference(ligne.getReference()); + + if (ligne.getEcriture() != null) { + dto.setEcritureId(ligne.getEcriture().getId()); + } + if (ligne.getCompteComptable() != null) { + dto.setCompteComptableId(ligne.getCompteComptable().getId()); + } + + dto.setDateCreation(ligne.getDateCreation()); + dto.setDateModification(ligne.getDateModification()); + dto.setActif(ligne.getActif()); + + return dto; + } + + /** Convertit un DTO en entité LigneEcriture */ + private LigneEcriture convertToEntity(LigneEcritureDTO dto) { + if (dto == null) { + return null; + } + + LigneEcriture ligne = new LigneEcriture(); + ligne.setNumeroLigne(dto.getNumeroLigne()); + ligne.setMontantDebit(dto.getMontantDebit() != null ? dto.getMontantDebit() : BigDecimal.ZERO); + ligne.setMontantCredit(dto.getMontantCredit() != null ? dto.getMontantCredit() : BigDecimal.ZERO); + ligne.setLibelle(dto.getLibelle()); + ligne.setReference(dto.getReference()); + + // Relation CompteComptable + if (dto.getCompteComptableId() != null) { + CompteComptable compte = + compteComptableRepository + .findCompteComptableById(dto.getCompteComptableId()) + .orElseThrow( + () -> + new NotFoundException( + "Compte comptable non trouvé avec l'ID: " + dto.getCompteComptableId())); + ligne.setCompteComptable(compte); + } + + return ligne; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/CotisationService.java b/src/main/java/dev/lions/unionflow/server/service/CotisationService.java new file mode 100644 index 0000000..a475c28 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/CotisationService.java @@ -0,0 +1,493 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.finance.CotisationDTO; +import dev.lions.unionflow.server.entity.Cotisation; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.repository.CotisationRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.NotFoundException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +/** + * Service métier pour la gestion des cotisations Contient la logique métier et les règles de + * validation + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@ApplicationScoped +@Slf4j +public class CotisationService { + + @Inject CotisationRepository cotisationRepository; + + @Inject MembreRepository membreRepository; + + /** + * Récupère toutes les cotisations avec pagination + * + * @param page numéro de page (0-based) + * @param size taille de la page + * @return liste des cotisations converties en DTO + */ + public List getAllCotisations(int page, int size) { + log.debug("Récupération des cotisations - page: {}, size: {}", page, size); + + // Utilisation de EntityManager pour la pagination + jakarta.persistence.TypedQuery query = + cotisationRepository.getEntityManager().createQuery( + "SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC", + Cotisation.class); + query.setFirstResult(page * size); + query.setMaxResults(size); + List cotisations = query.getResultList(); + + return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère une cotisation par son ID + * + * @param id identifiant UUID de la cotisation + * @return DTO de la cotisation + * @throws NotFoundException si la cotisation n'existe pas + */ + public CotisationDTO getCotisationById(@NotNull UUID id) { + log.debug("Récupération de la cotisation avec ID: {}", id); + + Cotisation cotisation = + cotisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id)); + + return convertToDTO(cotisation); + } + + /** + * Récupère une cotisation par son numéro de référence + * + * @param numeroReference numéro de référence unique + * @return DTO de la cotisation + * @throws NotFoundException si la cotisation n'existe pas + */ + public CotisationDTO getCotisationByReference(@NotNull String numeroReference) { + log.debug("Récupération de la cotisation avec référence: {}", numeroReference); + + Cotisation cotisation = + cotisationRepository + .findByNumeroReference(numeroReference) + .orElseThrow( + () -> + new NotFoundException( + "Cotisation non trouvée avec la référence: " + numeroReference)); + + return convertToDTO(cotisation); + } + + /** + * Crée une nouvelle cotisation + * + * @param cotisationDTO données de la cotisation à créer + * @return DTO de la cotisation créée + */ + @Transactional + public CotisationDTO createCotisation(@Valid CotisationDTO cotisationDTO) { + log.info("Création d'une nouvelle cotisation pour le membre: {}", cotisationDTO.getMembreId()); + + // Validation du membre - UUID direct maintenant + Membre membre = + membreRepository + .findByIdOptional(cotisationDTO.getMembreId()) + .orElseThrow( + () -> + new NotFoundException( + "Membre non trouvé avec l'ID: " + cotisationDTO.getMembreId())); + + // Conversion DTO vers entité + Cotisation cotisation = convertToEntity(cotisationDTO); + cotisation.setMembre(membre); + + // Génération automatique du numéro de référence si absent + if (cotisation.getNumeroReference() == null || cotisation.getNumeroReference().isEmpty()) { + cotisation.setNumeroReference(Cotisation.genererNumeroReference()); + } + + // Validation des règles métier + validateCotisationRules(cotisation); + + // Persistance + cotisationRepository.persist(cotisation); + + log.info( + "Cotisation créée avec succès - ID: {}, Référence: {}", + cotisation.getId(), + cotisation.getNumeroReference()); + + return convertToDTO(cotisation); + } + + /** + * Met à jour une cotisation existante + * + * @param id identifiant UUID de la cotisation + * @param cotisationDTO nouvelles données + * @return DTO de la cotisation mise à jour + */ + @Transactional + public CotisationDTO updateCotisation(@NotNull UUID id, @Valid CotisationDTO cotisationDTO) { + log.info("Mise à jour de la cotisation avec ID: {}", id); + + Cotisation cotisationExistante = + cotisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id)); + + // Mise à jour des champs modifiables + updateCotisationFields(cotisationExistante, cotisationDTO); + + // Validation des règles métier + validateCotisationRules(cotisationExistante); + + log.info("Cotisation mise à jour avec succès - ID: {}", id); + + return convertToDTO(cotisationExistante); + } + + /** + * Supprime (désactive) une cotisation + * + * @param id identifiant UUID de la cotisation + */ + @Transactional + public void deleteCotisation(@NotNull UUID id) { + log.info("Suppression de la cotisation avec ID: {}", id); + + Cotisation cotisation = + cotisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id)); + + // Vérification si la cotisation peut être supprimée + if ("PAYEE".equals(cotisation.getStatut())) { + throw new IllegalStateException("Impossible de supprimer une cotisation déjà payée"); + } + + cotisation.setStatut("ANNULEE"); + + log.info("Cotisation supprimée avec succès - ID: {}", id); + } + + /** + * Récupère les cotisations d'un membre + * + * @param membreId identifiant UUID du membre + * @param page numéro de page + * @param size taille de la page + * @return liste des cotisations du membre + */ + public List getCotisationsByMembre(@NotNull UUID membreId, int page, int size) { + log.debug("Récupération des cotisations du membre: {}", membreId); + + // Vérification de l'existence du membre + if (!membreRepository.findByIdOptional(membreId).isPresent()) { + throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId); + } + + List cotisations = + cotisationRepository.findByMembreId( + membreId, Page.of(page, size), Sort.by("dateEcheance").descending()); + + return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les cotisations par statut + * + * @param statut statut recherché + * @param page numéro de page + * @param size taille de la page + * @return liste des cotisations avec le statut spécifié + */ + public List getCotisationsByStatut(@NotNull String statut, int page, int size) { + log.debug("Récupération des cotisations avec statut: {}", statut); + + List cotisations = cotisationRepository.findByStatut(statut, Page.of(page, size)); + + return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les cotisations en retard + * + * @param page numéro de page + * @param size taille de la page + * @return liste des cotisations en retard + */ + public List getCotisationsEnRetard(int page, int size) { + log.debug("Récupération des cotisations en retard"); + + List cotisations = + cotisationRepository.findCotisationsEnRetard(LocalDate.now(), Page.of(page, size)); + + return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Recherche avancée de cotisations + * + * @param membreId identifiant du membre (optionnel) + * @param statut statut (optionnel) + * @param typeCotisation type (optionnel) + * @param annee année (optionnel) + * @param mois mois (optionnel) + * @param page numéro de page + * @param size taille de la page + * @return liste filtrée des cotisations + */ + public List rechercherCotisations( + UUID membreId, + String statut, + String typeCotisation, + Integer annee, + Integer mois, + int page, + int size) { + log.debug("Recherche avancée de cotisations avec filtres"); + + List cotisations = + cotisationRepository.rechercheAvancee( + membreId, statut, typeCotisation, annee, mois, Page.of(page, size)); + + return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** + * Récupère les statistiques des cotisations + * + * @return map contenant les statistiques + */ + public Map getStatistiquesCotisations() { + log.debug("Calcul des statistiques des cotisations"); + + long totalCotisations = cotisationRepository.count(); + long cotisationsPayees = cotisationRepository.compterParStatut("PAYEE"); + long cotisationsEnRetard = + cotisationRepository + .findCotisationsEnRetard(LocalDate.now(), Page.of(0, Integer.MAX_VALUE)) + .size(); + + return Map.of( + "totalCotisations", totalCotisations, + "cotisationsPayees", cotisationsPayees, + "cotisationsEnRetard", cotisationsEnRetard, + "tauxPaiement", + totalCotisations > 0 ? (cotisationsPayees * 100.0 / totalCotisations) : 0.0); + } + + /** Convertit une entité Cotisation en DTO */ + private CotisationDTO convertToDTO(Cotisation cotisation) { + if (cotisation == null) { + return null; + } + + CotisationDTO dto = new CotisationDTO(); + + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(cotisation.getId()); + dto.setNumeroReference(cotisation.getNumeroReference()); + + // Conversion du membre associé + if (cotisation.getMembre() != null) { + dto.setMembreId(cotisation.getMembre().getId()); + dto.setNomMembre(cotisation.getMembre().getNomComplet()); + dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre()); + + // Conversion de l'organisation du membre (associationId) + if (cotisation.getMembre().getOrganisation() != null + && cotisation.getMembre().getOrganisation().getId() != null) { + dto.setAssociationId(cotisation.getMembre().getOrganisation().getId()); + dto.setNomAssociation(cotisation.getMembre().getOrganisation().getNom()); + } + } + + // Propriétés de la cotisation + dto.setTypeCotisation(cotisation.getTypeCotisation()); + dto.setMontantDu(cotisation.getMontantDu()); + dto.setMontantPaye(cotisation.getMontantPaye()); + dto.setCodeDevise(cotisation.getCodeDevise()); + dto.setStatut(cotisation.getStatut()); + dto.setDateEcheance(cotisation.getDateEcheance()); + dto.setDatePaiement(cotisation.getDatePaiement()); + dto.setDescription(cotisation.getDescription()); + dto.setPeriode(cotisation.getPeriode()); + dto.setAnnee(cotisation.getAnnee()); + dto.setMois(cotisation.getMois()); + dto.setObservations(cotisation.getObservations()); + dto.setRecurrente(cotisation.getRecurrente()); + dto.setNombreRappels(cotisation.getNombreRappels()); + dto.setDateDernierRappel(cotisation.getDateDernierRappel()); + + // Conversion du validateur + dto.setValidePar( + cotisation.getValideParId() != null + ? cotisation.getValideParId() + : null); + dto.setNomValidateur(cotisation.getNomValidateur()); + + dto.setMethodePaiement(cotisation.getMethodePaiement()); + dto.setReferencePaiement(cotisation.getReferencePaiement()); + dto.setDateCreation(cotisation.getDateCreation()); + dto.setDateModification(cotisation.getDateModification()); + + // Propriétés héritées de BaseDTO + dto.setActif(true); // Les cotisations sont toujours actives + dto.setVersion(0L); // Version par défaut + + return dto; + } + + /** Convertit un DTO en entité Cotisation */ + private Cotisation convertToEntity(CotisationDTO dto) { + return Cotisation.builder() + .numeroReference(dto.getNumeroReference()) + .typeCotisation(dto.getTypeCotisation()) + .montantDu(dto.getMontantDu()) + .montantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO) + .codeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF") + .statut(dto.getStatut() != null ? dto.getStatut() : "EN_ATTENTE") + .dateEcheance(dto.getDateEcheance()) + .datePaiement(dto.getDatePaiement()) + .description(dto.getDescription()) + .periode(dto.getPeriode()) + .annee(dto.getAnnee()) + .mois(dto.getMois()) + .observations(dto.getObservations()) + .recurrente(dto.getRecurrente() != null ? dto.getRecurrente() : false) + .nombreRappels(dto.getNombreRappels() != null ? dto.getNombreRappels() : 0) + .dateDernierRappel(dto.getDateDernierRappel()) + .methodePaiement(dto.getMethodePaiement()) + .referencePaiement(dto.getReferencePaiement()) + .build(); + } + + /** Met à jour les champs d'une cotisation existante */ + private void updateCotisationFields(Cotisation cotisation, CotisationDTO dto) { + if (dto.getTypeCotisation() != null) { + cotisation.setTypeCotisation(dto.getTypeCotisation()); + } + if (dto.getMontantDu() != null) { + cotisation.setMontantDu(dto.getMontantDu()); + } + if (dto.getMontantPaye() != null) { + cotisation.setMontantPaye(dto.getMontantPaye()); + } + if (dto.getStatut() != null) { + cotisation.setStatut(dto.getStatut()); + } + if (dto.getDateEcheance() != null) { + cotisation.setDateEcheance(dto.getDateEcheance()); + } + if (dto.getDatePaiement() != null) { + cotisation.setDatePaiement(dto.getDatePaiement()); + } + if (dto.getDescription() != null) { + cotisation.setDescription(dto.getDescription()); + } + if (dto.getObservations() != null) { + cotisation.setObservations(dto.getObservations()); + } + if (dto.getMethodePaiement() != null) { + cotisation.setMethodePaiement(dto.getMethodePaiement()); + } + if (dto.getReferencePaiement() != null) { + cotisation.setReferencePaiement(dto.getReferencePaiement()); + } + } + + /** Valide les règles métier pour une cotisation */ + private void validateCotisationRules(Cotisation cotisation) { + // Validation du montant + if (cotisation.getMontantDu().compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Le montant dû doit être positif"); + } + + // Validation de la date d'échéance + if (cotisation.getDateEcheance().isBefore(LocalDate.now().minusYears(1))) { + throw new IllegalArgumentException("La date d'échéance ne peut pas être antérieure à un an"); + } + + // Validation du montant payé + if (cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) > 0) { + throw new IllegalArgumentException("Le montant payé ne peut pas dépasser le montant dû"); + } + + // Validation de la cohérence statut/paiement + if ("PAYEE".equals(cotisation.getStatut()) + && cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) < 0) { + throw new IllegalArgumentException( + "Une cotisation marquée comme payée doit avoir un montant payé égal au montant dû"); + } + } + + /** + * Envoie des rappels de cotisations groupés à plusieurs membres (WOU/DRY) + * + * @param membreIds Liste des IDs des membres destinataires + * @return Nombre de rappels envoyés + */ + @Transactional + public int envoyerRappelsCotisationsGroupes(List membreIds) { + log.info("Envoi de rappels de cotisations groupés à {} membres", membreIds.size()); + + if (membreIds == null || membreIds.isEmpty()) { + throw new IllegalArgumentException("La liste des membres ne peut pas être vide"); + } + + int rappelsEnvoyes = 0; + for (UUID membreId : membreIds) { + try { + Membre membre = + membreRepository + .findByIdOptional(membreId) + .orElseThrow( + () -> + new IllegalArgumentException( + "Membre non trouvé avec l'ID: " + membreId)); + + // Trouver les cotisations en retard pour ce membre + List cotisationsEnRetard = + cotisationRepository.findCotisationsAuRappel(7, 3).stream() + .filter(c -> c.getMembre() != null && c.getMembre().getId().equals(membreId)) + .collect(Collectors.toList()); + + for (Cotisation cotisation : cotisationsEnRetard) { + // Incrémenter le nombre de rappels + cotisationRepository.incrementerNombreRappels(cotisation.getId()); + rappelsEnvoyes++; + } + } catch (Exception e) { + log.warn( + "Erreur lors de l'envoi du rappel de cotisation pour le membre {}: {}", + membreId, + e.getMessage()); + } + } + + log.info("{} rappels envoyés sur {} membres demandés", rappelsEnvoyes, membreIds.size()); + return rappelsEnvoyes; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java b/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java new file mode 100644 index 0000000..dea41dd --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/DashboardServiceImpl.java @@ -0,0 +1,254 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataDTO; +import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsDTO; +import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityDTO; +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.api.service.dashboard.DashboardService; +import dev.lions.unionflow.server.entity.Cotisation; +import dev.lions.unionflow.server.entity.DemandeAide; +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.repository.CotisationRepository; +import dev.lions.unionflow.server.repository.DemandeAideRepository; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.TypedQuery; +import org.jboss.logging.Logger; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Implémentation du service Dashboard pour Quarkus + * + *

Cette implémentation récupère les données réelles depuis la base de données + * via les repositories. + * + * @author UnionFlow Team + * @version 2.0 + * @since 2025-01-17 + */ +@ApplicationScoped +public class DashboardServiceImpl implements DashboardService { + + private static final Logger LOG = Logger.getLogger(DashboardServiceImpl.class); + + @Inject + MembreRepository membreRepository; + + @Inject + EvenementRepository evenementRepository; + + @Inject + CotisationRepository cotisationRepository; + + @Inject + DemandeAideRepository demandeAideRepository; + + @Inject + OrganisationRepository organisationRepository; + + @Override + public DashboardDataDTO getDashboardData(String organizationId, String userId) { + LOG.infof("Récupération des données dashboard pour org: %s et user: %s", organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + return DashboardDataDTO.builder() + .stats(getDashboardStats(organizationId, userId)) + .recentActivities(getRecentActivities(organizationId, userId, 10)) + .upcomingEvents(getUpcomingEvents(organizationId, userId, 5)) + .userPreferences(getUserPreferences(userId)) + .organizationId(organizationId) + .userId(userId) + .build(); + } + + @Override + public DashboardStatsDTO getDashboardStats(String organizationId, String userId) { + LOG.infof("Récupération des stats dashboard pour org: %s et user: %s", organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + // Compter les membres + long totalMembers = membreRepository.count(); + long activeMembers = membreRepository.countActifs(); + + // Compter les événements + long totalEvents = evenementRepository.count(); + long upcomingEvents = evenementRepository.findEvenementsAVenir().size(); + + // Compter les cotisations + long totalContributions = cotisationRepository.count(); + BigDecimal totalContributionAmount = calculateTotalContributionAmount(orgId); + + // Compter les demandes en attente + List pendingRequests = demandeAideRepository.findByStatut(StatutAide.EN_ATTENTE); + long pendingRequestsCount = pendingRequests.stream() + .filter(d -> d.getOrganisation() != null && d.getOrganisation().getId().equals(orgId)) + .count(); + + // Calculer la croissance mensuelle (membres ajoutés ce mois) + LocalDate debutMois = LocalDate.now().withDayOfMonth(1); + long nouveauxMembresMois = membreRepository.countNouveauxMembres(debutMois); + long totalMembresAvant = totalMembers - nouveauxMembresMois; + double monthlyGrowth = totalMembresAvant > 0 + ? (double) nouveauxMembresMois / totalMembresAvant * 100.0 + : 0.0; + + // Calculer le taux d'engagement (membres actifs / total) + double engagementRate = totalMembers > 0 + ? (double) activeMembers / totalMembers + : 0.0; + + return DashboardStatsDTO.builder() + .totalMembers((int) totalMembers) + .activeMembers((int) activeMembers) + .totalEvents((int) totalEvents) + .upcomingEvents((int) upcomingEvents) + .totalContributions((int) totalContributions) + .totalContributionAmount(totalContributionAmount.doubleValue()) + .pendingRequests((int) pendingRequestsCount) + .completedProjects(0) // À implémenter si nécessaire + .monthlyGrowth(monthlyGrowth) + .engagementRate(engagementRate) + .lastUpdated(LocalDateTime.now()) + .build(); + } + + @Override + public List getRecentActivities(String organizationId, String userId, int limit) { + LOG.infof("Récupération de %d activités récentes pour org: %s et user: %s", limit, organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + List activities = new ArrayList<>(); + + // Récupérer les membres récemment créés + List nouveauxMembres = membreRepository.rechercheAvancee( + null, true, null, null, Page.of(0, limit), Sort.by("dateCreation", Sort.Direction.Descending)); + + for (Membre membre : nouveauxMembres) { + if (membre.getOrganisation() != null && membre.getOrganisation().getId().equals(orgId)) { + activities.add(RecentActivityDTO.builder() + .id(membre.getId().toString()) + .type("member") + .title("Nouveau membre inscrit") + .description(membre.getNomComplet() + " a rejoint l'organisation") + .userName(membre.getNomComplet()) + .timestamp(membre.getDateCreation()) + .userAvatar(null) + .actionUrl("/members/" + membre.getId()) + .build()); + } + } + + // Récupérer les événements récemment créés + List tousEvenements = evenementRepository.listAll(); + List nouveauxEvenements = tousEvenements.stream() + .filter(e -> e.getOrganisation() != null && e.getOrganisation().getId().equals(orgId)) + .sorted(Comparator.comparing(Evenement::getDateCreation).reversed()) + .limit(limit) + .collect(Collectors.toList()); + + for (Evenement evenement : nouveauxEvenements) { + activities.add(RecentActivityDTO.builder() + .id(evenement.getId().toString()) + .type("event") + .title("Événement créé") + .description(evenement.getTitre() + " a été programmé") + .userName(evenement.getOrganisation() != null ? evenement.getOrganisation().getNom() : "Système") + .timestamp(evenement.getDateCreation()) + .userAvatar(null) + .actionUrl("/events/" + evenement.getId()) + .build()); + } + + // Récupérer les cotisations récentes + List cotisationsRecentes = cotisationRepository.rechercheAvancee( + null, "PAYEE", null, null, null, Page.of(0, limit)); + + for (Cotisation cotisation : cotisationsRecentes) { + if (cotisation.getMembre() != null && + cotisation.getMembre().getOrganisation() != null && + cotisation.getMembre().getOrganisation().getId().equals(orgId)) { + activities.add(RecentActivityDTO.builder() + .id(cotisation.getId().toString()) + .type("contribution") + .title("Cotisation reçue") + .description("Paiement de " + cotisation.getMontantPaye() + " " + cotisation.getCodeDevise() + " reçu") + .userName(cotisation.getMembre().getNomComplet()) + .timestamp(cotisation.getDatePaiement() != null ? cotisation.getDatePaiement() : cotisation.getDateCreation()) + .userAvatar(null) + .actionUrl("/contributions/" + cotisation.getId()) + .build()); + } + } + + // Trier par timestamp décroissant et limiter + return activities.stream() + .sorted(Comparator.comparing(RecentActivityDTO::getTimestamp).reversed()) + .limit(limit) + .collect(Collectors.toList()); + } + + @Override + public List getUpcomingEvents(String organizationId, String userId, int limit) { + LOG.infof("Récupération de %d événements à venir pour org: %s et user: %s", limit, organizationId, userId); + + UUID orgId = UUID.fromString(organizationId); + + List evenements = evenementRepository.findEvenementsAVenir( + Page.of(0, limit), Sort.by("dateDebut", Sort.Direction.Ascending)); + + return evenements.stream() + .filter(e -> e.getOrganisation() == null || e.getOrganisation().getId().equals(orgId)) + .map(this::convertToUpcomingEventDTO) + .limit(limit) + .collect(Collectors.toList()); + } + + private UpcomingEventDTO convertToUpcomingEventDTO(Evenement evenement) { + return UpcomingEventDTO.builder() + .id(evenement.getId().toString()) + .title(evenement.getTitre()) + .description(evenement.getDescription()) + .startDate(evenement.getDateDebut()) + .endDate(evenement.getDateFin()) + .location(evenement.getLieu()) + .maxParticipants(evenement.getCapaciteMax()) + .currentParticipants(evenement.getNombreInscrits()) + .status(evenement.getStatut() != null ? evenement.getStatut().name() : "PLANIFIE") + .imageUrl(null) + .tags(Collections.emptyList()) + .build(); + } + + private BigDecimal calculateTotalContributionAmount(UUID organisationId) { + TypedQuery query = cotisationRepository.getEntityManager().createQuery( + "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId", + BigDecimal.class); + query.setParameter("organisationId", organisationId); + BigDecimal result = query.getSingleResult(); + return result != null ? result : BigDecimal.ZERO; + } + + private Map getUserPreferences(String userId) { + Map preferences = new HashMap<>(); + preferences.put("theme", "royal_teal"); + preferences.put("language", "fr"); + preferences.put("notifications", true); + preferences.put("autoRefresh", true); + preferences.put("refreshInterval", 300); + return preferences; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java b/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java new file mode 100644 index 0000000..0e977fb --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/DemandeAideService.java @@ -0,0 +1,400 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO; +import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service spécialisé pour la gestion des demandes d'aide + * + *

Ce service gère le cycle de vie complet des demandes d'aide : création, validation, + * changements de statut, recherche et suivi. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class DemandeAideService { + + private static final Logger LOG = Logger.getLogger(DemandeAideService.class); + + // Cache en mémoire pour les demandes fréquemment consultées + private final Map cacheDemandesRecentes = new HashMap<>(); + private final Map cacheTimestamps = new HashMap<>(); + private static final long CACHE_DURATION_MINUTES = 15; + + // === OPÉRATIONS CRUD === + + /** + * Crée une nouvelle demande d'aide + * + * @param demandeDTO La demande à créer + * @return La demande créée avec ID généré + */ + @Transactional + public DemandeAideDTO creerDemande(@Valid DemandeAideDTO demandeDTO) { + LOG.infof("Création d'une nouvelle demande d'aide: %s", demandeDTO.getTitre()); + + // Génération des identifiants + demandeDTO.setId(UUID.randomUUID()); + demandeDTO.setNumeroReference(genererNumeroReference()); + + // Initialisation des dates + LocalDateTime maintenant = LocalDateTime.now(); + demandeDTO.setDateCreation(maintenant); + demandeDTO.setDateModification(maintenant); + + // Statut initial + if (demandeDTO.getStatut() == null) { + demandeDTO.setStatut(StatutAide.BROUILLON); + } + + // Priorité par défaut si non définie + if (demandeDTO.getPriorite() == null) { + demandeDTO.setPriorite(PrioriteAide.NORMALE); + } + + // Initialisation de l'historique + HistoriqueStatutDTO historiqueInitial = + HistoriqueStatutDTO.builder() + .id(UUID.randomUUID().toString()) + .ancienStatut(null) + .nouveauStatut(demandeDTO.getStatut()) + .dateChangement(maintenant) + .auteurId(demandeDTO.getMembreDemandeurId() != null ? demandeDTO.getMembreDemandeurId().toString() : null) + .motif("Création de la demande") + .estAutomatique(true) + .build(); + + demandeDTO.setHistoriqueStatuts(List.of(historiqueInitial)); + + // Calcul du score de priorité + demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO)); + + // Sauvegarde en cache + ajouterAuCache(demandeDTO); + + LOG.infof("Demande d'aide créée avec succès: %s", demandeDTO.getId()); + return demandeDTO; + } + + /** + * Met à jour une demande d'aide existante + * + * @param demandeDTO La demande à mettre à jour + * @return La demande mise à jour + */ + @Transactional + public DemandeAideDTO mettreAJour(@Valid DemandeAideDTO demandeDTO) { + LOG.infof("Mise à jour de la demande d'aide: %s", demandeDTO.getId()); + + // Vérification que la demande peut être modifiée + if (!demandeDTO.estModifiable()) { + throw new IllegalStateException("Cette demande ne peut plus être modifiée"); + } + + // Mise à jour de la date de modification + demandeDTO.setDateModification(LocalDateTime.now()); + demandeDTO.setVersion(demandeDTO.getVersion() + 1); + + // Recalcul du score de priorité + demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO)); + + // Mise à jour du cache + ajouterAuCache(demandeDTO); + + LOG.infof("Demande d'aide mise à jour avec succès: %s", demandeDTO.getId()); + return demandeDTO; + } + + /** + * Obtient une demande d'aide par son ID + * + * @param id UUID de la demande + * @return La demande trouvée + */ + public DemandeAideDTO obtenirParId(@NotNull UUID id) { + LOG.debugf("Récupération de la demande d'aide: %s", id); + + // Vérification du cache + DemandeAideDTO demandeCachee = obtenirDuCache(id); + if (demandeCachee != null) { + LOG.debugf("Demande trouvée dans le cache: %s", id); + return demandeCachee; + } + + // Simulation de récupération depuis la base de données + // Dans une vraie implémentation, ceci ferait appel au repository + DemandeAideDTO demande = simulerRecuperationBDD(id); + + if (demande != null) { + ajouterAuCache(demande); + } + + return demande; + } + + /** + * Change le statut d'une demande d'aide + * + * @param demandeId UUID de la demande + * @param nouveauStatut Nouveau statut + * @param motif Motif du changement + * @return La demande avec le nouveau statut + */ + @Transactional + public DemandeAideDTO changerStatut( + @NotNull UUID demandeId, @NotNull StatutAide nouveauStatut, String motif) { + LOG.infof("Changement de statut pour la demande %s: %s", demandeId, nouveauStatut); + + DemandeAideDTO demande = obtenirParId(demandeId); + if (demande == null) { + throw new IllegalArgumentException("Demande non trouvée: " + demandeId); + } + + StatutAide ancienStatut = demande.getStatut(); + + // Validation de la transition + if (!ancienStatut.peutTransitionnerVers(nouveauStatut)) { + throw new IllegalStateException( + String.format("Transition invalide de %s vers %s", ancienStatut, nouveauStatut)); + } + + // Mise à jour du statut + demande.setStatut(nouveauStatut); + demande.setDateModification(LocalDateTime.now()); + + // Ajout à l'historique + HistoriqueStatutDTO nouvelHistorique = + HistoriqueStatutDTO.builder() + .id(UUID.randomUUID().toString()) + .ancienStatut(ancienStatut) + .nouveauStatut(nouveauStatut) + .dateChangement(LocalDateTime.now()) + .motif(motif) + .estAutomatique(false) + .build(); + + List historique = new ArrayList<>(demande.getHistoriqueStatuts()); + historique.add(nouvelHistorique); + demande.setHistoriqueStatuts(historique); + + // Actions spécifiques selon le nouveau statut + switch (nouveauStatut) { + case SOUMISE -> demande.setDateSoumission(LocalDateTime.now()); + case APPROUVEE, APPROUVEE_PARTIELLEMENT -> demande.setDateApprobation(LocalDateTime.now()); + case VERSEE -> demande.setDateVersement(LocalDateTime.now()); + case CLOTUREE -> demande.setDateCloture(LocalDateTime.now()); + } + + // Mise à jour du cache + ajouterAuCache(demande); + + LOG.infof( + "Statut changé avec succès pour la demande %s: %s -> %s", + demandeId, ancienStatut, nouveauStatut); + return demande; + } + + // === RECHERCHE ET FILTRAGE === + + /** + * Recherche des demandes avec filtres + * + * @param filtres Map des critères de recherche + * @return Liste des demandes correspondantes + */ + public List rechercherAvecFiltres(Map filtres) { + LOG.debugf("Recherche de demandes avec filtres: %s", filtres); + + // Simulation de recherche - dans une vraie implémentation, + // ceci utiliserait des requêtes de base de données optimisées + List toutesLesDemandes = simulerRecuperationToutesLesDemandes(); + + return toutesLesDemandes.stream() + .filter(demande -> correspondAuxFiltres(demande, filtres)) + .sorted(this::comparerParPriorite) + .collect(Collectors.toList()); + } + + /** + * Obtient les demandes urgentes pour une organisation + * + * @param organisationId UUID de l'organisation + * @return Liste des demandes urgentes + */ + public List obtenirDemandesUrgentes(UUID organisationId) { + LOG.debugf("Récupération des demandes urgentes pour: %s", organisationId); + + Map filtres = + Map.of( + "organisationId", organisationId, + "priorite", List.of(PrioriteAide.CRITIQUE, PrioriteAide.URGENTE), + "statut", + List.of( + StatutAide.SOUMISE, + StatutAide.EN_ATTENTE, + StatutAide.EN_COURS_EVALUATION, + StatutAide.APPROUVEE)); + + return rechercherAvecFiltres(filtres); + } + + /** + * Obtient les demandes en retard (délai dépassé) + * + * @param organisationId ID de l'organisation + * @return Liste des demandes en retard + */ + public List obtenirDemandesEnRetard(UUID organisationId) { + LOG.debugf("Récupération des demandes en retard pour: %s", organisationId); + + return simulerRecuperationToutesLesDemandes().stream() + .filter(demande -> demande.getAssociationId().equals(organisationId)) + .filter(DemandeAideDTO::estDelaiDepasse) + .filter(demande -> !demande.estTerminee()) + .sorted(this::comparerParPriorite) + .collect(Collectors.toList()); + } + + // === MÉTHODES UTILITAIRES PRIVÉES === + + /** Génère un numéro de référence unique */ + private String genererNumeroReference() { + int annee = LocalDateTime.now().getYear(); + int numero = (int) (Math.random() * 999999) + 1; + return String.format("DA-%04d-%06d", annee, numero); + } + + /** Calcule le score de priorité d'une demande */ + private double calculerScorePriorite(DemandeAideDTO demande) { + double score = demande.getPriorite().getScorePriorite(); + + // Bonus pour type d'aide urgent + if (demande.getTypeAide().isUrgent()) { + score -= 1.0; + } + + // Bonus pour montant élevé (aide financière) + if (demande.getTypeAide().isFinancier() && demande.getMontantDemande() != null) { + if (demande.getMontantDemande().compareTo(new BigDecimal("50000")) > 0) { + score -= 0.5; + } + } + + // Malus pour ancienneté + long joursDepuisCreation = + java.time.Duration.between(demande.getDateCreation(), LocalDateTime.now()).toDays(); + if (joursDepuisCreation > 7) { + score += 0.3; + } + + return Math.max(0.1, score); + } + + /** Vérifie si une demande correspond aux filtres */ + private boolean correspondAuxFiltres(DemandeAideDTO demande, Map filtres) { + for (Map.Entry filtre : filtres.entrySet()) { + String cle = filtre.getKey(); + Object valeur = filtre.getValue(); + + switch (cle) { + case "organisationId" -> { + if (!demande.getAssociationId().equals(valeur)) return false; + } + case "typeAide" -> { + if (valeur instanceof List liste) { + if (!liste.contains(demande.getTypeAide())) return false; + } else if (!demande.getTypeAide().equals(valeur)) { + return false; + } + } + case "statut" -> { + if (valeur instanceof List liste) { + if (!liste.contains(demande.getStatut())) return false; + } else if (!demande.getStatut().equals(valeur)) { + return false; + } + } + case "priorite" -> { + if (valeur instanceof List liste) { + if (!liste.contains(demande.getPriorite())) return false; + } else if (!demande.getPriorite().equals(valeur)) { + return false; + } + } + case "demandeurId" -> { + if (!demande.getMembreDemandeurId().equals(valeur)) return false; + } + } + } + return true; + } + + /** Compare deux demandes par priorité */ + private int comparerParPriorite(DemandeAideDTO d1, DemandeAideDTO d2) { + // D'abord par score de priorité (plus bas = plus prioritaire) + int comparaisonScore = Double.compare(d1.getScorePriorite(), d2.getScorePriorite()); + if (comparaisonScore != 0) return comparaisonScore; + + // Puis par date de création (plus ancien = plus prioritaire) + return d1.getDateCreation().compareTo(d2.getDateCreation()); + } + + // === GESTION DU CACHE === + + private void ajouterAuCache(DemandeAideDTO demande) { + cacheDemandesRecentes.put(demande.getId(), demande); + cacheTimestamps.put(demande.getId(), LocalDateTime.now()); + + // Nettoyage du cache si trop volumineux + if (cacheDemandesRecentes.size() > 100) { + nettoyerCache(); + } + } + + private DemandeAideDTO obtenirDuCache(UUID id) { + LocalDateTime timestamp = cacheTimestamps.get(id); + if (timestamp == null) return null; + + // Vérification de l'expiration + if (LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES).isAfter(timestamp)) { + cacheDemandesRecentes.remove(id); + cacheTimestamps.remove(id); + return null; + } + + return cacheDemandesRecentes.get(id); + } + + private void nettoyerCache() { + LocalDateTime limite = LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES); + + cacheTimestamps.entrySet().removeIf(entry -> entry.getValue().isBefore(limite)); + cacheDemandesRecentes.keySet().retainAll(cacheTimestamps.keySet()); + } + + // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) === + + private DemandeAideDTO simulerRecuperationBDD(UUID id) { + // Simulation - dans une vraie implémentation, ceci ferait appel au repository + return null; + } + + private List simulerRecuperationToutesLesDemandes() { + // Simulation - dans une vraie implémentation, ceci ferait appel au repository + return new ArrayList<>(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/DocumentService.java b/src/main/java/dev/lions/unionflow/server/service/DocumentService.java new file mode 100644 index 0000000..237df9c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/DocumentService.java @@ -0,0 +1,311 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.document.DocumentDTO; +import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO; +import dev.lions.unionflow.server.entity.*; +import dev.lions.unionflow.server.repository.*; +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.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion documentaire + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class DocumentService { + + private static final Logger LOG = Logger.getLogger(DocumentService.class); + + @Inject DocumentRepository documentRepository; + + @Inject PieceJointeRepository pieceJointeRepository; + + @Inject MembreRepository membreRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject CotisationRepository cotisationRepository; + + @Inject AdhesionRepository adhesionRepository; + + @Inject DemandeAideRepository demandeAideRepository; + + @Inject TransactionWaveRepository transactionWaveRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouveau document + * + * @param documentDTO DTO du document à créer + * @return DTO du document créé + */ + @Transactional + public DocumentDTO creerDocument(DocumentDTO documentDTO) { + LOG.infof("Création d'un nouveau document: %s", documentDTO.getNomFichier()); + + Document document = convertToEntity(documentDTO); + document.setCreePar(keycloakService.getCurrentUserEmail()); + + documentRepository.persist(document); + LOG.infof("Document créé avec succès: ID=%s, Fichier=%s", document.getId(), document.getNomFichier()); + + return convertToDTO(document); + } + + /** + * Trouve un document par son ID + * + * @param id ID du document + * @return DTO du document + */ + public DocumentDTO trouverParId(UUID id) { + return documentRepository + .findDocumentById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id)); + } + + /** + * Enregistre un téléchargement de document + * + * @param id ID du document + */ + @Transactional + public void enregistrerTelechargement(UUID id) { + Document document = + documentRepository + .findDocumentById(id) + .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id)); + + document.setNombreTelechargements( + (document.getNombreTelechargements() != null ? document.getNombreTelechargements() : 0) + 1); + document.setDateDernierTelechargement(LocalDateTime.now()); + document.setModifiePar(keycloakService.getCurrentUserEmail()); + + documentRepository.persist(document); + } + + /** + * Crée une pièce jointe + * + * @param pieceJointeDTO DTO de la pièce jointe à créer + * @return DTO de la pièce jointe créée + */ + @Transactional + public PieceJointeDTO creerPieceJointe(PieceJointeDTO pieceJointeDTO) { + LOG.infof("Création d'une nouvelle pièce jointe"); + + PieceJointe pieceJointe = convertToEntity(pieceJointeDTO); + + // Vérifier qu'une seule relation est renseignée + if (!pieceJointe.isValide()) { + throw new IllegalArgumentException("Une seule relation doit être renseignée pour une pièce jointe"); + } + + pieceJointe.setCreePar(keycloakService.getCurrentUserEmail()); + pieceJointeRepository.persist(pieceJointe); + + LOG.infof("Pièce jointe créée avec succès: ID=%s", pieceJointe.getId()); + return convertToDTO(pieceJointe); + } + + /** + * Liste toutes les pièces jointes d'un document + * + * @param documentId ID du document + * @return Liste des pièces jointes + */ + public List listerPiecesJointesParDocument(UUID documentId) { + return pieceJointeRepository.findByDocumentId(documentId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + // ======================================== + // MÉTHODES PRIVÉES + // ======================================== + + /** Convertit une entité Document en DTO */ + private DocumentDTO convertToDTO(Document document) { + if (document == null) { + return null; + } + + DocumentDTO dto = new DocumentDTO(); + dto.setId(document.getId()); + dto.setNomFichier(document.getNomFichier()); + dto.setNomOriginal(document.getNomOriginal()); + dto.setCheminStockage(document.getCheminStockage()); + dto.setTypeMime(document.getTypeMime()); + dto.setTailleOctets(document.getTailleOctets()); + dto.setTypeDocument(document.getTypeDocument()); + dto.setHashMd5(document.getHashMd5()); + dto.setHashSha256(document.getHashSha256()); + dto.setDescription(document.getDescription()); + dto.setNombreTelechargements(document.getNombreTelechargements()); + dto.setTailleFormatee(document.getTailleFormatee()); + dto.setDateCreation(document.getDateCreation()); + dto.setDateModification(document.getDateModification()); + dto.setActif(document.getActif()); + + return dto; + } + + /** Convertit un DTO en entité Document */ + private Document convertToEntity(DocumentDTO dto) { + if (dto == null) { + return null; + } + + Document document = new Document(); + document.setNomFichier(dto.getNomFichier()); + document.setNomOriginal(dto.getNomOriginal()); + document.setCheminStockage(dto.getCheminStockage()); + document.setTypeMime(dto.getTypeMime()); + document.setTailleOctets(dto.getTailleOctets()); + document.setTypeDocument(dto.getTypeDocument() != null ? dto.getTypeDocument() : dev.lions.unionflow.server.api.enums.document.TypeDocument.AUTRE); + document.setHashMd5(dto.getHashMd5()); + document.setHashSha256(dto.getHashSha256()); + document.setDescription(dto.getDescription()); + document.setNombreTelechargements(dto.getNombreTelechargements() != null ? dto.getNombreTelechargements() : 0); + + return document; + } + + /** Convertit une entité PieceJointe en DTO */ + private PieceJointeDTO convertToDTO(PieceJointe pieceJointe) { + if (pieceJointe == null) { + return null; + } + + PieceJointeDTO dto = new PieceJointeDTO(); + dto.setId(pieceJointe.getId()); + dto.setOrdre(pieceJointe.getOrdre()); + dto.setLibelle(pieceJointe.getLibelle()); + dto.setCommentaire(pieceJointe.getCommentaire()); + + if (pieceJointe.getDocument() != null) { + dto.setDocumentId(pieceJointe.getDocument().getId()); + } + if (pieceJointe.getMembre() != null) { + dto.setMembreId(pieceJointe.getMembre().getId()); + } + if (pieceJointe.getOrganisation() != null) { + dto.setOrganisationId(pieceJointe.getOrganisation().getId()); + } + if (pieceJointe.getCotisation() != null) { + dto.setCotisationId(pieceJointe.getCotisation().getId()); + } + if (pieceJointe.getAdhesion() != null) { + dto.setAdhesionId(pieceJointe.getAdhesion().getId()); + } + if (pieceJointe.getDemandeAide() != null) { + dto.setDemandeAideId(pieceJointe.getDemandeAide().getId()); + } + if (pieceJointe.getTransactionWave() != null) { + dto.setTransactionWaveId(pieceJointe.getTransactionWave().getId()); + } + + dto.setDateCreation(pieceJointe.getDateCreation()); + dto.setDateModification(pieceJointe.getDateModification()); + dto.setActif(pieceJointe.getActif()); + + return dto; + } + + /** Convertit un DTO en entité PieceJointe */ + private PieceJointe convertToEntity(PieceJointeDTO dto) { + if (dto == null) { + return null; + } + + PieceJointe pieceJointe = new PieceJointe(); + pieceJointe.setOrdre(dto.getOrdre() != null ? dto.getOrdre() : 1); + pieceJointe.setLibelle(dto.getLibelle()); + pieceJointe.setCommentaire(dto.getCommentaire()); + + // Relation Document + if (dto.getDocumentId() != null) { + Document document = + documentRepository + .findDocumentById(dto.getDocumentId()) + .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + dto.getDocumentId())); + pieceJointe.setDocument(document); + } + + // Relations flexibles (une seule doit être renseignée) + if (dto.getMembreId() != null) { + Membre membre = + membreRepository + .findByIdOptional(dto.getMembreId()) + .orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId())); + pieceJointe.setMembre(membre); + } + + if (dto.getOrganisationId() != null) { + Organisation org = + organisationRepository + .findByIdOptional(dto.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + dto.getOrganisationId())); + pieceJointe.setOrganisation(org); + } + + if (dto.getCotisationId() != null) { + Cotisation cotisation = + cotisationRepository + .findByIdOptional(dto.getCotisationId()) + .orElseThrow( + () -> new NotFoundException("Cotisation non trouvée avec l'ID: " + dto.getCotisationId())); + pieceJointe.setCotisation(cotisation); + } + + if (dto.getAdhesionId() != null) { + Adhesion adhesion = + adhesionRepository + .findByIdOptional(dto.getAdhesionId()) + .orElseThrow( + () -> new NotFoundException("Adhésion non trouvée avec l'ID: " + dto.getAdhesionId())); + pieceJointe.setAdhesion(adhesion); + } + + if (dto.getDemandeAideId() != null) { + DemandeAide demandeAide = + demandeAideRepository + .findByIdOptional(dto.getDemandeAideId()) + .orElseThrow( + () -> + new NotFoundException( + "Demande d'aide non trouvée avec l'ID: " + dto.getDemandeAideId())); + pieceJointe.setDemandeAide(demandeAide); + } + + if (dto.getTransactionWaveId() != null) { + TransactionWave transactionWave = + transactionWaveRepository + .findTransactionWaveById(dto.getTransactionWaveId()) + .orElseThrow( + () -> + new NotFoundException( + "Transaction Wave non trouvée avec l'ID: " + dto.getTransactionWaveId())); + pieceJointe.setTransactionWave(transactionWave); + } + + return pieceJointe; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/EvenementService.java b/src/main/java/dev/lions/unionflow/server/service/EvenementService.java new file mode 100644 index 0000000..209e4be --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/EvenementService.java @@ -0,0 +1,340 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.service.KeycloakService; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion des événements Version simplifiée pour tester les imports et + * Lombok + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@ApplicationScoped +public class EvenementService { + + private static final Logger LOG = Logger.getLogger(EvenementService.class); + + @Inject EvenementRepository evenementRepository; + + @Inject MembreRepository membreRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouvel événement + * + * @param evenement l'événement à créer + * @return l'événement créé + * @throws IllegalArgumentException si les données sont invalides + */ + @Transactional + public Evenement creerEvenement(Evenement evenement) { + LOG.infof("Création événement: %s", evenement.getTitre()); + + // Validation des données + validerEvenement(evenement); + + // Vérifier l'unicité du titre dans l'organisation + if (evenement.getOrganisation() != null) { + Optional existant = evenementRepository.findByTitre(evenement.getTitre()); + if (existant.isPresent() + && existant.get().getOrganisation().getId().equals(evenement.getOrganisation().getId())) { + throw new IllegalArgumentException( + "Un événement avec ce titre existe déjà dans cette organisation"); + } + } + + // Métadonnées de création + evenement.setCreePar(keycloakService.getCurrentUserEmail()); + + // Valeurs par défaut + if (evenement.getStatut() == null) { + evenement.setStatut(StatutEvenement.PLANIFIE); + } + if (evenement.getActif() == null) { + evenement.setActif(true); + } + if (evenement.getVisiblePublic() == null) { + evenement.setVisiblePublic(true); + } + if (evenement.getInscriptionRequise() == null) { + evenement.setInscriptionRequise(true); + } + + evenementRepository.persist(evenement); + + LOG.infof("Événement créé avec succès: ID=%s, Titre=%s", evenement.getId(), evenement.getTitre()); + return evenement; + } + + /** + * Met à jour un événement existant + * + * @param id l'UUID de l'événement + * @param evenementMisAJour les nouvelles données + * @return l'événement mis à jour + * @throws IllegalArgumentException si l'événement n'existe pas + */ + @Transactional + public Evenement mettreAJourEvenement(UUID id, Evenement evenementMisAJour) { + LOG.infof("Mise à jour événement ID: %s", id); + + Evenement evenementExistant = + evenementRepository + .findByIdOptional(id) + .orElseThrow( + () -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id)); + + // Vérifier les permissions + if (!peutModifierEvenement(evenementExistant)) { + throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement"); + } + + // Validation des nouvelles données + validerEvenement(evenementMisAJour); + + // Mise à jour des champs + evenementExistant.setTitre(evenementMisAJour.getTitre()); + evenementExistant.setDescription(evenementMisAJour.getDescription()); + evenementExistant.setDateDebut(evenementMisAJour.getDateDebut()); + evenementExistant.setDateFin(evenementMisAJour.getDateFin()); + evenementExistant.setLieu(evenementMisAJour.getLieu()); + evenementExistant.setAdresse(evenementMisAJour.getAdresse()); + evenementExistant.setTypeEvenement(evenementMisAJour.getTypeEvenement()); + evenementExistant.setCapaciteMax(evenementMisAJour.getCapaciteMax()); + evenementExistant.setPrix(evenementMisAJour.getPrix()); + evenementExistant.setInscriptionRequise(evenementMisAJour.getInscriptionRequise()); + evenementExistant.setDateLimiteInscription(evenementMisAJour.getDateLimiteInscription()); + evenementExistant.setInstructionsParticulieres( + evenementMisAJour.getInstructionsParticulieres()); + evenementExistant.setContactOrganisateur(evenementMisAJour.getContactOrganisateur()); + evenementExistant.setMaterielRequis(evenementMisAJour.getMaterielRequis()); + evenementExistant.setVisiblePublic(evenementMisAJour.getVisiblePublic()); + + // Métadonnées de modification + evenementExistant.setModifiePar(keycloakService.getCurrentUserEmail()); + + evenementRepository.update(evenementExistant); + + LOG.infof("Événement mis à jour avec succès: ID=%s", id); + return evenementExistant; + } + + /** Trouve un événement par ID */ + public Optional trouverParId(UUID id) { + return evenementRepository.findByIdOptional(id); + } + + /** Liste tous les événements actifs avec pagination */ + public List listerEvenementsActifs(Page page, Sort sort) { + return evenementRepository.findAllActifs(page, sort); + } + + /** Liste les événements à venir */ + public List listerEvenementsAVenir(Page page, Sort sort) { + return evenementRepository.findEvenementsAVenir(page, sort); + } + + /** Liste les événements publics */ + public List listerEvenementsPublics(Page page, Sort sort) { + return evenementRepository.findEvenementsPublics(page, sort); + } + + /** Recherche d'événements par terme */ + public List rechercherEvenements(String terme, Page page, Sort sort) { + return evenementRepository.rechercheAvancee( + terme, null, null, null, null, null, null, null, null, null, page, sort); + } + + /** Liste les événements par type */ + public List listerParType(TypeEvenement type, Page page, Sort sort) { + return evenementRepository.findByType(type, page, sort); + } + + /** + * Supprime logiquement un événement + * + * @param id l'UUID de l'événement à supprimer + * @throws IllegalArgumentException si l'événement n'existe pas + */ + @Transactional + public void supprimerEvenement(UUID id) { + LOG.infof("Suppression événement ID: %s", id); + + Evenement evenement = + evenementRepository + .findByIdOptional(id) + .orElseThrow( + () -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id)); + + // Vérifier les permissions + if (!peutModifierEvenement(evenement)) { + throw new SecurityException("Vous n'avez pas les permissions pour supprimer cet événement"); + } + + // Vérifier s'il y a des inscriptions + if (evenement.getNombreInscrits() > 0) { + throw new IllegalStateException("Impossible de supprimer un événement avec des inscriptions"); + } + + // Suppression logique + evenement.setActif(false); + evenement.setModifiePar(keycloakService.getCurrentUserEmail()); + + evenementRepository.update(evenement); + + LOG.infof("Événement supprimé avec succès: ID=%s", id); + } + + /** + * Change le statut d'un événement + * + * @param id l'UUID de l'événement + * @param nouveauStatut le nouveau statut + * @return l'événement mis à jour + */ + @Transactional + public Evenement changerStatut(UUID id, StatutEvenement nouveauStatut) { + LOG.infof("Changement statut événement ID: %s vers %s", id, nouveauStatut); + + Evenement evenement = + evenementRepository + .findByIdOptional(id) + .orElseThrow( + () -> new IllegalArgumentException("Événement non trouvé avec l'ID: " + id)); + + // Vérifier les permissions + if (!peutModifierEvenement(evenement)) { + throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement"); + } + + // Valider le changement de statut + validerChangementStatut(evenement.getStatut(), nouveauStatut); + + evenement.setStatut(nouveauStatut); + evenement.setModifiePar(keycloakService.getCurrentUserEmail()); + + evenementRepository.update(evenement); + + LOG.infof("Statut événement changé avec succès: ID=%s, Nouveau statut=%s", id, nouveauStatut); + return evenement; + } + + /** + * Compte le nombre total d'événements + * + * @return le nombre total d'événements + */ + public long countEvenements() { + return evenementRepository.count(); + } + + /** + * Compte le nombre d'événements actifs + * + * @return le nombre d'événements actifs + */ + public long countEvenementsActifs() { + return evenementRepository.countActifs(); + } + + /** + * Obtient les statistiques des événements + * + * @return les statistiques sous forme de Map + */ + public Map obtenirStatistiques() { + Map statsBase = evenementRepository.getStatistiques(); + + long total = statsBase.getOrDefault("total", 0L); + long actifs = statsBase.getOrDefault("actifs", 0L); + long aVenir = statsBase.getOrDefault("aVenir", 0L); + long enCours = statsBase.getOrDefault("enCours", 0L); + + Map result = new java.util.HashMap<>(); + result.put("total", total); + result.put("actifs", actifs); + result.put("aVenir", aVenir); + result.put("enCours", enCours); + result.put("passes", statsBase.getOrDefault("passes", 0L)); + result.put("publics", statsBase.getOrDefault("publics", 0L)); + result.put("avecInscription", statsBase.getOrDefault("avecInscription", 0L)); + result.put("tauxActivite", total > 0 ? (actifs * 100.0 / total) : 0.0); + result.put("tauxEvenementsAVenir", total > 0 ? (aVenir * 100.0 / total) : 0.0); + result.put("tauxEvenementsEnCours", total > 0 ? (enCours * 100.0 / total) : 0.0); + result.put("timestamp", LocalDateTime.now()); + return result; + } + + // Méthodes privées de validation et permissions + + /** Valide les données d'un événement */ + private void validerEvenement(Evenement evenement) { + if (evenement.getTitre() == null || evenement.getTitre().trim().isEmpty()) { + throw new IllegalArgumentException("Le titre de l'événement est obligatoire"); + } + + if (evenement.getDateDebut() == null) { + throw new IllegalArgumentException("La date de début est obligatoire"); + } + + if (evenement.getDateDebut().isBefore(LocalDateTime.now().minusHours(1))) { + throw new IllegalArgumentException("La date de début ne peut pas être dans le passé"); + } + + if (evenement.getDateFin() != null + && evenement.getDateFin().isBefore(evenement.getDateDebut())) { + throw new IllegalArgumentException( + "La date de fin ne peut pas être antérieure à la date de début"); + } + + if (evenement.getCapaciteMax() != null && evenement.getCapaciteMax() <= 0) { + throw new IllegalArgumentException("La capacité maximale doit être positive"); + } + + if (evenement.getPrix() != null + && evenement.getPrix().compareTo(java.math.BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Le prix ne peut pas être négatif"); + } + } + + /** Valide un changement de statut */ + private void validerChangementStatut( + StatutEvenement statutActuel, StatutEvenement nouveauStatut) { + // Règles de transition simplifiées pour la version mobile + if (statutActuel == StatutEvenement.TERMINE || statutActuel == StatutEvenement.ANNULE) { + throw new IllegalArgumentException( + "Impossible de changer le statut d'un événement terminé ou annulé"); + } + } + + /** Vérifie les permissions de modification pour l'application mobile */ + private boolean peutModifierEvenement(Evenement evenement) { + if (keycloakService.hasRole("ADMIN") || keycloakService.hasRole("ORGANISATEUR_EVENEMENT")) { + return true; + } + + String utilisateurActuel = keycloakService.getCurrentUserEmail(); + return utilisateurActuel != null && utilisateurActuel.equals(evenement.getCreePar()); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/ExportService.java b/src/main/java/dev/lions/unionflow/server/service/ExportService.java new file mode 100644 index 0000000..659e86d --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/ExportService.java @@ -0,0 +1,237 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.finance.CotisationDTO; +import dev.lions.unionflow.server.entity.Cotisation; +import dev.lions.unionflow.server.repository.CotisationRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jboss.logging.Logger; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * Service d'export des données en Excel et PDF + * + * @author UnionFlow Team + * @version 1.0 + */ +@ApplicationScoped +public class ExportService { + + private static final Logger LOG = Logger.getLogger(ExportService.class); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); + + @Inject + CotisationRepository cotisationRepository; + + @Inject + CotisationService cotisationService; + + /** + * Exporte les cotisations en format CSV (compatible Excel) + */ + public byte[] exporterCotisationsCSV(List cotisationIds) { + LOG.infof("Export CSV de %d cotisations", cotisationIds.size()); + + StringBuilder csv = new StringBuilder(); + csv.append("Numéro Référence;Membre;Type;Montant Dû;Montant Payé;Statut;Date Échéance;Date Paiement;Méthode Paiement\n"); + + for (UUID id : cotisationIds) { + Optional cotisationOpt = cotisationRepository.findByIdOptional(id); + if (cotisationOpt.isPresent()) { + Cotisation c = cotisationOpt.get(); + String nomMembre = c.getMembre() != null + ? c.getMembre().getNom() + " " + c.getMembre().getPrenom() + : ""; + csv.append(String.format("%s;%s;%s;%s;%s;%s;%s;%s;%s\n", + c.getNumeroReference() != null ? c.getNumeroReference() : "", + nomMembre, + c.getTypeCotisation() != null ? c.getTypeCotisation() : "", + c.getMontantDu() != null ? c.getMontantDu().toString() : "0", + c.getMontantPaye() != null ? c.getMontantPaye().toString() : "0", + c.getStatut() != null ? c.getStatut() : "", + c.getDateEcheance() != null ? c.getDateEcheance().format(DATE_FORMATTER) : "", + c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "", + c.getMethodePaiement() != null ? c.getMethodePaiement() : "" + )); + } + } + + return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + } + + /** + * Exporte toutes les cotisations filtrées en CSV + */ + public byte[] exporterToutesCotisationsCSV(String statut, String type, UUID associationId) { + LOG.info("Export CSV de toutes les cotisations"); + + List cotisations = cotisationRepository.listAll(); + + // Filtrer + if (statut != null && !statut.isEmpty()) { + cotisations = cotisations.stream() + .filter(c -> c.getStatut() != null && c.getStatut().equals(statut)) + .toList(); + } + if (type != null && !type.isEmpty()) { + cotisations = cotisations.stream() + .filter(c -> c.getTypeCotisation() != null && c.getTypeCotisation().equals(type)) + .toList(); + } + // Note: le filtrage par association n'est pas disponible car Membre n'a pas de lien direct + // avec Association dans cette version du modèle + + List ids = cotisations.stream().map(Cotisation::getId).toList(); + return exporterCotisationsCSV(ids); + } + + /** + * Génère un reçu de paiement en format texte (pour impression) + */ + public byte[] genererRecuPaiement(UUID cotisationId) { + LOG.infof("Génération reçu pour cotisation: %s", cotisationId); + + Optional cotisationOpt = cotisationRepository.findByIdOptional(cotisationId); + if (cotisationOpt.isEmpty()) { + return "Cotisation non trouvée".getBytes(); + } + + Cotisation c = cotisationOpt.get(); + + StringBuilder recu = new StringBuilder(); + recu.append("═══════════════════════════════════════════════════════════════\n"); + recu.append(" REÇU DE PAIEMENT\n"); + recu.append("═══════════════════════════════════════════════════════════════\n\n"); + + recu.append("Numéro de reçu : ").append(c.getNumeroReference()).append("\n"); + recu.append("Date : ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n"); + + recu.append("───────────────────────────────────────────────────────────────\n"); + recu.append(" INFORMATIONS MEMBRE\n"); + recu.append("───────────────────────────────────────────────────────────────\n"); + + if (c.getMembre() != null) { + recu.append("Nom : ").append(c.getMembre().getNom()).append(" ").append(c.getMembre().getPrenom()).append("\n"); + recu.append("Numéro membre : ").append(c.getMembre().getNumeroMembre()).append("\n"); + } + + recu.append("\n───────────────────────────────────────────────────────────────\n"); + recu.append(" DÉTAILS DU PAIEMENT\n"); + recu.append("───────────────────────────────────────────────────────────────\n"); + + recu.append("Type cotisation : ").append(c.getTypeCotisation() != null ? c.getTypeCotisation() : "").append("\n"); + recu.append("Période : ").append(c.getPeriode() != null ? c.getPeriode() : "").append("\n"); + recu.append("Montant dû : ").append(formatMontant(c.getMontantDu())).append("\n"); + recu.append("Montant payé : ").append(formatMontant(c.getMontantPaye())).append("\n"); + recu.append("Mode de paiement : ").append(c.getMethodePaiement() != null ? c.getMethodePaiement() : "").append("\n"); + recu.append("Date de paiement : ").append(c.getDatePaiement() != null ? c.getDatePaiement().format(DATETIME_FORMATTER) : "").append("\n"); + recu.append("Statut : ").append(c.getStatut() != null ? c.getStatut() : "").append("\n"); + + recu.append("\n═══════════════════════════════════════════════════════════════\n"); + recu.append(" Ce document fait foi de paiement de cotisation\n"); + recu.append(" Merci de votre confiance !\n"); + recu.append("═══════════════════════════════════════════════════════════════\n"); + + return recu.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + } + + /** + * Génère plusieurs reçus de paiement + */ + public byte[] genererRecusGroupes(List cotisationIds) { + LOG.infof("Génération de %d reçus groupés", cotisationIds.size()); + + StringBuilder allRecus = new StringBuilder(); + for (int i = 0; i < cotisationIds.size(); i++) { + byte[] recu = genererRecuPaiement(cotisationIds.get(i)); + allRecus.append(new String(recu, java.nio.charset.StandardCharsets.UTF_8)); + if (i < cotisationIds.size() - 1) { + allRecus.append("\n\n════════════════════════ PAGE SUIVANTE ════════════════════════\n\n"); + } + } + + return allRecus.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + } + + /** + * Génère un rapport mensuel + */ + public byte[] genererRapportMensuel(int annee, int mois, UUID associationId) { + LOG.infof("Génération rapport mensuel: %d/%d", mois, annee); + + List cotisations = cotisationRepository.listAll(); + + // Filtrer par mois/année et association + LocalDate debut = LocalDate.of(annee, mois, 1); + LocalDate fin = debut.plusMonths(1).minusDays(1); + + cotisations = cotisations.stream() + .filter(c -> { + if (c.getDateCreation() == null) return false; + LocalDate dateCot = c.getDateCreation().toLocalDate(); + return !dateCot.isBefore(debut) && !dateCot.isAfter(fin); + }) + // Note: le filtrage par association n'est pas implémenté ici + .toList(); + + // Calculer les statistiques + long total = cotisations.size(); + long payees = cotisations.stream().filter(c -> "PAYEE".equals(c.getStatut())).count(); + long enAttente = cotisations.stream().filter(c -> "EN_ATTENTE".equals(c.getStatut())).count(); + long enRetard = cotisations.stream().filter(c -> "EN_RETARD".equals(c.getStatut())).count(); + + BigDecimal montantTotal = cotisations.stream() + .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal montantCollecte = cotisations.stream() + .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())) + .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + double tauxRecouvrement = montantTotal.compareTo(BigDecimal.ZERO) > 0 + ? montantCollecte.multiply(BigDecimal.valueOf(100)).divide(montantTotal, 2, java.math.RoundingMode.HALF_UP).doubleValue() + : 0; + + // Construire le rapport + StringBuilder rapport = new StringBuilder(); + rapport.append("═══════════════════════════════════════════════════════════════\n"); + rapport.append(" RAPPORT MENSUEL DES COTISATIONS\n"); + rapport.append("═══════════════════════════════════════════════════════════════\n\n"); + + rapport.append("Période : ").append(String.format("%02d/%d", mois, annee)).append("\n"); + rapport.append("Date de génération: ").append(LocalDateTime.now().format(DATETIME_FORMATTER)).append("\n\n"); + + rapport.append("───────────────────────────────────────────────────────────────\n"); + rapport.append(" RÉSUMÉ\n"); + rapport.append("───────────────────────────────────────────────────────────────\n\n"); + + rapport.append("Total cotisations : ").append(total).append("\n"); + rapport.append("Cotisations payées : ").append(payees).append("\n"); + rapport.append("Cotisations en attente: ").append(enAttente).append("\n"); + rapport.append("Cotisations en retard : ").append(enRetard).append("\n\n"); + + rapport.append("───────────────────────────────────────────────────────────────\n"); + rapport.append(" FINANCIER\n"); + rapport.append("───────────────────────────────────────────────────────────────\n\n"); + + rapport.append("Montant total attendu : ").append(formatMontant(montantTotal)).append("\n"); + rapport.append("Montant collecté : ").append(formatMontant(montantCollecte)).append("\n"); + rapport.append("Taux de recouvrement : ").append(String.format("%.1f%%", tauxRecouvrement)).append("\n\n"); + + rapport.append("═══════════════════════════════════════════════════════════════\n"); + + return rapport.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + } + + private String formatMontant(BigDecimal montant) { + if (montant == null) return "0 FCFA"; + return String.format("%,.0f FCFA", montant.doubleValue()); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/KPICalculatorService.java b/src/main/java/dev/lions/unionflow/server/service/KPICalculatorService.java new file mode 100644 index 0000000..c99280b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/KPICalculatorService.java @@ -0,0 +1,363 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; +import dev.lions.unionflow.server.repository.CotisationRepository; +import dev.lions.unionflow.server.repository.DemandeAideRepository; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** + * Service spécialisé dans le calcul des KPI (Key Performance Indicators) + * + *

Ce service fournit des méthodes optimisées pour calculer les indicateurs de performance clés + * de l'application UnionFlow. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +@Slf4j +public class KPICalculatorService { + + @Inject MembreRepository membreRepository; + + @Inject CotisationRepository cotisationRepository; + + @Inject EvenementRepository evenementRepository; + + @Inject DemandeAideRepository demandeAideRepository; + + /** + * Calcule tous les KPI principaux pour une organisation + * + * @param organisationId L'ID de l'organisation + * @param dateDebut Date de début de la période + * @param dateFin Date de fin de la période + * @return Map contenant tous les KPI calculés + */ + public Map calculerTousLesKPI( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + log.info( + "Calcul de tous les KPI pour l'organisation {} sur la période {} - {}", + organisationId, + dateDebut, + dateFin); + + Map kpis = new HashMap<>(); + + // KPI Membres + kpis.put( + TypeMetrique.NOMBRE_MEMBRES_ACTIFS, + calculerKPIMembresActifs(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.NOMBRE_MEMBRES_INACTIFS, + calculerKPIMembresInactifs(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.TAUX_CROISSANCE_MEMBRES, + calculerKPITauxCroissanceMembres(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.MOYENNE_AGE_MEMBRES, + calculerKPIMoyenneAgeMembres(organisationId, dateDebut, dateFin)); + + // KPI Financiers + kpis.put( + TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, + calculerKPITotalCotisations(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.COTISATIONS_EN_ATTENTE, + calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS, + calculerKPITauxRecouvrement(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.MOYENNE_COTISATION_MEMBRE, + calculerKPIMoyenneCotisationMembre(organisationId, dateDebut, dateFin)); + + // KPI Événements + kpis.put( + TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES, + calculerKPINombreEvenements(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS, + calculerKPITauxParticipation(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.MOYENNE_PARTICIPANTS_EVENEMENT, + calculerKPIMoyenneParticipants(organisationId, dateDebut, dateFin)); + + // KPI Solidarité + kpis.put( + TypeMetrique.NOMBRE_DEMANDES_AIDE, + calculerKPINombreDemandesAide(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.MONTANT_AIDES_ACCORDEES, + calculerKPIMontantAides(organisationId, dateDebut, dateFin)); + kpis.put( + TypeMetrique.TAUX_APPROBATION_AIDES, + calculerKPITauxApprobationAides(organisationId, dateDebut, dateFin)); + + log.info("Calcul terminé : {} KPI calculés", kpis.size()); + return kpis; + } + + /** + * Calcule le KPI de performance globale de l'organisation + * + * @param organisationId L'ID de l'organisation + * @param dateDebut Date de début de la période + * @param dateFin Date de fin de la période + * @return Score de performance global (0-100) + */ + public BigDecimal calculerKPIPerformanceGlobale( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + log.info("Calcul du KPI de performance globale pour l'organisation {}", organisationId); + + Map kpis = calculerTousLesKPI(organisationId, dateDebut, dateFin); + + // Pondération des différents KPI pour le score global + BigDecimal scoreMembers = calculerScoreMembres(kpis).multiply(new BigDecimal("0.30")); // 30% + BigDecimal scoreFinancier = + calculerScoreFinancier(kpis).multiply(new BigDecimal("0.35")); // 35% + BigDecimal scoreEvenements = + calculerScoreEvenements(kpis).multiply(new BigDecimal("0.20")); // 20% + BigDecimal scoreSolidarite = + calculerScoreSolidarite(kpis).multiply(new BigDecimal("0.15")); // 15% + + BigDecimal scoreGlobal = + scoreMembers.add(scoreFinancier).add(scoreEvenements).add(scoreSolidarite); + + log.info("Score de performance globale calculé : {}", scoreGlobal); + return scoreGlobal.setScale(1, RoundingMode.HALF_UP); + } + + /** + * Calcule les KPI de comparaison avec la période précédente + * + * @param organisationId L'ID de l'organisation + * @param dateDebut Date de début de la période actuelle + * @param dateFin Date de fin de la période actuelle + * @return Map des évolutions en pourcentage + */ + public Map calculerEvolutionsKPI( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + log.info("Calcul des évolutions KPI pour l'organisation {}", organisationId); + + // Période actuelle + Map kpisActuels = + calculerTousLesKPI(organisationId, dateDebut, dateFin); + + // Période précédente (même durée, décalée) + long dureeJours = java.time.Duration.between(dateDebut, dateFin).toDays(); + LocalDateTime dateDebutPrecedente = dateDebut.minusDays(dureeJours); + LocalDateTime dateFinPrecedente = dateFin.minusDays(dureeJours); + Map kpisPrecedents = + calculerTousLesKPI(organisationId, dateDebutPrecedente, dateFinPrecedente); + + Map evolutions = new HashMap<>(); + + for (TypeMetrique typeMetrique : kpisActuels.keySet()) { + BigDecimal valeurActuelle = kpisActuels.get(typeMetrique); + BigDecimal valeurPrecedente = kpisPrecedents.get(typeMetrique); + + BigDecimal evolution = calculerPourcentageEvolution(valeurActuelle, valeurPrecedente); + evolutions.put(typeMetrique, evolution); + } + + return evolutions; + } + + // === MÉTHODES PRIVÉES DE CALCUL DES KPI === + + private BigDecimal calculerKPIMembresActifs( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerKPIMembresInactifs( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = membreRepository.countMembresInactifs(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerKPITauxCroissanceMembres( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long membresActuels = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + Long membresPrecedents = + membreRepository.countMembresActifs( + organisationId, dateDebut.minusMonths(1), dateFin.minusMonths(1)); + + return calculerTauxCroissance( + new BigDecimal(membresActuels), new BigDecimal(membresPrecedents)); + } + + private BigDecimal calculerKPIMoyenneAgeMembres( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Double moyenneAge = membreRepository.calculerMoyenneAge(organisationId, dateDebut, dateFin); + return moyenneAge != null + ? new BigDecimal(moyenneAge).setScale(1, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + } + + private BigDecimal calculerKPITotalCotisations( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = cotisationRepository.sumMontantsPayes(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerKPICotisationsEnAttente( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = + cotisationRepository.sumMontantsEnAttente(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerKPITauxRecouvrement( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal collectees = calculerKPITotalCotisations(organisationId, dateDebut, dateFin); + BigDecimal enAttente = calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin); + BigDecimal total = collectees.add(enAttente); + + if (total.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; + + return collectees.divide(total, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100")); + } + + private BigDecimal calculerKPIMoyenneCotisationMembre( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = calculerKPITotalCotisations(organisationId, dateDebut, dateFin); + Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + + if (nombreMembres == 0) return BigDecimal.ZERO; + + return total.divide(new BigDecimal(nombreMembres), 2, RoundingMode.HALF_UP); + } + + private BigDecimal calculerKPINombreEvenements( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = evenementRepository.countEvenements(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerKPITauxParticipation( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + // Calcul basé sur les participations aux événements + Long totalParticipations = + evenementRepository.countTotalParticipations(organisationId, dateDebut, dateFin); + Long nombreEvenements = evenementRepository.countEvenements(organisationId, dateDebut, dateFin); + Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin); + + if (nombreEvenements == 0 || nombreMembres == 0) return BigDecimal.ZERO; + + BigDecimal participationsAttendues = + new BigDecimal(nombreEvenements).multiply(new BigDecimal(nombreMembres)); + BigDecimal tauxParticipation = + new BigDecimal(totalParticipations) + .divide(participationsAttendues, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + + return tauxParticipation; + } + + private BigDecimal calculerKPIMoyenneParticipants( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Double moyenne = + evenementRepository.calculerMoyenneParticipants(organisationId, dateDebut, dateFin); + return moyenne != null + ? new BigDecimal(moyenne).setScale(1, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + } + + private BigDecimal calculerKPINombreDemandesAide( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long count = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin); + return new BigDecimal(count); + } + + private BigDecimal calculerKPIMontantAides( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + BigDecimal total = + demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin); + return total != null ? total : BigDecimal.ZERO; + } + + private BigDecimal calculerKPITauxApprobationAides( + UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) { + Long totalDemandes = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin); + Long demandesApprouvees = + demandeAideRepository.countDemandesApprouvees(organisationId, dateDebut, dateFin); + + if (totalDemandes == 0) return BigDecimal.ZERO; + + return new BigDecimal(demandesApprouvees) + .divide(new BigDecimal(totalDemandes), 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + // === MÉTHODES UTILITAIRES === + + private BigDecimal calculerTauxCroissance( + BigDecimal valeurActuelle, BigDecimal valeurPrecedente) { + if (valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; + + return valeurActuelle + .subtract(valeurPrecedente) + .divide(valeurPrecedente, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + private BigDecimal calculerPourcentageEvolution( + BigDecimal valeurActuelle, BigDecimal valeurPrecedente) { + if (valeurPrecedente == null || valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO; + } + + return valeurActuelle + .subtract(valeurPrecedente) + .divide(valeurPrecedente, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + private BigDecimal calculerScoreMembres(Map kpis) { + // Score basé sur la croissance et l'activité des membres + BigDecimal tauxCroissance = kpis.get(TypeMetrique.TAUX_CROISSANCE_MEMBRES); + BigDecimal nombreActifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_ACTIFS); + BigDecimal nombreInactifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_INACTIFS); + + // Calcul du score (logique simplifiée) + BigDecimal scoreActivite = + nombreActifs + .divide(nombreActifs.add(nombreInactifs), 2, RoundingMode.HALF_UP) + .multiply(new BigDecimal("50")); + BigDecimal scoreCroissance = tauxCroissance.min(new BigDecimal("50")); // Plafonné à 50 + + return scoreActivite.add(scoreCroissance); + } + + private BigDecimal calculerScoreFinancier(Map kpis) { + // Score basé sur le recouvrement et les montants + BigDecimal tauxRecouvrement = kpis.get(TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS); + return tauxRecouvrement; // Score direct basé sur le taux de recouvrement + } + + private BigDecimal calculerScoreEvenements(Map kpis) { + // Score basé sur la participation aux événements + BigDecimal tauxParticipation = kpis.get(TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS); + return tauxParticipation; // Score direct basé sur le taux de participation + } + + private BigDecimal calculerScoreSolidarite(Map kpis) { + // Score basé sur l'efficacité du système de solidarité + BigDecimal tauxApprobation = kpis.get(TypeMetrique.TAUX_APPROBATION_AIDES); + return tauxApprobation; // Score direct basé sur le taux d'approbation + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/KeycloakService.java b/src/main/java/dev/lions/unionflow/server/service/KeycloakService.java new file mode 100644 index 0000000..89bc5ae --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/KeycloakService.java @@ -0,0 +1,311 @@ +package dev.lions.unionflow.server.service; + +import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.Set; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.jboss.logging.Logger; + +/** + * Service pour l'intégration avec Keycloak + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@ApplicationScoped +public class KeycloakService { + + private static final Logger LOG = Logger.getLogger(KeycloakService.class); + + @Inject SecurityIdentity securityIdentity; + + @Inject JsonWebToken jwt; + + /** + * Vérifie si l'utilisateur actuel est authentifié + * + * @return true si l'utilisateur est authentifié + */ + public boolean isAuthenticated() { + return securityIdentity != null && !securityIdentity.isAnonymous(); + } + + /** + * Obtient l'ID de l'utilisateur actuel depuis Keycloak + * + * @return l'ID de l'utilisateur ou null si non authentifié + */ + public String getCurrentUserId() { + if (!isAuthenticated()) { + return null; + } + + try { + return jwt.getSubject(); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération de l'ID utilisateur: %s", e.getMessage()); + return null; + } + } + + /** + * Obtient l'email de l'utilisateur actuel + * + * @return l'email de l'utilisateur ou null si non authentifié + */ + public String getCurrentUserEmail() { + if (!isAuthenticated()) { + return null; + } + + try { + return jwt.getClaim("email"); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération de l'email utilisateur: %s", e.getMessage()); + return securityIdentity.getPrincipal().getName(); + } + } + + /** + * Obtient le nom complet de l'utilisateur actuel + * + * @return le nom complet ou null si non disponible + */ + public String getCurrentUserFullName() { + if (!isAuthenticated()) { + return null; + } + + try { + String firstName = jwt.getClaim("given_name"); + String lastName = jwt.getClaim("family_name"); + + if (firstName != null && lastName != null) { + return firstName + " " + lastName; + } else if (firstName != null) { + return firstName; + } else if (lastName != null) { + return lastName; + } + + // Fallback sur le nom d'utilisateur + return jwt.getClaim("preferred_username"); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération du nom utilisateur: %s", e.getMessage()); + return null; + } + } + + /** + * Obtient tous les rôles de l'utilisateur actuel + * + * @return les rôles de l'utilisateur + */ + public Set getCurrentUserRoles() { + if (!isAuthenticated()) { + return Set.of(); + } + + return securityIdentity.getRoles(); + } + + /** + * Vérifie si l'utilisateur actuel a un rôle spécifique + * + * @param role le rôle à vérifier + * @return true si l'utilisateur a le rôle + */ + public boolean hasRole(String role) { + if (!isAuthenticated()) { + return false; + } + + return securityIdentity.hasRole(role); + } + + /** + * Vérifie si l'utilisateur actuel a au moins un des rôles spécifiés + * + * @param roles les rôles à vérifier + * @return true si l'utilisateur a au moins un des rôles + */ + public boolean hasAnyRole(String... roles) { + if (!isAuthenticated()) { + return false; + } + + for (String role : roles) { + if (securityIdentity.hasRole(role)) { + return true; + } + } + return false; + } + + /** + * Vérifie si l'utilisateur actuel a tous les rôles spécifiés + * + * @param roles les rôles à vérifier + * @return true si l'utilisateur a tous les rôles + */ + public boolean hasAllRoles(String... roles) { + if (!isAuthenticated()) { + return false; + } + + for (String role : roles) { + if (!securityIdentity.hasRole(role)) { + return false; + } + } + return true; + } + + /** + * Obtient une claim spécifique du JWT + * + * @param claimName le nom de la claim + * @return la valeur de la claim ou null si non trouvée + */ + public T getClaim(String claimName) { + if (!isAuthenticated()) { + return null; + } + + try { + return jwt.getClaim(claimName); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération de la claim %s: %s", claimName, e.getMessage()); + return null; + } + } + + /** + * Obtient toutes les claims du JWT + * + * @return toutes les claims ou une map vide si non authentifié + */ + public Set getAllClaimNames() { + if (!isAuthenticated()) { + return Set.of(); + } + + try { + return jwt.getClaimNames(); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération des claims: %s", e.getMessage()); + return Set.of(); + } + } + + /** + * Obtient les informations utilisateur pour les logs + * + * @return informations utilisateur formatées + */ + public String getUserInfoForLogging() { + if (!isAuthenticated()) { + return "Utilisateur non authentifié"; + } + + String email = getCurrentUserEmail(); + String fullName = getCurrentUserFullName(); + Set roles = getCurrentUserRoles(); + + return String.format( + "Utilisateur: %s (%s), Rôles: %s", + fullName != null ? fullName : "N/A", email != null ? email : "N/A", roles); + } + + /** + * Vérifie si l'utilisateur actuel est un administrateur + * + * @return true si l'utilisateur est administrateur + */ + public boolean isAdmin() { + return hasRole("ADMIN") || hasRole("admin"); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les membres + * + * @return true si l'utilisateur peut gérer les membres + */ + public boolean canManageMembers() { + return hasAnyRole( + "ADMIN", + "GESTIONNAIRE_MEMBRE", + "PRESIDENT", + "SECRETAIRE", + "admin", + "gestionnaire_membre", + "president", + "secretaire"); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les finances + * + * @return true si l'utilisateur peut gérer les finances + */ + public boolean canManageFinances() { + return hasAnyRole("ADMIN", "TRESORIER", "PRESIDENT", "admin", "tresorier", "president"); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les événements + * + * @return true si l'utilisateur peut gérer les événements + */ + public boolean canManageEvents() { + return hasAnyRole( + "ADMIN", + "ORGANISATEUR_EVENEMENT", + "PRESIDENT", + "SECRETAIRE", + "admin", + "organisateur_evenement", + "president", + "secretaire"); + } + + /** + * Vérifie si l'utilisateur actuel peut gérer les organisations + * + * @return true si l'utilisateur peut gérer les organisations + */ + public boolean canManageOrganizations() { + return hasAnyRole("ADMIN", "PRESIDENT", "admin", "president"); + } + + /** Log les informations de sécurité pour debug */ + public void logSecurityInfo() { + if (LOG.isDebugEnabled()) { + LOG.debugf("Informations de sécurité: %s", getUserInfoForLogging()); + } + } + + /** + * Obtient le token d'accès brut + * + * @return le token JWT brut ou null si non disponible + */ + public String getRawAccessToken() { + if (!isAuthenticated()) { + return null; + } + + try { + if (jwt instanceof OidcJwtCallerPrincipal) { + return ((OidcJwtCallerPrincipal) jwt).getRawToken(); + } + return jwt.getRawToken(); + } catch (Exception e) { + LOG.warnf("Erreur lors de la récupération du token brut: %s", e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/MatchingService.java b/src/main/java/dev/lions/unionflow/server/service/MatchingService.java new file mode 100644 index 0000000..d66eafc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/MatchingService.java @@ -0,0 +1,428 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +/** + * Service intelligent de matching entre demandes et propositions d'aide + * + *

Ce service utilise des algorithmes avancés pour faire correspondre les demandes d'aide avec + * les propositions les plus appropriées. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class MatchingService { + + private static final Logger LOG = Logger.getLogger(MatchingService.class); + + @Inject PropositionAideService propositionAideService; + + @Inject DemandeAideService demandeAideService; + + @ConfigProperty(name = "unionflow.matching.score-minimum", defaultValue = "30.0") + double scoreMinimumMatching; + + @ConfigProperty(name = "unionflow.matching.max-resultats", defaultValue = "10") + int maxResultatsMatching; + + @ConfigProperty(name = "unionflow.matching.boost-geographique", defaultValue = "10.0") + double boostGeographique; + + @ConfigProperty(name = "unionflow.matching.boost-experience", defaultValue = "5.0") + double boostExperience; + + // === MATCHING DEMANDES -> PROPOSITIONS === + + /** + * Trouve les propositions compatibles avec une demande d'aide + * + * @param demande La demande d'aide + * @return Liste des propositions compatibles triées par score + */ + public List trouverPropositionsCompatibles(DemandeAideDTO demande) { + LOG.infof("Recherche de propositions compatibles pour la demande: %s", demande.getId()); + + long startTime = System.currentTimeMillis(); + + try { + // 1. Recherche de base par type d'aide + List candidats = + propositionAideService.obtenirPropositionsActives(demande.getTypeAide()); + + // 2. Si pas assez de candidats, élargir à la catégorie + if (candidats.size() < 3) { + candidats.addAll(rechercherParCategorie(demande.getTypeAide().getCategorie())); + } + + // 3. Filtrage et scoring + List resultats = + candidats.stream() + .filter(PropositionAideDTO::isActiveEtDisponible) + .filter(p -> p.peutAccepterBeneficiaires()) + .map( + proposition -> { + double score = calculerScoreCompatibilite(demande, proposition); + return new ResultatMatching(proposition, score); + }) + .filter(resultat -> resultat.score >= scoreMinimumMatching) + .sorted((r1, r2) -> Double.compare(r2.score, r1.score)) + .limit(maxResultatsMatching) + .collect(Collectors.toList()); + + // 4. Extraction des propositions + List propositionsCompatibles = + resultats.stream() + .map( + resultat -> { + // Stocker le score dans les données personnalisées + if (resultat.proposition.getDonneesPersonnalisees() == null) { + resultat.proposition.setDonneesPersonnalisees(new HashMap<>()); + } + resultat + .proposition + .getDonneesPersonnalisees() + .put("scoreMatching", resultat.score); + return resultat.proposition; + }) + .collect(Collectors.toList()); + + long duration = System.currentTimeMillis() - startTime; + LOG.infof( + "Matching terminé en %d ms. Trouvé %d propositions compatibles", + duration, propositionsCompatibles.size()); + + return propositionsCompatibles; + + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du matching pour la demande: %s", demande.getId()); + return new ArrayList<>(); + } + } + + /** + * Trouve les demandes compatibles avec une proposition d'aide + * + * @param proposition La proposition d'aide + * @return Liste des demandes compatibles triées par score + */ + public List trouverDemandesCompatibles(PropositionAideDTO proposition) { + LOG.infof("Recherche de demandes compatibles pour la proposition: %s", proposition.getId()); + + try { + // Recherche des demandes actives du même type + Map filtres = + Map.of( + "typeAide", proposition.getTypeAide(), + "statut", + List.of( + dev.lions.unionflow.server.api.enums.solidarite.StatutAide.SOUMISE, + dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE, + dev.lions.unionflow.server.api.enums.solidarite.StatutAide + .EN_COURS_EVALUATION, + dev.lions.unionflow.server.api.enums.solidarite.StatutAide.APPROUVEE)); + + List candidats = demandeAideService.rechercherAvecFiltres(filtres); + + // Scoring et tri + return candidats.stream() + .map( + demande -> { + double score = calculerScoreCompatibilite(demande, proposition); + // Stocker le score temporairement + if (demande.getDonneesPersonnalisees() == null) { + demande.setDonneesPersonnalisees(new HashMap<>()); + } + demande.getDonneesPersonnalisees().put("scoreMatching", score); + return demande; + }) + .filter( + demande -> + (Double) demande.getDonneesPersonnalisees().get("scoreMatching") + >= scoreMinimumMatching) + .sorted( + (d1, d2) -> { + Double score1 = (Double) d1.getDonneesPersonnalisees().get("scoreMatching"); + Double score2 = (Double) d2.getDonneesPersonnalisees().get("scoreMatching"); + return Double.compare(score2, score1); + }) + .limit(maxResultatsMatching) + .collect(Collectors.toList()); + + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du matching pour la proposition: %s", proposition.getId()); + return new ArrayList<>(); + } + } + + // === MATCHING SPÉCIALISÉ === + + /** + * Recherche spécialisée de proposants financiers pour une demande approuvée + * + * @param demande La demande d'aide financière approuvée + * @return Liste des proposants financiers compatibles + */ + public List rechercherProposantsFinanciers(DemandeAideDTO demande) { + LOG.infof("Recherche de proposants financiers pour la demande: %s", demande.getId()); + + if (!demande.getTypeAide().isFinancier()) { + LOG.warnf("La demande %s n'est pas de type financier", demande.getId()); + return new ArrayList<>(); + } + + // Filtres spécifiques pour les aides financières + Map filtres = + Map.of( + "typeAide", + demande.getTypeAide(), + "estDisponible", + true, + "montantMaximum", + demande.getMontantApprouve() != null + ? demande.getMontantApprouve() + : demande.getMontantDemande()); + + List propositions = propositionAideService.rechercherAvecFiltres(filtres); + + // Scoring spécialisé pour les aides financières + return propositions.stream() + .map( + proposition -> { + double score = calculerScoreFinancier(demande, proposition); + if (proposition.getDonneesPersonnalisees() == null) { + proposition.setDonneesPersonnalisees(new HashMap<>()); + } + proposition.getDonneesPersonnalisees().put("scoreFinancier", score); + return proposition; + }) + .filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreFinancier") >= 40.0) + .sorted( + (p1, p2) -> { + Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreFinancier"); + Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreFinancier"); + return Double.compare(score2, score1); + }) + .limit(5) // Limiter à 5 pour les aides financières + .collect(Collectors.toList()); + } + + /** + * Matching d'urgence pour les demandes critiques + * + * @param demande La demande d'aide urgente + * @return Liste des propositions d'urgence + */ + public List matchingUrgence(DemandeAideDTO demande) { + LOG.infof("Matching d'urgence pour la demande: %s", demande.getId()); + + // Recherche élargie pour les urgences + List candidats = new ArrayList<>(); + + // 1. Même type d'aide + candidats.addAll(propositionAideService.obtenirPropositionsActives(demande.getTypeAide())); + + // 2. Types d'aide de la même catégorie + candidats.addAll(rechercherParCategorie(demande.getTypeAide().getCategorie())); + + // 3. Propositions généralistes (type AUTRE) + candidats.addAll(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE)); + + // Scoring avec bonus d'urgence + return candidats.stream() + .distinct() + .filter(PropositionAideDTO::isActiveEtDisponible) + .map( + proposition -> { + double score = calculerScoreCompatibilite(demande, proposition); + // Bonus d'urgence + score += 20.0; + + if (proposition.getDonneesPersonnalisees() == null) { + proposition.setDonneesPersonnalisees(new HashMap<>()); + } + proposition.getDonneesPersonnalisees().put("scoreUrgence", score); + return proposition; + }) + .filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreUrgence") >= 25.0) + .sorted( + (p1, p2) -> { + Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreUrgence"); + Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreUrgence"); + return Double.compare(score2, score1); + }) + .limit(15) // Plus de résultats pour les urgences + .collect(Collectors.toList()); + } + + // === ALGORITHMES DE SCORING === + + /** Calcule le score de compatibilité entre une demande et une proposition */ + private double calculerScoreCompatibilite( + DemandeAideDTO demande, PropositionAideDTO proposition) { + double score = 0.0; + + // 1. Correspondance du type d'aide (40 points max) + if (demande.getTypeAide() == proposition.getTypeAide()) { + score += 40.0; + } else if (demande + .getTypeAide() + .getCategorie() + .equals(proposition.getTypeAide().getCategorie())) { + score += 25.0; + } else if (proposition.getTypeAide() == TypeAide.AUTRE) { + score += 15.0; + } + + // 2. Compatibilité financière (25 points max) + if (demande.getTypeAide().isNecessiteMontant() && proposition.getMontantMaximum() != null) { + BigDecimal montantDemande = + demande.getMontantApprouve() != null + ? demande.getMontantApprouve() + : demande.getMontantDemande(); + + if (montantDemande != null) { + if (montantDemande.compareTo(proposition.getMontantMaximum()) <= 0) { + score += 25.0; + } else { + // Pénalité proportionnelle au dépassement + double ratio = proposition.getMontantMaximum().divide(montantDemande, 4, java.math.RoundingMode.HALF_UP).doubleValue(); + score += 25.0 * ratio; + } + } + } else if (!demande.getTypeAide().isNecessiteMontant()) { + score += 25.0; // Pas de contrainte financière + } + + // 3. Expérience du proposant (15 points max) + if (proposition.getNombreBeneficiairesAides() > 0) { + score += Math.min(15.0, proposition.getNombreBeneficiairesAides() * boostExperience); + } + + // 4. Réputation (10 points max) + if (proposition.getNoteMoyenne() != null && proposition.getNombreEvaluations() >= 3) { + score += (proposition.getNoteMoyenne() - 3.0) * 3.33; // 0 à 10 points + } + + // 5. Disponibilité et capacité (10 points max) + if (proposition.peutAccepterBeneficiaires()) { + double ratioCapacite = + (double) proposition.getPlacesRestantes() / proposition.getNombreMaxBeneficiaires(); + score += 10.0 * ratioCapacite; + } + + // Bonus et malus additionnels + score += calculerBonusGeographique(demande, proposition); + score += calculerBonusTemporel(demande, proposition); + score -= calculerMalusDelai(demande, proposition); + + return Math.max(0.0, Math.min(100.0, score)); + } + + /** Calcule le score spécialisé pour les aides financières */ + private double calculerScoreFinancier(DemandeAideDTO demande, PropositionAideDTO proposition) { + double score = calculerScoreCompatibilite(demande, proposition); + + // Bonus spécifiques aux aides financières + + // 1. Historique de versements + if (proposition.getMontantTotalVerse() > 0) { + score += Math.min(10.0, proposition.getMontantTotalVerse() / 10000.0); + } + + // 2. Fiabilité (ratio versements/promesses) + if (proposition.getNombreDemandesTraitees() > 0) { + // Simulation d'un ratio de fiabilité + double ratioFiabilite = 0.9; // À calculer réellement + score += ratioFiabilite * 15.0; + } + + // 3. Rapidité de réponse + if (proposition.getDelaiReponseHeures() <= 24) { + score += 10.0; + } else if (proposition.getDelaiReponseHeures() <= 72) { + score += 5.0; + } + + return Math.max(0.0, Math.min(100.0, score)); + } + + /** Calcule le bonus géographique */ + private double calculerBonusGeographique(DemandeAideDTO demande, PropositionAideDTO proposition) { + // Simulation - dans une vraie implémentation, ceci utiliserait les données de localisation + if (demande.getLocalisation() != null && proposition.getZonesGeographiques() != null) { + // Logique de proximité géographique + return boostGeographique; + } + return 0.0; + } + + /** Calcule le bonus temporel (urgence, disponibilité) */ + private double calculerBonusTemporel(DemandeAideDTO demande, PropositionAideDTO proposition) { + double bonus = 0.0; + + // Bonus pour demande urgente + if (demande.estUrgente()) { + bonus += 5.0; + } + + // Bonus pour proposition récente + long joursDepuisCreation = + java.time.Duration.between(proposition.getDateCreation(), LocalDateTime.now()).toDays(); + if (joursDepuisCreation <= 30) { + bonus += 3.0; + } + + return bonus; + } + + /** Calcule le malus de délai */ + private double calculerMalusDelai(DemandeAideDTO demande, PropositionAideDTO proposition) { + double malus = 0.0; + + // Malus si la demande est en retard + if (demande.estDelaiDepasse()) { + malus += 5.0; + } + + // Malus si la proposition a un délai de réponse long + if (proposition.getDelaiReponseHeures() > 168) { // Plus d'une semaine + malus += 3.0; + } + + return malus; + } + + // === MÉTHODES UTILITAIRES === + + /** Recherche des propositions par catégorie */ + private List rechercherParCategorie(String categorie) { + Map filtres = Map.of("estDisponible", true); + + return propositionAideService.rechercherAvecFiltres(filtres).stream() + .filter(p -> p.getTypeAide().getCategorie().equals(categorie)) + .collect(Collectors.toList()); + } + + /** Classe interne pour stocker les résultats de matching */ + private static class ResultatMatching { + final PropositionAideDTO proposition; + final double score; + + ResultatMatching(PropositionAideDTO proposition, double score) { + this.proposition = proposition; + this.score = score; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java new file mode 100644 index 0000000..057fb5a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/MembreImportExportService.java @@ -0,0 +1,842 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.membre.MembreDTO; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +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 org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.csv.CSVRecord; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.jboss.logging.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; + +/** + * Service pour l'import et l'export de membres depuis/vers Excel et CSV + */ +@ApplicationScoped +public class MembreImportExportService { + + private static final Logger LOG = Logger.getLogger(MembreImportExportService.class); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + @Inject + MembreRepository membreRepository; + + @Inject + OrganisationRepository organisationRepository; + + @Inject + MembreService membreService; + + /** + * Importe des membres depuis un fichier Excel ou CSV + */ + @Transactional + public ResultatImport importerMembres( + InputStream fileInputStream, + String fileName, + UUID organisationId, + String typeMembreDefaut, + boolean mettreAJourExistants, + boolean ignorerErreurs) { + + LOG.infof("Import de membres depuis le fichier: %s", fileName); + + ResultatImport resultat = new ResultatImport(); + resultat.erreurs = new ArrayList<>(); + resultat.membresImportes = new ArrayList<>(); + + try { + if (fileName.toLowerCase().endsWith(".csv")) { + return importerDepuisCSV(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs); + } else if (fileName.toLowerCase().endsWith(".xlsx") || fileName.toLowerCase().endsWith(".xls")) { + return importerDepuisExcel(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs); + } else { + throw new IllegalArgumentException("Format de fichier non supporté. Formats acceptés: .xlsx, .xls, .csv"); + } + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'import"); + resultat.erreurs.add("Erreur générale: " + e.getMessage()); + return resultat; + } + } + + /** + * Importe depuis un fichier Excel + */ + private ResultatImport importerDepuisExcel( + InputStream fileInputStream, + UUID organisationId, + String typeMembreDefaut, + boolean mettreAJourExistants, + boolean ignorerErreurs) throws IOException { + + ResultatImport resultat = new ResultatImport(); + resultat.erreurs = new ArrayList<>(); + resultat.membresImportes = new ArrayList<>(); + int ligneNum = 0; + + try (Workbook workbook = new XSSFWorkbook(fileInputStream)) { + Sheet sheet = workbook.getSheetAt(0); + Row headerRow = sheet.getRow(0); + + if (headerRow == null) { + throw new IllegalArgumentException("Le fichier Excel est vide ou n'a pas d'en-têtes"); + } + + // Mapper les colonnes + Map colonnes = mapperColonnes(headerRow); + + // Vérifier les colonnes obligatoires + if (!colonnes.containsKey("nom") || !colonnes.containsKey("prenom") || + !colonnes.containsKey("email") || !colonnes.containsKey("telephone")) { + throw new IllegalArgumentException("Colonnes obligatoires manquantes: nom, prenom, email, telephone"); + } + + // Lire les données + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + ligneNum = i + 1; + Row row = sheet.getRow(i); + + if (row == null) { + continue; + } + + try { + Membre membre = lireLigneExcel(row, colonnes, organisationId, typeMembreDefaut); + + // Vérifier si le membre existe déjà + Optional membreExistant = membreRepository.findByEmail(membre.getEmail()); + + if (membreExistant.isPresent()) { + if (mettreAJourExistants) { + Membre existant = membreExistant.get(); + existant.setNom(membre.getNom()); + existant.setPrenom(membre.getPrenom()); + existant.setTelephone(membre.getTelephone()); + existant.setDateNaissance(membre.getDateNaissance()); + if (membre.getOrganisation() != null) { + existant.setOrganisation(membre.getOrganisation()); + } + membreRepository.persist(existant); + resultat.membresImportes.add(membreService.convertToDTO(existant)); + resultat.lignesTraitees++; + } else { + resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail())); + if (!ignorerErreurs) { + throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée"); + } + } + } else { + membre = membreService.creerMembre(membre); + resultat.membresImportes.add(membreService.convertToDTO(membre)); + resultat.lignesTraitees++; + } + } catch (Exception e) { + String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage()); + resultat.erreurs.add(erreur); + resultat.lignesErreur++; + + if (!ignorerErreurs) { + throw new RuntimeException(erreur, e); + } + } + } + + resultat.totalLignes = sheet.getLastRowNum(); + } + + LOG.infof("Import terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur); + return resultat; + } + + /** + * Importe depuis un fichier CSV + */ + private ResultatImport importerDepuisCSV( + InputStream fileInputStream, + UUID organisationId, + String typeMembreDefaut, + boolean mettreAJourExistants, + boolean ignorerErreurs) throws IOException { + + ResultatImport resultat = new ResultatImport(); + resultat.erreurs = new ArrayList<>(); + resultat.membresImportes = new ArrayList<>(); + + try (InputStreamReader reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8)) { + Iterable records = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build().parse(reader); + + int ligneNum = 0; + for (CSVRecord record : records) { + ligneNum++; + + try { + Membre membre = lireLigneCSV(record, organisationId, typeMembreDefaut); + + // Vérifier si le membre existe déjà + Optional membreExistant = membreRepository.findByEmail(membre.getEmail()); + + if (membreExistant.isPresent()) { + if (mettreAJourExistants) { + Membre existant = membreExistant.get(); + existant.setNom(membre.getNom()); + existant.setPrenom(membre.getPrenom()); + existant.setTelephone(membre.getTelephone()); + existant.setDateNaissance(membre.getDateNaissance()); + if (membre.getOrganisation() != null) { + existant.setOrganisation(membre.getOrganisation()); + } + membreRepository.persist(existant); + resultat.membresImportes.add(membreService.convertToDTO(existant)); + resultat.lignesTraitees++; + } else { + resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail())); + if (!ignorerErreurs) { + throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée"); + } + } + } else { + membre = membreService.creerMembre(membre); + resultat.membresImportes.add(membreService.convertToDTO(membre)); + resultat.lignesTraitees++; + } + } catch (Exception e) { + String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage()); + resultat.erreurs.add(erreur); + resultat.lignesErreur++; + + if (!ignorerErreurs) { + throw new RuntimeException(erreur, e); + } + } + } + + resultat.totalLignes = ligneNum; + } + + LOG.infof("Import CSV terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur); + return resultat; + } + + /** + * Lit une ligne Excel et crée un membre + */ + private Membre lireLigneExcel(Row row, Map colonnes, UUID organisationId, String typeMembreDefaut) { + Membre membre = new Membre(); + + // Colonnes obligatoires + String nom = getCellValueAsString(row, colonnes.get("nom")); + String prenom = getCellValueAsString(row, colonnes.get("prenom")); + String email = getCellValueAsString(row, colonnes.get("email")); + String telephone = getCellValueAsString(row, colonnes.get("telephone")); + + if (nom == null || nom.trim().isEmpty()) { + throw new IllegalArgumentException("Le nom est obligatoire"); + } + if (prenom == null || prenom.trim().isEmpty()) { + throw new IllegalArgumentException("Le prénom est obligatoire"); + } + if (email == null || email.trim().isEmpty()) { + throw new IllegalArgumentException("L'email est obligatoire"); + } + if (telephone == null || telephone.trim().isEmpty()) { + throw new IllegalArgumentException("Le téléphone est obligatoire"); + } + + membre.setNom(nom.trim()); + membre.setPrenom(prenom.trim()); + membre.setEmail(email.trim().toLowerCase()); + membre.setTelephone(telephone.trim()); + + // Colonnes optionnelles + if (colonnes.containsKey("date_naissance")) { + LocalDate dateNaissance = getCellValueAsDate(row, colonnes.get("date_naissance")); + if (dateNaissance != null) { + membre.setDateNaissance(dateNaissance); + } + } + if (membre.getDateNaissance() == null) { + membre.setDateNaissance(LocalDate.now().minusYears(18)); + } + + if (colonnes.containsKey("date_adhesion")) { + LocalDate dateAdhesion = getCellValueAsDate(row, colonnes.get("date_adhesion")); + if (dateAdhesion != null) { + membre.setDateAdhesion(dateAdhesion); + } + } + if (membre.getDateAdhesion() == null) { + membre.setDateAdhesion(LocalDate.now()); + } + + // Organisation + if (organisationId != null) { + Optional org = organisationRepository.findByIdOptional(organisationId); + if (org.isPresent()) { + membre.setOrganisation(org.get()); + } + } + + // Statut par défaut + membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut)); + + return membre; + } + + /** + * Lit une ligne CSV et crée un membre + */ + private Membre lireLigneCSV(CSVRecord record, UUID organisationId, String typeMembreDefaut) { + Membre membre = new Membre(); + + // Colonnes obligatoires + String nom = record.get("nom"); + String prenom = record.get("prenom"); + String email = record.get("email"); + String telephone = record.get("telephone"); + + if (nom == null || nom.trim().isEmpty()) { + throw new IllegalArgumentException("Le nom est obligatoire"); + } + if (prenom == null || prenom.trim().isEmpty()) { + throw new IllegalArgumentException("Le prénom est obligatoire"); + } + if (email == null || email.trim().isEmpty()) { + throw new IllegalArgumentException("L'email est obligatoire"); + } + if (telephone == null || telephone.trim().isEmpty()) { + throw new IllegalArgumentException("Le téléphone est obligatoire"); + } + + membre.setNom(nom.trim()); + membre.setPrenom(prenom.trim()); + membre.setEmail(email.trim().toLowerCase()); + membre.setTelephone(telephone.trim()); + + // Colonnes optionnelles + try { + String dateNaissanceStr = record.get("date_naissance"); + if (dateNaissanceStr != null && !dateNaissanceStr.trim().isEmpty()) { + membre.setDateNaissance(parseDate(dateNaissanceStr)); + } + } catch (Exception e) { + // Ignorer si la date est invalide + } + if (membre.getDateNaissance() == null) { + membre.setDateNaissance(LocalDate.now().minusYears(18)); + } + + try { + String dateAdhesionStr = record.get("date_adhesion"); + if (dateAdhesionStr != null && !dateAdhesionStr.trim().isEmpty()) { + membre.setDateAdhesion(parseDate(dateAdhesionStr)); + } + } catch (Exception e) { + // Ignorer si la date est invalide + } + if (membre.getDateAdhesion() == null) { + membre.setDateAdhesion(LocalDate.now()); + } + + // Organisation + if (organisationId != null) { + Optional org = organisationRepository.findByIdOptional(organisationId); + if (org.isPresent()) { + membre.setOrganisation(org.get()); + } + } + + // Statut par défaut + membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut)); + + return membre; + } + + /** + * Mappe les colonnes Excel + */ + private Map mapperColonnes(Row headerRow) { + Map colonnes = new HashMap<>(); + for (Cell cell : headerRow) { + String headerName = getCellValueAsString(headerRow, cell.getColumnIndex()).toLowerCase() + .replace(" ", "_") + .replace("é", "e") + .replace("è", "e") + .replace("ê", "e"); + colonnes.put(headerName, cell.getColumnIndex()); + } + return colonnes; + } + + /** + * Obtient la valeur d'une cellule comme String + */ + private String getCellValueAsString(Row row, Integer columnIndex) { + if (columnIndex == null || row == null) { + return null; + } + Cell cell = row.getCell(columnIndex); + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue(); + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue().toString(); + } else { + return String.valueOf((long) cell.getNumericCellValue()); + } + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + case FORMULA: + return cell.getCellFormula(); + default: + return null; + } + } + + /** + * Obtient la valeur d'une cellule comme Date + */ + private LocalDate getCellValueAsDate(Row row, Integer columnIndex) { + if (columnIndex == null || row == null) { + return null; + } + Cell cell = row.getCell(columnIndex); + if (cell == null) { + return null; + } + + try { + if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue().toInstant() + .atZone(java.time.ZoneId.systemDefault()) + .toLocalDate(); + } else if (cell.getCellType() == CellType.STRING) { + return parseDate(cell.getStringCellValue()); + } + } catch (Exception e) { + LOG.warnf("Erreur lors de la lecture de la date: %s", e.getMessage()); + } + return null; + } + + /** + * Parse une date depuis une String + */ + private LocalDate parseDate(String dateStr) { + if (dateStr == null || dateStr.trim().isEmpty()) { + return null; + } + + dateStr = dateStr.trim(); + + // Essayer différents formats + String[] formats = { + "dd/MM/yyyy", + "yyyy-MM-dd", + "dd-MM-yyyy", + "dd.MM.yyyy" + }; + + for (String format : formats) { + try { + return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(format)); + } catch (DateTimeParseException e) { + // Continuer avec le format suivant + } + } + + throw new IllegalArgumentException("Format de date non reconnu: " + dateStr); + } + + /** + * Exporte des membres vers Excel + */ + public byte[] exporterVersExcel(List membres, List colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) throws IOException { + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Membres"); + + int rowNum = 0; + + // En-têtes + if (inclureHeaders) { + Row headerRow = sheet.createRow(rowNum++); + int colNum = 0; + + if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) { + headerRow.createCell(colNum++).setCellValue("Nom"); + headerRow.createCell(colNum++).setCellValue("Prénom"); + headerRow.createCell(colNum++).setCellValue("Date de naissance"); + } + if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) { + headerRow.createCell(colNum++).setCellValue("Email"); + headerRow.createCell(colNum++).setCellValue("Téléphone"); + } + if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) { + headerRow.createCell(colNum++).setCellValue("Date adhésion"); + headerRow.createCell(colNum++).setCellValue("Statut"); + } + if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { + headerRow.createCell(colNum++).setCellValue("Organisation"); + } + } + + // Données + for (MembreDTO membre : membres) { + Row row = sheet.createRow(rowNum++); + int colNum = 0; + + if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) { + row.createCell(colNum++).setCellValue(membre.getNom() != null ? membre.getNom() : ""); + row.createCell(colNum++).setCellValue(membre.getPrenom() != null ? membre.getPrenom() : ""); + if (membre.getDateNaissance() != null) { + Cell dateCell = row.createCell(colNum++); + if (formaterDates) { + dateCell.setCellValue(membre.getDateNaissance().format(DATE_FORMATTER)); + } else { + dateCell.setCellValue(membre.getDateNaissance().toString()); + } + } else { + row.createCell(colNum++).setCellValue(""); + } + } + if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) { + row.createCell(colNum++).setCellValue(membre.getEmail() != null ? membre.getEmail() : ""); + row.createCell(colNum++).setCellValue(membre.getTelephone() != null ? membre.getTelephone() : ""); + } + if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) { + if (membre.getDateAdhesion() != null) { + Cell dateCell = row.createCell(colNum++); + if (formaterDates) { + dateCell.setCellValue(membre.getDateAdhesion().format(DATE_FORMATTER)); + } else { + dateCell.setCellValue(membre.getDateAdhesion().toString()); + } + } else { + row.createCell(colNum++).setCellValue(""); + } + row.createCell(colNum++).setCellValue(membre.getStatut() != null ? membre.getStatut().toString() : ""); + } + if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { + row.createCell(colNum++).setCellValue(membre.getAssociationNom() != null ? membre.getAssociationNom() : ""); + } + } + + // Auto-size columns + for (int i = 0; i < 10; i++) { + sheet.autoSizeColumn(i); + } + + // Ajouter un onglet statistiques si demandé + if (inclureStatistiques && !membres.isEmpty()) { + Sheet statsSheet = workbook.createSheet("Statistiques"); + creerOngletStatistiques(statsSheet, membres); + } + + // Écrire dans un ByteArrayOutputStream + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + workbook.write(outputStream); + byte[] excelData = outputStream.toByteArray(); + + // Chiffrer le fichier si un mot de passe est fourni + if (motDePasse != null && !motDePasse.trim().isEmpty()) { + return chiffrerExcel(excelData, motDePasse); + } + + return excelData; + } + } + } + + /** + * Crée un onglet statistiques dans le classeur Excel + */ + private void creerOngletStatistiques(Sheet sheet, List membres) { + int rowNum = 0; + + // Titre + Row titleRow = sheet.createRow(rowNum++); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellValue("Statistiques des Membres"); + CellStyle titleStyle = sheet.getWorkbook().createCellStyle(); + Font titleFont = sheet.getWorkbook().createFont(); + titleFont.setBold(true); + titleFont.setFontHeightInPoints((short) 14); + titleStyle.setFont(titleFont); + titleCell.setCellStyle(titleStyle); + + rowNum++; // Ligne vide + + // Statistiques générales + Row headerRow = sheet.createRow(rowNum++); + headerRow.createCell(0).setCellValue("Indicateur"); + headerRow.createCell(1).setCellValue("Valeur"); + + // Style pour les en-têtes + CellStyle headerStyle = sheet.getWorkbook().createCellStyle(); + Font headerFont = sheet.getWorkbook().createFont(); + headerFont.setBold(true); + headerStyle.setFont(headerFont); + headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + headerRow.getCell(0).setCellStyle(headerStyle); + headerRow.getCell(1).setCellStyle(headerStyle); + + // Calcul des statistiques + long totalMembres = membres.size(); + long membresActifs = membres.stream().filter(m -> "ACTIF".equals(m.getStatut())).count(); + long membresInactifs = membres.stream().filter(m -> "INACTIF".equals(m.getStatut())).count(); + long membresSuspendus = membres.stream().filter(m -> "SUSPENDU".equals(m.getStatut())).count(); + + // Organisations distinctes + long organisationsDistinctes = membres.stream() + .filter(m -> m.getAssociationNom() != null) + .map(MembreDTO::getAssociationNom) + .distinct() + .count(); + + // Statistiques par type (si disponible dans le DTO) + // Note: Le type de membre peut ne pas être disponible dans MembreDTO + // Pour l'instant, on utilise le statut comme indicateur + long typeActif = membresActifs; + long typeAssocie = 0; + long typeBienfaiteur = 0; + long typeHonoraire = 0; + + // Ajout des statistiques + int currentRow = rowNum; + sheet.createRow(currentRow++).createCell(0).setCellValue("Total Membres"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(totalMembres); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Actifs"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresActifs); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Inactifs"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresInactifs); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Suspendus"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresSuspendus); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Organisations Distinctes"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(organisationsDistinctes); + + currentRow++; // Ligne vide + + // Section par type + sheet.createRow(currentRow++).createCell(0).setCellValue("Répartition par Type"); + CellStyle sectionStyle = sheet.getWorkbook().createCellStyle(); + Font sectionFont = sheet.getWorkbook().createFont(); + sectionFont.setBold(true); + sectionStyle.setFont(sectionFont); + sheet.getRow(currentRow - 1).getCell(0).setCellStyle(sectionStyle); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Type Actif"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeActif); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Type Associé"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeAssocie); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Type Bienfaiteur"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeBienfaiteur); + + sheet.createRow(currentRow++).createCell(0).setCellValue("Type Honoraire"); + sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeHonoraire); + + // Auto-size columns + sheet.autoSizeColumn(0); + sheet.autoSizeColumn(1); + } + + /** + * Protège un fichier Excel avec un mot de passe + * Utilise Apache POI pour protéger les feuilles et la structure du workbook + * Note: Ceci protège contre la modification, pas un chiffrement complet du fichier + */ + private byte[] chiffrerExcel(byte[] excelData, String motDePasse) throws IOException { + try { + // Pour XLSX, on protège les feuilles et la structure du workbook + // Note: POI 5.2.5 ne supporte pas le chiffrement complet XLSX (nécessite des bibliothèques externes) + // On utilise la protection par mot de passe qui empêche la modification sans le mot de passe + + try (java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(excelData); + XSSFWorkbook workbook = new XSSFWorkbook(inputStream); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + // Protéger toutes les feuilles avec un mot de passe (empêche la modification des cellules) + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + Sheet sheet = workbook.getSheetAt(i); + sheet.protectSheet(motDePasse); + } + + // Protéger la structure du workbook (empêche l'ajout/suppression de feuilles) + org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbookProtection protection = + workbook.getCTWorkbook().getWorkbookProtection(); + if (protection == null) { + protection = workbook.getCTWorkbook().addNewWorkbookProtection(); + } + protection.setLockStructure(true); + // Le mot de passe doit être haché selon le format Excel + // Pour simplifier, on utilise le hash MD5 du mot de passe + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + byte[] passwordHash = md.digest(motDePasse.getBytes(java.nio.charset.StandardCharsets.UTF_16LE)); + protection.setWorkbookPassword(passwordHash); + } catch (java.security.NoSuchAlgorithmException e) { + LOG.warnf("Impossible de hasher le mot de passe, protection partielle uniquement"); + } + + workbook.write(outputStream); + return outputStream.toByteArray(); + } + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la protection du fichier Excel"); + // En cas d'erreur, retourner le fichier non protégé avec un avertissement + LOG.warnf("Le fichier sera exporté sans protection en raison d'une erreur"); + return excelData; + } + } + + /** + * Exporte des membres vers CSV + */ + public byte[] exporterVersCSV(List membres, List colonnesExport, boolean inclureHeaders, boolean formaterDates) throws IOException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CSVPrinter printer = new CSVPrinter( + new java.io.OutputStreamWriter(outputStream, StandardCharsets.UTF_8), + CSVFormat.DEFAULT)) { + + // En-têtes + if (inclureHeaders) { + List headers = new ArrayList<>(); + if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) { + headers.add("Nom"); + headers.add("Prénom"); + headers.add("Date de naissance"); + } + if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) { + headers.add("Email"); + headers.add("Téléphone"); + } + if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) { + headers.add("Date adhésion"); + headers.add("Statut"); + } + if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { + headers.add("Organisation"); + } + printer.printRecord(headers); + } + + // Données + for (MembreDTO membre : membres) { + List values = new ArrayList<>(); + + if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) { + values.add(membre.getNom() != null ? membre.getNom() : ""); + values.add(membre.getPrenom() != null ? membre.getPrenom() : ""); + if (membre.getDateNaissance() != null) { + values.add(formaterDates ? membre.getDateNaissance().format(DATE_FORMATTER) : membre.getDateNaissance().toString()); + } else { + values.add(""); + } + } + if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) { + values.add(membre.getEmail() != null ? membre.getEmail() : ""); + values.add(membre.getTelephone() != null ? membre.getTelephone() : ""); + } + if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) { + if (membre.getDateAdhesion() != null) { + values.add(formaterDates ? membre.getDateAdhesion().format(DATE_FORMATTER) : membre.getDateAdhesion().toString()); + } else { + values.add(""); + } + values.add(membre.getStatut() != null ? membre.getStatut().toString() : ""); + } + if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) { + values.add(membre.getAssociationNom() != null ? membre.getAssociationNom() : ""); + } + + printer.printRecord(values); + } + + printer.flush(); + return outputStream.toByteArray(); + } + } + + /** + * Génère un modèle Excel pour l'import + */ + public byte[] genererModeleImport() throws IOException { + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Modèle"); + + // En-têtes + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("Nom"); + headerRow.createCell(1).setCellValue("Prénom"); + headerRow.createCell(2).setCellValue("Email"); + headerRow.createCell(3).setCellValue("Téléphone"); + headerRow.createCell(4).setCellValue("Date naissance"); + headerRow.createCell(5).setCellValue("Date adhésion"); + headerRow.createCell(6).setCellValue("Adresse"); + headerRow.createCell(7).setCellValue("Profession"); + headerRow.createCell(8).setCellValue("Type membre"); + + // Exemple de ligne + Row exampleRow = sheet.createRow(1); + exampleRow.createCell(0).setCellValue("DUPONT"); + exampleRow.createCell(1).setCellValue("Jean"); + exampleRow.createCell(2).setCellValue("jean.dupont@example.com"); + exampleRow.createCell(3).setCellValue("+225 07 12 34 56 78"); + exampleRow.createCell(4).setCellValue("15/01/1990"); + exampleRow.createCell(5).setCellValue("01/01/2024"); + exampleRow.createCell(6).setCellValue("Abidjan, Cocody"); + exampleRow.createCell(7).setCellValue("Ingénieur"); + exampleRow.createCell(8).setCellValue("ACTIF"); + + // Auto-size columns + for (int i = 0; i < 9; i++) { + sheet.autoSizeColumn(i); + } + + // Écrire dans un ByteArrayOutputStream + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + workbook.write(outputStream); + return outputStream.toByteArray(); + } + } + } + + /** + * Classe pour le résultat de l'import + */ + public static class ResultatImport { + public int totalLignes; + public int lignesTraitees; + public int lignesErreur; + public List erreurs; + public List membresImportes; + } +} + diff --git a/src/main/java/dev/lions/unionflow/server/service/MembreService.java b/src/main/java/dev/lions/unionflow/server/service/MembreService.java new file mode 100644 index 0000000..375c844 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/MembreService.java @@ -0,0 +1,740 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.membre.MembreDTO; +import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; +import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; +import dev.lions.unionflow.server.api.enums.membre.StatutMembre; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.repository.MembreRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import jakarta.transaction.Transactional; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Period; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** Service métier pour les membres */ +@ApplicationScoped +public class MembreService { + + private static final Logger LOG = Logger.getLogger(MembreService.class); + + @Inject MembreRepository membreRepository; + + @Inject + MembreImportExportService membreImportExportService; + + @PersistenceContext + EntityManager entityManager; + + /** Crée un nouveau membre */ + @Transactional + public Membre creerMembre(Membre membre) { + LOG.infof("Création d'un nouveau membre: %s", membre.getEmail()); + + // Générer un numéro de membre unique + if (membre.getNumeroMembre() == null || membre.getNumeroMembre().isEmpty()) { + membre.setNumeroMembre(genererNumeroMembre()); + } + + // Définir la date d'adhésion si non fournie + if (membre.getDateAdhesion() == null) { + membre.setDateAdhesion(LocalDate.now()); + LOG.infof("Date d'adhésion automatiquement définie à: %s", membre.getDateAdhesion()); + } + + // Définir la date de naissance par défaut si non fournie (pour éviter @NotNull) + if (membre.getDateNaissance() == null) { + membre.setDateNaissance(LocalDate.now().minusYears(18)); // Majeur par défaut + LOG.warn("Date de naissance non fournie, définie par défaut à il y a 18 ans"); + } + + // Vérifier l'unicité de l'email + if (membreRepository.findByEmail(membre.getEmail()).isPresent()) { + throw new IllegalArgumentException("Un membre avec cet email existe déjà"); + } + + // Vérifier l'unicité du numéro de membre + if (membreRepository.findByNumeroMembre(membre.getNumeroMembre()).isPresent()) { + throw new IllegalArgumentException("Un membre avec ce numéro existe déjà"); + } + + membreRepository.persist(membre); + + // Mettre à jour le compteur de membres de l'organisation + if (membre.getOrganisation() != null) { + membre.getOrganisation().ajouterMembre(); + LOG.infof("Compteur de membres mis à jour pour l'organisation: %s", membre.getOrganisation().getNom()); + } + + LOG.infof("Membre créé avec succès: %s (ID: %s)", membre.getNomComplet(), membre.getId()); + return membre; + } + + /** Met à jour un membre existant */ + @Transactional + public Membre mettreAJourMembre(UUID id, Membre membreModifie) { + LOG.infof("Mise à jour du membre ID: %s", id); + + Membre membre = membreRepository.findById(id); + if (membre == null) { + throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id); + } + + // Vérifier l'unicité de l'email si modifié + if (!membre.getEmail().equals(membreModifie.getEmail())) { + if (membreRepository.findByEmail(membreModifie.getEmail()).isPresent()) { + throw new IllegalArgumentException("Un membre avec cet email existe déjà"); + } + } + + // Mettre à jour les champs + membre.setPrenom(membreModifie.getPrenom()); + membre.setNom(membreModifie.getNom()); + membre.setEmail(membreModifie.getEmail()); + membre.setTelephone(membreModifie.getTelephone()); + membre.setDateNaissance(membreModifie.getDateNaissance()); + membre.setActif(membreModifie.getActif()); + + LOG.infof("Membre mis à jour avec succès: %s", membre.getNomComplet()); + return membre; + } + + /** Trouve un membre par son ID */ + public Optional trouverParId(UUID id) { + return Optional.ofNullable(membreRepository.findById(id)); + } + + /** Trouve un membre par son email */ + public Optional trouverParEmail(String email) { + return membreRepository.findByEmail(email); + } + + /** Liste tous les membres actifs */ + public List listerMembresActifs() { + return membreRepository.findAllActifs(); + } + + /** Recherche des membres par nom ou prénom */ + public List rechercherMembres(String recherche) { + return membreRepository.findByNomOrPrenom(recherche); + } + + /** Désactive un membre */ + @Transactional + public void desactiverMembre(UUID id) { + LOG.infof("Désactivation du membre ID: %s", id); + + Membre membre = membreRepository.findById(id); + if (membre == null) { + throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id); + } + + membre.setActif(false); + LOG.infof("Membre désactivé: %s", membre.getNomComplet()); + } + + /** Génère un numéro de membre unique */ + private String genererNumeroMembre() { + String prefix = "UF" + LocalDate.now().getYear(); + String suffix = UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + return prefix + "-" + suffix; + } + + /** Compte le nombre total de membres actifs */ + public long compterMembresActifs() { + return membreRepository.countActifs(); + } + + /** Liste tous les membres actifs avec pagination */ + public List listerMembresActifs(Page page, Sort sort) { + return membreRepository.findAllActifs(page, sort); + } + + /** Recherche des membres avec pagination */ + public List rechercherMembres(String recherche, Page page, Sort sort) { + return membreRepository.findByNomOrPrenom(recherche, page, sort); + } + + /** Obtient les statistiques avancées des membres */ + public Map obtenirStatistiquesAvancees() { + LOG.info("Calcul des statistiques avancées des membres"); + + long totalMembres = membreRepository.count(); + long membresActifs = membreRepository.countActifs(); + long membresInactifs = totalMembres - membresActifs; + long nouveauxMembres30Jours = + membreRepository.countNouveauxMembres(LocalDate.now().minusDays(30)); + + return Map.of( + "totalMembres", totalMembres, + "membresActifs", membresActifs, + "membresInactifs", membresInactifs, + "nouveauxMembres30Jours", nouveauxMembres30Jours, + "tauxActivite", totalMembres > 0 ? (membresActifs * 100.0 / totalMembres) : 0.0, + "timestamp", LocalDateTime.now()); + } + + // ======================================== + // MÉTHODES DE CONVERSION DTO + // ======================================== + + /** Convertit une entité Membre en MembreDTO */ + public MembreDTO convertToDTO(Membre membre) { + if (membre == null) { + return null; + } + + MembreDTO dto = new MembreDTO(); + + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(membre.getId()); + + // Copie des champs de base + dto.setNumeroMembre(membre.getNumeroMembre()); + dto.setNom(membre.getNom()); + dto.setPrenom(membre.getPrenom()); + dto.setEmail(membre.getEmail()); + dto.setTelephone(membre.getTelephone()); + dto.setDateNaissance(membre.getDateNaissance()); + dto.setDateAdhesion(membre.getDateAdhesion()); + + // Conversion du statut boolean vers enum StatutMembre + // Règle métier: actif=true → ACTIF, actif=false → INACTIF + if (membre.getActif() == null || Boolean.TRUE.equals(membre.getActif())) { + dto.setStatut(StatutMembre.ACTIF); + } else { + dto.setStatut(StatutMembre.INACTIF); + } + + // Conversion de l'organisation (associationId) + // Utilisation directe de l'UUID de l'organisation + if (membre.getOrganisation() != null && membre.getOrganisation().getId() != null) { + dto.setAssociationId(membre.getOrganisation().getId()); + dto.setAssociationNom(membre.getOrganisation().getNom()); + } + + // Champs de base DTO + dto.setDateCreation(membre.getDateCreation()); + dto.setDateModification(membre.getDateModification()); + dto.setVersion(0L); // Version par défaut + + // Champs par défaut pour les champs manquants dans l'entité + dto.setMembreBureau(false); + dto.setResponsable(false); + + return dto; + } + + /** Convertit un MembreDTO en entité Membre */ + public Membre convertFromDTO(MembreDTO dto) { + if (dto == null) { + return null; + } + + Membre membre = new Membre(); + + // Copie des champs + membre.setNumeroMembre(dto.getNumeroMembre()); + membre.setNom(dto.getNom()); + membre.setPrenom(dto.getPrenom()); + membre.setEmail(dto.getEmail()); + membre.setTelephone(dto.getTelephone()); + membre.setDateNaissance(dto.getDateNaissance()); + membre.setDateAdhesion(dto.getDateAdhesion()); + + // Conversion du statut enum vers boolean + // Règle métier: ACTIF → true, autres statuts → false + membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut())); + + // Champs de base + if (dto.getDateCreation() != null) { + membre.setDateCreation(dto.getDateCreation()); + } + if (dto.getDateModification() != null) { + membre.setDateModification(dto.getDateModification()); + } + + return membre; + } + + /** Convertit une liste d'entités en liste de DTOs */ + public List convertToDTOList(List membres) { + return membres.stream().map(this::convertToDTO).collect(Collectors.toList()); + } + + /** Met à jour une entité Membre à partir d'un MembreDTO */ + public void updateFromDTO(Membre membre, MembreDTO dto) { + if (membre == null || dto == null) { + return; + } + + // Mise à jour des champs modifiables + membre.setPrenom(dto.getPrenom()); + membre.setNom(dto.getNom()); + membre.setEmail(dto.getEmail()); + membre.setTelephone(dto.getTelephone()); + membre.setDateNaissance(dto.getDateNaissance()); + // Conversion du statut enum vers boolean + membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut())); + membre.setDateModification(LocalDateTime.now()); + } + + /** Recherche avancée de membres avec filtres multiples (DEPRECATED) */ + public List rechercheAvancee( + String recherche, + Boolean actif, + LocalDate dateAdhesionMin, + LocalDate dateAdhesionMax, + Page page, + Sort sort) { + LOG.infof( + "Recherche avancée (DEPRECATED) - recherche: %s, actif: %s, dateMin: %s, dateMax: %s", + recherche, actif, dateAdhesionMin, dateAdhesionMax); + + return membreRepository.rechercheAvancee( + recherche, actif, dateAdhesionMin, dateAdhesionMax, page, sort); + } + + /** + * Nouvelle recherche avancée de membres avec critères complets Retourne des résultats paginés + * avec statistiques + * + * @param criteria Critères de recherche + * @param page Pagination + * @param sort Tri + * @return Résultats de recherche avec métadonnées + */ + public MembreSearchResultDTO searchMembresAdvanced( + MembreSearchCriteria criteria, Page page, Sort sort) { + LOG.infof("Recherche avancée de membres - critères: %s", criteria.getDescription()); + + try { + // Construction de la requête dynamique + StringBuilder queryBuilder = new StringBuilder("SELECT m FROM Membre m WHERE 1=1"); + Map parameters = new HashMap<>(); + + // Ajout des critères de recherche + addSearchCriteria(queryBuilder, parameters, criteria); + + // Requête pour compter le total + String countQuery = + queryBuilder + .toString() + .replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m"); + + // Exécution de la requête de comptage + TypedQuery countQueryTyped = entityManager.createQuery(countQuery, Long.class); + for (Map.Entry param : parameters.entrySet()) { + countQueryTyped.setParameter(param.getKey(), param.getValue()); + } + long totalElements = countQueryTyped.getSingleResult(); + + if (totalElements == 0) { + return MembreSearchResultDTO.empty(criteria, page.size, page.index); + } + + // Ajout du tri et pagination + String finalQuery = queryBuilder.toString(); + if (sort != null) { + finalQuery += " ORDER BY " + buildOrderByClause(sort); + } + + // Exécution de la requête principale + TypedQuery queryTyped = entityManager.createQuery(finalQuery, Membre.class); + for (Map.Entry param : parameters.entrySet()) { + queryTyped.setParameter(param.getKey(), param.getValue()); + } + queryTyped.setFirstResult(page.index * page.size); + queryTyped.setMaxResults(page.size); + List membres = queryTyped.getResultList(); + + // Conversion en DTOs + List membresDTO = convertToDTOList(membres); + + // Calcul des statistiques + MembreSearchResultDTO.SearchStatistics statistics = calculateSearchStatistics(membres); + + // Construction du résultat + MembreSearchResultDTO result = + MembreSearchResultDTO.builder() + .membres(membresDTO) + .totalElements(totalElements) + .totalPages((int) Math.ceil((double) totalElements / page.size)) + .currentPage(page.index) + .pageSize(page.size) + .criteria(criteria) + .statistics(statistics) + .build(); + + // Calcul des indicateurs de pagination + result.calculatePaginationFlags(); + + return result; + + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche avancée de membres"); + throw new RuntimeException("Erreur lors de la recherche avancée", e); + } + } + + /** Ajoute les critères de recherche à la requête */ + private void addSearchCriteria( + StringBuilder queryBuilder, Map parameters, MembreSearchCriteria criteria) { + + // Recherche générale dans nom, prénom, email + if (criteria.getQuery() != null) { + queryBuilder.append( + " AND (LOWER(m.nom) LIKE LOWER(:query) OR LOWER(m.prenom) LIKE LOWER(:query) OR" + + " LOWER(m.email) LIKE LOWER(:query))"); + parameters.put("query", "%" + criteria.getQuery() + "%"); + } + + // Recherche par nom + if (criteria.getNom() != null) { + queryBuilder.append(" AND LOWER(m.nom) LIKE LOWER(:nom)"); + parameters.put("nom", "%" + criteria.getNom() + "%"); + } + + // Recherche par prénom + if (criteria.getPrenom() != null) { + queryBuilder.append(" AND LOWER(m.prenom) LIKE LOWER(:prenom)"); + parameters.put("prenom", "%" + criteria.getPrenom() + "%"); + } + + // Recherche par email + if (criteria.getEmail() != null) { + queryBuilder.append(" AND LOWER(m.email) LIKE LOWER(:email)"); + parameters.put("email", "%" + criteria.getEmail() + "%"); + } + + // Recherche par téléphone + if (criteria.getTelephone() != null) { + queryBuilder.append(" AND m.telephone LIKE :telephone"); + parameters.put("telephone", "%" + criteria.getTelephone() + "%"); + } + + // Filtre par statut + if (criteria.getStatut() != null) { + boolean isActif = "ACTIF".equals(criteria.getStatut()); + queryBuilder.append(" AND m.actif = :actif"); + parameters.put("actif", isActif); + } else if (!Boolean.TRUE.equals(criteria.getIncludeInactifs())) { + // Par défaut, exclure les inactifs + queryBuilder.append(" AND m.actif = true"); + } + + // Filtre par dates d'adhésion + if (criteria.getDateAdhesionMin() != null) { + queryBuilder.append(" AND m.dateAdhesion >= :dateAdhesionMin"); + parameters.put("dateAdhesionMin", criteria.getDateAdhesionMin()); + } + + if (criteria.getDateAdhesionMax() != null) { + queryBuilder.append(" AND m.dateAdhesion <= :dateAdhesionMax"); + parameters.put("dateAdhesionMax", criteria.getDateAdhesionMax()); + } + + // Filtre par âge (calculé à partir de la date de naissance) + if (criteria.getAgeMin() != null) { + LocalDate maxBirthDate = LocalDate.now().minusYears(criteria.getAgeMin()); + queryBuilder.append(" AND m.dateNaissance <= :maxBirthDateForMinAge"); + parameters.put("maxBirthDateForMinAge", maxBirthDate); + } + + if (criteria.getAgeMax() != null) { + LocalDate minBirthDate = LocalDate.now().minusYears(criteria.getAgeMax() + 1).plusDays(1); + queryBuilder.append(" AND m.dateNaissance >= :minBirthDateForMaxAge"); + parameters.put("minBirthDateForMaxAge", minBirthDate); + } + + // Filtre par organisations (si implémenté dans l'entité) + if (criteria.getOrganisationIds() != null && !criteria.getOrganisationIds().isEmpty()) { + queryBuilder.append(" AND m.organisation.id IN :organisationIds"); + parameters.put("organisationIds", criteria.getOrganisationIds()); + } + + // Filtre par rôles (recherche via la relation MembreRole -> Role) + if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) { + // Utiliser EXISTS avec une sous-requête pour vérifier les rôles + queryBuilder.append(" AND EXISTS ("); + queryBuilder.append(" SELECT 1 FROM MembreRole mr WHERE mr.membre = m"); + queryBuilder.append(" AND mr.actif = true"); + queryBuilder.append(" AND mr.role.code IN :roleCodes"); + queryBuilder.append(")"); + // Convertir les noms de rôles en codes (supposant que criteria.getRoles() contient des codes) + parameters.put("roleCodes", criteria.getRoles()); + } + } + + /** Construit la clause ORDER BY à partir du Sort */ + private String buildOrderByClause(Sort sort) { + if (sort == null || sort.getColumns().isEmpty()) { + return "m.nom ASC"; + } + + return sort.getColumns().stream() + .map(column -> { + String direction = column.getDirection() == Sort.Direction.Descending ? "DESC" : "ASC"; + return "m." + column.getName() + " " + direction; + }) + .collect(Collectors.joining(", ")); + } + + /** Calcule les statistiques sur les résultats de recherche */ + private MembreSearchResultDTO.SearchStatistics calculateSearchStatistics(List membres) { + if (membres.isEmpty()) { + return MembreSearchResultDTO.SearchStatistics.builder() + .membresActifs(0) + .membresInactifs(0) + .ageMoyen(0.0) + .ageMin(0) + .ageMax(0) + .nombreOrganisations(0) + .nombreRegions(0) + .ancienneteMoyenne(0.0) + .build(); + } + + long membresActifs = + membres.stream().mapToLong(m -> Boolean.TRUE.equals(m.getActif()) ? 1 : 0).sum(); + long membresInactifs = membres.size() - membresActifs; + + // Calcul des âges + List ages = + membres.stream() + .filter(m -> m.getDateNaissance() != null) + .map(m -> Period.between(m.getDateNaissance(), LocalDate.now()).getYears()) + .collect(Collectors.toList()); + + double ageMoyen = ages.stream().mapToInt(Integer::intValue).average().orElse(0.0); + int ageMin = ages.stream().mapToInt(Integer::intValue).min().orElse(0); + int ageMax = ages.stream().mapToInt(Integer::intValue).max().orElse(0); + + // Calcul de l'ancienneté moyenne + double ancienneteMoyenne = + membres.stream() + .filter(m -> m.getDateAdhesion() != null) + .mapToDouble(m -> Period.between(m.getDateAdhesion(), LocalDate.now()).getYears()) + .average() + .orElse(0.0); + + // Nombre d'organisations (si relation disponible) + long nombreOrganisations = + membres.stream() + .filter(m -> m.getOrganisation() != null) + .map(m -> m.getOrganisation().getId()) + .distinct() + .count(); + + return MembreSearchResultDTO.SearchStatistics.builder() + .membresActifs(membresActifs) + .membresInactifs(membresInactifs) + .ageMoyen(ageMoyen) + .ageMin(ageMin) + .ageMax(ageMax) + .nombreOrganisations(nombreOrganisations) + .nombreRegions(0) // TODO: Calculer depuis les adresses + .ancienneteMoyenne(ancienneteMoyenne) + .build(); + } + + // ======================================== + // MÉTHODES D'AUTOCOMPLÉTION (WOU/DRY) + // ======================================== + + /** + * Obtient la liste des villes distinctes depuis les adresses des membres + * Réutilisable pour autocomplétion (WOU/DRY) + */ + public List obtenirVillesDistinctes(String query) { + LOG.infof("Récupération des villes distinctes - query: %s", query); + + String jpql = "SELECT DISTINCT a.ville FROM Adresse a WHERE a.ville IS NOT NULL AND a.ville != ''"; + if (query != null && !query.trim().isEmpty()) { + jpql += " AND LOWER(a.ville) LIKE LOWER(:query)"; + } + jpql += " ORDER BY a.ville ASC"; + + TypedQuery typedQuery = entityManager.createQuery(jpql, String.class); + if (query != null && !query.trim().isEmpty()) { + typedQuery.setParameter("query", "%" + query.trim() + "%"); + } + typedQuery.setMaxResults(50); // Limiter à 50 résultats pour performance + + List villes = typedQuery.getResultList(); + LOG.infof("Trouvé %d villes distinctes", villes.size()); + return villes; + } + + /** + * Obtient la liste des professions distinctes depuis les membres + * Note: Si le champ profession n'existe pas dans Membre, retourne une liste vide + * Réutilisable pour autocomplétion (WOU/DRY) + */ + public List obtenirProfessionsDistinctes(String query) { + LOG.infof("Récupération des professions distinctes - query: %s", query); + + // TODO: Vérifier si le champ profession existe dans Membre + // Pour l'instant, retourner une liste vide car le champ n'existe pas + // Cette méthode peut être étendue si un champ profession est ajouté plus tard + LOG.warn("Le champ profession n'existe pas dans l'entité Membre. Retour d'une liste vide."); + return new ArrayList<>(); + } + + /** + * Exporte une sélection de membres en Excel (WOU/DRY - réutilise la logique d'export) + * + * @param membreIds Liste des IDs des membres à exporter + * @param format Format d'export (EXCEL, CSV, etc.) + * @return Données binaires du fichier Excel + */ + public byte[] exporterMembresSelectionnes(List membreIds, String format) { + LOG.infof("Export de %d membres sélectionnés - format: %s", membreIds.size(), format); + + if (membreIds == null || membreIds.isEmpty()) { + throw new IllegalArgumentException("La liste des membres ne peut pas être vide"); + } + + // Récupérer les membres + List membres = + membreIds.stream() + .map(id -> membreRepository.findByIdOptional(id)) + .filter(opt -> opt.isPresent()) + .map(java.util.Optional::get) + .collect(Collectors.toList()); + + // Convertir en DTOs + List membresDTO = convertToDTOList(membres); + + // Générer le fichier Excel (simplifié - à améliorer avec Apache POI) + // Pour l'instant, générer un CSV simple + StringBuilder csv = new StringBuilder(); + csv.append("Numéro;Nom;Prénom;Email;Téléphone;Statut;Date Adhésion\n"); + for (MembreDTO m : membresDTO) { + csv.append( + String.format( + "%s;%s;%s;%s;%s;%s;%s\n", + m.getNumeroMembre() != null ? m.getNumeroMembre() : "", + m.getNom() != null ? m.getNom() : "", + m.getPrenom() != null ? m.getPrenom() : "", + m.getEmail() != null ? m.getEmail() : "", + m.getTelephone() != null ? m.getTelephone() : "", + m.getStatut() != null ? m.getStatut() : "", + m.getDateAdhesion() != null ? m.getDateAdhesion().toString() : "")); + } + + return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); + } + + /** + * Importe des membres depuis un fichier Excel ou CSV + */ + public MembreImportExportService.ResultatImport importerMembres( + InputStream fileInputStream, + String fileName, + UUID organisationId, + String typeMembreDefaut, + boolean mettreAJourExistants, + boolean ignorerErreurs) { + return membreImportExportService.importerMembres( + fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs); + } + + /** + * Exporte des membres vers Excel + */ + public byte[] exporterVersExcel(List membres, List colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) { + try { + return membreImportExportService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, inclureStatistiques, motDePasse); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'export Excel"); + throw new RuntimeException("Erreur lors de l'export Excel: " + e.getMessage(), e); + } + } + + /** + * Exporte des membres vers CSV + */ + public byte[] exporterVersCSV(List membres, List colonnesExport, boolean inclureHeaders, boolean formaterDates) { + try { + return membreImportExportService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'export CSV"); + throw new RuntimeException("Erreur lors de l'export CSV: " + e.getMessage(), e); + } + } + + /** + * Génère un modèle Excel pour l'import + */ + public byte[] genererModeleImport() { + try { + return membreImportExportService.genererModeleImport(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la génération du modèle"); + throw new RuntimeException("Erreur lors de la génération du modèle: " + e.getMessage(), e); + } + } + + /** + * Liste les membres pour l'export selon les filtres + */ + public List listerMembresPourExport( + UUID associationId, + String statut, + String type, + String dateAdhesionDebut, + String dateAdhesionFin) { + + List membres; + + if (associationId != null) { + TypedQuery query = entityManager.createQuery( + "SELECT m FROM Membre m WHERE m.organisation.id = :associationId", Membre.class); + query.setParameter("associationId", associationId); + membres = query.getResultList(); + } else { + membres = membreRepository.listAll(); + } + + // Filtrer par statut + if (statut != null && !statut.isEmpty()) { + boolean actif = "ACTIF".equals(statut); + membres = membres.stream() + .filter(m -> m.getActif() == actif) + .collect(Collectors.toList()); + } + + // Filtrer par dates d'adhésion + if (dateAdhesionDebut != null && !dateAdhesionDebut.isEmpty()) { + LocalDate dateDebut = LocalDate.parse(dateAdhesionDebut); + membres = membres.stream() + .filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isBefore(dateDebut)) + .collect(Collectors.toList()); + } + + if (dateAdhesionFin != null && !dateAdhesionFin.isEmpty()) { + LocalDate dateFin = LocalDate.parse(dateAdhesionFin); + membres = membres.stream() + .filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isAfter(dateFin)) + .collect(Collectors.toList()); + } + + return convertToDTOList(membres); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/NotificationHistoryService.java b/src/main/java/dev/lions/unionflow/server/service/NotificationHistoryService.java new file mode 100644 index 0000000..69fb4fc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/NotificationHistoryService.java @@ -0,0 +1,322 @@ +package dev.lions.unionflow.server.service; + +import jakarta.enterprise.context.ApplicationScoped; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** Service pour gérer l'historique des notifications */ +@ApplicationScoped +public class NotificationHistoryService { + + private static final Logger LOG = Logger.getLogger(NotificationHistoryService.class); + + // Stockage temporaire en mémoire (à remplacer par une base de données) + private final Map> historiqueNotifications = + new ConcurrentHashMap<>(); + + /** Enregistre une notification dans l'historique */ + public void enregistrerNotification( + UUID utilisateurId, String type, String titre, String message, String canal, boolean succes) { + LOG.infof("Enregistrement de la notification %s pour l'utilisateur %s", type, utilisateurId); + + NotificationHistoryEntry entry = + NotificationHistoryEntry.builder() + .id(UUID.randomUUID()) + .utilisateurId(utilisateurId) + .type(type) + .titre(titre) + .message(message) + .canal(canal) + .dateEnvoi(LocalDateTime.now()) + .succes(succes) + .lu(false) + .build(); + + historiqueNotifications.computeIfAbsent(utilisateurId, k -> new ArrayList<>()).add(entry); + + // Limiter l'historique à 1000 notifications par utilisateur + List historique = historiqueNotifications.get(utilisateurId); + if (historique.size() > 1000) { + historique.sort(Comparator.comparing(NotificationHistoryEntry::getDateEnvoi).reversed()); + historiqueNotifications.put(utilisateurId, historique.subList(0, 1000)); + } + } + + /** Obtient l'historique des notifications d'un utilisateur */ + public List obtenirHistorique(UUID utilisateurId) { + LOG.infof( + "Récupération de l'historique des notifications pour l'utilisateur %s", utilisateurId); + + return historiqueNotifications.getOrDefault(utilisateurId, new ArrayList<>()).stream() + .sorted(Comparator.comparing(NotificationHistoryEntry::getDateEnvoi).reversed()) + .collect(Collectors.toList()); + } + + /** Obtient l'historique des notifications d'un utilisateur avec pagination */ + public List obtenirHistorique( + UUID utilisateurId, int page, int taille) { + List historique = obtenirHistorique(utilisateurId); + + int debut = page * taille; + int fin = Math.min(debut + taille, historique.size()); + + if (debut >= historique.size()) { + return new ArrayList<>(); + } + + return historique.subList(debut, fin); + } + + /** Marque une notification comme lue */ + public void marquerCommeLue(UUID utilisateurId, UUID notificationId) { + LOG.infof( + "Marquage de la notification %s comme lue pour l'utilisateur %s", + notificationId, utilisateurId); + + List historique = historiqueNotifications.get(utilisateurId); + if (historique != null) { + historique.stream() + .filter(entry -> entry.getId().equals(notificationId)) + .findFirst() + .ifPresent(entry -> entry.setLu(true)); + } + } + + /** Marque toutes les notifications comme lues */ + public void marquerToutesCommeLues(UUID utilisateurId) { + LOG.infof( + "Marquage de toutes les notifications comme lues pour l'utilisateur %s", utilisateurId); + + List historique = historiqueNotifications.get(utilisateurId); + if (historique != null) { + historique.forEach(entry -> entry.setLu(true)); + } + } + + /** Compte le nombre de notifications non lues */ + public long compterNotificationsNonLues(UUID utilisateurId) { + return obtenirHistorique(utilisateurId).stream().filter(entry -> !entry.isLu()).count(); + } + + /** Obtient les notifications non lues */ + public List obtenirNotificationsNonLues(UUID utilisateurId) { + return obtenirHistorique(utilisateurId).stream() + .filter(entry -> !entry.isLu()) + .collect(Collectors.toList()); + } + + /** Supprime les notifications anciennes (plus de 90 jours) */ + public void nettoyerHistorique() { + LOG.info("Nettoyage de l'historique des notifications"); + + LocalDateTime dateLimit = LocalDateTime.now().minusDays(90); + + for (Map.Entry> entry : + historiqueNotifications.entrySet()) { + List historique = entry.getValue(); + List historiqueFiltre = + historique.stream() + .filter(notification -> notification.getDateEnvoi().isAfter(dateLimit)) + .collect(Collectors.toList()); + + entry.setValue(historiqueFiltre); + } + } + + /** Obtient les statistiques des notifications pour un utilisateur */ + public Map obtenirStatistiques(UUID utilisateurId) { + List historique = obtenirHistorique(utilisateurId); + + Map stats = new HashMap<>(); + stats.put("total", historique.size()); + stats.put("nonLues", historique.stream().filter(entry -> !entry.isLu()).count()); + stats.put("succes", historique.stream().filter(NotificationHistoryEntry::isSucces).count()); + stats.put("echecs", historique.stream().filter(entry -> !entry.isSucces()).count()); + + // Statistiques par type + Map parType = + historique.stream() + .collect( + Collectors.groupingBy(NotificationHistoryEntry::getType, Collectors.counting())); + stats.put("parType", parType); + + // Statistiques par canal + Map parCanal = + historique.stream() + .collect( + Collectors.groupingBy(NotificationHistoryEntry::getCanal, Collectors.counting())); + stats.put("parCanal", parCanal); + + return stats; + } + + /** Classe interne pour représenter une entrée d'historique */ + public static class NotificationHistoryEntry { + private UUID id; + private UUID utilisateurId; + private String type; + private String titre; + private String message; + private String canal; + private LocalDateTime dateEnvoi; + private boolean succes; + private boolean lu; + + // Constructeurs + public NotificationHistoryEntry() {} + + private NotificationHistoryEntry(Builder builder) { + this.id = builder.id; + this.utilisateurId = builder.utilisateurId; + this.type = builder.type; + this.titre = builder.titre; + this.message = builder.message; + this.canal = builder.canal; + this.dateEnvoi = builder.dateEnvoi; + this.succes = builder.succes; + this.lu = builder.lu; + } + + public static Builder builder() { + return new Builder(); + } + + // Getters et Setters + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getUtilisateurId() { + return utilisateurId; + } + + public void setUtilisateurId(UUID utilisateurId) { + this.utilisateurId = utilisateurId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getTitre() { + return titre; + } + + public void setTitre(String titre) { + this.titre = titre; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getCanal() { + return canal; + } + + public void setCanal(String canal) { + this.canal = canal; + } + + public LocalDateTime getDateEnvoi() { + return dateEnvoi; + } + + public void setDateEnvoi(LocalDateTime dateEnvoi) { + this.dateEnvoi = dateEnvoi; + } + + public boolean isSucces() { + return succes; + } + + public void setSucces(boolean succes) { + this.succes = succes; + } + + public boolean isLu() { + return lu; + } + + public void setLu(boolean lu) { + this.lu = lu; + } + + // Builder + public static class Builder { + private UUID id; + private UUID utilisateurId; + private String type; + private String titre; + private String message; + private String canal; + private LocalDateTime dateEnvoi; + private boolean succes; + private boolean lu; + + public Builder id(UUID id) { + this.id = id; + return this; + } + + public Builder utilisateurId(UUID utilisateurId) { + this.utilisateurId = utilisateurId; + return this; + } + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder titre(String titre) { + this.titre = titre; + return this; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder canal(String canal) { + this.canal = canal; + return this; + } + + public Builder dateEnvoi(LocalDateTime dateEnvoi) { + this.dateEnvoi = dateEnvoi; + return this; + } + + public Builder succes(boolean succes) { + this.succes = succes; + return this; + } + + public Builder lu(boolean lu) { + this.lu = lu; + return this; + } + + public NotificationHistoryEntry build() { + return new NotificationHistoryEntry(this); + } + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/NotificationService.java b/src/main/java/dev/lions/unionflow/server/service/NotificationService.java new file mode 100644 index 0000000..26c3a21 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/NotificationService.java @@ -0,0 +1,352 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.notification.NotificationDTO; +import dev.lions.unionflow.server.api.dto.notification.TemplateNotificationDTO; +import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification; +import dev.lions.unionflow.server.api.enums.notification.StatutNotification; +import dev.lions.unionflow.server.entity.*; +import dev.lions.unionflow.server.repository.*; +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.time.LocalDateTime; +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 notifications + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class NotificationService { + + private static final Logger LOG = Logger.getLogger(NotificationService.class); + + @Inject NotificationRepository notificationRepository; + + @Inject TemplateNotificationRepository templateNotificationRepository; + + @Inject MembreRepository membreRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouveau template de notification + * + * @param templateDTO DTO du template à créer + * @return DTO du template créé + */ + @Transactional + public TemplateNotificationDTO creerTemplate(TemplateNotificationDTO templateDTO) { + LOG.infof("Création d'un nouveau template: %s", templateDTO.getCode()); + + // Vérifier l'unicité du code + if (templateNotificationRepository.findByCode(templateDTO.getCode()).isPresent()) { + throw new IllegalArgumentException("Un template avec ce code existe déjà: " + templateDTO.getCode()); + } + + TemplateNotification template = convertToEntity(templateDTO); + template.setCreePar(keycloakService.getCurrentUserEmail()); + + templateNotificationRepository.persist(template); + LOG.infof("Template créé avec succès: ID=%s, Code=%s", template.getId(), template.getCode()); + + return convertToDTO(template); + } + + /** + * Crée une nouvelle notification + * + * @param notificationDTO DTO de la notification à créer + * @return DTO de la notification créée + */ + @Transactional + public NotificationDTO creerNotification(NotificationDTO notificationDTO) { + LOG.infof("Création d'une nouvelle notification: %s", notificationDTO.getTypeNotification()); + + Notification notification = convertToEntity(notificationDTO); + notification.setCreePar(keycloakService.getCurrentUserEmail()); + + notificationRepository.persist(notification); + LOG.infof("Notification créée avec succès: ID=%s", notification.getId()); + + return convertToDTO(notification); + } + + /** + * Marque une notification comme lue + * + * @param id ID de la notification + * @return DTO de la notification mise à jour + */ + @Transactional + public NotificationDTO marquerCommeLue(UUID id) { + LOG.infof("Marquage de la notification comme lue: ID=%s", id); + + Notification notification = + notificationRepository + .findNotificationById(id) + .orElseThrow(() -> new NotFoundException("Notification non trouvée avec l'ID: " + id)); + + notification.setStatut(StatutNotification.LUE); + notification.setDateLecture(LocalDateTime.now()); + notification.setModifiePar(keycloakService.getCurrentUserEmail()); + + notificationRepository.persist(notification); + LOG.infof("Notification marquée comme lue: ID=%s", id); + + return convertToDTO(notification); + } + + /** + * Trouve une notification par son ID + * + * @param id ID de la notification + * @return DTO de la notification + */ + public NotificationDTO trouverNotificationParId(UUID id) { + return notificationRepository + .findNotificationById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Notification non trouvée avec l'ID: " + id)); + } + + /** + * Liste toutes les notifications d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications + */ + public List listerNotificationsParMembre(UUID membreId) { + return notificationRepository.findByMembreId(membreId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Liste les notifications non lues d'un membre + * + * @param membreId ID du membre + * @return Liste des notifications non lues + */ + public List listerNotificationsNonLuesParMembre(UUID membreId) { + return notificationRepository.findNonLuesByMembreId(membreId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Liste les notifications en attente d'envoi + * + * @return Liste des notifications en attente + */ + public List listerNotificationsEnAttenteEnvoi() { + return notificationRepository.findEnAttenteEnvoi().stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Envoie des notifications groupées à plusieurs membres (WOU/DRY) + * + * @param membreIds Liste des IDs des membres destinataires + * @param sujet Sujet de la notification + * @param corps Corps du message + * @param canaux Canaux d'envoi (EMAIL, SMS, etc.) + * @return Nombre de notifications créées + */ + @Transactional + public int envoyerNotificationsGroupees( + List membreIds, String sujet, String corps, List canaux) { + LOG.infof( + "Envoi de notifications groupées à %d membres - sujet: %s", membreIds.size(), sujet); + + if (membreIds == null || membreIds.isEmpty()) { + throw new IllegalArgumentException("La liste des membres ne peut pas être vide"); + } + + int notificationsCreees = 0; + for (UUID membreId : membreIds) { + try { + Membre membre = + membreRepository + .findByIdOptional(membreId) + .orElseThrow( + () -> + new IllegalArgumentException( + "Membre non trouvé avec l'ID: " + membreId)); + + Notification notification = new Notification(); + notification.setMembre(membre); + notification.setSujet(sujet); + notification.setCorps(corps); + notification.setTypeNotification( + dev.lions.unionflow.server.api.enums.notification.TypeNotification.IN_APP); + notification.setPriorite(PrioriteNotification.NORMALE); + notification.setStatut(StatutNotification.EN_ATTENTE); + notification.setDateEnvoiPrevue(java.time.LocalDateTime.now()); + notification.setCreePar(keycloakService.getCurrentUserEmail()); + + notificationRepository.persist(notification); + notificationsCreees++; + } catch (Exception e) { + LOG.warnf( + "Erreur lors de la création de la notification pour le membre %s: %s", + membreId, e.getMessage()); + } + } + + LOG.infof( + "%d notifications créées sur %d membres demandés", notificationsCreees, membreIds.size()); + return notificationsCreees; + } + + // ======================================== + // MÉTHODES PRIVÉES + // ======================================== + + /** Convertit une entité TemplateNotification en DTO */ + private TemplateNotificationDTO convertToDTO(TemplateNotification template) { + if (template == null) { + return null; + } + + TemplateNotificationDTO dto = new TemplateNotificationDTO(); + dto.setId(template.getId()); + dto.setCode(template.getCode()); + dto.setSujet(template.getSujet()); + dto.setCorpsTexte(template.getCorpsTexte()); + dto.setCorpsHtml(template.getCorpsHtml()); + dto.setVariablesDisponibles(template.getVariablesDisponibles()); + dto.setCanauxSupportes(template.getCanauxSupportes()); + dto.setLangue(template.getLangue()); + dto.setDescription(template.getDescription()); + dto.setDateCreation(template.getDateCreation()); + dto.setDateModification(template.getDateModification()); + dto.setActif(template.getActif()); + + return dto; + } + + /** Convertit un DTO en entité TemplateNotification */ + private TemplateNotification convertToEntity(TemplateNotificationDTO dto) { + if (dto == null) { + return null; + } + + TemplateNotification template = new TemplateNotification(); + template.setCode(dto.getCode()); + template.setSujet(dto.getSujet()); + template.setCorpsTexte(dto.getCorpsTexte()); + template.setCorpsHtml(dto.getCorpsHtml()); + template.setVariablesDisponibles(dto.getVariablesDisponibles()); + template.setCanauxSupportes(dto.getCanauxSupportes()); + template.setLangue(dto.getLangue() != null ? dto.getLangue() : "fr"); + template.setDescription(dto.getDescription()); + + return template; + } + + /** Convertit une entité Notification en DTO */ + private NotificationDTO convertToDTO(Notification notification) { + if (notification == null) { + return null; + } + + NotificationDTO dto = new NotificationDTO(); + dto.setId(notification.getId()); + dto.setTypeNotification(notification.getTypeNotification()); + dto.setPriorite(notification.getPriorite()); + dto.setStatut(notification.getStatut()); + dto.setSujet(notification.getSujet()); + dto.setCorps(notification.getCorps()); + dto.setDateEnvoiPrevue(notification.getDateEnvoiPrevue()); + dto.setDateEnvoi(notification.getDateEnvoi()); + dto.setDateLecture(notification.getDateLecture()); + dto.setNombreTentatives(notification.getNombreTentatives()); + dto.setMessageErreur(notification.getMessageErreur()); + dto.setDonneesAdditionnelles(notification.getDonneesAdditionnelles()); + + if (notification.getMembre() != null) { + dto.setMembreId(notification.getMembre().getId()); + } + if (notification.getOrganisation() != null) { + dto.setOrganisationId(notification.getOrganisation().getId()); + } + if (notification.getTemplate() != null) { + dto.setTemplateId(notification.getTemplate().getId()); + } + + dto.setDateCreation(notification.getDateCreation()); + dto.setDateModification(notification.getDateModification()); + dto.setActif(notification.getActif()); + + return dto; + } + + /** Convertit un DTO en entité Notification */ + private Notification convertToEntity(NotificationDTO dto) { + if (dto == null) { + return null; + } + + Notification notification = new Notification(); + notification.setTypeNotification(dto.getTypeNotification()); + notification.setPriorite( + dto.getPriorite() != null ? dto.getPriorite() : PrioriteNotification.NORMALE); + notification.setStatut( + dto.getStatut() != null ? dto.getStatut() : StatutNotification.EN_ATTENTE); + notification.setSujet(dto.getSujet()); + notification.setCorps(dto.getCorps()); + notification.setDateEnvoiPrevue( + dto.getDateEnvoiPrevue() != null ? dto.getDateEnvoiPrevue() : LocalDateTime.now()); + notification.setDateEnvoi(dto.getDateEnvoi()); + notification.setDateLecture(dto.getDateLecture()); + notification.setNombreTentatives(dto.getNombreTentatives() != null ? dto.getNombreTentatives() : 0); + notification.setMessageErreur(dto.getMessageErreur()); + notification.setDonneesAdditionnelles(dto.getDonneesAdditionnelles()); + + // Relations + if (dto.getMembreId() != null) { + Membre membre = + membreRepository + .findByIdOptional(dto.getMembreId()) + .orElseThrow( + () -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId())); + notification.setMembre(membre); + } + + if (dto.getOrganisationId() != null) { + Organisation org = + organisationRepository + .findByIdOptional(dto.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + dto.getOrganisationId())); + notification.setOrganisation(org); + } + + if (dto.getTemplateId() != null) { + TemplateNotification template = + templateNotificationRepository + .findTemplateNotificationById(dto.getTemplateId()) + .orElseThrow( + () -> + new NotFoundException( + "Template non trouvé avec l'ID: " + dto.getTemplateId())); + notification.setTemplate(template); + } + + return notification; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java b/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java new file mode 100644 index 0000000..26f7dad --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/OrganisationService.java @@ -0,0 +1,443 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO; +import dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation; +import dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.NotFoundException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Service métier pour la gestion des organisations + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@ApplicationScoped +public class OrganisationService { + + private static final Logger LOG = Logger.getLogger(OrganisationService.class); + + @Inject OrganisationRepository organisationRepository; + + /** + * Crée une nouvelle organisation + * + * @param organisation l'organisation à créer + * @return l'organisation créée + */ + @Transactional + public Organisation creerOrganisation(Organisation organisation) { + LOG.infof("Création d'une nouvelle organisation: %s", organisation.getNom()); + + // Vérifier l'unicité de l'email + if (organisationRepository.findByEmail(organisation.getEmail()).isPresent()) { + throw new IllegalArgumentException("Une organisation avec cet email existe déjà"); + } + + // Vérifier l'unicité du nom + if (organisationRepository.findByNom(organisation.getNom()).isPresent()) { + throw new IllegalArgumentException("Une organisation avec ce nom existe déjà"); + } + + // Vérifier l'unicité du numéro d'enregistrement si fourni + if (organisation.getNumeroEnregistrement() != null + && !organisation.getNumeroEnregistrement().isEmpty()) { + if (organisationRepository + .findByNumeroEnregistrement(organisation.getNumeroEnregistrement()) + .isPresent()) { + throw new IllegalArgumentException( + "Une organisation avec ce numéro d'enregistrement existe déjà"); + } + } + + // Définir les valeurs par défaut + if (organisation.getStatut() == null) { + organisation.setStatut("ACTIVE"); + } + if (organisation.getTypeOrganisation() == null) { + organisation.setTypeOrganisation("ASSOCIATION"); + } + + organisationRepository.persist(organisation); + LOG.infof( + "Organisation créée avec succès: ID=%s, Nom=%s", organisation.getId(), organisation.getNom()); + + return organisation; + } + + /** + * Met à jour une organisation existante + * + * @param id l'ID de l'organisation + * @param organisationMiseAJour les données de mise à jour + * @param utilisateur l'utilisateur effectuant la modification + * @return l'organisation mise à jour + */ + @Transactional + public Organisation mettreAJourOrganisation( + UUID id, Organisation organisationMiseAJour, String utilisateur) { + LOG.infof("Mise à jour de l'organisation ID: %s", id); + + Organisation organisation = + organisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); + + // Vérifier l'unicité de l'email si modifié + if (!organisation.getEmail().equals(organisationMiseAJour.getEmail())) { + if (organisationRepository.findByEmail(organisationMiseAJour.getEmail()).isPresent()) { + throw new IllegalArgumentException("Une organisation avec cet email existe déjà"); + } + organisation.setEmail(organisationMiseAJour.getEmail()); + } + + // Vérifier l'unicité du nom si modifié + if (!organisation.getNom().equals(organisationMiseAJour.getNom())) { + if (organisationRepository.findByNom(organisationMiseAJour.getNom()).isPresent()) { + throw new IllegalArgumentException("Une organisation avec ce nom existe déjà"); + } + organisation.setNom(organisationMiseAJour.getNom()); + } + + // Mettre à jour les autres champs + organisation.setNomCourt(organisationMiseAJour.getNomCourt()); + organisation.setDescription(organisationMiseAJour.getDescription()); + organisation.setTelephone(organisationMiseAJour.getTelephone()); + organisation.setAdresse(organisationMiseAJour.getAdresse()); + organisation.setVille(organisationMiseAJour.getVille()); + organisation.setCodePostal(organisationMiseAJour.getCodePostal()); + organisation.setRegion(organisationMiseAJour.getRegion()); + organisation.setPays(organisationMiseAJour.getPays()); + organisation.setSiteWeb(organisationMiseAJour.getSiteWeb()); + organisation.setObjectifs(organisationMiseAJour.getObjectifs()); + organisation.setActivitesPrincipales(organisationMiseAJour.getActivitesPrincipales()); + + organisation.marquerCommeModifie(utilisateur); + + LOG.infof("Organisation mise à jour avec succès: ID=%s", id); + return organisation; + } + + /** + * Supprime une organisation + * + * @param id l'UUID de l'organisation + * @param utilisateur l'utilisateur effectuant la suppression + */ + @Transactional + public void supprimerOrganisation(UUID id, String utilisateur) { + LOG.infof("Suppression de l'organisation ID: %s", id); + + Organisation organisation = + organisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); + + // Vérifier qu'il n'y a pas de membres actifs + if (organisation.getNombreMembres() > 0) { + throw new IllegalStateException( + "Impossible de supprimer une organisation avec des membres actifs"); + } + + // Soft delete - marquer comme inactive + organisation.setActif(false); + organisation.setStatut("DISSOUTE"); + organisation.marquerCommeModifie(utilisateur); + + LOG.infof("Organisation supprimée (soft delete) avec succès: ID=%s", id); + } + + /** + * Trouve une organisation par son ID + * + * @param id l'UUID de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional trouverParId(UUID id) { + return organisationRepository.findByIdOptional(id); + } + + /** + * Trouve une organisation par son email + * + * @param email l'email de l'organisation + * @return Optional contenant l'organisation si trouvée + */ + public Optional trouverParEmail(String email) { + return organisationRepository.findByEmail(email); + } + + /** + * Liste toutes les organisations actives + * + * @return liste des organisations actives + */ + public List listerOrganisationsActives() { + return organisationRepository.findAllActives(); + } + + /** + * Liste toutes les organisations actives avec pagination + * + * @param page numéro de page + * @param size taille de la page + * @return liste paginée des organisations actives + */ + public List listerOrganisationsActives(int page, int size) { + return organisationRepository.findAllActives(Page.of(page, size), Sort.by("nom").ascending()); + } + + /** + * Recherche d'organisations par nom + * + * @param recherche terme de recherche + * @param page numéro de page + * @param size taille de la page + * @return liste paginée des organisations correspondantes + */ + public List rechercherOrganisations(String recherche, int page, int size) { + return organisationRepository.findByNomOrNomCourt( + recherche, Page.of(page, size), Sort.by("nom").ascending()); + } + + /** + * Recherche avancée d'organisations + * + * @param nom nom (optionnel) + * @param typeOrganisation type (optionnel) + * @param statut statut (optionnel) + * @param ville ville (optionnel) + * @param region région (optionnel) + * @param pays pays (optionnel) + * @param page numéro de page + * @param size taille de la page + * @return liste filtrée des organisations + */ + public List rechercheAvancee( + String nom, + String typeOrganisation, + String statut, + String ville, + String region, + String pays, + int page, + int size) { + return organisationRepository.rechercheAvancee( + nom, typeOrganisation, statut, ville, region, pays, Page.of(page, size)); + } + + /** + * Active une organisation + * + * @param id l'ID de l'organisation + * @param utilisateur l'utilisateur effectuant l'activation + * @return l'organisation activée + */ + @Transactional + public Organisation activerOrganisation(UUID id, String utilisateur) { + LOG.infof("Activation de l'organisation ID: %s", id); + + Organisation organisation = + organisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); + + organisation.activer(utilisateur); + + LOG.infof("Organisation activée avec succès: ID=%s", id); + return organisation; + } + + /** + * Suspend une organisation + * + * @param id l'UUID de l'organisation + * @param utilisateur l'utilisateur effectuant la suspension + * @return l'organisation suspendue + */ + @Transactional + public Organisation suspendreOrganisation(UUID id, String utilisateur) { + LOG.infof("Suspension de l'organisation ID: %s", id); + + Organisation organisation = + organisationRepository + .findByIdOptional(id) + .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id)); + + organisation.suspendre(utilisateur); + + LOG.infof("Organisation suspendue avec succès: ID=%s", id); + return organisation; + } + + /** + * Obtient les statistiques des organisations + * + * @return map contenant les statistiques + */ + public Map obtenirStatistiques() { + LOG.info("Calcul des statistiques des organisations"); + + long totalOrganisations = organisationRepository.count(); + long organisationsActives = organisationRepository.countActives(); + long organisationsInactives = totalOrganisations - organisationsActives; + long nouvellesOrganisations30Jours = + organisationRepository.countNouvellesOrganisations(LocalDate.now().minusDays(30)); + + return Map.of( + "totalOrganisations", totalOrganisations, + "organisationsActives", organisationsActives, + "organisationsInactives", organisationsInactives, + "nouvellesOrganisations30Jours", nouvellesOrganisations30Jours, + "tauxActivite", + totalOrganisations > 0 ? (organisationsActives * 100.0 / totalOrganisations) : 0.0, + "timestamp", LocalDateTime.now()); + } + + /** + * Convertit une entité Organisation en DTO + * + * @param organisation l'entité à convertir + * @return le DTO correspondant + */ + public OrganisationDTO convertToDTO(Organisation organisation) { + if (organisation == null) { + return null; + } + + OrganisationDTO dto = new OrganisationDTO(); + + // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant) + dto.setId(organisation.getId()); + + // Informations de base + dto.setNom(organisation.getNom()); + dto.setNomCourt(organisation.getNomCourt()); + dto.setDescription(organisation.getDescription()); + dto.setEmail(organisation.getEmail()); + dto.setTelephone(organisation.getTelephone()); + dto.setTelephoneSecondaire(organisation.getTelephoneSecondaire()); + dto.setEmailSecondaire(organisation.getEmailSecondaire()); + dto.setAdresse(organisation.getAdresse()); + dto.setVille(organisation.getVille()); + dto.setCodePostal(organisation.getCodePostal()); + dto.setRegion(organisation.getRegion()); + dto.setPays(organisation.getPays()); + dto.setLatitude(organisation.getLatitude()); + dto.setLongitude(organisation.getLongitude()); + dto.setSiteWeb(organisation.getSiteWeb()); + dto.setLogo(organisation.getLogo()); + dto.setReseauxSociaux(organisation.getReseauxSociaux()); + dto.setObjectifs(organisation.getObjectifs()); + dto.setActivitesPrincipales(organisation.getActivitesPrincipales()); + dto.setNombreMembres(organisation.getNombreMembres()); + dto.setNombreAdministrateurs(organisation.getNombreAdministrateurs()); + dto.setBudgetAnnuel(organisation.getBudgetAnnuel()); + dto.setDevise(organisation.getDevise()); + dto.setDateFondation(organisation.getDateFondation()); + dto.setNumeroEnregistrement(organisation.getNumeroEnregistrement()); + dto.setNiveauHierarchique(organisation.getNiveauHierarchique()); + + // Conversion de l'organisation parente (UUID → UUID, pas de conversion nécessaire) + if (organisation.getOrganisationParenteId() != null) { + dto.setOrganisationParenteId(organisation.getOrganisationParenteId()); + } + + // Conversion du type d'organisation (String → Enum) + if (organisation.getTypeOrganisation() != null) { + try { + dto.setTypeOrganisation( + TypeOrganisation.valueOf(organisation.getTypeOrganisation().toUpperCase())); + } catch (IllegalArgumentException e) { + // Valeur par défaut si la conversion échoue + LOG.warnf( + "Type d'organisation inconnu: %s, utilisation de ASSOCIATION par défaut", + organisation.getTypeOrganisation()); + dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION); + } + } else { + dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION); + } + + // Conversion du statut (String → Enum) + if (organisation.getStatut() != null) { + try { + dto.setStatut( + StatutOrganisation.valueOf(organisation.getStatut().toUpperCase())); + } catch (IllegalArgumentException e) { + // Valeur par défaut si la conversion échoue + LOG.warnf( + "Statut d'organisation inconnu: %s, utilisation de ACTIVE par défaut", + organisation.getStatut()); + dto.setStatut(StatutOrganisation.ACTIVE); + } + } else { + dto.setStatut(StatutOrganisation.ACTIVE); + } + + // Champs de base DTO + dto.setDateCreation(organisation.getDateCreation()); + dto.setDateModification(organisation.getDateModification()); + dto.setActif(organisation.getActif()); + dto.setVersion(organisation.getVersion() != null ? organisation.getVersion() : 0L); + + // Champs par défaut + dto.setOrganisationPublique( + organisation.getOrganisationPublique() != null + ? organisation.getOrganisationPublique() + : true); + dto.setAccepteNouveauxMembres( + organisation.getAccepteNouveauxMembres() != null + ? organisation.getAccepteNouveauxMembres() + : true); + dto.setCotisationObligatoire( + organisation.getCotisationObligatoire() != null + ? organisation.getCotisationObligatoire() + : false); + dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle()); + + return dto; + } + + /** + * Convertit un DTO en entité Organisation + * + * @param dto le DTO à convertir + * @return l'entité correspondante + */ + public Organisation convertFromDTO(OrganisationDTO dto) { + if (dto == null) { + return null; + } + + return Organisation.builder() + .nom(dto.getNom()) + .nomCourt(dto.getNomCourt()) + .description(dto.getDescription()) + .email(dto.getEmail()) + .telephone(dto.getTelephone()) + .adresse(dto.getAdresse()) + .ville(dto.getVille()) + .codePostal(dto.getCodePostal()) + .region(dto.getRegion()) + .pays(dto.getPays()) + .siteWeb(dto.getSiteWeb()) + .objectifs(dto.getObjectifs()) + .activitesPrincipales(dto.getActivitesPrincipales()) + .build(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/PaiementService.java b/src/main/java/dev/lions/unionflow/server/service/PaiementService.java new file mode 100644 index 0000000..6c5bfb4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/PaiementService.java @@ -0,0 +1,309 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO; +import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Paiement; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.PaiementRepository; +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.math.BigDecimal; +import java.time.LocalDateTime; +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 paiements + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class PaiementService { + + private static final Logger LOG = Logger.getLogger(PaiementService.class); + + @Inject PaiementRepository paiementRepository; + + @Inject MembreRepository membreRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouveau paiement + * + * @param paiementDTO DTO du paiement à créer + * @return DTO du paiement créé + */ + @Transactional + public PaiementDTO creerPaiement(PaiementDTO paiementDTO) { + LOG.infof("Création d'un nouveau paiement: %s", paiementDTO.getNumeroReference()); + + Paiement paiement = convertToEntity(paiementDTO); + paiement.setCreePar(keycloakService.getCurrentUserEmail()); + + paiementRepository.persist(paiement); + LOG.infof("Paiement créé avec succès: ID=%s, Référence=%s", paiement.getId(), paiement.getNumeroReference()); + + return convertToDTO(paiement); + } + + /** + * Met à jour un paiement existant + * + * @param id ID du paiement + * @param paiementDTO DTO avec les modifications + * @return DTO du paiement mis à jour + */ + @Transactional + public PaiementDTO mettreAJourPaiement(UUID id, PaiementDTO paiementDTO) { + LOG.infof("Mise à jour du paiement ID: %s", id); + + Paiement paiement = + paiementRepository + .findPaiementById(id) + .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id)); + + if (!paiement.peutEtreModifie()) { + throw new IllegalStateException("Le paiement ne peut plus être modifié (statut finalisé)"); + } + + updateFromDTO(paiement, paiementDTO); + paiement.setModifiePar(keycloakService.getCurrentUserEmail()); + + paiementRepository.persist(paiement); + LOG.infof("Paiement mis à jour avec succès: ID=%s", id); + + return convertToDTO(paiement); + } + + /** + * Valide un paiement + * + * @param id ID du paiement + * @return DTO du paiement validé + */ + @Transactional + public PaiementDTO validerPaiement(UUID id) { + LOG.infof("Validation du paiement ID: %s", id); + + Paiement paiement = + paiementRepository + .findPaiementById(id) + .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id)); + + if (paiement.isValide()) { + LOG.warnf("Le paiement ID=%s est déjà validé", id); + return convertToDTO(paiement); + } + + paiement.setStatutPaiement(StatutPaiement.VALIDE); + paiement.setDateValidation(LocalDateTime.now()); + paiement.setValidateur(keycloakService.getCurrentUserEmail()); + paiement.setModifiePar(keycloakService.getCurrentUserEmail()); + + paiementRepository.persist(paiement); + LOG.infof("Paiement validé avec succès: ID=%s", id); + + return convertToDTO(paiement); + } + + /** + * Annule un paiement + * + * @param id ID du paiement + * @return DTO du paiement annulé + */ + @Transactional + public PaiementDTO annulerPaiement(UUID id) { + LOG.infof("Annulation du paiement ID: %s", id); + + Paiement paiement = + paiementRepository + .findPaiementById(id) + .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id)); + + if (!paiement.peutEtreModifie()) { + throw new IllegalStateException("Le paiement ne peut plus être annulé (statut finalisé)"); + } + + paiement.setStatutPaiement(StatutPaiement.ANNULE); + paiement.setModifiePar(keycloakService.getCurrentUserEmail()); + + paiementRepository.persist(paiement); + LOG.infof("Paiement annulé avec succès: ID=%s", id); + + return convertToDTO(paiement); + } + + /** + * Trouve un paiement par son ID + * + * @param id ID du paiement + * @return DTO du paiement + */ + public PaiementDTO trouverParId(UUID id) { + return paiementRepository + .findPaiementById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id)); + } + + /** + * Trouve un paiement par son numéro de référence + * + * @param numeroReference Numéro de référence + * @return DTO du paiement + */ + public PaiementDTO trouverParNumeroReference(String numeroReference) { + return paiementRepository + .findByNumeroReference(numeroReference) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec la référence: " + numeroReference)); + } + + /** + * Liste tous les paiements d'un membre + * + * @param membreId ID du membre + * @return Liste des paiements + */ + public List listerParMembre(UUID membreId) { + return paiementRepository.findByMembreId(membreId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Calcule le montant total des paiements validés dans une période + * + * @param dateDebut Date de début + * @param dateFin Date de fin + * @return Montant total + */ + public BigDecimal calculerMontantTotalValides(LocalDateTime dateDebut, LocalDateTime dateFin) { + return paiementRepository.calculerMontantTotalValides(dateDebut, dateFin); + } + + // ======================================== + // MÉTHODES PRIVÉES + // ======================================== + + /** Convertit une entité en DTO */ + private PaiementDTO convertToDTO(Paiement paiement) { + if (paiement == null) { + return null; + } + + PaiementDTO dto = new PaiementDTO(); + dto.setId(paiement.getId()); + dto.setNumeroReference(paiement.getNumeroReference()); + dto.setMontant(paiement.getMontant()); + dto.setCodeDevise(paiement.getCodeDevise()); + dto.setMethodePaiement(paiement.getMethodePaiement()); + dto.setStatutPaiement(paiement.getStatutPaiement()); + dto.setDatePaiement(paiement.getDatePaiement()); + dto.setDateValidation(paiement.getDateValidation()); + dto.setValidateur(paiement.getValidateur()); + dto.setReferenceExterne(paiement.getReferenceExterne()); + dto.setUrlPreuve(paiement.getUrlPreuve()); + dto.setCommentaire(paiement.getCommentaire()); + dto.setIpAddress(paiement.getIpAddress()); + dto.setUserAgent(paiement.getUserAgent()); + + if (paiement.getMembre() != null) { + dto.setMembreId(paiement.getMembre().getId()); + } + if (paiement.getTransactionWave() != null) { + dto.setTransactionWaveId(paiement.getTransactionWave().getId()); + } + + dto.setDateCreation(paiement.getDateCreation()); + dto.setDateModification(paiement.getDateModification()); + dto.setActif(paiement.getActif()); + + return dto; + } + + /** Convertit un DTO en entité */ + private Paiement convertToEntity(PaiementDTO dto) { + if (dto == null) { + return null; + } + + Paiement paiement = new Paiement(); + paiement.setNumeroReference(dto.getNumeroReference()); + paiement.setMontant(dto.getMontant()); + paiement.setCodeDevise(dto.getCodeDevise()); + paiement.setMethodePaiement(dto.getMethodePaiement()); + paiement.setStatutPaiement(dto.getStatutPaiement() != null ? dto.getStatutPaiement() : StatutPaiement.EN_ATTENTE); + paiement.setDatePaiement(dto.getDatePaiement()); + paiement.setDateValidation(dto.getDateValidation()); + paiement.setValidateur(dto.getValidateur()); + paiement.setReferenceExterne(dto.getReferenceExterne()); + paiement.setUrlPreuve(dto.getUrlPreuve()); + paiement.setCommentaire(dto.getCommentaire()); + paiement.setIpAddress(dto.getIpAddress()); + paiement.setUserAgent(dto.getUserAgent()); + + // Relation Membre + if (dto.getMembreId() != null) { + Membre membre = + membreRepository + .findByIdOptional(dto.getMembreId()) + .orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId())); + paiement.setMembre(membre); + } + + // Relation TransactionWave sera gérée par WaveService + + return paiement; + } + + /** Met à jour une entité à partir d'un DTO */ + private void updateFromDTO(Paiement paiement, PaiementDTO dto) { + if (dto.getMontant() != null) { + paiement.setMontant(dto.getMontant()); + } + if (dto.getCodeDevise() != null) { + paiement.setCodeDevise(dto.getCodeDevise()); + } + if (dto.getMethodePaiement() != null) { + paiement.setMethodePaiement(dto.getMethodePaiement()); + } + if (dto.getStatutPaiement() != null) { + paiement.setStatutPaiement(dto.getStatutPaiement()); + } + if (dto.getDatePaiement() != null) { + paiement.setDatePaiement(dto.getDatePaiement()); + } + if (dto.getDateValidation() != null) { + paiement.setDateValidation(dto.getDateValidation()); + } + if (dto.getValidateur() != null) { + paiement.setValidateur(dto.getValidateur()); + } + if (dto.getReferenceExterne() != null) { + paiement.setReferenceExterne(dto.getReferenceExterne()); + } + if (dto.getUrlPreuve() != null) { + paiement.setUrlPreuve(dto.getUrlPreuve()); + } + if (dto.getCommentaire() != null) { + paiement.setCommentaire(dto.getCommentaire()); + } + if (dto.getIpAddress() != null) { + paiement.setIpAddress(dto.getIpAddress()); + } + if (dto.getUserAgent() != null) { + paiement.setUserAgent(dto.getUserAgent()); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/PermissionService.java b/src/main/java/dev/lions/unionflow/server/service/PermissionService.java new file mode 100644 index 0000000..f927c61 --- /dev/null +++ b/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 + .findPermissionById(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.findPermissionById(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 + .findPermissionById(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/src/main/java/dev/lions/unionflow/server/service/PreferencesNotificationService.java b/src/main/java/dev/lions/unionflow/server/service/PreferencesNotificationService.java new file mode 100644 index 0000000..65c00c6 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/PreferencesNotificationService.java @@ -0,0 +1,140 @@ +package dev.lions.unionflow.server.service; + +import jakarta.enterprise.context.ApplicationScoped; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** Service pour gérer les préférences de notification des utilisateurs */ +@ApplicationScoped +public class PreferencesNotificationService { + + private static final Logger LOG = Logger.getLogger(PreferencesNotificationService.class); + + // Stockage temporaire en mémoire (à remplacer par une base de données) + private final Map> preferencesUtilisateurs = new HashMap<>(); + + /** Obtient les préférences de notification d'un utilisateur */ + public Map obtenirPreferences(UUID utilisateurId) { + LOG.infof("Récupération des préférences de notification pour l'utilisateur %s", utilisateurId); + + return preferencesUtilisateurs.getOrDefault(utilisateurId, getPreferencesParDefaut()); + } + + /** Met à jour les préférences de notification d'un utilisateur */ + public void mettreAJourPreferences(UUID utilisateurId, Map preferences) { + LOG.infof("Mise à jour des préférences de notification pour l'utilisateur %s", utilisateurId); + + preferencesUtilisateurs.put(utilisateurId, new HashMap<>(preferences)); + } + + /** Vérifie si un utilisateur souhaite recevoir un type de notification */ + public boolean accepteNotification(UUID utilisateurId, String typeNotification) { + Map preferences = obtenirPreferences(utilisateurId); + return preferences.getOrDefault(typeNotification, true); + } + + /** Active un type de notification pour un utilisateur */ + public void activerNotification(UUID utilisateurId, String typeNotification) { + LOG.infof( + "Activation de la notification %s pour l'utilisateur %s", typeNotification, utilisateurId); + + Map preferences = obtenirPreferences(utilisateurId); + preferences.put(typeNotification, true); + mettreAJourPreferences(utilisateurId, preferences); + } + + /** Désactive un type de notification pour un utilisateur */ + public void desactiverNotification(UUID utilisateurId, String typeNotification) { + LOG.infof( + "Désactivation de la notification %s pour l'utilisateur %s", + typeNotification, utilisateurId); + + Map preferences = obtenirPreferences(utilisateurId); + preferences.put(typeNotification, false); + mettreAJourPreferences(utilisateurId, preferences); + } + + /** Réinitialise les préférences d'un utilisateur aux valeurs par défaut */ + public void reinitialiserPreferences(UUID utilisateurId) { + LOG.infof("Réinitialisation des préférences pour l'utilisateur %s", utilisateurId); + + mettreAJourPreferences(utilisateurId, getPreferencesParDefaut()); + } + + /** Obtient les préférences par défaut */ + private Map getPreferencesParDefaut() { + Map preferences = new HashMap<>(); + + // Notifications générales + preferences.put("NOUVELLE_COTISATION", true); + preferences.put("RAPPEL_COTISATION", true); + preferences.put("COTISATION_RETARD", true); + + // Notifications d'événements + preferences.put("NOUVEL_EVENEMENT", true); + preferences.put("RAPPEL_EVENEMENT", true); + preferences.put("MODIFICATION_EVENEMENT", true); + preferences.put("ANNULATION_EVENEMENT", true); + + // Notifications de solidarité + preferences.put("NOUVELLE_DEMANDE_AIDE", true); + preferences.put("DEMANDE_AIDE_APPROUVEE", true); + preferences.put("DEMANDE_AIDE_REJETEE", true); + preferences.put("NOUVELLE_PROPOSITION_AIDE", true); + + // Notifications administratives + preferences.put("NOUVEAU_MEMBRE", false); + preferences.put("MODIFICATION_PROFIL", false); + preferences.put("RAPPORT_MENSUEL", true); + + // Notifications push + preferences.put("PUSH_MOBILE", true); + preferences.put("EMAIL", true); + preferences.put("SMS", false); + + return preferences; + } + + /** Obtient tous les utilisateurs qui acceptent un type de notification */ + public Map obtenirUtilisateursAcceptantNotification(String typeNotification) { + LOG.infof("Recherche des utilisateurs acceptant la notification %s", typeNotification); + + Map utilisateursAcceptant = new HashMap<>(); + + for (Map.Entry> entry : preferencesUtilisateurs.entrySet()) { + UUID utilisateurId = entry.getKey(); + Map preferences = entry.getValue(); + + if (preferences.getOrDefault(typeNotification, true)) { + utilisateursAcceptant.put(utilisateurId, true); + } + } + + return utilisateursAcceptant; + } + + /** Exporte les préférences d'un utilisateur */ + public Map exporterPreferences(UUID utilisateurId) { + LOG.infof("Export des préférences pour l'utilisateur %s", utilisateurId); + + Map export = new HashMap<>(); + export.put("utilisateurId", utilisateurId); + export.put("preferences", obtenirPreferences(utilisateurId)); + export.put("dateExport", java.time.LocalDateTime.now()); + + return export; + } + + /** Importe les préférences d'un utilisateur */ + @SuppressWarnings("unchecked") + public void importerPreferences(UUID utilisateurId, Map donnees) { + LOG.infof("Import des préférences pour l'utilisateur %s", utilisateurId); + + if (donnees.containsKey("preferences")) { + Map preferences = (Map) donnees.get("preferences"); + mettreAJourPreferences(utilisateurId, preferences); + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/PropositionAideService.java b/src/main/java/dev/lions/unionflow/server/service/PropositionAideService.java new file mode 100644 index 0000000..cb1ef43 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/PropositionAideService.java @@ -0,0 +1,442 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO; +import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service spécialisé pour la gestion des propositions d'aide + * + *

Ce service gère le cycle de vie des propositions d'aide : création, activation, matching, + * suivi des performances. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +public class PropositionAideService { + + private static final Logger LOG = Logger.getLogger(PropositionAideService.class); + + // Cache pour les propositions actives + private final Map cachePropositionsActives = new HashMap<>(); + private final Map> indexParType = new HashMap<>(); + + // === OPÉRATIONS CRUD === + + /** + * Crée une nouvelle proposition d'aide + * + * @param propositionDTO La proposition à créer + * @return La proposition créée avec ID généré + */ + @Transactional + public PropositionAideDTO creerProposition(@Valid PropositionAideDTO propositionDTO) { + LOG.infof("Création d'une nouvelle proposition d'aide: %s", propositionDTO.getTitre()); + + // Génération des identifiants + propositionDTO.setId(UUID.randomUUID().toString()); + propositionDTO.setNumeroReference(genererNumeroReference()); + + // Initialisation des dates + LocalDateTime maintenant = LocalDateTime.now(); + propositionDTO.setDateCreation(maintenant); + propositionDTO.setDateModification(maintenant); + + // Statut initial + if (propositionDTO.getStatut() == null) { + propositionDTO.setStatut(PropositionAideDTO.StatutProposition.ACTIVE); + } + + // Calcul de la date d'expiration si non définie + if (propositionDTO.getDateExpiration() == null) { + propositionDTO.setDateExpiration(maintenant.plusMonths(6)); // 6 mois par défaut + } + + // Initialisation des compteurs + propositionDTO.setNombreDemandesTraitees(0); + propositionDTO.setNombreBeneficiairesAides(0); + propositionDTO.setMontantTotalVerse(0.0); + propositionDTO.setNombreVues(0); + propositionDTO.setNombreCandidatures(0); + propositionDTO.setNombreEvaluations(0); + + // Calcul du score de pertinence initial + propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO)); + + // Ajout au cache et index + ajouterAuCache(propositionDTO); + ajouterAIndex(propositionDTO); + + LOG.infof("Proposition d'aide créée avec succès: %s", propositionDTO.getId()); + return propositionDTO; + } + + /** + * Met à jour une proposition d'aide existante + * + * @param propositionDTO La proposition à mettre à jour + * @return La proposition mise à jour + */ + @Transactional + public PropositionAideDTO mettreAJour(@Valid PropositionAideDTO propositionDTO) { + LOG.infof("Mise à jour de la proposition d'aide: %s", propositionDTO.getId()); + + // Mise à jour de la date de modification + propositionDTO.setDateModification(LocalDateTime.now()); + + // Recalcul du score de pertinence + propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO)); + + // Mise à jour du cache et index + ajouterAuCache(propositionDTO); + mettreAJourIndex(propositionDTO); + + LOG.infof("Proposition d'aide mise à jour avec succès: %s", propositionDTO.getId()); + return propositionDTO; + } + + /** + * Obtient une proposition d'aide par son ID + * + * @param id ID de la proposition + * @return La proposition trouvée + */ + public PropositionAideDTO obtenirParId(@NotBlank String id) { + LOG.debugf("Récupération de la proposition d'aide: %s", id); + + // Vérification du cache + PropositionAideDTO propositionCachee = cachePropositionsActives.get(id); + if (propositionCachee != null) { + // Incrémenter le nombre de vues + propositionCachee.setNombreVues(propositionCachee.getNombreVues() + 1); + return propositionCachee; + } + + // Simulation de récupération depuis la base de données + PropositionAideDTO proposition = simulerRecuperationBDD(id); + + if (proposition != null) { + ajouterAuCache(proposition); + ajouterAIndex(proposition); + } + + return proposition; + } + + /** + * Active ou désactive une proposition d'aide + * + * @param propositionId ID de la proposition + * @param activer true pour activer, false pour désactiver + * @return La proposition mise à jour + */ + @Transactional + public PropositionAideDTO changerStatutActivation( + @NotBlank String propositionId, boolean activer) { + LOG.infof( + "Changement de statut d'activation pour la proposition %s: %s", + propositionId, activer ? "ACTIVE" : "SUSPENDUE"); + + PropositionAideDTO proposition = obtenirParId(propositionId); + if (proposition == null) { + throw new IllegalArgumentException("Proposition non trouvée: " + propositionId); + } + + if (activer) { + // Vérifications avant activation + if (proposition.isExpiree()) { + throw new IllegalStateException("Impossible d'activer une proposition expirée"); + } + proposition.setStatut(PropositionAideDTO.StatutProposition.ACTIVE); + proposition.setEstDisponible(true); + } else { + proposition.setStatut(PropositionAideDTO.StatutProposition.SUSPENDUE); + proposition.setEstDisponible(false); + } + + proposition.setDateModification(LocalDateTime.now()); + + // Mise à jour du cache et index + ajouterAuCache(proposition); + mettreAJourIndex(proposition); + + return proposition; + } + + // === RECHERCHE ET MATCHING === + + /** + * Recherche des propositions compatibles avec une demande + * + * @param demande La demande d'aide + * @return Liste des propositions compatibles triées par score + */ + public List rechercherPropositionsCompatibles(DemandeAideDTO demande) { + LOG.debugf("Recherche de propositions compatibles pour la demande: %s", demande.getId()); + + // Recherche par type d'aide d'abord + List candidats = + indexParType.getOrDefault(demande.getTypeAide(), new ArrayList<>()); + + // Si pas de correspondance exacte, chercher dans la même catégorie + if (candidats.isEmpty()) { + candidats = + cachePropositionsActives.values().stream() + .filter( + p -> p.getTypeAide().getCategorie().equals(demande.getTypeAide().getCategorie())) + .collect(Collectors.toList()); + } + + // Filtrage et scoring + return candidats.stream() + .filter(PropositionAideDTO::isActiveEtDisponible) + .filter(p -> p.peutAccepterBeneficiaires()) + .map( + p -> { + double score = p.getScoreCompatibilite(demande); + // Stocker le score temporairement dans les données personnalisées + if (p.getDonneesPersonnalisees() == null) { + p.setDonneesPersonnalisees(new HashMap<>()); + } + p.getDonneesPersonnalisees().put("scoreCompatibilite", score); + return p; + }) + .filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreCompatibilite") >= 30.0) + .sorted( + (p1, p2) -> { + Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreCompatibilite"); + Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreCompatibilite"); + return Double.compare(score2, score1); // Ordre décroissant + }) + .limit(10) // Limiter à 10 meilleures propositions + .collect(Collectors.toList()); + } + + /** + * Recherche des propositions par critères + * + * @param filtres Map des critères de recherche + * @return Liste des propositions correspondantes + */ + public List rechercherAvecFiltres(Map filtres) { + LOG.debugf("Recherche de propositions avec filtres: %s", filtres); + + return cachePropositionsActives.values().stream() + .filter(proposition -> correspondAuxFiltres(proposition, filtres)) + .sorted(this::comparerParPertinence) + .collect(Collectors.toList()); + } + + /** + * Obtient les propositions actives pour un type d'aide + * + * @param typeAide Type d'aide recherché + * @return Liste des propositions actives + */ + public List obtenirPropositionsActives(TypeAide typeAide) { + LOG.debugf("Récupération des propositions actives pour le type: %s", typeAide); + + return indexParType.getOrDefault(typeAide, new ArrayList<>()).stream() + .filter(PropositionAideDTO::isActiveEtDisponible) + .sorted(this::comparerParPertinence) + .collect(Collectors.toList()); + } + + /** + * Obtient les meilleures propositions (top performers) + * + * @param limite Nombre maximum de propositions à retourner + * @return Liste des meilleures propositions + */ + public List obtenirMeilleuresPropositions(int limite) { + LOG.debugf("Récupération des %d meilleures propositions", limite); + + return cachePropositionsActives.values().stream() + .filter(PropositionAideDTO::isActiveEtDisponible) + .filter(p -> p.getNombreEvaluations() >= 3) // Au moins 3 évaluations + .filter(p -> p.getNoteMoyenne() != null && p.getNoteMoyenne() >= 4.0) + .sorted( + (p1, p2) -> { + // Tri par note moyenne puis par nombre d'aides réalisées + int compareNote = Double.compare(p2.getNoteMoyenne(), p1.getNoteMoyenne()); + if (compareNote != 0) return compareNote; + return Integer.compare( + p2.getNombreBeneficiairesAides(), p1.getNombreBeneficiairesAides()); + }) + .limit(limite) + .collect(Collectors.toList()); + } + + // === GESTION DES PERFORMANCES === + + /** + * Met à jour les statistiques d'une proposition après une aide fournie + * + * @param propositionId ID de la proposition + * @param montantVerse Montant versé (si applicable) + * @param nombreBeneficiaires Nombre de bénéficiaires aidés + * @return La proposition mise à jour + */ + @Transactional + public PropositionAideDTO mettreAJourStatistiques( + @NotBlank String propositionId, Double montantVerse, int nombreBeneficiaires) { + LOG.infof("Mise à jour des statistiques pour la proposition: %s", propositionId); + + PropositionAideDTO proposition = obtenirParId(propositionId); + if (proposition == null) { + throw new IllegalArgumentException("Proposition non trouvée: " + propositionId); + } + + // Mise à jour des compteurs + proposition.setNombreDemandesTraitees(proposition.getNombreDemandesTraitees() + 1); + proposition.setNombreBeneficiairesAides( + proposition.getNombreBeneficiairesAides() + nombreBeneficiaires); + + if (montantVerse != null) { + proposition.setMontantTotalVerse(proposition.getMontantTotalVerse() + montantVerse); + } + + // Recalcul du score de pertinence + proposition.setScorePertinence(calculerScorePertinence(proposition)); + + // Vérification si la capacité maximale est atteinte + if (proposition.getNombreBeneficiairesAides() >= proposition.getNombreMaxBeneficiaires()) { + proposition.setEstDisponible(false); + proposition.setStatut(PropositionAideDTO.StatutProposition.TERMINEE); + } + + proposition.setDateModification(LocalDateTime.now()); + + // Mise à jour du cache + ajouterAuCache(proposition); + + return proposition; + } + + // === MÉTHODES UTILITAIRES PRIVÉES === + + /** Génère un numéro de référence unique pour les propositions */ + private String genererNumeroReference() { + int annee = LocalDateTime.now().getYear(); + int numero = (int) (Math.random() * 999999) + 1; + return String.format("PA-%04d-%06d", annee, numero); + } + + /** Calcule le score de pertinence d'une proposition */ + private double calculerScorePertinence(PropositionAideDTO proposition) { + double score = 50.0; // Score de base + + // Bonus pour l'expérience (nombre d'aides réalisées) + score += Math.min(20.0, proposition.getNombreBeneficiairesAides() * 2.0); + + // Bonus pour la note moyenne + if (proposition.getNoteMoyenne() != null) { + score += (proposition.getNoteMoyenne() - 3.0) * 10.0; // +10 par point au-dessus de 3 + } + + // Bonus pour la récence + long joursDepuisCreation = + java.time.Duration.between(proposition.getDateCreation(), LocalDateTime.now()).toDays(); + if (joursDepuisCreation <= 30) { + score += 10.0; + } else if (joursDepuisCreation <= 90) { + score += 5.0; + } + + // Bonus pour la disponibilité + if (proposition.isActiveEtDisponible()) { + score += 15.0; + } + + // Malus pour l'inactivité + if (proposition.getNombreVues() == 0) { + score -= 10.0; + } + + return Math.max(0.0, Math.min(100.0, score)); + } + + /** Vérifie si une proposition correspond aux filtres */ + private boolean correspondAuxFiltres( + PropositionAideDTO proposition, Map filtres) { + for (Map.Entry filtre : filtres.entrySet()) { + String cle = filtre.getKey(); + Object valeur = filtre.getValue(); + + switch (cle) { + case "typeAide" -> { + if (!proposition.getTypeAide().equals(valeur)) return false; + } + case "statut" -> { + if (!proposition.getStatut().equals(valeur)) return false; + } + case "proposantId" -> { + if (!proposition.getProposantId().equals(valeur)) return false; + } + case "organisationId" -> { + if (!proposition.getOrganisationId().equals(valeur)) return false; + } + case "estDisponible" -> { + if (!proposition.getEstDisponible().equals(valeur)) return false; + } + case "montantMaximum" -> { + if (proposition.getMontantMaximum() == null + || proposition.getMontantMaximum().compareTo(BigDecimal.valueOf((Double) valeur)) < 0) return false; + } + } + } + return true; + } + + /** Compare deux propositions par pertinence */ + private int comparerParPertinence(PropositionAideDTO p1, PropositionAideDTO p2) { + // D'abord par score de pertinence (plus haut = meilleur) + int compareScore = Double.compare(p2.getScorePertinence(), p1.getScorePertinence()); + if (compareScore != 0) return compareScore; + + // Puis par date de création (plus récent = meilleur) + return p2.getDateCreation().compareTo(p1.getDateCreation()); + } + + // === GESTION DU CACHE ET INDEX === + + private void ajouterAuCache(PropositionAideDTO proposition) { + cachePropositionsActives.put(proposition.getId(), proposition); + } + + private void ajouterAIndex(PropositionAideDTO proposition) { + indexParType + .computeIfAbsent(proposition.getTypeAide(), k -> new ArrayList<>()) + .add(proposition); + } + + private void mettreAJourIndex(PropositionAideDTO proposition) { + // Supprimer de tous les index + indexParType + .values() + .forEach(liste -> liste.removeIf(p -> p.getId().equals(proposition.getId()))); + + // Ré-ajouter si la proposition est active + if (proposition.isActiveEtDisponible()) { + ajouterAIndex(proposition); + } + } + + // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) === + + private PropositionAideDTO simulerRecuperationBDD(String id) { + // Simulation - dans une vraie implémentation, ceci ferait appel au repository + return null; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/RoleService.java b/src/main/java/dev/lions/unionflow/server/service/RoleService.java new file mode 100644 index 0000000..35c57a8 --- /dev/null +++ b/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 + .findRoleById(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.findRoleById(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 + .findRoleById(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/src/main/java/dev/lions/unionflow/server/service/TrendAnalysisService.java b/src/main/java/dev/lions/unionflow/server/service/TrendAnalysisService.java new file mode 100644 index 0000000..a2fbfe9 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/TrendAnalysisService.java @@ -0,0 +1,412 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.analytics.KPITrendDTO; +import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse; +import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** + * Service d'analyse des tendances et prédictions pour les KPI + * + *

Ce service calcule les tendances, effectue des analyses statistiques et génère des prédictions + * basées sur l'historique des données. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + */ +@ApplicationScoped +@Slf4j +public class TrendAnalysisService { + + @Inject AnalyticsService analyticsService; + + @Inject KPICalculatorService kpiCalculatorService; + + /** + * Calcule la tendance d'un KPI sur une période donnée + * + * @param typeMetrique Le type de métrique à analyser + * @param periodeAnalyse La période d'analyse + * @param organisationId L'ID de l'organisation (optionnel) + * @return Les données de tendance du KPI + */ + public KPITrendDTO calculerTendance( + TypeMetrique typeMetrique, PeriodeAnalyse periodeAnalyse, UUID organisationId) { + log.info( + "Calcul de la tendance pour {} sur la période {} et l'organisation {}", + typeMetrique, + periodeAnalyse, + organisationId); + + LocalDateTime dateDebut = periodeAnalyse.getDateDebut(); + LocalDateTime dateFin = periodeAnalyse.getDateFin(); + + // Génération des points de données historiques + List pointsDonnees = + genererPointsDonnees(typeMetrique, dateDebut, dateFin, organisationId); + + // Calculs statistiques + StatistiquesDTO stats = calculerStatistiques(pointsDonnees); + + // Analyse de tendance (régression linéaire simple) + TendanceDTO tendance = calculerTendanceLineaire(pointsDonnees); + + // Prédiction pour la prochaine période + BigDecimal prediction = calculerPrediction(pointsDonnees, tendance); + + // Détection d'anomalies + detecterAnomalies(pointsDonnees, stats); + + return KPITrendDTO.builder() + .typeMetrique(typeMetrique) + .periodeAnalyse(periodeAnalyse) + .organisationId(organisationId) + .nomOrganisation(obtenirNomOrganisation(organisationId)) + .dateDebut(dateDebut) + .dateFin(dateFin) + .pointsDonnees(pointsDonnees) + .valeurActuelle(stats.valeurActuelle) + .valeurMinimale(stats.valeurMinimale) + .valeurMaximale(stats.valeurMaximale) + .valeurMoyenne(stats.valeurMoyenne) + .ecartType(stats.ecartType) + .coefficientVariation(stats.coefficientVariation) + .tendanceGenerale(tendance.pente) + .coefficientCorrelation(tendance.coefficientCorrelation) + .pourcentageEvolutionGlobale(calculerEvolutionGlobale(pointsDonnees)) + .predictionProchainePeriode(prediction) + .margeErreurPrediction(calculerMargeErreur(tendance)) + .seuilAlerteBas(calculerSeuilAlerteBas(stats)) + .seuilAlerteHaut(calculerSeuilAlerteHaut(stats)) + .alerteActive(verifierAlertes(stats.valeurActuelle, stats)) + .intervalleRegroupement(periodeAnalyse.getIntervalleRegroupement()) + .formatDate(periodeAnalyse.getFormatDate()) + .dateDerniereMiseAJour(LocalDateTime.now()) + .frequenceMiseAJourMinutes(determinerFrequenceMiseAJour(periodeAnalyse)) + .build(); + } + + /** Génère les points de données historiques pour la période */ + private List genererPointsDonnees( + TypeMetrique typeMetrique, + LocalDateTime dateDebut, + LocalDateTime dateFin, + UUID organisationId) { + List points = new ArrayList<>(); + + // Déterminer l'intervalle entre les points + ChronoUnit unite = determinerUniteIntervalle(dateDebut, dateFin); + long intervalleValeur = determinerValeurIntervalle(dateDebut, dateFin, unite); + + LocalDateTime dateCourante = dateDebut; + int index = 0; + + while (!dateCourante.isAfter(dateFin)) { + LocalDateTime dateFinIntervalle = dateCourante.plus(intervalleValeur, unite); + if (dateFinIntervalle.isAfter(dateFin)) { + dateFinIntervalle = dateFin; + } + + // Calcul de la valeur pour cet intervalle + BigDecimal valeur = + calculerValeurPourIntervalle( + typeMetrique, dateCourante, dateFinIntervalle, organisationId); + + KPITrendDTO.PointDonneeDTO point = + KPITrendDTO.PointDonneeDTO.builder() + .date(dateCourante) + .valeur(valeur) + .libelle(formaterLibellePoint(dateCourante, unite)) + .anomalie(false) // Sera déterminé plus tard + .prediction(false) + .build(); + + points.add(point); + dateCourante = dateCourante.plus(intervalleValeur, unite); + index++; + } + + log.info("Généré {} points de données pour la tendance", points.size()); + return points; + } + + /** Calcule les statistiques descriptives des points de données */ + private StatistiquesDTO calculerStatistiques(List points) { + if (points.isEmpty()) { + return new StatistiquesDTO(); + } + + List valeurs = points.stream().map(KPITrendDTO.PointDonneeDTO::getValeur).toList(); + + BigDecimal valeurActuelle = points.get(points.size() - 1).getValeur(); + BigDecimal valeurMinimale = valeurs.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + BigDecimal valeurMaximale = valeurs.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + + // Calcul de la moyenne + BigDecimal somme = valeurs.stream().reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal moyenne = somme.divide(new BigDecimal(valeurs.size()), 4, RoundingMode.HALF_UP); + + // Calcul de l'écart-type + BigDecimal sommeDifferencesCarrees = + valeurs.stream() + .map(v -> v.subtract(moyenne).pow(2)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + BigDecimal variance = + sommeDifferencesCarrees.divide(new BigDecimal(valeurs.size()), 4, RoundingMode.HALF_UP); + BigDecimal ecartType = + new BigDecimal(Math.sqrt(variance.doubleValue())).setScale(4, RoundingMode.HALF_UP); + + // Coefficient de variation + BigDecimal coefficientVariation = + moyenne.compareTo(BigDecimal.ZERO) != 0 + ? ecartType.divide(moyenne, 4, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + + return new StatistiquesDTO( + valeurActuelle, valeurMinimale, valeurMaximale, moyenne, ecartType, coefficientVariation); + } + + /** Calcule la tendance linéaire (régression linéaire simple) */ + private TendanceDTO calculerTendanceLineaire(List points) { + if (points.size() < 2) { + return new TendanceDTO(BigDecimal.ZERO, BigDecimal.ZERO); + } + + int n = points.size(); + BigDecimal sommeX = BigDecimal.ZERO; + BigDecimal sommeY = BigDecimal.ZERO; + BigDecimal sommeXY = BigDecimal.ZERO; + BigDecimal sommeX2 = BigDecimal.ZERO; + BigDecimal sommeY2 = BigDecimal.ZERO; + + for (int i = 0; i < n; i++) { + BigDecimal x = new BigDecimal(i); // Index comme variable X + BigDecimal y = points.get(i).getValeur(); // Valeur comme variable Y + + sommeX = sommeX.add(x); + sommeY = sommeY.add(y); + sommeXY = sommeXY.add(x.multiply(y)); + sommeX2 = sommeX2.add(x.multiply(x)); + sommeY2 = sommeY2.add(y.multiply(y)); + } + + // Calcul de la pente (coefficient directeur) + BigDecimal nBD = new BigDecimal(n); + BigDecimal numerateur = nBD.multiply(sommeXY).subtract(sommeX.multiply(sommeY)); + BigDecimal denominateur = nBD.multiply(sommeX2).subtract(sommeX.multiply(sommeX)); + + BigDecimal pente = + denominateur.compareTo(BigDecimal.ZERO) != 0 + ? numerateur.divide(denominateur, 6, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + + // Calcul du coefficient de corrélation R² + BigDecimal numerateurR = numerateur; + BigDecimal denominateurR1 = nBD.multiply(sommeX2).subtract(sommeX.multiply(sommeX)); + BigDecimal denominateurR2 = nBD.multiply(sommeY2).subtract(sommeY.multiply(sommeY)); + + BigDecimal coefficientCorrelation = BigDecimal.ZERO; + if (denominateurR1.compareTo(BigDecimal.ZERO) != 0 + && denominateurR2.compareTo(BigDecimal.ZERO) != 0) { + BigDecimal denominateurR = + new BigDecimal(Math.sqrt(denominateurR1.multiply(denominateurR2).doubleValue())); + + if (denominateurR.compareTo(BigDecimal.ZERO) != 0) { + BigDecimal r = numerateurR.divide(denominateurR, 6, RoundingMode.HALF_UP); + coefficientCorrelation = r.multiply(r); // R² + } + } + + return new TendanceDTO(pente, coefficientCorrelation); + } + + /** Calcule une prédiction pour la prochaine période */ + private BigDecimal calculerPrediction( + List points, TendanceDTO tendance) { + if (points.isEmpty()) return BigDecimal.ZERO; + + BigDecimal derniereValeur = points.get(points.size() - 1).getValeur(); + BigDecimal prediction = derniereValeur.add(tendance.pente); + + // S'assurer que la prédiction est positive + return prediction.max(BigDecimal.ZERO); + } + + /** Détecte les anomalies dans les points de données */ + private void detecterAnomalies(List points, StatistiquesDTO stats) { + BigDecimal seuilAnomalie = stats.ecartType.multiply(new BigDecimal("2")); // 2 écarts-types + + for (KPITrendDTO.PointDonneeDTO point : points) { + BigDecimal ecartMoyenne = point.getValeur().subtract(stats.valeurMoyenne).abs(); + if (ecartMoyenne.compareTo(seuilAnomalie) > 0) { + point.setAnomalie(true); + } + } + } + + // === MÉTHODES UTILITAIRES === + + private ChronoUnit determinerUniteIntervalle(LocalDateTime dateDebut, LocalDateTime dateFin) { + long joursTotal = ChronoUnit.DAYS.between(dateDebut, dateFin); + + if (joursTotal <= 7) return ChronoUnit.DAYS; + if (joursTotal <= 90) return ChronoUnit.DAYS; + if (joursTotal <= 365) return ChronoUnit.WEEKS; + return ChronoUnit.MONTHS; + } + + private long determinerValeurIntervalle( + LocalDateTime dateDebut, LocalDateTime dateFin, ChronoUnit unite) { + long dureeTotal = unite.between(dateDebut, dateFin); + + // Viser environ 10-20 points de données + if (dureeTotal <= 20) return 1; + if (dureeTotal <= 40) return 2; + if (dureeTotal <= 100) return 5; + return dureeTotal / 15; // Environ 15 points + } + + private BigDecimal calculerValeurPourIntervalle( + TypeMetrique typeMetrique, + LocalDateTime dateDebut, + LocalDateTime dateFin, + UUID organisationId) { + // Utiliser le service KPI pour calculer la valeur + return switch (typeMetrique) { + case NOMBRE_MEMBRES_ACTIFS -> { + // Calcul direct via le service KPI + var kpis = kpiCalculatorService.calculerTousLesKPI(organisationId, dateDebut, dateFin); + yield kpis.getOrDefault(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, BigDecimal.ZERO); + } + case TOTAL_COTISATIONS_COLLECTEES -> { + var kpis = kpiCalculatorService.calculerTousLesKPI(organisationId, dateDebut, dateFin); + yield kpis.getOrDefault(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, BigDecimal.ZERO); + } + case NOMBRE_EVENEMENTS_ORGANISES -> { + var kpis = kpiCalculatorService.calculerTousLesKPI(organisationId, dateDebut, dateFin); + yield kpis.getOrDefault(TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES, BigDecimal.ZERO); + } + case NOMBRE_DEMANDES_AIDE -> { + var kpis = kpiCalculatorService.calculerTousLesKPI(organisationId, dateDebut, dateFin); + yield kpis.getOrDefault(TypeMetrique.NOMBRE_DEMANDES_AIDE, BigDecimal.ZERO); + } + default -> BigDecimal.ZERO; + }; + } + + private String formaterLibellePoint(LocalDateTime date, ChronoUnit unite) { + return switch (unite) { + case DAYS -> date.toLocalDate().toString(); + case WEEKS -> "S" + date.get(java.time.temporal.WeekFields.ISO.weekOfYear()); + case MONTHS -> date.getMonth().toString() + " " + date.getYear(); + default -> date.toString(); + }; + } + + private BigDecimal calculerEvolutionGlobale(List points) { + if (points.size() < 2) return BigDecimal.ZERO; + + BigDecimal premiereValeur = points.get(0).getValeur(); + BigDecimal derniereValeur = points.get(points.size() - 1).getValeur(); + + if (premiereValeur.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; + + return derniereValeur + .subtract(premiereValeur) + .divide(premiereValeur, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")); + } + + private BigDecimal calculerMargeErreur(TendanceDTO tendance) { + // Marge d'erreur basée sur le coefficient de corrélation + BigDecimal precision = tendance.coefficientCorrelation; + BigDecimal margeErreur = BigDecimal.ONE.subtract(precision).multiply(new BigDecimal("100")); + return margeErreur.min(new BigDecimal("50")); // Plafonnée à 50% + } + + private BigDecimal calculerSeuilAlerteBas(StatistiquesDTO stats) { + return stats.valeurMoyenne.subtract(stats.ecartType.multiply(new BigDecimal("1.5"))); + } + + private BigDecimal calculerSeuilAlerteHaut(StatistiquesDTO stats) { + return stats.valeurMoyenne.add(stats.ecartType.multiply(new BigDecimal("1.5"))); + } + + private Boolean verifierAlertes(BigDecimal valeurActuelle, StatistiquesDTO stats) { + BigDecimal seuilBas = calculerSeuilAlerteBas(stats); + BigDecimal seuilHaut = calculerSeuilAlerteHaut(stats); + + return valeurActuelle.compareTo(seuilBas) < 0 || valeurActuelle.compareTo(seuilHaut) > 0; + } + + private Integer determinerFrequenceMiseAJour(PeriodeAnalyse periode) { + return switch (periode) { + case AUJOURD_HUI, HIER -> 15; // 15 minutes + case CETTE_SEMAINE, SEMAINE_DERNIERE -> 60; // 1 heure + case CE_MOIS, MOIS_DERNIER -> 240; // 4 heures + default -> 1440; // 24 heures + }; + } + + private String obtenirNomOrganisation(UUID organisationId) { + // À implémenter avec le repository + return null; + } + + // === CLASSES INTERNES === + + private static class StatistiquesDTO { + final BigDecimal valeurActuelle; + final BigDecimal valeurMinimale; + final BigDecimal valeurMaximale; + final BigDecimal valeurMoyenne; + final BigDecimal ecartType; + final BigDecimal coefficientVariation; + + StatistiquesDTO() { + this( + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO, + BigDecimal.ZERO); + } + + StatistiquesDTO( + BigDecimal valeurActuelle, + BigDecimal valeurMinimale, + BigDecimal valeurMaximale, + BigDecimal valeurMoyenne, + BigDecimal ecartType, + BigDecimal coefficientVariation) { + this.valeurActuelle = valeurActuelle; + this.valeurMinimale = valeurMinimale; + this.valeurMaximale = valeurMaximale; + this.valeurMoyenne = valeurMoyenne; + this.ecartType = ecartType; + this.coefficientVariation = coefficientVariation; + } + } + + private static class TendanceDTO { + final BigDecimal pente; + final BigDecimal coefficientCorrelation; + + TendanceDTO(BigDecimal pente, BigDecimal coefficientCorrelation) { + this.pente = pente; + this.coefficientCorrelation = coefficientCorrelation; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/server/service/TypeOrganisationService.java b/src/main/java/dev/lions/unionflow/server/service/TypeOrganisationService.java new file mode 100644 index 0000000..3e3518a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/TypeOrganisationService.java @@ -0,0 +1,146 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.organisation.TypeOrganisationDTO; +import dev.lions.unionflow.server.entity.TypeOrganisationEntity; +import dev.lions.unionflow.server.repository.TypeOrganisationRepository; +import dev.lions.unionflow.server.service.KeycloakService; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + + /** + * Service de gestion du catalogue des types d'organisation. + * + *

Synchronise les types persistés avec l'enum {@link TypeOrganisation} pour les valeurs + * par défaut, puis permet un CRUD entièrement dynamique (les nouveaux codes ne sont plus + * limités aux valeurs de l'enum). + */ +@ApplicationScoped +public class TypeOrganisationService { + + private static final Logger LOG = Logger.getLogger(TypeOrganisationService.class); + + @Inject TypeOrganisationRepository repository; + @Inject KeycloakService keycloakService; + + // Plus d'initialisation automatique : le catalogue des types est désormais entièrement + // géré en mode CRUD via l'UI d'administration. Aucune donnée fictive n'est injectée + // au démarrage ; si nécessaire, utilisez des scripts de migration (Flyway) ou l'UI. + + /** Retourne la liste de tous les types (optionnellement seulement actifs). */ + public List listAll(boolean onlyActifs) { + List entities = + onlyActifs ? repository.listActifsOrdennes() : repository.listAll(); + return entities.stream().map(this::toDTO).collect(Collectors.toList()); + } + + /** Crée un nouveau type. Le code doit être non vide et unique. */ + @Transactional + public TypeOrganisationDTO create(TypeOrganisationDTO dto) { + validateCode(dto.getCode()); + + // Si un type existe déjà pour ce code, on retourne simplement l'existant + // (comportement idempotent côté API) plutôt que de remonter une 400. + // Le CRUD complet reste possible via l'écran d'édition. + var existingOpt = repository.findByCode(dto.getCode()); + if (existingOpt.isPresent()) { + LOG.infof( + "Type d'organisation déjà existant pour le code %s, retour de l'entrée existante.", + dto.getCode()); + return toDTO(existingOpt.get()); + } + + TypeOrganisationEntity entity = new TypeOrganisationEntity(); + // métadonnées de création + entity.setCreePar(keycloakService.getCurrentUserEmail()); + applyToEntity(dto, entity); + repository.persist(entity); + return toDTO(entity); + } + + /** Met à jour un type existant. L'ID est utilisé comme identifiant principal. */ + @Transactional + public TypeOrganisationDTO update(UUID id, TypeOrganisationDTO dto) { + TypeOrganisationEntity entity = + repository + .findByIdOptional(id) + .orElseThrow(() -> new IllegalArgumentException("Type d'organisation introuvable")); + + if (dto.getCode() != null && !dto.getCode().equalsIgnoreCase(entity.getCode())) { + validateCode(dto.getCode()); + repository + .findByCode(dto.getCode()) + .ifPresent( + existing -> { + if (!existing.getId().equals(id)) { + throw new IllegalArgumentException( + "Un autre type d'organisation utilise déjà le code: " + dto.getCode()); + } + }); + entity.setCode(dto.getCode()); + } + + // métadonnées de modification + entity.setModifiePar(keycloakService.getCurrentUserEmail()); + applyToEntity(dto, entity); + repository.update(entity); + return toDTO(entity); + } + + /** Désactive logiquement un type. */ + @Transactional + public void disable(UUID id) { + TypeOrganisationEntity entity = + repository + .findByIdOptional(id) + .orElseThrow(() -> new IllegalArgumentException("Type d'organisation introuvable")); + entity.setActif(false); + repository.update(entity); + } + + private void validateCode(String code) { + if (code == null || code.trim().isEmpty()) { + throw new IllegalArgumentException("Le code du type d'organisation est obligatoire"); + } + // Plus aucune contrainte de format technique côté backend pour éviter les 400 inutiles. + // Le code est simplement normalisé en majuscules dans applyToEntity, ce qui suffit + // pour garantir la cohérence métier et la clé fonctionnelle. + } + + private TypeOrganisationDTO toDTO(TypeOrganisationEntity entity) { + TypeOrganisationDTO dto = new TypeOrganisationDTO(); + dto.setId(entity.getId()); + dto.setDateCreation(entity.getDateCreation()); + dto.setDateModification(entity.getDateModification()); + dto.setActif(entity.getActif()); + dto.setVersion(entity.getVersion()); + + dto.setCode(entity.getCode()); + dto.setLibelle(entity.getLibelle()); + dto.setDescription(entity.getDescription()); + dto.setOrdreAffichage(entity.getOrdreAffichage()); + return dto; + } + + private void applyToEntity(TypeOrganisationDTO dto, TypeOrganisationEntity entity) { + if (dto.getCode() != null) { + entity.setCode(dto.getCode().toUpperCase()); + } + if (dto.getLibelle() != null) { + entity.setLibelle(dto.getLibelle()); + } + entity.setDescription(dto.getDescription()); + entity.setOrdreAffichage(dto.getOrdreAffichage()); + if (dto.getActif() != null) { + entity.setActif(dto.getActif()); + } + } +} + + diff --git a/src/main/java/dev/lions/unionflow/server/service/WaveService.java b/src/main/java/dev/lions/unionflow/server/service/WaveService.java new file mode 100644 index 0000000..d1db3da --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/WaveService.java @@ -0,0 +1,393 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO; +import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO; +import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave; +import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave; +import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave; +import dev.lions.unionflow.server.entity.CompteWave; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.TransactionWave; +import dev.lions.unionflow.server.repository.CompteWaveRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.TransactionWaveRepository; +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.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; + +/** + * Service métier pour l'intégration Wave Mobile Money + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@ApplicationScoped +public class WaveService { + + private static final Logger LOG = Logger.getLogger(WaveService.class); + + @Inject CompteWaveRepository compteWaveRepository; + + @Inject TransactionWaveRepository transactionWaveRepository; + + @Inject OrganisationRepository organisationRepository; + + @Inject MembreRepository membreRepository; + + @Inject KeycloakService keycloakService; + + /** + * Crée un nouveau compte Wave + * + * @param compteWaveDTO DTO du compte à créer + * @return DTO du compte créé + */ + @Transactional + public CompteWaveDTO creerCompteWave(CompteWaveDTO compteWaveDTO) { + LOG.infof("Création d'un nouveau compte Wave: %s", compteWaveDTO.getNumeroTelephone()); + + // Vérifier l'unicité du numéro de téléphone + if (compteWaveRepository.findByNumeroTelephone(compteWaveDTO.getNumeroTelephone()).isPresent()) { + throw new IllegalArgumentException( + "Un compte Wave existe déjà pour ce numéro: " + compteWaveDTO.getNumeroTelephone()); + } + + CompteWave compteWave = convertToEntity(compteWaveDTO); + compteWave.setCreePar(keycloakService.getCurrentUserEmail()); + + compteWaveRepository.persist(compteWave); + LOG.infof("Compte Wave créé avec succès: ID=%s, Téléphone=%s", compteWave.getId(), compteWave.getNumeroTelephone()); + + return convertToDTO(compteWave); + } + + /** + * Met à jour un compte Wave + * + * @param id ID du compte + * @param compteWaveDTO DTO avec les modifications + * @return DTO du compte mis à jour + */ + @Transactional + public CompteWaveDTO mettreAJourCompteWave(UUID id, CompteWaveDTO compteWaveDTO) { + LOG.infof("Mise à jour du compte Wave ID: %s", id); + + CompteWave compteWave = + compteWaveRepository + .findCompteWaveById(id) + .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id)); + + updateFromDTO(compteWave, compteWaveDTO); + compteWave.setModifiePar(keycloakService.getCurrentUserEmail()); + + compteWaveRepository.persist(compteWave); + LOG.infof("Compte Wave mis à jour avec succès: ID=%s", id); + + return convertToDTO(compteWave); + } + + /** + * Vérifie un compte Wave + * + * @param id ID du compte + * @return DTO du compte vérifié + */ + @Transactional + public CompteWaveDTO verifierCompteWave(UUID id) { + LOG.infof("Vérification du compte Wave ID: %s", id); + + CompteWave compteWave = + compteWaveRepository + .findCompteWaveById(id) + .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id)); + + compteWave.setStatutCompte(StatutCompteWave.VERIFIE); + compteWave.setDateDerniereVerification(LocalDateTime.now()); + compteWave.setModifiePar(keycloakService.getCurrentUserEmail()); + + compteWaveRepository.persist(compteWave); + LOG.infof("Compte Wave vérifié avec succès: ID=%s", id); + + return convertToDTO(compteWave); + } + + /** + * Trouve un compte Wave par son ID + * + * @param id ID du compte + * @return DTO du compte + */ + public CompteWaveDTO trouverCompteWaveParId(UUID id) { + return compteWaveRepository + .findCompteWaveById(id) + .map(this::convertToDTO) + .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id)); + } + + /** + * Trouve un compte Wave par numéro de téléphone + * + * @param numeroTelephone Numéro de téléphone + * @return DTO du compte ou null + */ + public CompteWaveDTO trouverCompteWaveParTelephone(String numeroTelephone) { + return compteWaveRepository + .findByNumeroTelephone(numeroTelephone) + .map(this::convertToDTO) + .orElse(null); + } + + /** + * Liste tous les comptes Wave d'une organisation + * + * @param organisationId ID de l'organisation + * @return Liste des comptes Wave + */ + public List listerComptesWaveParOrganisation(UUID organisationId) { + return compteWaveRepository.findByOrganisationId(organisationId).stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + /** + * Crée une nouvelle transaction Wave + * + * @param transactionWaveDTO DTO de la transaction à créer + * @return DTO de la transaction créée + */ + @Transactional + public TransactionWaveDTO creerTransactionWave(TransactionWaveDTO transactionWaveDTO) { + LOG.infof("Création d'une nouvelle transaction Wave: %s", transactionWaveDTO.getWaveTransactionId()); + + TransactionWave transactionWave = convertToEntity(transactionWaveDTO); + transactionWave.setCreePar(keycloakService.getCurrentUserEmail()); + + transactionWaveRepository.persist(transactionWave); + LOG.infof( + "Transaction Wave créée avec succès: ID=%s, WaveTransactionId=%s", + transactionWave.getId(), transactionWave.getWaveTransactionId()); + + return convertToDTO(transactionWave); + } + + /** + * Met à jour le statut d'une transaction Wave + * + * @param waveTransactionId Identifiant Wave de la transaction + * @param nouveauStatut Nouveau statut + * @return DTO de la transaction mise à jour + */ + @Transactional + public TransactionWaveDTO mettreAJourStatutTransaction( + String waveTransactionId, StatutTransactionWave nouveauStatut) { + LOG.infof("Mise à jour du statut de la transaction Wave: %s -> %s", waveTransactionId, nouveauStatut); + + TransactionWave transactionWave = + transactionWaveRepository + .findByWaveTransactionId(waveTransactionId) + .orElseThrow( + () -> + new NotFoundException( + "Transaction Wave non trouvée avec l'ID: " + waveTransactionId)); + + transactionWave.setStatutTransaction(nouveauStatut); + transactionWave.setDateDerniereTentative(LocalDateTime.now()); + transactionWave.setModifiePar(keycloakService.getCurrentUserEmail()); + + transactionWaveRepository.persist(transactionWave); + LOG.infof("Statut de la transaction Wave mis à jour: %s", waveTransactionId); + + return convertToDTO(transactionWave); + } + + /** + * Trouve une transaction Wave par son identifiant Wave + * + * @param waveTransactionId Identifiant Wave + * @return DTO de la transaction + */ + public TransactionWaveDTO trouverTransactionWaveParId(String waveTransactionId) { + return transactionWaveRepository + .findByWaveTransactionId(waveTransactionId) + .map(this::convertToDTO) + .orElseThrow( + () -> new NotFoundException("Transaction Wave non trouvée avec l'ID: " + waveTransactionId)); + } + + // ======================================== + // MÉTHODES PRIVÉES + // ======================================== + + /** Convertit une entité CompteWave en DTO */ + private CompteWaveDTO convertToDTO(CompteWave compteWave) { + if (compteWave == null) { + return null; + } + + CompteWaveDTO dto = new CompteWaveDTO(); + dto.setId(compteWave.getId()); + dto.setNumeroTelephone(compteWave.getNumeroTelephone()); + dto.setStatutCompte(compteWave.getStatutCompte()); + dto.setWaveAccountId(compteWave.getWaveAccountId()); + dto.setEnvironnement(compteWave.getEnvironnement()); + dto.setDateDerniereVerification(compteWave.getDateDerniereVerification()); + dto.setCommentaire(compteWave.getCommentaire()); + + if (compteWave.getOrganisation() != null) { + dto.setOrganisationId(compteWave.getOrganisation().getId()); + } + if (compteWave.getMembre() != null) { + dto.setMembreId(compteWave.getMembre().getId()); + } + + dto.setDateCreation(compteWave.getDateCreation()); + dto.setDateModification(compteWave.getDateModification()); + dto.setActif(compteWave.getActif()); + + return dto; + } + + /** Convertit un DTO en entité CompteWave */ + private CompteWave convertToEntity(CompteWaveDTO dto) { + if (dto == null) { + return null; + } + + CompteWave compteWave = new CompteWave(); + compteWave.setNumeroTelephone(dto.getNumeroTelephone()); + compteWave.setStatutCompte(dto.getStatutCompte() != null ? dto.getStatutCompte() : StatutCompteWave.NON_VERIFIE); + compteWave.setWaveAccountId(dto.getWaveAccountId()); + compteWave.setEnvironnement(dto.getEnvironnement() != null ? dto.getEnvironnement() : "SANDBOX"); + compteWave.setDateDerniereVerification(dto.getDateDerniereVerification()); + compteWave.setCommentaire(dto.getCommentaire()); + + // Relations + if (dto.getOrganisationId() != null) { + Organisation org = + organisationRepository + .findByIdOptional(dto.getOrganisationId()) + .orElseThrow( + () -> + new NotFoundException( + "Organisation non trouvée avec l'ID: " + dto.getOrganisationId())); + compteWave.setOrganisation(org); + } + + if (dto.getMembreId() != null) { + Membre membre = + membreRepository + .findByIdOptional(dto.getMembreId()) + .orElseThrow( + () -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId())); + compteWave.setMembre(membre); + } + + return compteWave; + } + + /** Met à jour une entité CompteWave à partir d'un DTO */ + private void updateFromDTO(CompteWave compteWave, CompteWaveDTO dto) { + if (dto.getStatutCompte() != null) { + compteWave.setStatutCompte(dto.getStatutCompte()); + } + if (dto.getWaveAccountId() != null) { + compteWave.setWaveAccountId(dto.getWaveAccountId()); + } + if (dto.getEnvironnement() != null) { + compteWave.setEnvironnement(dto.getEnvironnement()); + } + if (dto.getDateDerniereVerification() != null) { + compteWave.setDateDerniereVerification(dto.getDateDerniereVerification()); + } + if (dto.getCommentaire() != null) { + compteWave.setCommentaire(dto.getCommentaire()); + } + } + + /** Convertit une entité TransactionWave en DTO */ + private TransactionWaveDTO convertToDTO(TransactionWave transactionWave) { + if (transactionWave == null) { + return null; + } + + TransactionWaveDTO dto = new TransactionWaveDTO(); + dto.setId(transactionWave.getId()); + dto.setWaveTransactionId(transactionWave.getWaveTransactionId()); + dto.setWaveRequestId(transactionWave.getWaveRequestId()); + dto.setWaveReference(transactionWave.getWaveReference()); + dto.setTypeTransaction(transactionWave.getTypeTransaction()); + dto.setStatutTransaction(transactionWave.getStatutTransaction()); + dto.setMontant(transactionWave.getMontant()); + dto.setFrais(transactionWave.getFrais()); + dto.setMontantNet(transactionWave.getMontantNet()); + dto.setCodeDevise(transactionWave.getCodeDevise()); + dto.setTelephonePayeur(transactionWave.getTelephonePayeur()); + dto.setTelephoneBeneficiaire(transactionWave.getTelephoneBeneficiaire()); + dto.setMetadonnees(transactionWave.getMetadonnees()); + dto.setNombreTentatives(transactionWave.getNombreTentatives()); + dto.setDateDerniereTentative(transactionWave.getDateDerniereTentative()); + dto.setMessageErreur(transactionWave.getMessageErreur()); + + if (transactionWave.getCompteWave() != null) { + dto.setCompteWaveId(transactionWave.getCompteWave().getId()); + } + + dto.setDateCreation(transactionWave.getDateCreation()); + dto.setDateModification(transactionWave.getDateModification()); + dto.setActif(transactionWave.getActif()); + + return dto; + } + + /** Convertit un DTO en entité TransactionWave */ + private TransactionWave convertToEntity(TransactionWaveDTO dto) { + if (dto == null) { + return null; + } + + TransactionWave transactionWave = new TransactionWave(); + transactionWave.setWaveTransactionId(dto.getWaveTransactionId()); + transactionWave.setWaveRequestId(dto.getWaveRequestId()); + transactionWave.setWaveReference(dto.getWaveReference()); + transactionWave.setTypeTransaction(dto.getTypeTransaction()); + transactionWave.setStatutTransaction( + dto.getStatutTransaction() != null + ? dto.getStatutTransaction() + : StatutTransactionWave.INITIALISE); + transactionWave.setMontant(dto.getMontant()); + transactionWave.setFrais(dto.getFrais()); + transactionWave.setMontantNet(dto.getMontantNet()); + transactionWave.setCodeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF"); + transactionWave.setTelephonePayeur(dto.getTelephonePayeur()); + transactionWave.setTelephoneBeneficiaire(dto.getTelephoneBeneficiaire()); + transactionWave.setMetadonnees(dto.getMetadonnees()); + transactionWave.setNombreTentatives(dto.getNombreTentatives() != null ? dto.getNombreTentatives() : 0); + transactionWave.setDateDerniereTentative(dto.getDateDerniereTentative()); + transactionWave.setMessageErreur(dto.getMessageErreur()); + + // Relation CompteWave + if (dto.getCompteWaveId() != null) { + CompteWave compteWave = + compteWaveRepository + .findCompteWaveById(dto.getCompteWaveId()) + .orElseThrow( + () -> + new NotFoundException( + "Compte Wave non trouvé avec l'ID: " + dto.getCompteWaveId())); + transactionWave.setCompteWave(compteWave); + } + + return transactionWave; + } +} diff --git a/src/main/java/dev/lions/unionflow/server/util/IdConverter.java b/src/main/java/dev/lions/unionflow/server/util/IdConverter.java new file mode 100644 index 0000000..bb2b78b --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/util/IdConverter.java @@ -0,0 +1,150 @@ +package dev.lions.unionflow.server.util; + +import java.util.UUID; + +/** + * Utilitaire pour la conversion entre IDs Long (entités Panache) et UUID (DTOs) + * + *

DÉPRÉCIÉ: Cette classe est maintenant obsolète car toutes les entités + * utilisent désormais UUID directement. Elle est conservée uniquement pour compatibilité + * avec d'éventuels anciens scripts de migration de données. + * + *

Cette classe fournit des méthodes pour convertir de manière cohérente + * entre les identifiants Long utilisés par PanacheEntity et les UUID utilisés + * par les DTOs de l'API. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-16 + * @deprecated Depuis la migration UUID complète (2025-01-16). Utilisez directement UUID dans toutes les entités. + */ +@Deprecated(since = "2025-01-16", forRemoval = true) +public final class IdConverter { + + private IdConverter() { + // Classe utilitaire - constructeur privé + } + + /** + * Convertit un ID Long en UUID de manière déterministe + * + *

DÉPRÉCIÉ: Utilisez directement UUID dans vos entités. + * + *

Utilise un namespace UUID fixe pour garantir la cohérence et éviter les collisions. + * Le même Long produira toujours le même UUID. + * + * @param entityType Le type d'entité (ex: "membre", "organisation", "cotisation") + * @param id L'ID Long de l'entité + * @return L'UUID correspondant, ou null si id est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID longToUUID(String entityType, Long id) { + if (id == null) { + return null; + } + + // Utilisation d'un namespace UUID fixe par type d'entité pour garantir la cohérence + UUID namespace = getNamespaceForEntityType(entityType); + String name = entityType + "-" + id; + return UUID.nameUUIDFromBytes((namespace.toString() + name).getBytes()); + } + + /** + * Convertit un UUID en ID Long approximatif + * + *

DÉPRÉCIÉ: Utilisez directement UUID dans vos entités. + * + *

ATTENTION: Cette conversion n'est pas parfaitement réversible car UUID → Long + * perd de l'information. Cette méthode est principalement utilisée pour la recherche + * approximative. Pour une conversion réversible, il faudrait stocker le mapping dans la DB. + * + * @param uuid L'UUID à convertir + * @return Une approximation de l'ID Long, ou null si uuid est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static Long uuidToLong(UUID uuid) { + if (uuid == null) { + return null; + } + + // Extraction d'une approximation de Long depuis les bits de l'UUID + // Cette méthode n'est pas parfaitement réversible + long mostSignificantBits = uuid.getMostSignificantBits(); + long leastSignificantBits = uuid.getLeastSignificantBits(); + + // Combinaison des bits pour obtenir un Long + // Utilisation de XOR pour mélanger les bits + long combined = mostSignificantBits ^ leastSignificantBits; + + // Conversion en valeur positive + return Math.abs(combined); + } + + /** + * Obtient le namespace UUID pour un type d'entité donné + * + * @param entityType Le type d'entité + * @return Le namespace UUID correspondant + */ + private static UUID getNamespaceForEntityType(String entityType) { + return switch (entityType.toLowerCase()) { + case "membre" -> UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + case "organisation" -> UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + case "cotisation" -> UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); + case "evenement" -> UUID.fromString("6ba7b813-9dad-11d1-80b4-00c04fd430c8"); + case "demandeaide" -> UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"); + case "inscriptionevenement" -> UUID.fromString("6ba7b815-9dad-11d1-80b4-00c04fd430c8"); + default -> UUID.fromString("6ba7b816-9dad-11d1-80b4-00c04fd430c8"); // Namespace par défaut + }; + } + + /** + * Convertit un ID Long d'organisation en UUID pour le DTO + * + * @param organisationId L'ID Long de l'organisation + * @return L'UUID correspondant, ou null si organisationId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID organisationIdToUUID(Long organisationId) { + return longToUUID("organisation", organisationId); + } + + /** + * Convertit un ID Long de membre en UUID pour le DTO + * + * @param membreId L'ID Long du membre + * @return L'UUID correspondant, ou null si membreId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID membreIdToUUID(Long membreId) { + return longToUUID("membre", membreId); + } + + /** + * Convertit un ID Long de cotisation en UUID pour le DTO + * + * @param cotisationId L'ID Long de la cotisation + * @return L'UUID correspondant, ou null si cotisationId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID cotisationIdToUUID(Long cotisationId) { + return longToUUID("cotisation", cotisationId); + } + + /** + * Convertit un ID Long d'événement en UUID pour le DTO + * + * @param evenementId L'ID Long de l'événement + * @return L'UUID correspondant, ou null si evenementId est null + * @deprecated Utilisez directement UUID dans vos entités + */ + @Deprecated + public static UUID evenementIdToUUID(Long evenementId) { + return longToUUID("evenement", evenementId); + } +} diff --git a/src/main/resources/META-INF/beans.xml b/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000..1ba4e60 --- /dev/null +++ b/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + diff --git a/src/main/resources/application-minimal.properties b/src/main/resources/application-minimal.properties new file mode 100644 index 0000000..309e021 --- /dev/null +++ b/src/main/resources/application-minimal.properties @@ -0,0 +1,56 @@ +# Configuration UnionFlow Server - Mode Minimal +quarkus.application.name=unionflow-server-minimal +quarkus.application.version=1.0.0 + +# Configuration HTTP +quarkus.http.port=8080 +quarkus.http.host=0.0.0.0 + +# Configuration CORS +quarkus.http.cors=true +quarkus.http.cors.origins=* +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization + +# Configuration Base de données H2 (en mémoire) +quarkus.datasource.db-kind=h2 +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.datasource.jdbc.url=jdbc:h2:mem:unionflow_minimal;DB_CLOSE_DELAY=-1;MODE=PostgreSQL + +# Configuration Hibernate +quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity + +# Désactiver Flyway +quarkus.flyway.migrate-at-start=false + +# Désactiver Keycloak temporairement +quarkus.oidc.tenant-enabled=false + +# Chemins publics (tous publics en mode minimal) +quarkus.http.auth.permission.public.paths=/* +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI +quarkus.smallrye-openapi.info-title=UnionFlow Server API - Minimal +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union (mode minimal) +quarkus.smallrye-openapi.servers=http://localhost:8080 + +# Configuration Swagger UI +quarkus.swagger-ui.always-include=true +quarkus.swagger-ui.path=/swagger-ui + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=DEBUG +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..d1dc9c8 --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,77 @@ +# Configuration UnionFlow Server - PRODUCTION +# Ce fichier est utilisé avec le profil Quarkus "prod" + +# Configuration HTTP +quarkus.http.port=8085 +quarkus.http.host=0.0.0.0 + +# Configuration CORS - Production (strict) +quarkus.http.cors=true +quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev} +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization +quarkus.http.cors.allow-credentials=true + +# Configuration Base de données PostgreSQL - Production +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=${DB_USERNAME:unionflow} +quarkus.datasource.password=${DB_PASSWORD} +quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow} +quarkus.datasource.jdbc.min-size=5 +quarkus.datasource.jdbc.max-size=20 + +# Configuration Hibernate - Production (IMPORTANT: update, pas drop-and-create) +quarkus.hibernate-orm.database.generation=update +quarkus.hibernate-orm.log.sql=false +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity +quarkus.hibernate-orm.metrics.enabled=false + +# Configuration Flyway - Production (ACTIVÉ) +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0.0 + +# Configuration Keycloak OIDC - Production +quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow} +quarkus.oidc.client-id=unionflow-server +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc.tls.verification=required +quarkus.oidc.application-type=service + +# Configuration Keycloak Policy Enforcer +quarkus.keycloak.policy-enforcer.enable=false +quarkus.keycloak.policy-enforcer.lazy-load-paths=true +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE + +# Chemins publics (non protégés) +quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI - Production (Swagger désactivé ou protégé) +quarkus.smallrye-openapi.info-title=UnionFlow Server API +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak +quarkus.smallrye-openapi.servers=https://api.lions.dev/unionflow + +# Configuration Swagger UI - Production (DÉSACTIVÉ pour sécurité) +quarkus.swagger-ui.always-include=false + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging - Production +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=INFO +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO +quarkus.log.category."org.jboss.resteasy".level=WARN + +# Configuration Wave Money - Production +wave.api.key=${WAVE_API_KEY:} +wave.api.secret=${WAVE_API_SECRET:} +wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1} +wave.environment=${WAVE_ENVIRONMENT:production} +wave.webhook.secret=${WAVE_WEBHOOK_SECRET:} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties new file mode 100644 index 0000000..173d6db --- /dev/null +++ b/src/main/resources/application-test.properties @@ -0,0 +1,31 @@ +# Configuration UnionFlow Server - Profil Test +# Ce fichier est chargé automatiquement quand le profil 'test' est actif + +# Configuration Base de données H2 pour tests +quarkus.datasource.db-kind=h2 +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL + +# Configuration Hibernate pour tests +quarkus.hibernate-orm.database.generation=drop-and-create +# Désactiver complètement l'exécution des scripts SQL au démarrage +quarkus.hibernate-orm.sql-load-script-source=none +# Empêcher Hibernate d'exécuter les scripts SQL automatiquement +# Note: Ne pas définir quarkus.hibernate-orm.sql-load-script car une chaîne vide peut causer des problèmes + +# Configuration Flyway pour tests (désactivé complètement) +quarkus.flyway.migrate-at-start=false +quarkus.flyway.enabled=false +quarkus.flyway.baseline-on-migrate=false +# Note: Ne pas définir quarkus.flyway.locations car une chaîne vide cause une erreur de configuration + +# Configuration Keycloak pour tests (désactivé) +quarkus.oidc.tenant-enabled=false +quarkus.keycloak.policy-enforcer.enable=false + +# Configuration HTTP pour tests +quarkus.http.port=0 +quarkus.http.test-port=0 + + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..c81a866 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,103 @@ +# Configuration UnionFlow Server +quarkus.application.name=unionflow-server +quarkus.application.version=1.0.0 + +# Configuration HTTP +quarkus.http.port=8085 +quarkus.http.host=0.0.0.0 + +# Configuration CORS +quarkus.http.cors=true +quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:8086,https://unionflow.lions.dev,https://security.lions.dev} +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization + +# Configuration Base de données PostgreSQL (par défaut) +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=${DB_USERNAME:unionflow} +quarkus.datasource.password=${DB_PASSWORD} +quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow} +quarkus.datasource.jdbc.min-size=2 +quarkus.datasource.jdbc.max-size=10 + +# Configuration Base de données PostgreSQL pour développement +%dev.quarkus.datasource.username=skyfile +%dev.quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile} +%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow + +# Configuration Hibernate +quarkus.hibernate-orm.database.generation=update +quarkus.hibernate-orm.log.sql=false +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity +# Désactiver l'avertissement PanacheEntity (nous utilisons BaseEntity personnalisé) +quarkus.hibernate-orm.metrics.enabled=false + +# Configuration Hibernate pour développement +%dev.quarkus.hibernate-orm.database.generation=drop-and-create +%dev.quarkus.hibernate-orm.sql-load-script=import.sql +%dev.quarkus.hibernate-orm.log.sql=true + +# Configuration Flyway pour migrations +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0.0 + +# Configuration Flyway pour développement (désactivé) +%dev.quarkus.flyway.migrate-at-start=false + +# Configuration Keycloak OIDC (par défaut) +quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow +quarkus.oidc.client-id=unionflow-server +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc.tls.verification=none +quarkus.oidc.application-type=service + +# Configuration Keycloak pour développement +%dev.quarkus.oidc.tenant-enabled=false +%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow + +# Configuration Keycloak Policy Enforcer (temporairement désactivé) +quarkus.keycloak.policy-enforcer.enable=false +quarkus.keycloak.policy-enforcer.lazy-load-paths=true +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE + +# Chemins publics (non protégés) +quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico,/auth/callback,/auth/* +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI +quarkus.smallrye-openapi.info-title=UnionFlow Server API +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak +quarkus.smallrye-openapi.servers=http://localhost:8085 + +# Configuration Swagger UI +quarkus.swagger-ui.always-include=true +quarkus.swagger-ui.path=/swagger-ui + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=INFO +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO + +# Configuration logging pour développement +%dev.quarkus.log.category."dev.lions.unionflow".level=DEBUG +%dev.quarkus.log.category."org.hibernate.SQL".level=DEBUG + +# Configuration Jandex pour résoudre les warnings de réflexion +quarkus.index-dependency.unionflow-server-api.group-id=dev.lions.unionflow +quarkus.index-dependency.unionflow-server-api.artifact-id=unionflow-server-api + +# Configuration Wave Money +wave.api.key=${WAVE_API_KEY:} +wave.api.secret=${WAVE_API_SECRET:} +wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1} +wave.environment=${WAVE_ENVIRONMENT:sandbox} +wave.webhook.secret=${WAVE_WEBHOOK_SECRET:} diff --git a/src/main/resources/db/migration/V1.2__Create_Organisation_Table.sql b/src/main/resources/db/migration/V1.2__Create_Organisation_Table.sql new file mode 100644 index 0000000..7329794 --- /dev/null +++ b/src/main/resources/db/migration/V1.2__Create_Organisation_Table.sql @@ -0,0 +1,143 @@ +-- Migration V1.2: Création de la table organisations +-- Auteur: UnionFlow Team +-- Date: 2025-01-15 +-- Description: Création de la table organisations avec toutes les colonnes nécessaires + +-- Création de la table organisations +CREATE TABLE organisations ( + id BIGSERIAL PRIMARY KEY, + + -- Informations de base + nom VARCHAR(200) NOT NULL, + nom_court VARCHAR(50), + type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION', + statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + description TEXT, + date_fondation DATE, + numero_enregistrement VARCHAR(100) UNIQUE, + + -- Informations de contact + email VARCHAR(255) NOT NULL UNIQUE, + telephone VARCHAR(20), + telephone_secondaire VARCHAR(20), + email_secondaire VARCHAR(255), + + -- Adresse + adresse VARCHAR(500), + ville VARCHAR(100), + code_postal VARCHAR(20), + region VARCHAR(100), + pays VARCHAR(100), + + -- Coordonnées géographiques + latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90), + longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180), + + -- Web et réseaux sociaux + site_web VARCHAR(500), + logo VARCHAR(500), + reseaux_sociaux VARCHAR(1000), + + -- Hiérarchie + organisation_parente_id UUID, + niveau_hierarchique INTEGER NOT NULL DEFAULT 0, + + -- Statistiques + nombre_membres INTEGER NOT NULL DEFAULT 0, + nombre_administrateurs INTEGER NOT NULL DEFAULT 0, + + -- Finances + budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0), + devise VARCHAR(3) DEFAULT 'XOF', + cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE, + montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0), + + -- Informations complémentaires + objectifs TEXT, + activites_principales TEXT, + certifications VARCHAR(500), + partenaires VARCHAR(1000), + notes VARCHAR(1000), + + -- Paramètres + organisation_publique BOOLEAN NOT NULL DEFAULT TRUE, + accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE, + + -- Métadonnées + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(100), + modifie_par VARCHAR(100), + version BIGINT NOT NULL DEFAULT 0, + + -- Contraintes + CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')), + CONSTRAINT chk_organisation_type CHECK (type_organisation IN ( + 'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE', + 'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE' + )), + CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')), + CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10), + CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0), + CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0) +); + +-- Création des index pour optimiser les performances +CREATE INDEX idx_organisation_nom ON organisations(nom); +CREATE INDEX idx_organisation_email ON organisations(email); +CREATE INDEX idx_organisation_statut ON organisations(statut); +CREATE INDEX idx_organisation_type ON organisations(type_organisation); +CREATE INDEX idx_organisation_ville ON organisations(ville); +CREATE INDEX idx_organisation_pays ON organisations(pays); +CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id); +CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement); +CREATE INDEX idx_organisation_actif ON organisations(actif); +CREATE INDEX idx_organisation_date_creation ON organisations(date_creation); +CREATE INDEX idx_organisation_publique ON organisations(organisation_publique); +CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres); + +-- Index composites pour les recherches fréquentes +CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif); +CREATE INDEX idx_organisation_type_ville ON organisations(type_organisation, ville); +CREATE INDEX idx_organisation_pays_region ON organisations(pays, region); +CREATE INDEX idx_organisation_publique_actif ON organisations(organisation_publique, actif); + +-- Index pour les recherches textuelles +CREATE INDEX idx_organisation_nom_lower ON organisations(LOWER(nom)); +CREATE INDEX idx_organisation_nom_court_lower ON organisations(LOWER(nom_court)); +CREATE INDEX idx_organisation_ville_lower ON organisations(LOWER(ville)); + +-- Ajout de la colonne organisation_id à la table membres (si elle n'existe pas déjà) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'membres' AND column_name = 'organisation_id' + ) THEN + ALTER TABLE membres ADD COLUMN organisation_id BIGINT; + ALTER TABLE membres ADD CONSTRAINT fk_membre_organisation + FOREIGN KEY (organisation_id) REFERENCES organisations(id); + CREATE INDEX idx_membre_organisation ON membres(organisation_id); + END IF; +END $$; + +-- IMPORTANT: Aucune donnée fictive n'est insérée dans ce script de migration. +-- Les données doivent être insérées manuellement via l'interface d'administration +-- ou via des scripts de migration séparés si nécessaire pour la production. + +-- Mise à jour des statistiques de la base de données +ANALYZE organisations; + +-- Commentaires sur la table et les colonnes principales +COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.)'; +COMMENT ON COLUMN organisations.nom IS 'Nom officiel de l''organisation'; +COMMENT ON COLUMN organisations.nom_court IS 'Nom court ou sigle de l''organisation'; +COMMENT ON COLUMN organisations.type_organisation IS 'Type d''organisation (LIONS_CLUB, ASSOCIATION, etc.)'; +COMMENT ON COLUMN organisations.statut IS 'Statut actuel de l''organisation (ACTIVE, SUSPENDUE, etc.)'; +COMMENT ON COLUMN organisations.organisation_parente_id IS 'ID de l''organisation parente pour la hiérarchie'; +COMMENT ON COLUMN organisations.niveau_hierarchique IS 'Niveau dans la hiérarchie (0 = racine)'; +COMMENT ON COLUMN organisations.nombre_membres IS 'Nombre total de membres actifs'; +COMMENT ON COLUMN organisations.organisation_publique IS 'Si l''organisation est visible publiquement'; +COMMENT ON COLUMN organisations.accepte_nouveaux_membres IS 'Si l''organisation accepte de nouveaux membres'; +COMMENT ON COLUMN organisations.version IS 'Version pour le contrôle de concurrence optimiste'; diff --git a/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql b/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql new file mode 100644 index 0000000..c921d22 --- /dev/null +++ b/src/main/resources/db/migration/V1.3__Convert_Ids_To_UUID.sql @@ -0,0 +1,419 @@ +-- Migration V1.3: Conversion des colonnes ID de BIGINT vers UUID +-- Auteur: UnionFlow Team +-- Date: 2025-01-16 +-- Description: Convertit toutes les colonnes ID et clés étrangères de BIGINT vers UUID +-- ATTENTION: Cette migration supprime toutes les données existantes pour simplifier la conversion +-- Pour une migration avec préservation des données, voir V1.3.1__Convert_Ids_To_UUID_With_Data.sql + +-- ============================================ +-- ÉTAPE 1: Suppression des contraintes de clés étrangères +-- ============================================ + +-- Supprimer les contraintes de clés étrangères existantes +DO $$ +BEGIN + -- Supprimer FK membres -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_membre_organisation' + AND table_name = 'membres' + ) THEN + ALTER TABLE membres DROP CONSTRAINT fk_membre_organisation; + END IF; + + -- Supprimer FK cotisations -> membres + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_cotisation%' + AND table_name = 'cotisations' + ) THEN + ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS fk_cotisation_membre CASCADE; + END IF; + + -- Supprimer FK evenements -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_evenement%' + AND table_name = 'evenements' + ) THEN + ALTER TABLE evenements DROP CONSTRAINT IF EXISTS fk_evenement_organisation CASCADE; + END IF; + + -- Supprimer FK inscriptions_evenement -> membres et evenements + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_inscription%' + AND table_name = 'inscriptions_evenement' + ) THEN + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_membre CASCADE; + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_evenement CASCADE; + END IF; + + -- Supprimer FK demandes_aide -> membres et organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_demande%' + AND table_name = 'demandes_aide' + ) THEN + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_demandeur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_evaluateur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_organisation CASCADE; + END IF; +END $$; + +-- ============================================ +-- ÉTAPE 2: Supprimer les séquences (BIGSERIAL) +-- ============================================ + +DROP SEQUENCE IF EXISTS membres_SEQ CASCADE; +DROP SEQUENCE IF EXISTS cotisations_SEQ CASCADE; +DROP SEQUENCE IF EXISTS evenements_SEQ CASCADE; +DROP SEQUENCE IF EXISTS organisations_id_seq CASCADE; + +-- ============================================ +-- ÉTAPE 3: Supprimer les tables existantes (pour recréation avec UUID) +-- ============================================ + +-- Supprimer les tables dans l'ordre inverse des dépendances +DROP TABLE IF EXISTS inscriptions_evenement CASCADE; +DROP TABLE IF EXISTS demandes_aide CASCADE; +DROP TABLE IF EXISTS cotisations CASCADE; +DROP TABLE IF EXISTS evenements CASCADE; +DROP TABLE IF EXISTS membres CASCADE; +DROP TABLE IF EXISTS organisations CASCADE; + +-- ============================================ +-- ÉTAPE 4: Recréer les tables avec UUID +-- ============================================ + +-- Table organisations avec UUID +CREATE TABLE organisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Informations de base + nom VARCHAR(200) NOT NULL, + nom_court VARCHAR(50), + type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION', + statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + description TEXT, + date_fondation DATE, + numero_enregistrement VARCHAR(100) UNIQUE, + + -- Informations de contact + email VARCHAR(255) NOT NULL UNIQUE, + telephone VARCHAR(20), + telephone_secondaire VARCHAR(20), + email_secondaire VARCHAR(255), + + -- Adresse + adresse VARCHAR(500), + ville VARCHAR(100), + code_postal VARCHAR(20), + region VARCHAR(100), + pays VARCHAR(100), + + -- Coordonnées géographiques + latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90), + longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180), + + -- Web et réseaux sociaux + site_web VARCHAR(500), + logo VARCHAR(500), + reseaux_sociaux VARCHAR(1000), + + -- Hiérarchie + organisation_parente_id UUID, + niveau_hierarchique INTEGER NOT NULL DEFAULT 0, + + -- Statistiques + nombre_membres INTEGER NOT NULL DEFAULT 0, + nombre_administrateurs INTEGER NOT NULL DEFAULT 0, + + -- Finances + budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0), + devise VARCHAR(3) DEFAULT 'XOF', + cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE, + montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0), + + -- Informations complémentaires + objectifs TEXT, + activites_principales TEXT, + certifications VARCHAR(500), + partenaires VARCHAR(1000), + notes VARCHAR(1000), + + -- Paramètres + organisation_publique BOOLEAN NOT NULL DEFAULT TRUE, + accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + -- Contraintes + CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')), + CONSTRAINT chk_organisation_type CHECK (type_organisation IN ( + 'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE', + 'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE' + )), + CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')), + CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10), + CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0), + CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0), + + -- Clé étrangère pour hiérarchie + CONSTRAINT fk_organisation_parente FOREIGN KEY (organisation_parente_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table membres avec UUID +CREATE TABLE membres ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_membre VARCHAR(20) UNIQUE NOT NULL, + prenom VARCHAR(100) NOT NULL, + nom VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + mot_de_passe VARCHAR(255), + telephone VARCHAR(20), + date_naissance DATE NOT NULL, + date_adhesion DATE NOT NULL, + roles VARCHAR(500), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_membre_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table cotisations avec UUID +CREATE TABLE cotisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_reference VARCHAR(50) UNIQUE NOT NULL, + membre_id UUID NOT NULL, + type_cotisation VARCHAR(50) NOT NULL, + montant_du DECIMAL(12,2) NOT NULL CHECK (montant_du >= 0), + montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (montant_paye >= 0), + code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF', + statut VARCHAR(30) NOT NULL, + date_echeance DATE NOT NULL, + date_paiement TIMESTAMP, + description VARCHAR(500), + periode VARCHAR(20), + annee INTEGER NOT NULL CHECK (annee >= 2020 AND annee <= 2100), + mois INTEGER CHECK (mois >= 1 AND mois <= 12), + observations VARCHAR(1000), + recurrente BOOLEAN NOT NULL DEFAULT FALSE, + nombre_rappels INTEGER NOT NULL DEFAULT 0 CHECK (nombre_rappels >= 0), + date_dernier_rappel TIMESTAMP, + valide_par_id UUID, + nom_validateur VARCHAR(100), + date_validation TIMESTAMP, + methode_paiement VARCHAR(50), + reference_paiement VARCHAR(100), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_cotisation_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT chk_cotisation_statut CHECK (statut IN ('EN_ATTENTE', 'PAYEE', 'EN_RETARD', 'PARTIELLEMENT_PAYEE', 'ANNULEE')), + CONSTRAINT chk_cotisation_devise CHECK (code_devise ~ '^[A-Z]{3}$') +); + +-- Table evenements avec UUID +CREATE TABLE evenements ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description VARCHAR(2000), + date_debut TIMESTAMP NOT NULL, + date_fin TIMESTAMP, + lieu VARCHAR(255) NOT NULL, + adresse VARCHAR(500), + ville VARCHAR(100), + pays VARCHAR(100), + code_postal VARCHAR(20), + latitude DECIMAL(9,6), + longitude DECIMAL(9,6), + type_evenement VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + url_inscription VARCHAR(500), + url_informations VARCHAR(500), + image_url VARCHAR(500), + capacite_max INTEGER, + cout_participation DECIMAL(12,2), + devise VARCHAR(3), + est_public BOOLEAN NOT NULL DEFAULT TRUE, + tags VARCHAR(500), + notes VARCHAR(1000), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_evenement_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table inscriptions_evenement avec UUID +CREATE TABLE inscriptions_evenement ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + membre_id UUID NOT NULL, + evenement_id UUID NOT NULL, + date_inscription TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + statut VARCHAR(20) DEFAULT 'CONFIRMEE', + commentaire VARCHAR(500), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_inscription_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_inscription_evenement FOREIGN KEY (evenement_id) + REFERENCES evenements(id) ON DELETE CASCADE, + CONSTRAINT chk_inscription_statut CHECK (statut IN ('CONFIRMEE', 'EN_ATTENTE', 'ANNULEE', 'REFUSEE')), + CONSTRAINT uk_inscription_membre_evenement UNIQUE (membre_id, evenement_id) +); + +-- Table demandes_aide avec UUID +CREATE TABLE demandes_aide ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description TEXT NOT NULL, + type_aide VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + montant_demande DECIMAL(10,2), + montant_approuve DECIMAL(10,2), + date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_evaluation TIMESTAMP, + date_versement TIMESTAMP, + justification TEXT, + commentaire_evaluation TEXT, + urgence BOOLEAN NOT NULL DEFAULT FALSE, + documents_fournis VARCHAR(500), + + -- Clés étrangères + demandeur_id UUID NOT NULL, + evaluateur_id UUID, + organisation_id UUID NOT NULL, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_demande_demandeur FOREIGN KEY (demandeur_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id) + REFERENCES membres(id) ON DELETE SET NULL, + CONSTRAINT fk_demande_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE CASCADE +); + +-- ============================================ +-- ÉTAPE 5: Recréer les index +-- ============================================ + +-- Index pour organisations +CREATE INDEX idx_organisation_nom ON organisations(nom); +CREATE INDEX idx_organisation_email ON organisations(email); +CREATE INDEX idx_organisation_statut ON organisations(statut); +CREATE INDEX idx_organisation_type ON organisations(type_organisation); +CREATE INDEX idx_organisation_ville ON organisations(ville); +CREATE INDEX idx_organisation_pays ON organisations(pays); +CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id); +CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement); +CREATE INDEX idx_organisation_actif ON organisations(actif); +CREATE INDEX idx_organisation_date_creation ON organisations(date_creation); +CREATE INDEX idx_organisation_publique ON organisations(organisation_publique); +CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres); +CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif); + +-- Index pour membres +CREATE INDEX idx_membre_email ON membres(email); +CREATE INDEX idx_membre_numero ON membres(numero_membre); +CREATE INDEX idx_membre_actif ON membres(actif); +CREATE INDEX idx_membre_organisation ON membres(organisation_id); + +-- Index pour cotisations +CREATE INDEX idx_cotisation_membre ON cotisations(membre_id); +CREATE INDEX idx_cotisation_reference ON cotisations(numero_reference); +CREATE INDEX idx_cotisation_statut ON cotisations(statut); +CREATE INDEX idx_cotisation_echeance ON cotisations(date_echeance); +CREATE INDEX idx_cotisation_type ON cotisations(type_cotisation); +CREATE INDEX idx_cotisation_annee_mois ON cotisations(annee, mois); + +-- Index pour evenements +CREATE INDEX idx_evenement_date_debut ON evenements(date_debut); +CREATE INDEX idx_evenement_statut ON evenements(statut); +CREATE INDEX idx_evenement_type ON evenements(type_evenement); +CREATE INDEX idx_evenement_organisation ON evenements(organisation_id); + +-- Index pour inscriptions_evenement +CREATE INDEX idx_inscription_membre ON inscriptions_evenement(membre_id); +CREATE INDEX idx_inscription_evenement ON inscriptions_evenement(evenement_id); +CREATE INDEX idx_inscription_date ON inscriptions_evenement(date_inscription); + +-- Index pour demandes_aide +CREATE INDEX idx_demande_demandeur ON demandes_aide(demandeur_id); +CREATE INDEX idx_demande_evaluateur ON demandes_aide(evaluateur_id); +CREATE INDEX idx_demande_organisation ON demandes_aide(organisation_id); +CREATE INDEX idx_demande_statut ON demandes_aide(statut); +CREATE INDEX idx_demande_type ON demandes_aide(type_aide); +CREATE INDEX idx_demande_date_demande ON demandes_aide(date_demande); + +-- ============================================ +-- ÉTAPE 6: Commentaires sur les tables +-- ============================================ + +COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.) avec UUID'; +COMMENT ON TABLE membres IS 'Table des membres avec UUID'; +COMMENT ON TABLE cotisations IS 'Table des cotisations avec UUID'; +COMMENT ON TABLE evenements IS 'Table des événements avec UUID'; +COMMENT ON TABLE inscriptions_evenement IS 'Table des inscriptions aux événements avec UUID'; +COMMENT ON TABLE demandes_aide IS 'Table des demandes d''aide avec UUID'; + +COMMENT ON COLUMN organisations.id IS 'UUID unique de l''organisation'; +COMMENT ON COLUMN membres.id IS 'UUID unique du membre'; +COMMENT ON COLUMN cotisations.id IS 'UUID unique de la cotisation'; +COMMENT ON COLUMN evenements.id IS 'UUID unique de l''événement'; +COMMENT ON COLUMN inscriptions_evenement.id IS 'UUID unique de l''inscription'; +COMMENT ON COLUMN demandes_aide.id IS 'UUID unique de la demande d''aide'; + diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql new file mode 100644 index 0000000..8b3c0d8 --- /dev/null +++ b/src/main/resources/import.sql @@ -0,0 +1,10 @@ +-- Script d'insertion de données initiales pour UnionFlow +-- Ce fichier est exécuté automatiquement par Hibernate au démarrage +-- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create) +-- +-- IMPORTANT: Ce fichier ne doit PAS contenir de données fictives pour la production. +-- Les données doivent être insérées manuellement via l'interface d'administration +-- ou via des scripts de migration Flyway si nécessaire. +-- +-- Ce fichier est laissé vide intentionnellement pour éviter l'insertion automatique +-- de données fictives lors du démarrage du serveur. diff --git a/src/main/resources/keycloak/unionflow-realm.json b/src/main/resources/keycloak/unionflow-realm.json new file mode 100644 index 0000000..218ff11 --- /dev/null +++ b/src/main/resources/keycloak/unionflow-realm.json @@ -0,0 +1,307 @@ +{ + "realm": "unionflow", + "displayName": "UnionFlow", + "displayNameHtml": "

UnionFlow
", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": true, + "rememberMe": true, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": true, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRoles": ["offline_access", "uma_authorization", "default-roles-unionflow"], + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "supportedLocales": ["fr", "en"], + "defaultLocale": "fr", + "internationalizationEnabled": true, + "clients": [ + { + "clientId": "unionflow-server", + "name": "UnionFlow Server API", + "description": "Client pour l'API serveur UnionFlow", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "dev-secret", + "redirectUris": ["http://localhost:8080/*"], + "webOrigins": ["http://localhost:8080", "http://localhost:3000"], + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "given_name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "family_name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ], + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "clientId": "unionflow-mobile", + "name": "UnionFlow Mobile App", + "description": "Client pour l'application mobile UnionFlow", + "enabled": true, + "publicClient": true, + "redirectUris": ["unionflow://callback", "http://localhost:3000/callback"], + "webOrigins": ["*"], + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "fullScopeAllowed": true, + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + } + ], + "roles": { + "realm": [ + { + "name": "ADMIN", + "description": "Administrateur système avec tous les droits", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "PRESIDENT", + "description": "Président de l'union avec droits de gestion complète", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "SECRETAIRE", + "description": "Secrétaire avec droits de gestion des membres et événements", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "TRESORIER", + "description": "Trésorier avec droits de gestion financière", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "GESTIONNAIRE_MEMBRE", + "description": "Gestionnaire des membres avec droits de CRUD sur les membres", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "ORGANISATEUR_EVENEMENT", + "description": "Organisateur d'événements avec droits de gestion des événements", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "MEMBRE", + "description": "Membre standard avec droits de consultation", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + } + ] + }, + "users": [ + { + "username": "admin", + "enabled": true, + "emailVerified": true, + "firstName": "Administrateur", + "lastName": "Système", + "email": "admin@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "admin123", + "temporary": false + } + ], + "realmRoles": ["ADMIN", "PRESIDENT"], + "clientRoles": {} + }, + { + "username": "president", + "enabled": true, + "emailVerified": true, + "firstName": "Jean", + "lastName": "Dupont", + "email": "president@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "president123", + "temporary": false + } + ], + "realmRoles": ["PRESIDENT", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "secretaire", + "enabled": true, + "emailVerified": true, + "firstName": "Marie", + "lastName": "Martin", + "email": "secretaire@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "secretaire123", + "temporary": false + } + ], + "realmRoles": ["SECRETAIRE", "GESTIONNAIRE_MEMBRE", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "tresorier", + "enabled": true, + "emailVerified": true, + "firstName": "Pierre", + "lastName": "Durand", + "email": "tresorier@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "tresorier123", + "temporary": false + } + ], + "realmRoles": ["TRESORIER", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "membre1", + "enabled": true, + "emailVerified": true, + "firstName": "Sophie", + "lastName": "Bernard", + "email": "membre1@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "membre123", + "temporary": false + } + ], + "realmRoles": ["MEMBRE"], + "clientRoles": {} + } + ], + "groups": [ + { + "name": "Administration", + "path": "/Administration", + "realmRoles": ["ADMIN"], + "subGroups": [] + }, + { + "name": "Bureau", + "path": "/Bureau", + "realmRoles": ["PRESIDENT", "SECRETAIRE", "TRESORIER"], + "subGroups": [] + }, + { + "name": "Gestionnaires", + "path": "/Gestionnaires", + "realmRoles": ["GESTIONNAIRE_MEMBRE", "ORGANISATEUR_EVENEMENT"], + "subGroups": [] + }, + { + "name": "Membres", + "path": "/Membres", + "realmRoles": ["MEMBRE"], + "subGroups": [] + } + ] +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/UnionFlowServerApplicationTest.java b/src/test.bak/java/dev/lions/unionflow/server/UnionFlowServerApplicationTest.java new file mode 100644 index 0000000..1926bf7 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/UnionFlowServerApplicationTest.java @@ -0,0 +1,155 @@ +package dev.lions.unionflow.server; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests pour UnionFlowServerApplication + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@QuarkusTest +@DisplayName("Tests UnionFlowServerApplication") +class UnionFlowServerApplicationTest { + + @Test + @DisplayName("Test de l'application - Contexte Quarkus") + void testApplicationContext() { + // Given & When & Then + // Le simple fait que ce test s'exécute sans erreur + // prouve que l'application Quarkus démarre correctement + assertThat(true).isTrue(); + } + + @Test + @DisplayName("Test de l'application - Classe principale existe") + void testMainClassExists() { + // Given & When & Then + assertThat(UnionFlowServerApplication.class).isNotNull(); + assertThat( + UnionFlowServerApplication.class.getAnnotation( + io.quarkus.runtime.annotations.QuarkusMain.class)) + .isNotNull(); + } + + @Test + @DisplayName("Test de l'application - Implémente QuarkusApplication") + void testImplementsQuarkusApplication() { + // Given & When & Then + assertThat(io.quarkus.runtime.QuarkusApplication.class) + .isAssignableFrom(UnionFlowServerApplication.class); + } + + @Test + @DisplayName("Test de l'application - Méthode main existe") + void testMainMethodExists() throws NoSuchMethodException { + // Given & When & Then + assertThat(UnionFlowServerApplication.class.getMethod("main", String[].class)).isNotNull(); + } + + @Test + @DisplayName("Test de l'application - Méthode run existe") + void testRunMethodExists() throws NoSuchMethodException { + // Given & When & Then + assertThat(UnionFlowServerApplication.class.getMethod("run", String[].class)).isNotNull(); + } + + @Test + @DisplayName("Test de l'application - Annotation ApplicationScoped") + void testApplicationScopedAnnotation() { + // Given & When & Then + assertThat( + UnionFlowServerApplication.class.getAnnotation( + jakarta.enterprise.context.ApplicationScoped.class)) + .isNotNull(); + } + + @Test + @DisplayName("Test de l'application - Logger statique") + void testStaticLogger() throws NoSuchFieldException { + // Given & When & Then + assertThat(UnionFlowServerApplication.class.getDeclaredField("LOG")).isNotNull(); + } + + @Test + @DisplayName("Test de l'application - Instance créable") + void testInstanceCreation() { + // Given & When + UnionFlowServerApplication app = new UnionFlowServerApplication(); + + // Then + assertThat(app).isNotNull(); + assertThat(app).isInstanceOf(io.quarkus.runtime.QuarkusApplication.class); + } + + @Test + @DisplayName("Test de la méthode main - Signature correcte") + void testMainMethodSignature() throws NoSuchMethodException { + // Given & When + var mainMethod = UnionFlowServerApplication.class.getMethod("main", String[].class); + + // Then + assertThat(mainMethod.getReturnType()).isEqualTo(void.class); + assertThat(java.lang.reflect.Modifier.isStatic(mainMethod.getModifiers())).isTrue(); + assertThat(java.lang.reflect.Modifier.isPublic(mainMethod.getModifiers())).isTrue(); + } + + @Test + @DisplayName("Test de la méthode run - Signature correcte") + void testRunMethodSignature() throws NoSuchMethodException { + // Given & When + var runMethod = UnionFlowServerApplication.class.getMethod("run", String[].class); + + // Then + assertThat(runMethod.getReturnType()).isEqualTo(int.class); + assertThat(java.lang.reflect.Modifier.isPublic(runMethod.getModifiers())).isTrue(); + assertThat(runMethod.getExceptionTypes()).contains(Exception.class); + } + + @Test + @DisplayName("Test de l'implémentation QuarkusApplication") + void testQuarkusApplicationImplementation() { + // Given & When & Then + assertThat( + io.quarkus.runtime.QuarkusApplication.class.isAssignableFrom( + UnionFlowServerApplication.class)) + .isTrue(); + } + + @Test + @DisplayName("Test du package de la classe") + void testPackageName() { + // Given & When & Then + assertThat(UnionFlowServerApplication.class.getPackage().getName()) + .isEqualTo("dev.lions.unionflow.server"); + } + + @Test + @DisplayName("Test de la classe - Modificateurs") + void testClassModifiers() { + // Given & When & Then + assertThat(java.lang.reflect.Modifier.isPublic(UnionFlowServerApplication.class.getModifiers())) + .isTrue(); + assertThat(java.lang.reflect.Modifier.isFinal(UnionFlowServerApplication.class.getModifiers())) + .isFalse(); + assertThat( + java.lang.reflect.Modifier.isAbstract(UnionFlowServerApplication.class.getModifiers())) + .isFalse(); + } + + @Test + @DisplayName("Test des constructeurs") + void testConstructors() { + // Given & When + var constructors = UnionFlowServerApplication.class.getConstructors(); + + // Then + assertThat(constructors).hasSize(1); + assertThat(constructors[0].getParameterCount()).isEqualTo(0); + assertThat(java.lang.reflect.Modifier.isPublic(constructors[0].getModifiers())).isTrue(); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/entity/MembreSimpleTest.java b/src/test.bak/java/dev/lions/unionflow/server/entity/MembreSimpleTest.java new file mode 100644 index 0000000..d407bc4 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/entity/MembreSimpleTest.java @@ -0,0 +1,237 @@ +package dev.lions.unionflow.server.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests simples pour l'entité Membre + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@DisplayName("Tests simples Membre") +class MembreSimpleTest { + + @Test + @DisplayName("Test de création d'un membre avec builder") + void testCreationMembreAvecBuilder() { + // Given & When + Membre membre = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .telephone("221701234567") + .dateNaissance(LocalDate.of(1990, 5, 15)) + .dateAdhesion(LocalDate.now()) + .actif(true) + .build(); + + // Then + assertThat(membre).isNotNull(); + assertThat(membre.getNumeroMembre()).isEqualTo("UF2025-TEST01"); + assertThat(membre.getPrenom()).isEqualTo("Jean"); + assertThat(membre.getNom()).isEqualTo("Dupont"); + assertThat(membre.getEmail()).isEqualTo("jean.dupont@test.com"); + assertThat(membre.getTelephone()).isEqualTo("221701234567"); + assertThat(membre.getDateNaissance()).isEqualTo(LocalDate.of(1990, 5, 15)); + assertThat(membre.getActif()).isTrue(); + } + + @Test + @DisplayName("Test de la méthode getNomComplet") + void testGetNomComplet() { + // Given + Membre membre = Membre.builder().prenom("Jean").nom("Dupont").build(); + + // When + String nomComplet = membre.getNomComplet(); + + // Then + assertThat(nomComplet).isEqualTo("Jean Dupont"); + } + + @Test + @DisplayName("Test de la méthode isMajeur - Majeur") + void testIsMajeurMajeur() { + // Given + Membre membre = Membre.builder().dateNaissance(LocalDate.of(1990, 5, 15)).build(); + + // When + boolean majeur = membre.isMajeur(); + + // Then + assertThat(majeur).isTrue(); + } + + @Test + @DisplayName("Test de la méthode isMajeur - Mineur") + void testIsMajeurMineur() { + // Given + Membre membre = Membre.builder().dateNaissance(LocalDate.now().minusYears(17)).build(); + + // When + boolean majeur = membre.isMajeur(); + + // Then + assertThat(majeur).isFalse(); + } + + @Test + @DisplayName("Test de la méthode getAge") + void testGetAge() { + // Given + Membre membre = Membre.builder().dateNaissance(LocalDate.now().minusYears(25)).build(); + + // When + int age = membre.getAge(); + + // Then + assertThat(age).isEqualTo(25); + } + + @Test + @DisplayName("Test de création d'un membre sans builder") + void testCreationMembreSansBuilder() { + // Given & When + Membre membre = new Membre(); + membre.setNumeroMembre("UF2025-TEST02"); + membre.setPrenom("Marie"); + membre.setNom("Martin"); + membre.setEmail("marie.martin@test.com"); + membre.setActif(true); + + // Then + assertThat(membre).isNotNull(); + assertThat(membre.getNumeroMembre()).isEqualTo("UF2025-TEST02"); + assertThat(membre.getPrenom()).isEqualTo("Marie"); + assertThat(membre.getNom()).isEqualTo("Martin"); + assertThat(membre.getEmail()).isEqualTo("marie.martin@test.com"); + assertThat(membre.getActif()).isTrue(); + } + + @Test + @DisplayName("Test des annotations JPA") + void testAnnotationsJPA() { + // Given & When & Then + assertThat(Membre.class.getAnnotation(jakarta.persistence.Entity.class)).isNotNull(); + assertThat(Membre.class.getAnnotation(jakarta.persistence.Table.class)).isNotNull(); + assertThat(Membre.class.getAnnotation(jakarta.persistence.Table.class).name()) + .isEqualTo("membres"); + } + + @Test + @DisplayName("Test des annotations Lombok") + void testAnnotationsLombok() { + // Given & When & Then + // Vérifier que les annotations Lombok sont présentes (peuvent être null selon la compilation) + // Nous testons plutôt que les méthodes générées existent + assertThat(Membre.builder()).isNotNull(); + + Membre membre = new Membre(); + assertThat(membre.toString()).isNotNull(); + assertThat(membre.hashCode()).isNotZero(); + } + + @Test + @DisplayName("Test de l'héritage PanacheEntity") + void testHeritageePanacheEntity() { + // Given & When & Then + assertThat(io.quarkus.hibernate.orm.panache.PanacheEntity.class).isAssignableFrom(Membre.class); + } + + @Test + @DisplayName("Test des méthodes héritées de PanacheEntity") + void testMethodesHeriteesPanacheEntity() throws NoSuchMethodException { + // Given & When & Then + // Vérifier que les méthodes de PanacheEntity sont disponibles + assertThat(Membre.class.getMethod("persist")).isNotNull(); + assertThat(Membre.class.getMethod("delete")).isNotNull(); + assertThat(Membre.class.getMethod("isPersistent")).isNotNull(); + } + + @Test + @DisplayName("Test de toString") + void testToString() { + // Given + Membre membre = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .actif(true) + .build(); + + // When + String toString = membre.toString(); + + // Then + assertThat(toString).isNotNull(); + assertThat(toString).contains("Jean"); + assertThat(toString).contains("Dupont"); + assertThat(toString).contains("UF2025-TEST01"); + assertThat(toString).contains("jean.dupont@test.com"); + } + + @Test + @DisplayName("Test de hashCode") + void testHashCode() { + // Given + Membre membre1 = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .build(); + + Membre membre2 = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .build(); + + // When & Then + assertThat(membre1.hashCode()).isNotZero(); + assertThat(membre2.hashCode()).isNotZero(); + } + + @Test + @DisplayName("Test des propriétés nulles") + void testProprietesNulles() { + // Given + Membre membre = new Membre(); + + // When & Then + assertThat(membre.getNumeroMembre()).isNull(); + assertThat(membre.getPrenom()).isNull(); + assertThat(membre.getNom()).isNull(); + assertThat(membre.getEmail()).isNull(); + assertThat(membre.getTelephone()).isNull(); + assertThat(membre.getDateNaissance()).isNull(); + assertThat(membre.getDateAdhesion()).isNull(); + // Le champ actif a une valeur par défaut à true dans l'entité + // assertThat(membre.getActif()).isNull(); + } + + @Test + @DisplayName("Test de la méthode preUpdate") + void testPreUpdate() { + // Given + Membre membre = new Membre(); + assertThat(membre.getDateModification()).isNull(); + + // When + membre.preUpdate(); + + // Then + assertThat(membre.getDateModification()).isNotNull(); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryIntegrationTest.java b/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryIntegrationTest.java new file mode 100644 index 0000000..7ae9eff --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryIntegrationTest.java @@ -0,0 +1,184 @@ +package dev.lions.unionflow.server.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.entity.Membre; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests d'intégration pour MembreRepository + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@QuarkusTest +@DisplayName("Tests d'intégration MembreRepository") +class MembreRepositoryIntegrationTest { + + @Inject MembreRepository membreRepository; + + private Membre membreTest; + + @BeforeEach + @Transactional + void setUp() { + // Nettoyer la base de données + membreRepository.deleteAll(); + + // Créer un membre de test + membreTest = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .telephone("221701234567") + .dateNaissance(LocalDate.of(1990, 5, 15)) + .dateAdhesion(LocalDate.now()) + .actif(true) + .build(); + + membreRepository.persist(membreTest); + } + + @Test + @DisplayName("Test findByEmail - Membre existant") + @Transactional + void testFindByEmailExistant() { + // When + Optional result = membreRepository.findByEmail("jean.dupont@test.com"); + + // Then + assertThat(result).isPresent(); + assertThat(result.get().getPrenom()).isEqualTo("Jean"); + assertThat(result.get().getNom()).isEqualTo("Dupont"); + } + + @Test + @DisplayName("Test findByEmail - Membre inexistant") + @Transactional + void testFindByEmailInexistant() { + // When + Optional result = membreRepository.findByEmail("inexistant@test.com"); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Test findByNumeroMembre - Membre existant") + @Transactional + void testFindByNumeroMembreExistant() { + // When + Optional result = membreRepository.findByNumeroMembre("UF2025-TEST01"); + + // Then + assertThat(result).isPresent(); + assertThat(result.get().getPrenom()).isEqualTo("Jean"); + assertThat(result.get().getNom()).isEqualTo("Dupont"); + } + + @Test + @DisplayName("Test findByNumeroMembre - Membre inexistant") + @Transactional + void testFindByNumeroMembreInexistant() { + // When + Optional result = membreRepository.findByNumeroMembre("UF2025-INEXISTANT"); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Test findAllActifs - Seuls les membres actifs") + @Transactional + void testFindAllActifs() { + // Given - Ajouter un membre inactif + Membre membreInactif = + Membre.builder() + .numeroMembre("UF2025-TEST02") + .prenom("Marie") + .nom("Martin") + .email("marie.martin@test.com") + .telephone("221701234568") + .dateNaissance(LocalDate.of(1985, 8, 20)) + .dateAdhesion(LocalDate.now()) + .actif(false) + .build(); + membreRepository.persist(membreInactif); + + // When + List result = membreRepository.findAllActifs(); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0).getActif()).isTrue(); + assertThat(result.get(0).getPrenom()).isEqualTo("Jean"); + } + + @Test + @DisplayName("Test countActifs - Nombre de membres actifs") + @Transactional + void testCountActifs() { + // When + long count = membreRepository.countActifs(); + + // Then + assertThat(count).isEqualTo(1); + } + + @Test + @DisplayName("Test findByNomOrPrenom - Recherche par nom") + @Transactional + void testFindByNomOrPrenomParNom() { + // When + List result = membreRepository.findByNomOrPrenom("dupont"); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0).getNom()).isEqualTo("Dupont"); + } + + @Test + @DisplayName("Test findByNomOrPrenom - Recherche par prénom") + @Transactional + void testFindByNomOrPrenomParPrenom() { + // When + List result = membreRepository.findByNomOrPrenom("jean"); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0).getPrenom()).isEqualTo("Jean"); + } + + @Test + @DisplayName("Test findByNomOrPrenom - Aucun résultat") + @Transactional + void testFindByNomOrPrenomAucunResultat() { + // When + List result = membreRepository.findByNomOrPrenom("inexistant"); + + // Then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Test findByNomOrPrenom - Recherche insensible à la casse") + @Transactional + void testFindByNomOrPrenomCaseInsensitive() { + // When + List result = membreRepository.findByNomOrPrenom("DUPONT"); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0).getNom()).isEqualTo("Dupont"); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryTest.java b/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryTest.java new file mode 100644 index 0000000..d45e356 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/repository/MembreRepositoryTest.java @@ -0,0 +1,105 @@ +package dev.lions.unionflow.server.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.lions.unionflow.server.entity.Membre; +import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Tests pour MembreRepository + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("Tests MembreRepository") +class MembreRepositoryTest { + + @Mock MembreRepository membreRepository; + + private Membre membreTest; + private Membre membreInactif; + + @BeforeEach + void setUp() { + + // Créer des membres de test + membreTest = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .telephone("221701234567") + .dateNaissance(LocalDate.of(1990, 5, 15)) + .dateAdhesion(LocalDate.now()) + .actif(true) + .build(); + + membreInactif = + Membre.builder() + .numeroMembre("UF2025-TEST02") + .prenom("Marie") + .nom("Martin") + .email("marie.martin@test.com") + .telephone("221701234568") + .dateNaissance(LocalDate.of(1985, 8, 20)) + .dateAdhesion(LocalDate.now()) + .actif(false) + .build(); + } + + @Test + @DisplayName("Test de l'existence de la classe MembreRepository") + void testMembreRepositoryExists() { + // Given & When & Then + assertThat(MembreRepository.class).isNotNull(); + assertThat(membreRepository).isNotNull(); + } + + @Test + @DisplayName("Test des méthodes du repository") + void testRepositoryMethods() throws NoSuchMethodException { + // Given & When & Then + assertThat(MembreRepository.class.getMethod("findByEmail", String.class)).isNotNull(); + assertThat(MembreRepository.class.getMethod("findByNumeroMembre", String.class)).isNotNull(); + assertThat(MembreRepository.class.getMethod("findAllActifs")).isNotNull(); + assertThat(MembreRepository.class.getMethod("countActifs")).isNotNull(); + assertThat(MembreRepository.class.getMethod("findByNomOrPrenom", String.class)).isNotNull(); + } + + @Test + @DisplayName("Test de l'annotation ApplicationScoped") + void testApplicationScopedAnnotation() { + // Given & When & Then + assertThat( + MembreRepository.class.getAnnotation( + jakarta.enterprise.context.ApplicationScoped.class)) + .isNotNull(); + } + + @Test + @DisplayName("Test de l'implémentation PanacheRepository") + void testPanacheRepositoryImplementation() { + // Given & When & Then + assertThat(io.quarkus.hibernate.orm.panache.PanacheRepository.class) + .isAssignableFrom(MembreRepository.class); + } + + @Test + @DisplayName("Test de la création d'instance") + void testInstanceCreation() { + // Given & When + MembreRepository repository = new MembreRepository(); + + // Then + assertThat(repository).isNotNull(); + assertThat(repository).isInstanceOf(io.quarkus.hibernate.orm.panache.PanacheRepository.class); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/AideResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/AideResourceTest.java new file mode 100644 index 0000000..ba0d28e --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/AideResourceTest.java @@ -0,0 +1,394 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import dev.lions.unionflow.server.api.dto.solidarite.aide.AideDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.service.AideService; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import jakarta.ws.rs.NotFoundException; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +/** + * Tests d'intégration pour AideResource + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +@DisplayName("AideResource - Tests d'intégration") +class AideResourceTest { + + @Mock AideService aideService; + + private AideDTO aideDTOTest; + private List listeAidesTest; + + @BeforeEach + void setUp() { + // DTO de test + aideDTOTest = new AideDTO(); + aideDTOTest.setId(UUID.randomUUID()); + aideDTOTest.setNumeroReference("AIDE-2025-TEST01"); + aideDTOTest.setTitre("Aide médicale urgente"); + aideDTOTest.setDescription("Demande d'aide pour frais médicaux urgents"); + aideDTOTest.setTypeAide("MEDICALE"); + aideDTOTest.setMontantDemande(new BigDecimal("500000.00")); + aideDTOTest.setStatut("EN_ATTENTE"); + aideDTOTest.setPriorite("URGENTE"); + aideDTOTest.setMembreDemandeurId(UUID.randomUUID()); + aideDTOTest.setAssociationId(UUID.randomUUID()); + aideDTOTest.setActif(true); + + // Liste de test + listeAidesTest = Arrays.asList(aideDTOTest); + } + + @Nested + @DisplayName("Tests des endpoints CRUD") + class CrudEndpointsTests { + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("GET /api/aides - Liste des aides") + void testListerAides() { + // Given + when(aideService.listerAidesActives(0, 20)).thenReturn(listeAidesTest); + + // When & Then + given() + .when() + .get("/api/aides") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", is(1)) + .body("[0].titre", equalTo("Aide médicale urgente")) + .body("[0].statut", equalTo("EN_ATTENTE")); + } + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("GET /api/aides/{id} - Récupération par ID") + void testObtenirAideParId() { + // Given + when(aideService.obtenirAideParId(1L)).thenReturn(aideDTOTest); + + // When & Then + given() + .when() + .get("/api/aides/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("titre", equalTo("Aide médicale urgente")) + .body("numeroReference", equalTo("AIDE-2025-TEST01")); + } + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("GET /api/aides/{id} - Aide non trouvée") + void testObtenirAideParId_NonTrouvee() { + // Given + when(aideService.obtenirAideParId(999L)) + .thenThrow(new NotFoundException("Demande d'aide non trouvée")); + + // When & Then + given() + .when() + .get("/api/aides/999") + .then() + .statusCode(404) + .contentType(ContentType.JSON) + .body("error", equalTo("Demande d'aide non trouvée")); + } + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("POST /api/aides - Création d'aide") + void testCreerAide() { + // Given + when(aideService.creerAide(any(AideDTO.class))).thenReturn(aideDTOTest); + + // When & Then + given() + .contentType(ContentType.JSON) + .body(aideDTOTest) + .when() + .post("/api/aides") + .then() + .statusCode(201) + .contentType(ContentType.JSON) + .body("titre", equalTo("Aide médicale urgente")) + .body("numeroReference", equalTo("AIDE-2025-TEST01")); + } + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("PUT /api/aides/{id} - Mise à jour d'aide") + void testMettreAJourAide() { + // Given + AideDTO aideMiseAJour = new AideDTO(); + aideMiseAJour.setTitre("Titre modifié"); + aideMiseAJour.setDescription("Description modifiée"); + + when(aideService.mettreAJourAide(eq(1L), any(AideDTO.class))).thenReturn(aideMiseAJour); + + // When & Then + given() + .contentType(ContentType.JSON) + .body(aideMiseAJour) + .when() + .put("/api/aides/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("titre", equalTo("Titre modifié")); + } + } + + @Nested + @DisplayName("Tests des endpoints métier") + class EndpointsMetierTests { + + @Test + @TestSecurity( + user = "evaluateur", + roles = {"evaluateur_aide"}) + @DisplayName("POST /api/aides/{id}/approuver - Approbation d'aide") + void testApprouverAide() { + // Given + AideDTO aideApprouvee = new AideDTO(); + aideApprouvee.setStatut("APPROUVEE"); + aideApprouvee.setMontantApprouve(new BigDecimal("400000.00")); + + when(aideService.approuverAide(eq(1L), any(BigDecimal.class), anyString())) + .thenReturn(aideApprouvee); + + Map approbationData = + Map.of( + "montantApprouve", "400000.00", + "commentaires", "Aide approuvée après évaluation"); + + // When & Then + given() + .contentType(ContentType.JSON) + .body(approbationData) + .when() + .post("/api/aides/1/approuver") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("statut", equalTo("APPROUVEE")); + } + + @Test + @TestSecurity( + user = "evaluateur", + roles = {"evaluateur_aide"}) + @DisplayName("POST /api/aides/{id}/rejeter - Rejet d'aide") + void testRejeterAide() { + // Given + AideDTO aideRejetee = new AideDTO(); + aideRejetee.setStatut("REJETEE"); + aideRejetee.setRaisonRejet("Dossier incomplet"); + + when(aideService.rejeterAide(eq(1L), anyString())).thenReturn(aideRejetee); + + Map rejetData = Map.of("raisonRejet", "Dossier incomplet"); + + // When & Then + given() + .contentType(ContentType.JSON) + .body(rejetData) + .when() + .post("/api/aides/1/rejeter") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("statut", equalTo("REJETEE")); + } + + @Test + @TestSecurity( + user = "tresorier", + roles = {"tresorier"}) + @DisplayName("POST /api/aides/{id}/verser - Versement d'aide") + void testMarquerCommeVersee() { + // Given + AideDTO aideVersee = new AideDTO(); + aideVersee.setStatut("VERSEE"); + aideVersee.setMontantVerse(new BigDecimal("400000.00")); + + when(aideService.marquerCommeVersee(eq(1L), any(BigDecimal.class), anyString(), anyString())) + .thenReturn(aideVersee); + + Map versementData = + Map.of( + "montantVerse", "400000.00", + "modeVersement", "MOBILE_MONEY", + "numeroTransaction", "TXN123456789"); + + // When & Then + given() + .contentType(ContentType.JSON) + .body(versementData) + .when() + .post("/api/aides/1/verser") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("statut", equalTo("VERSEE")); + } + } + + @Nested + @DisplayName("Tests des endpoints de recherche") + class EndpointsRechercheTests { + + @Test + @TestSecurity( + user = "membre", + roles = {"membre"}) + @DisplayName("GET /api/aides/statut/{statut} - Filtrage par statut") + void testListerAidesParStatut() { + // Given + when(aideService.listerAidesParStatut(StatutAide.EN_ATTENTE, 0, 20)) + .thenReturn(listeAidesTest); + + // When & Then + given() + .when() + .get("/api/aides/statut/EN_ATTENTE") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", is(1)) + .body("[0].statut", equalTo("EN_ATTENTE")); + } + + @Test + @TestSecurity( + user = "membre", + roles = {"membre"}) + @DisplayName("GET /api/aides/membre/{membreId} - Aides d'un membre") + void testListerAidesParMembre() { + // Given + when(aideService.listerAidesParMembre(1L, 0, 20)).thenReturn(listeAidesTest); + + // When & Then + given() + .when() + .get("/api/aides/membre/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", is(1)); + } + + @Test + @TestSecurity( + user = "membre", + roles = {"membre"}) + @DisplayName("GET /api/aides/recherche - Recherche textuelle") + void testRechercherAides() { + // Given + when(aideService.rechercherAides("médical", 0, 20)).thenReturn(listeAidesTest); + + // When & Then + given() + .queryParam("q", "médical") + .when() + .get("/api/aides/recherche") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", is(1)); + } + + @Test + @TestSecurity( + user = "admin", + roles = {"admin"}) + @DisplayName("GET /api/aides/statistiques - Statistiques") + void testObtenirStatistiques() { + // Given + Map statistiques = + Map.of( + "total", 100L, + "enAttente", 25L, + "approuvees", 50L, + "versees", 20L); + when(aideService.obtenirStatistiquesGlobales()).thenReturn(statistiques); + + // When & Then + given() + .when() + .get("/api/aides/statistiques") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("total", equalTo(100)) + .body("enAttente", equalTo(25)) + .body("approuvees", equalTo(50)) + .body("versees", equalTo(20)); + } + } + + @Nested + @DisplayName("Tests de sécurité") + class SecurityTests { + + @Test + @DisplayName("Accès non authentifié - 401") + void testAccesNonAuthentifie() { + given().when().get("/api/aides").then().statusCode(401); + } + + @Test + @TestSecurity( + user = "membre", + roles = {"membre"}) + @DisplayName("Accès non autorisé pour approbation - 403") + void testAccesNonAutorisePourApprobation() { + Map approbationData = + Map.of( + "montantApprouve", "400000.00", + "commentaires", "Test"); + + given() + .contentType(ContentType.JSON) + .body(approbationData) + .when() + .post("/api/aides/1/approuver") + .then() + .statusCode(403); + } + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/CotisationResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/CotisationResourceTest.java new file mode 100644 index 0000000..68e14ff --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/CotisationResourceTest.java @@ -0,0 +1,325 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.api.dto.finance.CotisationDTO; +import dev.lions.unionflow.server.entity.Cotisation; +import dev.lions.unionflow.server.entity.Membre; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import jakarta.transaction.Transactional; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Tests d'intégration pour CotisationResource Teste tous les endpoints REST de l'API cotisations + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@DisplayName("Tests d'intégration - API Cotisations") +class CotisationResourceTest { + + private static Long membreTestId; + private static Long cotisationTestId; + private static String numeroReferenceTest; + + @BeforeEach + @Transactional + void setUp() { + // Nettoyage et création des données de test + Cotisation.deleteAll(); + Membre.deleteAll(); + + // Création d'un membre de test + Membre membreTest = new Membre(); + membreTest.setNumeroMembre("MBR-TEST-001"); + membreTest.setNom("Dupont"); + membreTest.setPrenom("Jean"); + membreTest.setEmail("jean.dupont@test.com"); + membreTest.setTelephone("+225070123456"); + membreTest.setDateNaissance(LocalDate.of(1985, 5, 15)); + membreTest.setActif(true); + membreTest.persist(); + + membreTestId = membreTest.id; + } + + @Test + @org.junit.jupiter.api.Order(1) + @DisplayName("POST /api/cotisations - Création d'une cotisation") + void testCreateCotisation() { + CotisationDTO nouvelleCotisation = new CotisationDTO(); + nouvelleCotisation.setMembreId(UUID.fromString(membreTestId.toString())); + nouvelleCotisation.setTypeCotisation("MENSUELLE"); + nouvelleCotisation.setMontantDu(new BigDecimal("25000.00")); + nouvelleCotisation.setDateEcheance(LocalDate.now().plusDays(30)); + nouvelleCotisation.setDescription("Cotisation mensuelle janvier 2025"); + nouvelleCotisation.setPeriode("Janvier 2025"); + nouvelleCotisation.setAnnee(2025); + nouvelleCotisation.setMois(1); + + given() + .contentType(ContentType.JSON) + .body(nouvelleCotisation) + .when() + .post("/api/cotisations") + .then() + .statusCode(201) + .body("numeroReference", notNullValue()) + .body("membreId", equalTo(membreTestId.toString())) + .body("typeCotisation", equalTo("MENSUELLE")) + .body("montantDu", equalTo(25000.00f)) + .body("montantPaye", equalTo(0.0f)) + .body("statut", equalTo("EN_ATTENTE")) + .body("codeDevise", equalTo("XOF")) + .body("annee", equalTo(2025)) + .body("mois", equalTo(1)); + } + + @Test + @org.junit.jupiter.api.Order(2) + @DisplayName("GET /api/cotisations - Liste des cotisations") + void testGetAllCotisations() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/cotisations") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @org.junit.jupiter.api.Order(3) + @DisplayName("GET /api/cotisations/{id} - Récupération par ID") + void testGetCotisationById() { + // Créer d'abord une cotisation + CotisationDTO cotisation = createTestCotisation(); + cotisationTestId = Long.valueOf(cotisation.getId().toString()); + + given() + .pathParam("id", cotisationTestId) + .when() + .get("/api/cotisations/{id}") + .then() + .statusCode(200) + .body("id", equalTo(cotisationTestId.toString())) + .body("typeCotisation", equalTo("MENSUELLE")); + } + + @Test + @org.junit.jupiter.api.Order(4) + @DisplayName("GET /api/cotisations/reference/{numeroReference} - Récupération par référence") + void testGetCotisationByReference() { + // Utiliser la cotisation créée précédemment + if (numeroReferenceTest == null) { + CotisationDTO cotisation = createTestCotisation(); + numeroReferenceTest = cotisation.getNumeroReference(); + } + + given() + .pathParam("numeroReference", numeroReferenceTest) + .when() + .get("/api/cotisations/reference/{numeroReference}") + .then() + .statusCode(200) + .body("numeroReference", equalTo(numeroReferenceTest)) + .body("typeCotisation", equalTo("MENSUELLE")); + } + + @Test + @org.junit.jupiter.api.Order(5) + @DisplayName("PUT /api/cotisations/{id} - Mise à jour d'une cotisation") + void testUpdateCotisation() { + // Créer une cotisation si nécessaire + if (cotisationTestId == null) { + CotisationDTO cotisation = createTestCotisation(); + cotisationTestId = Long.valueOf(cotisation.getId().toString()); + } + + CotisationDTO cotisationMiseAJour = new CotisationDTO(); + cotisationMiseAJour.setTypeCotisation("TRIMESTRIELLE"); + cotisationMiseAJour.setMontantDu(new BigDecimal("75000.00")); + cotisationMiseAJour.setDescription("Cotisation trimestrielle Q1 2025"); + cotisationMiseAJour.setObservations("Mise à jour du type de cotisation"); + + given() + .contentType(ContentType.JSON) + .pathParam("id", cotisationTestId) + .body(cotisationMiseAJour) + .when() + .put("/api/cotisations/{id}") + .then() + .statusCode(200) + .body("typeCotisation", equalTo("TRIMESTRIELLE")) + .body("montantDu", equalTo(75000.00f)) + .body("observations", equalTo("Mise à jour du type de cotisation")); + } + + @Test + @org.junit.jupiter.api.Order(6) + @DisplayName("GET /api/cotisations/membre/{membreId} - Cotisations d'un membre") + void testGetCotisationsByMembre() { + given() + .pathParam("membreId", membreTestId) + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/cotisations/membre/{membreId}") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @org.junit.jupiter.api.Order(7) + @DisplayName("GET /api/cotisations/statut/{statut} - Cotisations par statut") + void testGetCotisationsByStatut() { + given() + .pathParam("statut", "EN_ATTENTE") + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/cotisations/statut/{statut}") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @org.junit.jupiter.api.Order(8) + @DisplayName("GET /api/cotisations/en-retard - Cotisations en retard") + void testGetCotisationsEnRetard() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/cotisations/en-retard") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @org.junit.jupiter.api.Order(9) + @DisplayName("GET /api/cotisations/recherche - Recherche avancée") + void testRechercherCotisations() { + given() + .queryParam("membreId", membreTestId) + .queryParam("statut", "EN_ATTENTE") + .queryParam("annee", 2025) + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/cotisations/recherche") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @org.junit.jupiter.api.Order(10) + @DisplayName("GET /api/cotisations/stats - Statistiques des cotisations") + void testGetStatistiquesCotisations() { + given() + .when() + .get("/api/cotisations/stats") + .then() + .statusCode(200) + .body("totalCotisations", notNullValue()) + .body("cotisationsPayees", notNullValue()) + .body("cotisationsEnRetard", notNullValue()) + .body("tauxPaiement", notNullValue()); + } + + @Test + @org.junit.jupiter.api.Order(11) + @DisplayName("DELETE /api/cotisations/{id} - Suppression d'une cotisation") + void testDeleteCotisation() { + // Créer une cotisation si nécessaire + if (cotisationTestId == null) { + CotisationDTO cotisation = createTestCotisation(); + cotisationTestId = Long.valueOf(cotisation.getId().toString()); + } + + given() + .pathParam("id", cotisationTestId) + .when() + .delete("/api/cotisations/{id}") + .then() + .statusCode(204); + + // Vérifier que la cotisation est marquée comme annulée + given() + .pathParam("id", cotisationTestId) + .when() + .get("/api/cotisations/{id}") + .then() + .statusCode(200) + .body("statut", equalTo("ANNULEE")); + } + + @Test + @DisplayName("GET /api/cotisations/{id} - Cotisation inexistante") + void testGetCotisationByIdNotFound() { + given() + .pathParam("id", 99999L) + .when() + .get("/api/cotisations/{id}") + .then() + .statusCode(404) + .body("error", equalTo("Cotisation non trouvée")); + } + + @Test + @DisplayName("POST /api/cotisations - Données invalides") + void testCreateCotisationInvalidData() { + CotisationDTO cotisationInvalide = new CotisationDTO(); + // Données manquantes ou invalides + cotisationInvalide.setTypeCotisation(""); + cotisationInvalide.setMontantDu(new BigDecimal("-100")); + + given() + .contentType(ContentType.JSON) + .body(cotisationInvalide) + .when() + .post("/api/cotisations") + .then() + .statusCode(400); + } + + /** Méthode utilitaire pour créer une cotisation de test */ + private CotisationDTO createTestCotisation() { + CotisationDTO cotisation = new CotisationDTO(); + cotisation.setMembreId(UUID.fromString(membreTestId.toString())); + cotisation.setTypeCotisation("MENSUELLE"); + cotisation.setMontantDu(new BigDecimal("25000.00")); + cotisation.setDateEcheance(LocalDate.now().plusDays(30)); + cotisation.setDescription("Cotisation de test"); + cotisation.setPeriode("Test 2025"); + cotisation.setAnnee(2025); + cotisation.setMois(1); + + return given() + .contentType(ContentType.JSON) + .body(cotisation) + .when() + .post("/api/cotisations") + .then() + .statusCode(201) + .extract() + .as(CotisationDTO.class); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java new file mode 100644 index 0000000..02f098d --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java @@ -0,0 +1,448 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import jakarta.transaction.Transactional; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import org.junit.jupiter.api.*; + +/** + * Tests d'intégration pour EvenementResource + * + *

Tests complets de l'API REST des événements avec authentification et validation des + * permissions. Optimisé pour l'intégration mobile. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@DisplayName("Tests d'intégration - API Événements") +class EvenementResourceTest { + + private static Long evenementTestId; + private static Long organisationTestId; + private static Long membreTestId; + + @BeforeAll + @Transactional + static void setupTestData() { + // Créer une organisation de test + Organisation organisation = + Organisation.builder() + .nom("Union Test API") + .typeOrganisation("ASSOCIATION") + .statut("ACTIVE") + .email("test-api@union.com") + .telephone("0123456789") + .adresse("123 Rue de Test") + .codePostal("75001") + .ville("Paris") + .pays("France") + .actif(true) + .creePar("test@unionflow.dev") + .dateCreation(LocalDateTime.now()) + .build(); + organisation.persist(); + organisationTestId = organisation.id; + + // Créer un membre de test + Membre membre = + Membre.builder() + .numeroMembre("UF2025-API01") + .prenom("Marie") + .nom("Martin") + .email("marie.martin@test.com") + .telephone("0987654321") + .dateNaissance(LocalDate.of(1990, 5, 15)) + .dateAdhesion(LocalDate.now()) + .actif(true) + .organisation(organisation) + .build(); + membre.persist(); + membreTestId = membre.id; + + // Créer un événement de test + Evenement evenement = + Evenement.builder() + .titre("Conférence API Test") + .description("Conférence de test pour l'API") + .dateDebut(LocalDateTime.now().plusDays(15)) + .dateFin(LocalDateTime.now().plusDays(15).plusHours(2)) + .lieu("Centre de conférence Test") + .typeEvenement(TypeEvenement.CONFERENCE) + .statut(StatutEvenement.PLANIFIE) + .capaciteMax(50) + .prix(BigDecimal.valueOf(15.00)) + .inscriptionRequise(true) + .visiblePublic(true) + .actif(true) + .organisation(organisation) + .organisateur(membre) + .creePar("test@unionflow.dev") + .dateCreation(LocalDateTime.now()) + .build(); + evenement.persist(); + evenementTestId = evenement.id; + } + + @Test + @Order(1) + @DisplayName("GET /api/evenements - Lister événements (authentifié)") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testListerEvenements_Authentifie() { + given() + .when() + .get("/api/evenements") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThanOrEqualTo(1)) + .body("[0].titre", notNullValue()) + .body("[0].dateDebut", notNullValue()) + .body("[0].statut", notNullValue()); + } + + @Test + @Order(2) + @DisplayName("GET /api/evenements - Non authentifié") + void testListerEvenements_NonAuthentifie() { + given().when().get("/api/evenements").then().statusCode(401); + } + + @Test + @Order(3) + @DisplayName("GET /api/evenements/{id} - Récupérer événement") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testObtenirEvenement() { + given() + .pathParam("id", evenementTestId) + .when() + .get("/api/evenements/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(evenementTestId.intValue())) + .body("titre", equalTo("Conférence API Test")) + .body("description", equalTo("Conférence de test pour l'API")) + .body("typeEvenement", equalTo("CONFERENCE")) + .body("statut", equalTo("PLANIFIE")) + .body("capaciteMax", equalTo(50)) + .body("prix", equalTo(15.0f)) + .body("inscriptionRequise", equalTo(true)) + .body("visiblePublic", equalTo(true)) + .body("actif", equalTo(true)); + } + + @Test + @Order(4) + @DisplayName("GET /api/evenements/{id} - Événement non trouvé") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testObtenirEvenement_NonTrouve() { + given() + .pathParam("id", 99999) + .when() + .get("/api/evenements/{id}") + .then() + .statusCode(404) + .body("error", equalTo("Événement non trouvé")); + } + + @Test + @Order(5) + @DisplayName("POST /api/evenements - Créer événement (organisateur)") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"ORGANISATEUR_EVENEMENT"}) + void testCreerEvenement_Organisateur() { + String nouvelEvenement = + String.format( + """ + { + "titre": "Nouvel Événement Test", + "description": "Description du nouvel événement", + "dateDebut": "%s", + "dateFin": "%s", + "lieu": "Lieu de test", + "typeEvenement": "FORMATION", + "capaciteMax": 30, + "prix": 20.00, + "inscriptionRequise": true, + "visiblePublic": true, + "organisation": {"id": %d}, + "organisateur": {"id": %d} + } + """, + LocalDateTime.now().plusDays(20).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), + LocalDateTime.now() + .plusDays(20) + .plusHours(3) + .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), + organisationTestId, + membreTestId); + + given() + .contentType(ContentType.JSON) + .body(nouvelEvenement) + .when() + .post("/api/evenements") + .then() + .statusCode(201) + .contentType(ContentType.JSON) + .body("titre", equalTo("Nouvel Événement Test")) + .body("typeEvenement", equalTo("FORMATION")) + .body("capaciteMax", equalTo(30)) + .body("prix", equalTo(20.0f)) + .body("actif", equalTo(true)); + } + + @Test + @Order(6) + @DisplayName("POST /api/evenements - Permissions insuffisantes") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testCreerEvenement_PermissionsInsuffisantes() { + String nouvelEvenement = + """ + { + "titre": "Événement Non Autorisé", + "description": "Test permissions", + "dateDebut": "2025-02-15T10:00:00", + "dateFin": "2025-02-15T12:00:00", + "lieu": "Lieu test", + "typeEvenement": "FORMATION" + } + """; + + given() + .contentType(ContentType.JSON) + .body(nouvelEvenement) + .when() + .post("/api/evenements") + .then() + .statusCode(403); + } + + @Test + @Order(7) + @DisplayName("PUT /api/evenements/{id} - Mettre à jour événement") + @TestSecurity( + user = "admin@unionflow.dev", + roles = {"ADMIN"}) + void testMettreAJourEvenement_Admin() { + String evenementModifie = + String.format( + """ + { + "titre": "Conférence API Test - Modifiée", + "description": "Description mise à jour", + "dateDebut": "%s", + "dateFin": "%s", + "lieu": "Nouveau lieu", + "typeEvenement": "CONFERENCE", + "capaciteMax": 75, + "prix": 25.00, + "inscriptionRequise": true, + "visiblePublic": true + } + """, + LocalDateTime.now().plusDays(16).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), + LocalDateTime.now() + .plusDays(16) + .plusHours(3) + .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + + given() + .pathParam("id", evenementTestId) + .contentType(ContentType.JSON) + .body(evenementModifie) + .when() + .put("/api/evenements/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("titre", equalTo("Conférence API Test - Modifiée")) + .body("description", equalTo("Description mise à jour")) + .body("lieu", equalTo("Nouveau lieu")) + .body("capaciteMax", equalTo(75)) + .body("prix", equalTo(25.0f)); + } + + @Test + @Order(8) + @DisplayName("GET /api/evenements/a-venir - Événements à venir") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testEvenementsAVenir() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/evenements/a-venir") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @Order(9) + @DisplayName("GET /api/evenements/publics - Événements publics (non authentifié)") + void testEvenementsPublics_NonAuthentifie() { + given() + .queryParam("page", 0) + .queryParam("size", 20) + .when() + .get("/api/evenements/publics") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @Order(10) + @DisplayName("GET /api/evenements/recherche - Recherche d'événements") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testRechercherEvenements() { + given() + .queryParam("q", "Conférence") + .queryParam("page", 0) + .queryParam("size", 20) + .when() + .get("/api/evenements/recherche") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @Order(11) + @DisplayName("GET /api/evenements/recherche - Terme de recherche manquant") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testRechercherEvenements_TermeManquant() { + given() + .queryParam("page", 0) + .queryParam("size", 20) + .when() + .get("/api/evenements/recherche") + .then() + .statusCode(400) + .body("error", equalTo("Le terme de recherche est obligatoire")); + } + + @Test + @Order(12) + @DisplayName("GET /api/evenements/type/{type} - Événements par type") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testEvenementsParType() { + given() + .pathParam("type", "CONFERENCE") + .queryParam("page", 0) + .queryParam("size", 20) + .when() + .get("/api/evenements/type/{type}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @Order(13) + @DisplayName("PATCH /api/evenements/{id}/statut - Changer statut") + @TestSecurity( + user = "admin@unionflow.dev", + roles = {"ADMIN"}) + void testChangerStatut() { + given() + .pathParam("id", evenementTestId) + .queryParam("statut", "CONFIRME") + .when() + .patch("/api/evenements/{id}/statut") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("statut", equalTo("CONFIRME")); + } + + @Test + @Order(14) + @DisplayName("GET /api/evenements/statistiques - Statistiques") + @TestSecurity( + user = "admin@unionflow.dev", + roles = {"ADMIN"}) + void testObtenirStatistiques() { + given() + .when() + .get("/api/evenements/statistiques") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("total", notNullValue()) + .body("actifs", notNullValue()) + .body("timestamp", notNullValue()); + } + + @Test + @Order(15) + @DisplayName("DELETE /api/evenements/{id} - Supprimer événement") + @TestSecurity( + user = "admin@unionflow.dev", + roles = {"ADMIN"}) + void testSupprimerEvenement() { + given() + .pathParam("id", evenementTestId) + .when() + .delete("/api/evenements/{id}") + .then() + .statusCode(204); + } + + @Test + @Order(16) + @DisplayName("Pagination - Paramètres valides") + @TestSecurity( + user = "marie.martin@test.com", + roles = {"MEMBRE"}) + void testPagination() { + given() + .queryParam("page", 0) + .queryParam("size", 5) + .queryParam("sort", "titre") + .queryParam("direction", "asc") + .when() + .get("/api/evenements") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/HealthResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/HealthResourceTest.java new file mode 100644 index 0000000..fbe56d6 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/HealthResourceTest.java @@ -0,0 +1,69 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests pour HealthResource + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@QuarkusTest +@DisplayName("Tests HealthResource") +class HealthResourceTest { + + @Test + @DisplayName("Test GET /api/status - Statut du serveur") + void testGetStatus() { + given() + .when() + .get("/api/status") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("status", equalTo("UP")) + .body("service", equalTo("UnionFlow Server")) + .body("version", equalTo("1.0.0")) + .body("message", equalTo("Serveur opérationnel")) + .body("timestamp", notNullValue()); + } + + @Test + @DisplayName("Test GET /api/status - Vérification de la structure de la réponse") + void testGetStatusStructure() { + given() + .when() + .get("/api/status") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("$", hasKey("status")) + .body("$", hasKey("service")) + .body("$", hasKey("version")) + .body("$", hasKey("timestamp")) + .body("$", hasKey("message")); + } + + @Test + @DisplayName("Test GET /api/status - Vérification du Content-Type") + void testGetStatusContentType() { + given().when().get("/api/status").then().statusCode(200).contentType("application/json"); + } + + @Test + @DisplayName("Test GET /api/status - Réponse rapide") + void testGetStatusPerformance() { + given() + .when() + .get("/api/status") + .then() + .statusCode(200) + .time(lessThan(1000L)); // Moins d'1 seconde + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceCompleteIntegrationTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceCompleteIntegrationTest.java new file mode 100644 index 0000000..ea643b5 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceCompleteIntegrationTest.java @@ -0,0 +1,318 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Tests d'intégration complets pour MembreResource Couvre tous les endpoints et cas d'erreur */ +@QuarkusTest +@DisplayName("Tests d'intégration complets MembreResource") +class MembreResourceCompleteIntegrationTest { + + @Test + @DisplayName("POST /api/membres - Création avec email existant") + void testCreerMembreEmailExistant() { + // Créer un premier membre + String membreJson1 = + """ + { + "numeroMembre": "UF2025-EXIST01", + "prenom": "Premier", + "nom": "Membre", + "email": "existe@test.com", + "telephone": "221701234567", + "dateNaissance": "1990-05-15", + "dateAdhesion": "2025-01-10", + "actif": true + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson1) + .when() + .post("/api/membres") + .then() + .statusCode(anyOf(is(201), is(400))); // 201 si nouveau, 400 si existe déjà + + // Essayer de créer un deuxième membre avec le même email + String membreJson2 = + """ + { + "numeroMembre": "UF2025-EXIST02", + "prenom": "Deuxieme", + "nom": "Membre", + "email": "existe@test.com", + "telephone": "221701234568", + "dateNaissance": "1985-08-20", + "dateAdhesion": "2025-01-10", + "actif": true + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson2) + .when() + .post("/api/membres") + .then() + .statusCode(400) + .body("message", notNullValue()); + } + + @Test + @DisplayName("POST /api/membres - Validation des champs obligatoires") + void testCreerMembreValidationChamps() { + // Test avec prénom manquant + String membreSansPrenom = + """ + { + "nom": "Test", + "email": "test.sans.prenom@test.com", + "telephone": "221701234567" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreSansPrenom) + .when() + .post("/api/membres") + .then() + .statusCode(400); + + // Test avec email invalide + String membreEmailInvalide = + """ + { + "prenom": "Test", + "nom": "Test", + "email": "email-invalide", + "telephone": "221701234567" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreEmailInvalide) + .when() + .post("/api/membres") + .then() + .statusCode(400); + } + + @Test + @DisplayName("PUT /api/membres/{id} - Mise à jour membre existant") + void testMettreAJourMembreExistant() { + // D'abord créer un membre + String membreOriginal = + """ + { + "numeroMembre": "UF2025-UPDATE01", + "prenom": "Original", + "nom": "Membre", + "email": "original.update@test.com", + "telephone": "221701234567", + "dateNaissance": "1990-05-15", + "dateAdhesion": "2025-01-10", + "actif": true + } + """; + + // Créer le membre (peut réussir ou échouer si existe déjà) + given() + .contentType(ContentType.JSON) + .body(membreOriginal) + .when() + .post("/api/membres") + .then() + .statusCode(anyOf(is(201), is(400))); + + // Essayer de mettre à jour avec ID 1 (peut exister ou non) + String membreMisAJour = + """ + { + "numeroMembre": "UF2025-UPDATE01", + "prenom": "Modifie", + "nom": "Membre", + "email": "modifie.update@test.com", + "telephone": "221701234567", + "dateNaissance": "1990-05-15", + "dateAdhesion": "2025-01-10", + "actif": true + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreMisAJour) + .when() + .put("/api/membres/1") + .then() + .statusCode(anyOf(is(200), is(400))); // 200 si trouvé, 400 si non trouvé + } + + @Test + @DisplayName("PUT /api/membres/{id} - Membre inexistant") + void testMettreAJourMembreInexistant() { + String membreJson = + """ + { + "numeroMembre": "UF2025-INEXIST01", + "prenom": "Inexistant", + "nom": "Membre", + "email": "inexistant@test.com", + "telephone": "221701234567", + "dateNaissance": "1990-05-15", + "dateAdhesion": "2025-01-10", + "actif": true + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson) + .when() + .put("/api/membres/99999") + .then() + .statusCode(400) + .body("message", notNullValue()); + } + + @Test + @DisplayName("DELETE /api/membres/{id} - Désactiver membre existant") + void testDesactiverMembreExistant() { + // Essayer de désactiver le membre ID 1 (peut exister ou non) + given() + .when() + .delete("/api/membres/1") + .then() + .statusCode(anyOf(is(204), is(404))); // 204 si trouvé, 404 si non trouvé + } + + @Test + @DisplayName("DELETE /api/membres/{id} - Membre inexistant") + void testDesactiverMembreInexistant() { + given() + .when() + .delete("/api/membres/99999") + .then() + .statusCode(404) + .body("message", notNullValue()); + } + + @Test + @DisplayName("GET /api/membres/{id} - Membre existant") + void testObtenirMembreExistant() { + // Essayer d'obtenir le membre ID 1 (peut exister ou non) + given() + .when() + .get("/api/membres/1") + .then() + .statusCode(anyOf(is(200), is(404))); // 200 si trouvé, 404 si non trouvé + } + + @Test + @DisplayName("GET /api/membres/{id} - Membre inexistant") + void testObtenirMembreInexistant() { + given() + .when() + .get("/api/membres/99999") + .then() + .statusCode(404) + .body("message", equalTo("Membre non trouvé")); + } + + @Test + @DisplayName("GET /api/membres/recherche - Recherche avec terme null") + void testRechercherMembresTermeNull() { + given() + .when() + .get("/api/membres/recherche") + .then() + .statusCode(400) + .body("message", equalTo("Le terme de recherche est requis")); + } + + @Test + @DisplayName("GET /api/membres/recherche - Recherche avec terme valide") + void testRechercherMembresTermeValide() { + given() + .queryParam("q", "test") + .when() + .get("/api/membres/recherche") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("Test des headers HTTP") + void testHeadersHTTP() { + // Test avec différents Accept headers + given() + .accept(ContentType.JSON) + .when() + .get("/api/membres") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + + given() + .accept(ContentType.XML) + .when() + .get("/api/membres") + .then() + .statusCode(anyOf(is(200), is(406))); // 200 si supporté, 406 si non supporté + } + + @Test + @DisplayName("Test des méthodes HTTP non supportées") + void testMethodesHTTPNonSupportees() { + // OPTIONS peut être supporté ou non + given().when().options("/api/membres").then().statusCode(anyOf(is(200), is(405))); + + // HEAD peut être supporté ou non + given().when().head("/api/membres").then().statusCode(anyOf(is(200), is(405))); + } + + @Test + @DisplayName("Test de performance et robustesse") + void testPerformanceEtRobustesse() { + // Test avec une grande quantité de données + StringBuilder largeJson = new StringBuilder(); + largeJson.append("{"); + largeJson.append("\"prenom\": \"").append("A".repeat(100)).append("\","); + largeJson.append("\"nom\": \"").append("B".repeat(100)).append("\","); + largeJson.append("\"email\": \"large.test@test.com\","); + largeJson.append("\"telephone\": \"221701234567\""); + largeJson.append("}"); + + given() + .contentType(ContentType.JSON) + .body(largeJson.toString()) + .when() + .post("/api/membres") + .then() + .statusCode(anyOf(is(201), is(400))); // Peut réussir ou échouer selon la validation + } + + @Test + @DisplayName("Test de gestion des erreurs serveur") + void testGestionErreursServeur() { + // Test avec des données qui peuvent causer des erreurs internes + String jsonMalformed = "{ invalid json }"; + + given() + .contentType(ContentType.JSON) + .body(jsonMalformed) + .when() + .post("/api/membres") + .then() + .statusCode(400); // Bad Request pour JSON malformé + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceSimpleIntegrationTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceSimpleIntegrationTest.java new file mode 100644 index 0000000..e4aa5b3 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceSimpleIntegrationTest.java @@ -0,0 +1,259 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Tests d'intégration simples pour MembreResource + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@QuarkusTest +@DisplayName("Tests d'intégration simples MembreResource") +class MembreResourceSimpleIntegrationTest { + + @Test + @DisplayName("GET /api/membres - Lister tous les membres actifs") + void testListerMembres() { + given() + .when() + .get("/api/membres") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("$", notNullValue()); + } + + @Test + @DisplayName("GET /api/membres/999 - Membre non trouvé") + void testObtenirMembreNonTrouve() { + given() + .when() + .get("/api/membres/999") + .then() + .statusCode(404) + .contentType(ContentType.JSON) + .body("message", equalTo("Membre non trouvé")); + } + + @Test + @DisplayName("POST /api/membres - Données invalides") + void testCreerMembreDonneesInvalides() { + String membreJson = + """ + { + "prenom": "", + "nom": "", + "email": "email-invalide", + "telephone": "123", + "dateNaissance": "2030-01-01" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson) + .when() + .post("/api/membres") + .then() + .statusCode(400); + } + + @Test + @DisplayName("PUT /api/membres/999 - Membre non trouvé") + void testMettreAJourMembreNonTrouve() { + String membreJson = + """ + { + "prenom": "Pierre", + "nom": "Martin", + "email": "pierre.martin@test.com" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson) + .when() + .put("/api/membres/999") + .then() + .statusCode(400); // Simplement vérifier le code de statut + } + + @Test + @DisplayName("DELETE /api/membres/999 - Membre non trouvé") + void testDesactiverMembreNonTrouve() { + given() + .when() + .delete("/api/membres/999") + .then() + .statusCode(404) + .contentType(ContentType.JSON) + .body("message", containsString("Membre non trouvé")); + } + + @Test + @DisplayName("GET /api/membres/recherche - Terme manquant") + void testRechercherMembresTermeManquant() { + given() + .when() + .get("/api/membres/recherche") + .then() + .statusCode(400) + .contentType(ContentType.JSON) + .body("message", equalTo("Le terme de recherche est requis")); + } + + @Test + @DisplayName("GET /api/membres/recherche - Terme vide") + void testRechercherMembresTermeVide() { + given() + .queryParam("q", " ") + .when() + .get("/api/membres/recherche") + .then() + .statusCode(400) + .contentType(ContentType.JSON) + .body("message", equalTo("Le terme de recherche est requis")); + } + + @Test + @DisplayName("GET /api/membres/recherche - Recherche valide") + void testRechercherMembresValide() { + given() + .queryParam("q", "test") + .when() + .get("/api/membres/recherche") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("$", notNullValue()); + } + + @Test + @DisplayName("GET /api/membres/stats - Statistiques") + void testObtenirStatistiques() { + given() + .when() + .get("/api/membres/stats") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("nombreMembresActifs", notNullValue()) + .body("timestamp", notNullValue()); + } + + @Test + @DisplayName("POST /api/membres - Membre valide") + void testCreerMembreValide() { + String membreJson = + """ + { + "prenom": "Jean", + "nom": "Dupont", + "email": "jean.dupont.test@example.com", + "telephone": "221701234567", + "dateNaissance": "1990-05-15", + "dateAdhesion": "2025-01-10" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreJson) + .when() + .post("/api/membres") + .then() + .statusCode(anyOf(is(201), is(400))); // 201 si succès, 400 si email existe déjà + } + + @Test + @DisplayName("Test des endpoints avec différents content types") + void testContentTypes() { + // Test avec Accept header + given() + .accept(ContentType.JSON) + .when() + .get("/api/membres") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + + // Test avec Accept header pour les stats + given() + .accept(ContentType.JSON) + .when() + .get("/api/membres/stats") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + + @Test + @DisplayName("Test des méthodes HTTP non supportées") + void testMethodesNonSupportees() { + // PATCH n'est pas supporté + given().when().patch("/api/membres/1").then().statusCode(405); // Method Not Allowed + } + + @Test + @DisplayName("PUT /api/membres/{id} - Mise à jour avec données invalides") + void testMettreAJourMembreAvecDonneesInvalides() { + String membreInvalideJson = + """ + { + "prenom": "", + "nom": "", + "email": "email-invalide" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreInvalideJson) + .when() + .put("/api/membres/1") + .then() + .statusCode(400); + } + + @Test + @DisplayName("POST /api/membres - Données invalides") + void testCreerMembreAvecDonneesInvalides() { + String membreInvalideJson = + """ + { + "prenom": "", + "nom": "", + "email": "email-invalide" + } + """; + + given() + .contentType(ContentType.JSON) + .body(membreInvalideJson) + .when() + .post("/api/membres") + .then() + .statusCode(400); + } + + @Test + @DisplayName("GET /api/membres/recherche - Terme avec espaces seulement") + void testRechercherMembresTermeAvecEspacesUniquement() { + given() + .queryParam("q", " ") + .when() + .get("/api/membres/recherche") + .then() + .statusCode(400) + .contentType(ContentType.JSON) + .body("message", equalTo("Le terme de recherche est requis")); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceTest.java new file mode 100644 index 0000000..5f658e7 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/MembreResourceTest.java @@ -0,0 +1,275 @@ +package dev.lions.unionflow.server.resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +import dev.lions.unionflow.server.api.dto.membre.MembreDTO; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.service.MembreService; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import jakarta.ws.rs.core.Response; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Tests pour MembreResource + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("Tests MembreResource") +class MembreResourceTest { + + @InjectMocks MembreResource membreResource; + + @Mock MembreService membreService; + + @Test + @DisplayName("Test de l'existence de la classe MembreResource") + void testMembreResourceExists() { + // Given & When & Then + assertThat(MembreResource.class).isNotNull(); + assertThat(membreResource).isNotNull(); + } + + @Test + @DisplayName("Test de l'annotation Path") + void testPathAnnotation() { + // Given & When & Then + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Path.class)).isNotNull(); + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Path.class).value()) + .isEqualTo("/api/membres"); + } + + @Test + @DisplayName("Test de l'annotation ApplicationScoped") + void testApplicationScopedAnnotation() { + // Given & When & Then + assertThat( + MembreResource.class.getAnnotation(jakarta.enterprise.context.ApplicationScoped.class)) + .isNotNull(); + } + + @Test + @DisplayName("Test de l'annotation Produces") + void testProducesAnnotation() { + // Given & When & Then + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Produces.class)).isNotNull(); + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Produces.class).value()) + .contains("application/json"); + } + + @Test + @DisplayName("Test de l'annotation Consumes") + void testConsumesAnnotation() { + // Given & When & Then + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Consumes.class)).isNotNull(); + assertThat(MembreResource.class.getAnnotation(jakarta.ws.rs.Consumes.class).value()) + .contains("application/json"); + } + + @Test + @DisplayName("Test des méthodes du resource") + void testResourceMethods() throws NoSuchMethodException { + // Given & When & Then + assertThat(MembreResource.class.getMethod("listerMembres")).isNotNull(); + assertThat(MembreResource.class.getMethod("obtenirMembre", Long.class)).isNotNull(); + assertThat(MembreResource.class.getMethod("creerMembre", Membre.class)).isNotNull(); + assertThat(MembreResource.class.getMethod("mettreAJourMembre", Long.class, Membre.class)) + .isNotNull(); + assertThat(MembreResource.class.getMethod("desactiverMembre", Long.class)).isNotNull(); + assertThat(MembreResource.class.getMethod("rechercherMembres", String.class)).isNotNull(); + assertThat(MembreResource.class.getMethod("obtenirStatistiques")).isNotNull(); + } + + @Test + @DisplayName("Test de la création d'instance") + void testInstanceCreation() { + // Given & When + MembreResource resource = new MembreResource(); + + // Then + assertThat(resource).isNotNull(); + } + + @Test + @DisplayName("Test listerMembres") + void testListerMembres() { + // Given + List membres = + Arrays.asList(createTestMembre("Jean", "Dupont"), createTestMembre("Marie", "Martin")); + List membresDTO = + Arrays.asList( + createTestMembreDTO("Jean", "Dupont"), createTestMembreDTO("Marie", "Martin")); + + when(membreService.listerMembresActifs(any(Page.class), any(Sort.class))).thenReturn(membres); + when(membreService.convertToDTOList(membres)).thenReturn(membresDTO); + + // When + Response response = membreResource.listerMembres(0, 20, "nom", "asc"); + + // Then + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isEqualTo(membresDTO); + } + + @Test + @DisplayName("Test obtenirMembre") + void testObtenirMembre() { + // Given + Long id = 1L; + Membre membre = createTestMembre("Jean", "Dupont"); + when(membreService.trouverParId(id)).thenReturn(Optional.of(membre)); + + // When + Response response = membreResource.obtenirMembre(id); + + // Then + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isEqualTo(membre); + } + + @Test + @DisplayName("Test obtenirMembre - membre non trouvé") + void testObtenirMembreNonTrouve() { + // Given + Long id = 999L; + when(membreService.trouverParId(id)).thenReturn(Optional.empty()); + + // When + Response response = membreResource.obtenirMembre(id); + + // Then + assertThat(response.getStatus()).isEqualTo(404); + } + + @Test + @DisplayName("Test creerMembre") + void testCreerMembre() { + // Given + MembreDTO membreDTO = createTestMembreDTO("Jean", "Dupont"); + Membre membre = createTestMembre("Jean", "Dupont"); + Membre membreCreated = createTestMembre("Jean", "Dupont"); + membreCreated.id = 1L; + MembreDTO membreCreatedDTO = createTestMembreDTO("Jean", "Dupont"); + + when(membreService.convertFromDTO(any(MembreDTO.class))).thenReturn(membre); + when(membreService.creerMembre(any(Membre.class))).thenReturn(membreCreated); + when(membreService.convertToDTO(any(Membre.class))).thenReturn(membreCreatedDTO); + + // When + Response response = membreResource.creerMembre(membreDTO); + + // Then + assertThat(response.getStatus()).isEqualTo(201); + assertThat(response.getEntity()).isEqualTo(membreCreatedDTO); + } + + @Test + @DisplayName("Test mettreAJourMembre") + void testMettreAJourMembre() { + // Given + Long id = 1L; + MembreDTO membreDTO = createTestMembreDTO("Jean", "Dupont"); + Membre membre = createTestMembre("Jean", "Dupont"); + Membre membreUpdated = createTestMembre("Jean", "Martin"); + membreUpdated.id = id; + MembreDTO membreUpdatedDTO = createTestMembreDTO("Jean", "Martin"); + + when(membreService.convertFromDTO(any(MembreDTO.class))).thenReturn(membre); + when(membreService.mettreAJourMembre(anyLong(), any(Membre.class))).thenReturn(membreUpdated); + when(membreService.convertToDTO(any(Membre.class))).thenReturn(membreUpdatedDTO); + + // When + Response response = membreResource.mettreAJourMembre(id, membreDTO); + + // Then + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isEqualTo(membreUpdatedDTO); + } + + @Test + @DisplayName("Test desactiverMembre") + void testDesactiverMembre() { + // Given + Long id = 1L; + + // When + Response response = membreResource.desactiverMembre(id); + + // Then + assertThat(response.getStatus()).isEqualTo(204); + } + + @Test + @DisplayName("Test rechercherMembres") + void testRechercherMembres() { + // Given + String recherche = "Jean"; + List membres = Arrays.asList(createTestMembre("Jean", "Dupont")); + List membresDTO = Arrays.asList(createTestMembreDTO("Jean", "Dupont")); + when(membreService.rechercherMembres(anyString(), any(Page.class), any(Sort.class))) + .thenReturn(membres); + when(membreService.convertToDTOList(membres)).thenReturn(membresDTO); + + // When + Response response = membreResource.rechercherMembres(recherche, 0, 20, "nom", "asc"); + + // Then + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isEqualTo(membresDTO); + } + + @Test + @DisplayName("Test obtenirStatistiques") + void testObtenirStatistiques() { + // Given + long count = 42L; + when(membreService.compterMembresActifs()).thenReturn(count); + + // When + Response response = membreResource.obtenirStatistiques(); + + // Then + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isInstanceOf(java.util.Map.class); + } + + private Membre createTestMembre(String prenom, String nom) { + Membre membre = new Membre(); + membre.setPrenom(prenom); + membre.setNom(nom); + membre.setEmail(prenom.toLowerCase() + "." + nom.toLowerCase() + "@test.com"); + membre.setTelephone("221701234567"); + membre.setDateNaissance(LocalDate.of(1990, 1, 1)); + membre.setDateAdhesion(LocalDate.now()); + membre.setActif(true); + membre.setNumeroMembre("UF-2025-TEST01"); + return membre; + } + + private MembreDTO createTestMembreDTO(String prenom, String nom) { + MembreDTO dto = new MembreDTO(); + dto.setPrenom(prenom); + dto.setNom(nom); + dto.setEmail(prenom.toLowerCase() + "." + nom.toLowerCase() + "@test.com"); + dto.setTelephone("221701234567"); + dto.setDateNaissance(LocalDate.of(1990, 1, 1)); + dto.setDateAdhesion(LocalDate.now()); + dto.setStatut("ACTIF"); + dto.setNumeroMembre("UF-2025-TEST01"); + dto.setAssociationId(1L); + return dto; + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java b/src/test.bak/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java new file mode 100644 index 0000000..6a313a3 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java @@ -0,0 +1,345 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; + +import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +/** + * Tests d'intégration pour OrganisationResource + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +class OrganisationResourceTest { + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testCreerOrganisation_Success() { + OrganisationDTO organisation = createTestOrganisationDTO(); + + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .post("/api/organisations") + .then() + .statusCode(201) + .body("nom", equalTo("Lions Club Test API")) + .body("email", equalTo("testapi@lionsclub.org")) + .body("actif", equalTo(true)); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testCreerOrganisation_EmailInvalide() { + OrganisationDTO organisation = createTestOrganisationDTO(); + organisation.setEmail("email-invalide"); + + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .post("/api/organisations") + .then() + .statusCode(400); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testCreerOrganisation_NomVide() { + OrganisationDTO organisation = createTestOrganisationDTO(); + organisation.setNom(""); + + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .post("/api/organisations") + .then() + .statusCode(400); + } + + @Test + void testCreerOrganisation_NonAuthentifie() { + OrganisationDTO organisation = createTestOrganisationDTO(); + + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .post("/api/organisations") + .then() + .statusCode(401); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testListerOrganisations_Success() { + given() + .when() + .get("/api/organisations") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testListerOrganisations_AvecPagination() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/organisations") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testListerOrganisations_AvecRecherche() { + given() + .queryParam("recherche", "Lions") + .when() + .get("/api/organisations") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + void testListerOrganisations_NonAuthentifie() { + given().when().get("/api/organisations").then().statusCode(401); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testObtenirOrganisation_NonTrouvee() { + given() + .when() + .get("/api/organisations/99999") + .then() + .statusCode(404) + .body("error", equalTo("Organisation non trouvée")); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testMettreAJourOrganisation_NonTrouvee() { + OrganisationDTO organisation = createTestOrganisationDTO(); + + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .put("/api/organisations/99999") + .then() + .statusCode(404) + .body("error", containsString("Organisation non trouvée")); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testSupprimerOrganisation_NonTrouvee() { + given() + .when() + .delete("/api/organisations/99999") + .then() + .statusCode(404) + .body("error", containsString("Organisation non trouvée")); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testRechercheAvancee_Success() { + given() + .queryParam("nom", "Lions") + .queryParam("ville", "Abidjan") + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/organisations/recherche") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testRechercheAvancee_SansCriteres() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .get("/api/organisations/recherche") + .then() + .statusCode(200) + .body("size()", greaterThanOrEqualTo(0)); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testActiverOrganisation_NonTrouvee() { + given() + .when() + .post("/api/organisations/99999/activer") + .then() + .statusCode(404) + .body("error", containsString("Organisation non trouvée")); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testSuspendreOrganisation_NonTrouvee() { + given() + .when() + .post("/api/organisations/99999/suspendre") + .then() + .statusCode(404) + .body("error", containsString("Organisation non trouvée")); + } + + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testObtenirStatistiques_Success() { + given() + .when() + .get("/api/organisations/statistiques") + .then() + .statusCode(200) + .body("totalOrganisations", notNullValue()) + .body("organisationsActives", notNullValue()) + .body("organisationsInactives", notNullValue()) + .body("nouvellesOrganisations30Jours", notNullValue()) + .body("tauxActivite", notNullValue()) + .body("timestamp", notNullValue()); + } + + @Test + void testObtenirStatistiques_NonAuthentifie() { + given().when().get("/api/organisations/statistiques").then().statusCode(401); + } + + /** Test de workflow complet : création, lecture, mise à jour, suppression */ + @Test + @TestSecurity( + user = "testUser", + roles = {"ADMIN"}) + void testWorkflowComplet() { + // 1. Créer une organisation + OrganisationDTO organisation = createTestOrganisationDTO(); + organisation.setNom("Lions Club Workflow Test"); + organisation.setEmail("workflow@lionsclub.org"); + + String location = + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .post("/api/organisations") + .then() + .statusCode(201) + .extract() + .header("Location"); + + // Extraire l'ID de l'organisation créée + String organisationId = location.substring(location.lastIndexOf("/") + 1); + + // 2. Lire l'organisation créée + given() + .when() + .get("/api/organisations/" + organisationId) + .then() + .statusCode(200) + .body("nom", equalTo("Lions Club Workflow Test")) + .body("email", equalTo("workflow@lionsclub.org")); + + // 3. Mettre à jour l'organisation + organisation.setDescription("Description mise à jour"); + given() + .contentType(ContentType.JSON) + .body(organisation) + .when() + .put("/api/organisations/" + organisationId) + .then() + .statusCode(200) + .body("description", equalTo("Description mise à jour")); + + // 4. Suspendre l'organisation + given() + .when() + .post("/api/organisations/" + organisationId + "/suspendre") + .then() + .statusCode(200); + + // 5. Activer l'organisation + given().when().post("/api/organisations/" + organisationId + "/activer").then().statusCode(200); + + // 6. Supprimer l'organisation (soft delete) + given().when().delete("/api/organisations/" + organisationId).then().statusCode(204); + } + + /** Crée un DTO d'organisation pour les tests */ + private OrganisationDTO createTestOrganisationDTO() { + OrganisationDTO dto = new OrganisationDTO(); + dto.setId(UUID.randomUUID()); + dto.setNom("Lions Club Test API"); + dto.setNomCourt("LC Test API"); + dto.setEmail("testapi@lionsclub.org"); + dto.setDescription("Organisation de test pour l'API"); + dto.setTelephone("+225 01 02 03 04 05"); + dto.setAdresse("123 Rue de Test API"); + dto.setVille("Abidjan"); + dto.setCodePostal("00225"); + dto.setRegion("Lagunes"); + dto.setPays("Côte d'Ivoire"); + dto.setSiteWeb("https://testapi.lionsclub.org"); + dto.setObjectifs("Servir la communauté"); + dto.setActivitesPrincipales("Actions sociales et humanitaires"); + dto.setNombreMembres(0); + dto.setDateCreation(LocalDateTime.now()); + dto.setActif(true); + dto.setVersion(0L); + + return dto; + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/service/AideServiceTest.java b/src/test.bak/java/dev/lions/unionflow/server/service/AideServiceTest.java new file mode 100644 index 0000000..e82ad49 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/service/AideServiceTest.java @@ -0,0 +1,327 @@ +package dev.lions.unionflow.server.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import dev.lions.unionflow.server.api.dto.solidarite.aide.AideDTO; +import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; +import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; +import dev.lions.unionflow.server.entity.Aide; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.AideRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.security.KeycloakService; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +/** + * Tests unitaires pour AideService + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +@DisplayName("AideService - Tests unitaires") +class AideServiceTest { + + @Inject AideService aideService; + + @Mock AideRepository aideRepository; + + @Mock MembreRepository membreRepository; + + @Mock OrganisationRepository organisationRepository; + + @Mock KeycloakService keycloakService; + + private Membre membreTest; + private Organisation organisationTest; + private Aide aideTest; + private AideDTO aideDTOTest; + + @BeforeEach + void setUp() { + // Membre de test + membreTest = new Membre(); + membreTest.id = 1L; + membreTest.setNumeroMembre("UF-2025-TEST001"); + membreTest.setNom("Dupont"); + membreTest.setPrenom("Jean"); + membreTest.setEmail("jean.dupont@test.com"); + membreTest.setActif(true); + + // Organisation de test + organisationTest = new Organisation(); + organisationTest.id = 1L; + organisationTest.setNom("Lions Club Test"); + organisationTest.setEmail("contact@lionstest.com"); + organisationTest.setActif(true); + + // Aide de test + aideTest = new Aide(); + aideTest.id = 1L; + aideTest.setNumeroReference("AIDE-2025-TEST01"); + aideTest.setTitre("Aide médicale urgente"); + aideTest.setDescription("Demande d'aide pour frais médicaux urgents"); + aideTest.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX); + aideTest.setMontantDemande(new BigDecimal("500000.00")); + aideTest.setStatut(StatutAide.EN_ATTENTE); + aideTest.setPriorite("URGENTE"); + aideTest.setMembreDemandeur(membreTest); + aideTest.setOrganisation(organisationTest); + aideTest.setActif(true); + aideTest.setDateCreation(LocalDateTime.now()); + + // DTO de test + aideDTOTest = new AideDTO(); + aideDTOTest.setId(UUID.randomUUID()); + aideDTOTest.setNumeroReference("AIDE-2025-TEST01"); + aideDTOTest.setTitre("Aide médicale urgente"); + aideDTOTest.setDescription("Demande d'aide pour frais médicaux urgents"); + aideDTOTest.setTypeAide("MEDICALE"); + aideDTOTest.setMontantDemande(new BigDecimal("500000.00")); + aideDTOTest.setStatut("EN_ATTENTE"); + aideDTOTest.setPriorite("URGENTE"); + aideDTOTest.setMembreDemandeurId(UUID.randomUUID()); + aideDTOTest.setAssociationId(UUID.randomUUID()); + aideDTOTest.setActif(true); + } + + @Nested + @DisplayName("Tests de création d'aide") + class CreationAideTests { + + @Test + @DisplayName("Création d'aide réussie") + void testCreerAide_Success() { + // Given + when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest)); + when(organisationRepository.findByIdOptional(anyLong())) + .thenReturn(Optional.of(organisationTest)); + when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com"); + + ArgumentCaptor aideCaptor = ArgumentCaptor.forClass(Aide.class); + doNothing().when(aideRepository).persist(aideCaptor.capture()); + + // When + AideDTO result = aideService.creerAide(aideDTOTest); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTitre()).isEqualTo(aideDTOTest.getTitre()); + assertThat(result.getDescription()).isEqualTo(aideDTOTest.getDescription()); + + Aide aidePersistee = aideCaptor.getValue(); + assertThat(aidePersistee.getTitre()).isEqualTo(aideDTOTest.getTitre()); + assertThat(aidePersistee.getMembreDemandeur()).isEqualTo(membreTest); + assertThat(aidePersistee.getOrganisation()).isEqualTo(organisationTest); + assertThat(aidePersistee.getCreePar()).isEqualTo("admin@test.com"); + + verify(aideRepository).persist(any(Aide.class)); + } + + @Test + @DisplayName("Création d'aide - Membre non trouvé") + void testCreerAide_MembreNonTrouve() { + // Given + when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> aideService.creerAide(aideDTOTest)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining("Membre demandeur non trouvé"); + + verify(aideRepository, never()).persist(any(Aide.class)); + } + + @Test + @DisplayName("Création d'aide - Organisation non trouvée") + void testCreerAide_OrganisationNonTrouvee() { + // Given + when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest)); + when(organisationRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> aideService.creerAide(aideDTOTest)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining("Organisation non trouvée"); + + verify(aideRepository, never()).persist(any(Aide.class)); + } + + @Test + @DisplayName("Création d'aide - Montant invalide") + void testCreerAide_MontantInvalide() { + // Given + aideDTOTest.setMontantDemande(new BigDecimal("-100.00")); + when(membreRepository.findByIdOptional(anyLong())).thenReturn(Optional.of(membreTest)); + when(organisationRepository.findByIdOptional(anyLong())) + .thenReturn(Optional.of(organisationTest)); + + // When & Then + assertThatThrownBy(() -> aideService.creerAide(aideDTOTest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Le montant demandé doit être positif"); + + verify(aideRepository, never()).persist(any(Aide.class)); + } + } + + @Nested + @DisplayName("Tests de récupération d'aide") + class RecuperationAideTests { + + @Test + @DisplayName("Récupération d'aide par ID réussie") + void testObtenirAideParId_Success() { + // Given + when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest)); + when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com"); + + // When + AideDTO result = aideService.obtenirAideParId(1L); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTitre()).isEqualTo(aideTest.getTitre()); + assertThat(result.getDescription()).isEqualTo(aideTest.getDescription()); + assertThat(result.getStatut()).isEqualTo(aideTest.getStatut().name()); + } + + @Test + @DisplayName("Récupération d'aide par ID - Non trouvée") + void testObtenirAideParId_NonTrouvee() { + // Given + when(aideRepository.findByIdOptional(999L)).thenReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> aideService.obtenirAideParId(999L)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining("Demande d'aide non trouvée"); + } + + @Test + @DisplayName("Récupération d'aide par référence réussie") + void testObtenirAideParReference_Success() { + // Given + String reference = "AIDE-2025-TEST01"; + when(aideRepository.findByNumeroReference(reference)).thenReturn(Optional.of(aideTest)); + when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com"); + + // When + AideDTO result = aideService.obtenirAideParReference(reference); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getNumeroReference()).isEqualTo(reference); + } + } + + @Nested + @DisplayName("Tests de mise à jour d'aide") + class MiseAJourAideTests { + + @Test + @DisplayName("Mise à jour d'aide réussie") + void testMettreAJourAide_Success() { + // Given + when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest)); + when(keycloakService.getCurrentUserEmail()).thenReturn("jean.dupont@test.com"); + when(keycloakService.hasRole("admin")).thenReturn(false); + when(keycloakService.hasRole("gestionnaire_aide")).thenReturn(false); + + AideDTO aideMiseAJour = new AideDTO(); + aideMiseAJour.setTitre("Titre modifié"); + aideMiseAJour.setDescription("Description modifiée"); + aideMiseAJour.setMontantDemande(new BigDecimal("600000.00")); + aideMiseAJour.setPriorite("HAUTE"); + + // When + AideDTO result = aideService.mettreAJourAide(1L, aideMiseAJour); + + // Then + assertThat(result).isNotNull(); + assertThat(aideTest.getTitre()).isEqualTo("Titre modifié"); + assertThat(aideTest.getDescription()).isEqualTo("Description modifiée"); + assertThat(aideTest.getMontantDemande()).isEqualTo(new BigDecimal("600000.00")); + assertThat(aideTest.getPriorite()).isEqualTo("HAUTE"); + } + + @Test + @DisplayName("Mise à jour d'aide - Accès non autorisé") + void testMettreAJourAide_AccesNonAutorise() { + // Given + when(aideRepository.findByIdOptional(1L)).thenReturn(Optional.of(aideTest)); + when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com"); + when(keycloakService.hasRole("admin")).thenReturn(false); + when(keycloakService.hasRole("gestionnaire_aide")).thenReturn(false); + + AideDTO aideMiseAJour = new AideDTO(); + aideMiseAJour.setTitre("Titre modifié"); + + // When & Then + assertThatThrownBy(() -> aideService.mettreAJourAide(1L, aideMiseAJour)) + .isInstanceOf(SecurityException.class) + .hasMessageContaining("Vous n'avez pas les permissions"); + } + } + + @Nested + @DisplayName("Tests de conversion DTO/Entity") + class ConversionTests { + + @Test + @DisplayName("Conversion Entity vers DTO") + void testConvertToDTO() { + // When + AideDTO result = aideService.convertToDTO(aideTest); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTitre()).isEqualTo(aideTest.getTitre()); + assertThat(result.getDescription()).isEqualTo(aideTest.getDescription()); + assertThat(result.getMontantDemande()).isEqualTo(aideTest.getMontantDemande()); + assertThat(result.getStatut()).isEqualTo(aideTest.getStatut().name()); + assertThat(result.getTypeAide()).isEqualTo(aideTest.getTypeAide().name()); + } + + @Test + @DisplayName("Conversion DTO vers Entity") + void testConvertFromDTO() { + // When + Aide result = aideService.convertFromDTO(aideDTOTest); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTitre()).isEqualTo(aideDTOTest.getTitre()); + assertThat(result.getDescription()).isEqualTo(aideDTOTest.getDescription()); + assertThat(result.getMontantDemande()).isEqualTo(aideDTOTest.getMontantDemande()); + assertThat(result.getStatut()).isEqualTo(StatutAide.EN_ATTENTE); + assertThat(result.getTypeAide()).isEqualTo(TypeAide.AIDE_FRAIS_MEDICAUX); + } + + @Test + @DisplayName("Conversion DTO null") + void testConvertFromDTO_Null() { + // When + Aide result = aideService.convertFromDTO(null); + + // Then + assertThat(result).isNull(); + } + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/service/EvenementServiceTest.java b/src/test.bak/java/dev/lions/unionflow/server/service/EvenementServiceTest.java new file mode 100644 index 0000000..9d4dcf0 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/service/EvenementServiceTest.java @@ -0,0 +1,403 @@ +package dev.lions.unionflow.server.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.mockito.Mock; + +/** + * Tests unitaires pour EvenementService + * + *

Tests complets du service de gestion des événements avec validation des règles métier et + * intégration Keycloak. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@DisplayName("Tests unitaires - Service Événements") +class EvenementServiceTest { + + @Inject EvenementService evenementService; + + @Mock EvenementRepository evenementRepository; + + @Mock MembreRepository membreRepository; + + @Mock OrganisationRepository organisationRepository; + + @Mock KeycloakService keycloakService; + + private Evenement evenementTest; + private Organisation organisationTest; + private Membre membreTest; + + @BeforeEach + void setUp() { + // Données de test + organisationTest = + Organisation.builder() + .nom("Union Test") + .typeOrganisation("ASSOCIATION") + .statut("ACTIVE") + .email("test@union.com") + .actif(true) + .build(); + organisationTest.id = 1L; + + membreTest = + Membre.builder() + .numeroMembre("UF2025-TEST01") + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .actif(true) + .build(); + membreTest.id = 1L; + + evenementTest = + Evenement.builder() + .titre("Assemblée Générale 2025") + .description("Assemblée générale annuelle de l'union") + .dateDebut(LocalDateTime.now().plusDays(30)) + .dateFin(LocalDateTime.now().plusDays(30).plusHours(3)) + .lieu("Salle de conférence") + .typeEvenement(TypeEvenement.ASSEMBLEE_GENERALE) + .statut(StatutEvenement.PLANIFIE) + .capaciteMax(100) + .prix(BigDecimal.valueOf(25.00)) + .inscriptionRequise(true) + .visiblePublic(true) + .actif(true) + .organisation(organisationTest) + .organisateur(membreTest) + .build(); + evenementTest.id = 1L; + } + + @Test + @Order(1) + @DisplayName("Création d'événement - Succès") + void testCreerEvenement_Succes() { + // Given + when(keycloakService.getCurrentUserEmail()).thenReturn("jean.dupont@test.com"); + when(evenementRepository.findByTitre(anyString())).thenReturn(Optional.empty()); + doNothing().when(evenementRepository).persist(any(Evenement.class)); + + // When + Evenement resultat = evenementService.creerEvenement(evenementTest); + + // Then + assertNotNull(resultat); + assertEquals("Assemblée Générale 2025", resultat.getTitre()); + assertEquals(StatutEvenement.PLANIFIE, resultat.getStatut()); + assertTrue(resultat.getActif()); + assertEquals("jean.dupont@test.com", resultat.getCreePar()); + + verify(evenementRepository).persist(any(Evenement.class)); + } + + @Test + @Order(2) + @DisplayName("Création d'événement - Titre obligatoire") + void testCreerEvenement_TitreObligatoire() { + // Given + evenementTest.setTitre(null); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals("Le titre de l'événement est obligatoire", exception.getMessage()); + verify(evenementRepository, never()).persist(any(Evenement.class)); + } + + @Test + @Order(3) + @DisplayName("Création d'événement - Date de début obligatoire") + void testCreerEvenement_DateDebutObligatoire() { + // Given + evenementTest.setDateDebut(null); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals("La date de début est obligatoire", exception.getMessage()); + } + + @Test + @Order(4) + @DisplayName("Création d'événement - Date de début dans le passé") + void testCreerEvenement_DateDebutPassee() { + // Given + evenementTest.setDateDebut(LocalDateTime.now().minusDays(1)); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals("La date de début ne peut pas être dans le passé", exception.getMessage()); + } + + @Test + @Order(5) + @DisplayName("Création d'événement - Date de fin antérieure à date de début") + void testCreerEvenement_DateFinInvalide() { + // Given + evenementTest.setDateFin(evenementTest.getDateDebut().minusHours(1)); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals( + "La date de fin ne peut pas être antérieure à la date de début", exception.getMessage()); + } + + @Test + @Order(6) + @DisplayName("Mise à jour d'événement - Succès") + void testMettreAJourEvenement_Succes() { + // Given + when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest)); + when(keycloakService.hasRole("ADMIN")).thenReturn(true); + when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com"); + doNothing().when(evenementRepository).persist(any(Evenement.class)); + + Evenement evenementMisAJour = + Evenement.builder() + .titre("Assemblée Générale 2025 - Modifiée") + .description("Description mise à jour") + .dateDebut(LocalDateTime.now().plusDays(35)) + .dateFin(LocalDateTime.now().plusDays(35).plusHours(4)) + .lieu("Nouvelle salle") + .typeEvenement(TypeEvenement.ASSEMBLEE_GENERALE) + .capaciteMax(150) + .prix(BigDecimal.valueOf(30.00)) + .inscriptionRequise(true) + .visiblePublic(true) + .build(); + + // When + Evenement resultat = evenementService.mettreAJourEvenement(1L, evenementMisAJour); + + // Then + assertNotNull(resultat); + assertEquals("Assemblée Générale 2025 - Modifiée", resultat.getTitre()); + assertEquals("Description mise à jour", resultat.getDescription()); + assertEquals("Nouvelle salle", resultat.getLieu()); + assertEquals(150, resultat.getCapaciteMax()); + assertEquals(BigDecimal.valueOf(30.00), resultat.getPrix()); + assertEquals("admin@test.com", resultat.getModifiePar()); + + verify(evenementRepository).persist(any(Evenement.class)); + } + + @Test + @Order(7) + @DisplayName("Mise à jour d'événement - Événement non trouvé") + void testMettreAJourEvenement_NonTrouve() { + // Given + when(evenementRepository.findByIdOptional(999L)).thenReturn(Optional.empty()); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> evenementService.mettreAJourEvenement(999L, evenementTest)); + + assertEquals("Événement non trouvé avec l'ID: 999", exception.getMessage()); + } + + @Test + @Order(8) + @DisplayName("Suppression d'événement - Succès") + void testSupprimerEvenement_Succes() { + // Given + when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest)); + when(keycloakService.hasRole("ADMIN")).thenReturn(true); + when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com"); + when(evenementTest.getNombreInscrits()).thenReturn(0); + doNothing().when(evenementRepository).persist(any(Evenement.class)); + + // When + assertDoesNotThrow(() -> evenementService.supprimerEvenement(1L)); + + // Then + assertFalse(evenementTest.getActif()); + assertEquals("admin@test.com", evenementTest.getModifiePar()); + verify(evenementRepository).persist(any(Evenement.class)); + } + + @Test + @Order(9) + @DisplayName("Recherche d'événements - Succès") + void testRechercherEvenements_Succes() { + // Given + List evenementsAttendus = List.of(evenementTest); + when(evenementRepository.findByTitreOrDescription( + anyString(), any(Page.class), any(Sort.class))) + .thenReturn(evenementsAttendus); + + // When + List resultat = + evenementService.rechercherEvenements("Assemblée", Page.of(0, 10), Sort.by("dateDebut")); + + // Then + assertNotNull(resultat); + assertEquals(1, resultat.size()); + assertEquals("Assemblée Générale 2025", resultat.get(0).getTitre()); + + verify(evenementRepository) + .findByTitreOrDescription(eq("Assemblée"), any(Page.class), any(Sort.class)); + } + + @Test + @Order(10) + @DisplayName("Changement de statut - Succès") + void testChangerStatut_Succes() { + // Given + when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest)); + when(keycloakService.hasRole("ADMIN")).thenReturn(true); + when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com"); + doNothing().when(evenementRepository).persist(any(Evenement.class)); + + // When + Evenement resultat = evenementService.changerStatut(1L, StatutEvenement.CONFIRME); + + // Then + assertNotNull(resultat); + assertEquals(StatutEvenement.CONFIRME, resultat.getStatut()); + assertEquals("admin@test.com", resultat.getModifiePar()); + + verify(evenementRepository).persist(any(Evenement.class)); + } + + @Test + @Order(11) + @DisplayName("Statistiques des événements") + void testObtenirStatistiques() { + // Given + Map statsBase = + Map.of( + "total", 100L, + "actifs", 80L, + "aVenir", 30L, + "enCours", 5L, + "passes", 45L, + "publics", 70L, + "avecInscription", 25L); + when(evenementRepository.getStatistiques()).thenReturn(statsBase); + + // When + Map resultat = evenementService.obtenirStatistiques(); + + // Then + assertNotNull(resultat); + assertEquals(100L, resultat.get("total")); + assertEquals(80L, resultat.get("actifs")); + assertEquals(30L, resultat.get("aVenir")); + assertEquals(80.0, resultat.get("tauxActivite")); + assertEquals(37.5, resultat.get("tauxEvenementsAVenir")); + assertEquals(6.25, resultat.get("tauxEvenementsEnCours")); + assertNotNull(resultat.get("timestamp")); + + verify(evenementRepository).getStatistiques(); + } + + @Test + @Order(12) + @DisplayName("Lister événements actifs avec pagination") + void testListerEvenementsActifs() { + // Given + List evenementsAttendus = List.of(evenementTest); + when(evenementRepository.findAllActifs(any(Page.class), any(Sort.class))) + .thenReturn(evenementsAttendus); + + // When + List resultat = + evenementService.listerEvenementsActifs(Page.of(0, 20), Sort.by("dateDebut")); + + // Then + assertNotNull(resultat); + assertEquals(1, resultat.size()); + assertEquals("Assemblée Générale 2025", resultat.get(0).getTitre()); + + verify(evenementRepository).findAllActifs(any(Page.class), any(Sort.class)); + } + + @Test + @Order(13) + @DisplayName("Validation des règles métier - Prix négatif") + void testValidation_PrixNegatif() { + // Given + evenementTest.setPrix(BigDecimal.valueOf(-10.00)); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals("Le prix ne peut pas être négatif", exception.getMessage()); + } + + @Test + @Order(14) + @DisplayName("Validation des règles métier - Capacité négative") + void testValidation_CapaciteNegative() { + // Given + evenementTest.setCapaciteMax(-5); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest)); + + assertEquals("La capacité maximale ne peut pas être négative", exception.getMessage()); + } + + @Test + @Order(15) + @DisplayName("Permissions - Utilisateur non autorisé") + void testPermissions_UtilisateurNonAutorise() { + // Given + when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest)); + when(keycloakService.hasRole(anyString())).thenReturn(false); + when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com"); + + // When & Then + SecurityException exception = + assertThrows( + SecurityException.class, + () -> evenementService.mettreAJourEvenement(1L, evenementTest)); + + assertEquals( + "Vous n'avez pas les permissions pour modifier cet événement", exception.getMessage()); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/service/MembreServiceTest.java b/src/test.bak/java/dev/lions/unionflow/server/service/MembreServiceTest.java new file mode 100644 index 0000000..6d2b884 --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/service/MembreServiceTest.java @@ -0,0 +1,344 @@ +package dev.lions.unionflow.server.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.repository.MembreRepository; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Tests pour MembreService + * + * @author Lions Dev Team + * @since 2025-01-10 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("Tests MembreService") +class MembreServiceTest { + + @InjectMocks MembreService membreService; + + @Mock MembreRepository membreRepository; + + private Membre membreTest; + + @BeforeEach + void setUp() { + membreTest = + Membre.builder() + .prenom("Jean") + .nom("Dupont") + .email("jean.dupont@test.com") + .telephone("221701234567") + .dateNaissance(LocalDate.of(1990, 5, 15)) + .dateAdhesion(LocalDate.now()) + .actif(true) + .build(); + } + + @Nested + @DisplayName("Tests creerMembre") + class CreerMembreTests { + + @Test + @DisplayName("Création réussie d'un membre") + void testCreerMembreReussi() { + // Given + when(membreRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(membreRepository.findByNumeroMembre(anyString())).thenReturn(Optional.empty()); + + // When + Membre result = membreService.creerMembre(membreTest); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getNumeroMembre()).isNotNull(); + assertThat(result.getNumeroMembre()).startsWith("UF2025-"); + verify(membreRepository).persist(membreTest); + } + + @Test + @DisplayName("Erreur si email déjà existant") + void testCreerMembreEmailExistant() { + // Given + when(membreRepository.findByEmail(membreTest.getEmail())).thenReturn(Optional.of(membreTest)); + + // When & Then + assertThatThrownBy(() -> membreService.creerMembre(membreTest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Un membre avec cet email existe déjà"); + } + + @Test + @DisplayName("Erreur si numéro de membre déjà existant") + void testCreerMembreNumeroExistant() { + // Given + membreTest.setNumeroMembre("UF2025-EXIST"); + when(membreRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(membreRepository.findByNumeroMembre("UF2025-EXIST")).thenReturn(Optional.of(membreTest)); + + // When & Then + assertThatThrownBy(() -> membreService.creerMembre(membreTest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Un membre avec ce numéro existe déjà"); + } + + @Test + @DisplayName("Génération automatique du numéro de membre") + void testGenerationNumeroMembre() { + // Given + membreTest.setNumeroMembre(null); + when(membreRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(membreRepository.findByNumeroMembre(anyString())).thenReturn(Optional.empty()); + + // When + membreService.creerMembre(membreTest); + + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(Membre.class); + verify(membreRepository).persist(captor.capture()); + assertThat(captor.getValue().getNumeroMembre()).isNotNull(); + assertThat(captor.getValue().getNumeroMembre()).startsWith("UF2025-"); + } + + @Test + @DisplayName("Génération automatique du numéro de membre avec chaîne vide") + void testGenerationNumeroMembreChainVide() { + // Given + membreTest.setNumeroMembre(""); // Chaîne vide + when(membreRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(membreRepository.findByNumeroMembre(anyString())).thenReturn(Optional.empty()); + + // When + membreService.creerMembre(membreTest); + + // Then + ArgumentCaptor captor = ArgumentCaptor.forClass(Membre.class); + verify(membreRepository).persist(captor.capture()); + assertThat(captor.getValue().getNumeroMembre()).isNotNull(); + assertThat(captor.getValue().getNumeroMembre()).isNotEmpty(); + assertThat(captor.getValue().getNumeroMembre()).startsWith("UF2025-"); + } + } + + @Nested + @DisplayName("Tests mettreAJourMembre") + class MettreAJourMembreTests { + + @Test + @DisplayName("Mise à jour réussie d'un membre") + void testMettreAJourMembreReussi() { + // Given + Long id = 1L; + membreTest.id = id; // Utiliser le champ directement + Membre membreModifie = + Membre.builder() + .prenom("Pierre") + .nom("Martin") + .email("pierre.martin@test.com") + .telephone("221701234568") + .dateNaissance(LocalDate.of(1985, 8, 20)) + .actif(false) + .build(); + + when(membreRepository.findById(id)).thenReturn(membreTest); + when(membreRepository.findByEmail("pierre.martin@test.com")).thenReturn(Optional.empty()); + + // When + Membre result = membreService.mettreAJourMembre(id, membreModifie); + + // Then + assertThat(result.getPrenom()).isEqualTo("Pierre"); + assertThat(result.getNom()).isEqualTo("Martin"); + assertThat(result.getEmail()).isEqualTo("pierre.martin@test.com"); + assertThat(result.getTelephone()).isEqualTo("221701234568"); + assertThat(result.getDateNaissance()).isEqualTo(LocalDate.of(1985, 8, 20)); + assertThat(result.getActif()).isFalse(); + } + + @Test + @DisplayName("Erreur si membre non trouvé") + void testMettreAJourMembreNonTrouve() { + // Given + Long id = 999L; + when(membreRepository.findById(id)).thenReturn(null); + + // When & Then + assertThatThrownBy(() -> membreService.mettreAJourMembre(id, membreTest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Membre non trouvé avec l'ID: " + id); + } + + @Test + @DisplayName("Erreur si nouvel email déjà existant") + void testMettreAJourMembreEmailExistant() { + // Given + Long id = 1L; + membreTest.id = id; // Utiliser le champ directement + membreTest.setEmail("ancien@test.com"); + + Membre membreModifie = Membre.builder().email("nouveau@test.com").build(); + + Membre autreMembreAvecEmail = Membre.builder().email("nouveau@test.com").build(); + autreMembreAvecEmail.id = 2L; // Utiliser le champ directement + + when(membreRepository.findById(id)).thenReturn(membreTest); + when(membreRepository.findByEmail("nouveau@test.com")) + .thenReturn(Optional.of(autreMembreAvecEmail)); + + // When & Then + assertThatThrownBy(() -> membreService.mettreAJourMembre(id, membreModifie)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Un membre avec cet email existe déjà"); + } + + @Test + @DisplayName("Mise à jour sans changement d'email") + void testMettreAJourMembreSansChangementEmail() { + // Given + Long id = 1L; + membreTest.id = id; // Utiliser le champ directement + membreTest.setEmail("meme@test.com"); + + Membre membreModifie = + Membre.builder() + .prenom("Pierre") + .nom("Martin") + .email("meme@test.com") // Même email + .telephone("221701234568") + .dateNaissance(LocalDate.of(1985, 8, 20)) + .actif(false) + .build(); + + when(membreRepository.findById(id)).thenReturn(membreTest); + // Pas besoin de mocker findByEmail car l'email n'a pas changé + + // When + Membre result = membreService.mettreAJourMembre(id, membreModifie); + + // Then + assertThat(result.getPrenom()).isEqualTo("Pierre"); + assertThat(result.getNom()).isEqualTo("Martin"); + assertThat(result.getEmail()).isEqualTo("meme@test.com"); + // Vérifier que findByEmail n'a pas été appelé + verify(membreRepository, never()).findByEmail("meme@test.com"); + } + } + + @Test + @DisplayName("Test trouverParId") + void testTrouverParId() { + // Given + Long id = 1L; + when(membreRepository.findById(id)).thenReturn(membreTest); + + // When + Optional result = membreService.trouverParId(id); + + // Then + assertThat(result).isPresent(); + assertThat(result.get()).isEqualTo(membreTest); + } + + @Test + @DisplayName("Test trouverParEmail") + void testTrouverParEmail() { + // Given + String email = "jean.dupont@test.com"; + when(membreRepository.findByEmail(email)).thenReturn(Optional.of(membreTest)); + + // When + Optional result = membreService.trouverParEmail(email); + + // Then + assertThat(result).isPresent(); + assertThat(result.get()).isEqualTo(membreTest); + } + + @Test + @DisplayName("Test listerMembresActifs") + void testListerMembresActifs() { + // Given + List membresActifs = Arrays.asList(membreTest); + when(membreRepository.findAllActifs()).thenReturn(membresActifs); + + // When + List result = membreService.listerMembresActifs(); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(membreTest); + } + + @Test + @DisplayName("Test rechercherMembres") + void testRechercherMembres() { + // Given + String recherche = "Jean"; + List resultatsRecherche = Arrays.asList(membreTest); + when(membreRepository.findByNomOrPrenom(recherche)).thenReturn(resultatsRecherche); + + // When + List result = membreService.rechercherMembres(recherche); + + // Then + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(membreTest); + } + + @Test + @DisplayName("Test desactiverMembre - Succès") + void testDesactiverMembreReussi() { + // Given + Long id = 1L; + membreTest.id = id; // Utiliser le champ directement + when(membreRepository.findById(id)).thenReturn(membreTest); + + // When + membreService.desactiverMembre(id); + + // Then + assertThat(membreTest.getActif()).isFalse(); + } + + @Test + @DisplayName("Test desactiverMembre - Membre non trouvé") + void testDesactiverMembreNonTrouve() { + // Given + Long id = 999L; + when(membreRepository.findById(id)).thenReturn(null); + + // When & Then + assertThatThrownBy(() -> membreService.desactiverMembre(id)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Membre non trouvé avec l'ID: " + id); + } + + @Test + @DisplayName("Test compterMembresActifs") + void testCompterMembresActifs() { + // Given + when(membreRepository.countActifs()).thenReturn(5L); + + // When + long result = membreService.compterMembresActifs(); + + // Then + assertThat(result).isEqualTo(5L); + } +} diff --git a/src/test.bak/java/dev/lions/unionflow/server/service/OrganisationServiceTest.java b/src/test.bak/java/dev/lions/unionflow/server/service/OrganisationServiceTest.java new file mode 100644 index 0000000..0d15e6f --- /dev/null +++ b/src/test.bak/java/dev/lions/unionflow/server/service/OrganisationServiceTest.java @@ -0,0 +1,356 @@ +package dev.lions.unionflow.server.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +/** + * Tests unitaires pour OrganisationService + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-15 + */ +@QuarkusTest +class OrganisationServiceTest { + + @Inject OrganisationService organisationService; + + @Mock OrganisationRepository organisationRepository; + + private Organisation organisationTest; + + @BeforeEach + void setUp() { + organisationTest = + Organisation.builder() + .nom("Lions Club Test") + .nomCourt("LC Test") + .email("test@lionsclub.org") + .typeOrganisation("LIONS_CLUB") + .statut("ACTIVE") + .description("Organisation de test") + .telephone("+225 01 02 03 04 05") + .adresse("123 Rue de Test") + .ville("Abidjan") + .region("Lagunes") + .pays("Côte d'Ivoire") + .nombreMembres(25) + .actif(true) + .dateCreation(LocalDateTime.now()) + .version(0L) + .build(); + organisationTest.id = 1L; + } + + @Test + void testCreerOrganisation_Success() { + // Given + Organisation organisationToCreate = + Organisation.builder() + .nom("Lions Club Test New") + .email("testnew@lionsclub.org") + .typeOrganisation("LIONS_CLUB") + .build(); + + when(organisationRepository.findByEmail("testnew@lionsclub.org")).thenReturn(Optional.empty()); + when(organisationRepository.findByNom("Lions Club Test New")).thenReturn(Optional.empty()); + + // When + Organisation result = organisationService.creerOrganisation(organisationToCreate); + + // Then + assertNotNull(result); + assertEquals("Lions Club Test New", result.getNom()); + assertEquals("ACTIVE", result.getStatut()); + verify(organisationRepository).findByEmail("testnew@lionsclub.org"); + verify(organisationRepository).findByNom("Lions Club Test New"); + } + + @Test + void testCreerOrganisation_EmailDejaExistant() { + // Given + when(organisationRepository.findByEmail(anyString())).thenReturn(Optional.of(organisationTest)); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> organisationService.creerOrganisation(organisationTest)); + + assertEquals("Une organisation avec cet email existe déjà", exception.getMessage()); + verify(organisationRepository).findByEmail("test@lionsclub.org"); + verify(organisationRepository, never()).findByNom(anyString()); + } + + @Test + void testCreerOrganisation_NomDejaExistant() { + // Given + when(organisationRepository.findByEmail(anyString())).thenReturn(Optional.empty()); + when(organisationRepository.findByNom(anyString())).thenReturn(Optional.of(organisationTest)); + + // When & Then + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> organisationService.creerOrganisation(organisationTest)); + + assertEquals("Une organisation avec ce nom existe déjà", exception.getMessage()); + verify(organisationRepository).findByEmail("test@lionsclub.org"); + verify(organisationRepository).findByNom("Lions Club Test"); + } + + @Test + void testMettreAJourOrganisation_Success() { + // Given + Organisation organisationMiseAJour = + Organisation.builder() + .nom("Lions Club Test Modifié") + .email("test@lionsclub.org") + .description("Description modifiée") + .telephone("+225 01 02 03 04 06") + .build(); + + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + when(organisationRepository.findByNom("Lions Club Test Modifié")).thenReturn(Optional.empty()); + + // When + Organisation result = + organisationService.mettreAJourOrganisation(1L, organisationMiseAJour, "testUser"); + + // Then + assertNotNull(result); + assertEquals("Lions Club Test Modifié", result.getNom()); + assertEquals("Description modifiée", result.getDescription()); + assertEquals("+225 01 02 03 04 06", result.getTelephone()); + assertEquals("testUser", result.getModifiePar()); + assertNotNull(result.getDateModification()); + assertEquals(1L, result.getVersion()); + } + + @Test + void testMettreAJourOrganisation_OrganisationNonTrouvee() { + // Given + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.empty()); + + // When & Then + NotFoundException exception = + assertThrows( + NotFoundException.class, + () -> organisationService.mettreAJourOrganisation(1L, organisationTest, "testUser")); + + assertEquals("Organisation non trouvée avec l'ID: 1", exception.getMessage()); + } + + @Test + void testSupprimerOrganisation_Success() { + // Given + organisationTest.setNombreMembres(0); + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + + // When + organisationService.supprimerOrganisation(1L, "testUser"); + + // Then + assertFalse(organisationTest.getActif()); + assertEquals("DISSOUTE", organisationTest.getStatut()); + assertEquals("testUser", organisationTest.getModifiePar()); + assertNotNull(organisationTest.getDateModification()); + } + + @Test + void testSupprimerOrganisation_AvecMembresActifs() { + // Given + organisationTest.setNombreMembres(5); + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + + // When & Then + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> organisationService.supprimerOrganisation(1L, "testUser")); + + assertEquals( + "Impossible de supprimer une organisation avec des membres actifs", exception.getMessage()); + } + + @Test + void testTrouverParId_Success() { + // Given + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + + // When + Optional result = organisationService.trouverParId(1L); + + // Then + assertTrue(result.isPresent()); + assertEquals("Lions Club Test", result.get().getNom()); + verify(organisationRepository).findByIdOptional(1L); + } + + @Test + void testTrouverParId_NonTrouve() { + // Given + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.empty()); + + // When + Optional result = organisationService.trouverParId(1L); + + // Then + assertFalse(result.isPresent()); + verify(organisationRepository).findByIdOptional(1L); + } + + @Test + void testTrouverParEmail_Success() { + // Given + when(organisationRepository.findByEmail("test@lionsclub.org")) + .thenReturn(Optional.of(organisationTest)); + + // When + Optional result = organisationService.trouverParEmail("test@lionsclub.org"); + + // Then + assertTrue(result.isPresent()); + assertEquals("Lions Club Test", result.get().getNom()); + verify(organisationRepository).findByEmail("test@lionsclub.org"); + } + + @Test + void testListerOrganisationsActives() { + // Given + List organisations = Arrays.asList(organisationTest); + when(organisationRepository.findAllActives()).thenReturn(organisations); + + // When + List result = organisationService.listerOrganisationsActives(); + + // Then + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Lions Club Test", result.get(0).getNom()); + verify(organisationRepository).findAllActives(); + } + + @Test + void testActiverOrganisation_Success() { + // Given + organisationTest.setStatut("SUSPENDUE"); + organisationTest.setActif(false); + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + + // When + Organisation result = organisationService.activerOrganisation(1L, "testUser"); + + // Then + assertNotNull(result); + assertEquals("ACTIVE", result.getStatut()); + assertTrue(result.getActif()); + assertEquals("testUser", result.getModifiePar()); + assertNotNull(result.getDateModification()); + } + + @Test + void testSuspendreOrganisation_Success() { + // Given + when(organisationRepository.findByIdOptional(1L)).thenReturn(Optional.of(organisationTest)); + + // When + Organisation result = organisationService.suspendreOrganisation(1L, "testUser"); + + // Then + assertNotNull(result); + assertEquals("SUSPENDUE", result.getStatut()); + assertFalse(result.getAccepteNouveauxMembres()); + assertEquals("testUser", result.getModifiePar()); + assertNotNull(result.getDateModification()); + } + + @Test + void testObtenirStatistiques() { + // Given + when(organisationRepository.count()).thenReturn(100L); + when(organisationRepository.countActives()).thenReturn(85L); + when(organisationRepository.countNouvellesOrganisations(any(LocalDate.class))).thenReturn(5L); + + // When + Map result = organisationService.obtenirStatistiques(); + + // Then + assertNotNull(result); + assertEquals(100L, result.get("totalOrganisations")); + assertEquals(85L, result.get("organisationsActives")); + assertEquals(15L, result.get("organisationsInactives")); + assertEquals(5L, result.get("nouvellesOrganisations30Jours")); + assertEquals(85.0, result.get("tauxActivite")); + assertNotNull(result.get("timestamp")); + } + + @Test + void testConvertToDTO() { + // When + var dto = organisationService.convertToDTO(organisationTest); + + // Then + assertNotNull(dto); + assertEquals("Lions Club Test", dto.getNom()); + assertEquals("LC Test", dto.getNomCourt()); + assertEquals("test@lionsclub.org", dto.getEmail()); + assertEquals("Organisation de test", dto.getDescription()); + assertEquals("+225 01 02 03 04 05", dto.getTelephone()); + assertEquals("Abidjan", dto.getVille()); + assertEquals(25, dto.getNombreMembres()); + assertTrue(dto.getActif()); + } + + @Test + void testConvertToDTO_Null() { + // When + var dto = organisationService.convertToDTO(null); + + // Then + assertNull(dto); + } + + @Test + void testConvertFromDTO() { + // Given + var dto = organisationService.convertToDTO(organisationTest); + + // When + Organisation result = organisationService.convertFromDTO(dto); + + // Then + assertNotNull(result); + assertEquals("Lions Club Test", result.getNom()); + assertEquals("LC Test", result.getNomCourt()); + assertEquals("test@lionsclub.org", result.getEmail()); + assertEquals("Organisation de test", result.getDescription()); + assertEquals("+225 01 02 03 04 05", result.getTelephone()); + assertEquals("Abidjan", result.getVille()); + } + + @Test + void testConvertFromDTO_Null() { + // When + Organisation result = organisationService.convertFromDTO(null); + + // Then + assertNull(result); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java b/src/test/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java new file mode 100644 index 0000000..c220305 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/resource/EvenementResourceTest.java @@ -0,0 +1,400 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.entity.Evenement; +import dev.lions.unionflow.server.entity.Evenement.StatutEvenement; +import dev.lions.unionflow.server.entity.Evenement.TypeEvenement; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.EvenementRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.*; + +/** + * Tests d'intégration pour EvenementResource + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class EvenementResourceTest { + + private static final String BASE_PATH = "/api/evenements"; + + @Inject EvenementRepository evenementRepository; + @Inject OrganisationRepository organisationRepository; + + private Evenement testEvenement; + private Organisation testOrganisation; + + @BeforeEach + @Transactional + void setupTestData() { + // Créer une organisation de test + testOrganisation = + Organisation.builder() + .nom("Organisation Test Événements") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("org-events-" + System.currentTimeMillis() + "@test.com") + .build(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); + + // Créer un événement de test + testEvenement = + Evenement.builder() + .titre("Événement Test") + .description("Description de l'événement de test") + .dateDebut(LocalDateTime.now().plusDays(7)) + .dateFin(LocalDateTime.now().plusDays(7).plusHours(3)) + .lieu("Lieu Test") + .typeEvenement(TypeEvenement.REUNION) + .statut(StatutEvenement.PLANIFIE) + .capaciteMax(50) + .prix(BigDecimal.valueOf(5000)) + .organisation(testOrganisation) + .build(); + testEvenement.setDateCreation(LocalDateTime.now()); + testEvenement.setActif(true); + evenementRepository.persist(testEvenement); + } + + @AfterEach + @Transactional + void cleanupTestData() { + // Supprimer tous les événements liés à l'organisation avant de supprimer l'organisation + if (testOrganisation != null && testOrganisation.getId() != null) { + // Supprimer tous les événements de cette organisation + java.util.List evenements = + evenementRepository.findByOrganisation(testOrganisation.getId()); + for (Evenement evt : evenements) { + evenementRepository.delete(evt); + } + } + + // Supprimer l'événement de test s'il existe encore + if (testEvenement != null && testEvenement.getId() != null) { + Evenement evtToDelete = evenementRepository.findById(testEvenement.getId()); + if (evtToDelete != null) { + evenementRepository.delete(evtToDelete); + } + } + + // Supprimer l'organisation après avoir supprimé tous ses événements + if (testOrganisation != null && testOrganisation.getId() != null) { + Organisation orgToDelete = organisationRepository.findById(testOrganisation.getId()); + if (orgToDelete != null) { + organisationRepository.delete(orgToDelete); + } + } + } + + @Test + @Order(1) + @DisplayName("GET /api/evenements/test doit retourner un statut de connectivité") + void testConnectivity() { + given() + .when() + .get(BASE_PATH + "/test") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("status", equalTo("success")) + .body("message", notNullValue()); + } + + @Test + @Order(2) + @DisplayName("GET /api/evenements/count doit retourner le nombre d'événements") + void testCountEvenements() { + given() + .when() + .get(BASE_PATH + "/count") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("count", greaterThanOrEqualTo(0)) + .body("status", equalTo("success")); + } + + @Test + @Order(3) + @DisplayName("GET /api/evenements doit retourner la liste des événements") + void testListerEvenements() { + given() + .queryParam("page", 0) + .queryParam("size", 20) + .when() + .get(BASE_PATH) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("$", notNullValue()); + } + + @Test + @Order(4) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/evenements/{id} doit retourner un événement existant") + void testObtenirEvenement() { + UUID eventId = testEvenement.getId(); + + given() + .pathParam("id", eventId) + .when() + .get(BASE_PATH + "/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(eventId.toString())) + .body("titre", equalTo("Événement Test")); + } + + @Test + @Order(5) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/evenements/{id} doit retourner 404 pour un ID inexistant") + void testObtenirEvenementInexistant() { + UUID fakeId = UUID.randomUUID(); + + given() + .pathParam("id", fakeId) + .when() + .get(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(6) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/evenements doit créer un nouvel événement") + void testCreerEvenement() { + String eventJson = + """ + { + "titre": "Nouvel Événement Test", + "description": "Description du nouvel événement", + "dateDebut": "%s", + "dateFin": "%s", + "lieu": "Nouveau Lieu", + "typeEvenement": "REUNION", + "statut": "PLANIFIE", + "capaciteMax": 100, + "prix": 10000, + "organisationId": "%s" + } + """ + .formatted( + LocalDateTime.now().plusDays(14).toString(), + LocalDateTime.now().plusDays(14).plusHours(4).toString(), + testOrganisation.getId().toString()); + + UUID createdId = + UUID.fromString( + given() + .contentType(ContentType.JSON) + .body(eventJson) + .when() + .post(BASE_PATH) + .then() + .statusCode(201) + .contentType(ContentType.JSON) + .body("titre", equalTo("Nouvel Événement Test")) + .body("id", notNullValue()) + .extract() + .path("id")); + + // Nettoyer l'événement créé + Evenement created = evenementRepository.findById(createdId); + if (created != null) { + evenementRepository.delete(created); + } + } + + @Test + @Order(7) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/evenements doit retourner 400 pour données invalides") + void testCreerEvenementInvalide() { + String invalidEventJson = + """ + { + "titre": "", + "description": "Description", + "dateDebut": "%s" + } + """ + .formatted(LocalDateTime.now().plusDays(1).toString()); + + given() + .contentType(ContentType.JSON) + .body(invalidEventJson) + .when() + .post(BASE_PATH) + .then() + .statusCode(400); + } + + @Test + @Order(8) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("PUT /api/evenements/{id} doit mettre à jour un événement") + void testModifierEvenement() { + UUID eventId = testEvenement.getId(); + // Récupérer l'événement existant pour préserver l'organisation + Evenement existing = evenementRepository.findById(eventId); + String updatedEventJson = + """ + { + "titre": "Événement Modifié", + "description": "Description modifiée", + "dateDebut": "%s", + "dateFin": "%s", + "lieu": "Lieu Modifié", + "typeEvenement": "REUNION", + "statut": "PLANIFIE", + "capaciteMax": 75, + "prix": 7500, + "actif": true, + "visiblePublic": true, + "inscriptionRequise": false + } + """ + .formatted( + LocalDateTime.now().plusDays(10).toString(), + LocalDateTime.now().plusDays(10).plusHours(5).toString()); + + given() + .contentType(ContentType.JSON) + .pathParam("id", eventId) + .body(updatedEventJson) + .when() + .put(BASE_PATH + "/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("titre", equalTo("Événement Modifié")) + .body("lieu", equalTo("Lieu Modifié")); + } + + @Test + @Order(9) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("PUT /api/evenements/{id} doit retourner 404 pour ID inexistant") + void testModifierEvenementInexistant() { + UUID fakeId = UUID.randomUUID(); + String updatedEventJson = + """ + { + "titre": "Événement Test", + "dateDebut": "%s", + "actif": true, + "visiblePublic": true, + "inscriptionRequise": false + } + """ + .formatted(LocalDateTime.now().plusDays(1).toString()); + + given() + .contentType(ContentType.JSON) + .pathParam("id", fakeId) + .body(updatedEventJson) + .when() + .put(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(10) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("DELETE /api/evenements/{id} doit supprimer un événement") + void testSupprimerEvenement() { + // Créer un événement temporaire pour la suppression + Evenement tempEvent = + Evenement.builder() + .titre("Événement à Supprimer") + .description("Description") + .dateDebut(LocalDateTime.now().plusDays(5)) + .typeEvenement(TypeEvenement.REUNION) + .statut(StatutEvenement.PLANIFIE) + .organisation(testOrganisation) + .build(); + tempEvent.setDateCreation(LocalDateTime.now()); + tempEvent.setActif(true); + evenementRepository.persist(tempEvent); + + UUID tempId = tempEvent.getId(); + + given() + .pathParam("id", tempId) + .when() + .delete(BASE_PATH + "/{id}") + .then() + .statusCode(204); + + // Vérifier que l'événement a été supprimé + Evenement deleted = evenementRepository.findById(tempId); + assert deleted == null : "L'événement devrait être supprimé"; + } + + @Test + @Order(11) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("DELETE /api/evenements/{id} doit retourner 404 pour ID inexistant") + void testSupprimerEvenementInexistant() { + UUID fakeId = UUID.randomUUID(); + + given() + .pathParam("id", fakeId) + .when() + .delete(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(12) + @DisplayName("GET /api/evenements doit supporter la pagination") + void testPagination() { + given() + .queryParam("page", 0) + .queryParam("size", 5) + .when() + .get(BASE_PATH) + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } + + @Test + @Order(13) + @DisplayName("GET /api/evenements doit supporter le tri") + void testTri() { + given() + .queryParam("page", 0) + .queryParam("size", 10) + .queryParam("sort", "dateDebut") + .queryParam("direction", "desc") + .when() + .get(BASE_PATH) + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.java b/src/test/java/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.java new file mode 100644 index 0000000..1aeca82 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.java @@ -0,0 +1,334 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.*; + +/** + * Tests d'intégration pour l'endpoint de recherche avancée des membres + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MembreResourceAdvancedSearchTest { + + private static final String ADVANCED_SEARCH_ENDPOINT = "/api/membres/search/advanced"; + + @Test + @Order(1) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit fonctionner avec critères valides") + void testAdvancedSearchWithValidCriteria() { + MembreSearchCriteria criteria = + MembreSearchCriteria.builder().query("marie").statut("ACTIF").ageMin(20).ageMax(50).build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .queryParam("page", 0) + .queryParam("size", 20) + .queryParam("sort", "nom") + .queryParam("direction", "asc") + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("membres", notNullValue()) + .body("totalElements", greaterThanOrEqualTo(0)) + .body("totalPages", greaterThanOrEqualTo(0)) + .body("currentPage", equalTo(0)) + .body("pageSize", equalTo(20)) + .body("hasNext", notNullValue()) + .body("hasPrevious", equalTo(false)) + .body("isFirst", equalTo(true)) + .body("executionTimeMs", greaterThan(0)) + .body("statistics", notNullValue()) + .body("statistics.membresActifs", greaterThanOrEqualTo(0)) + .body("statistics.membresInactifs", greaterThanOrEqualTo(0)) + .body("criteria.query", equalTo("marie")) + .body("criteria.statut", equalTo("ACTIF")); + } + + @Test + @Order(2) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit fonctionner avec critères multiples") + void testAdvancedSearchWithMultipleCriteria() { + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .email("@unionflow.com") + .dateAdhesionMin(LocalDate.of(2020, 1, 1)) + .dateAdhesionMax(LocalDate.of(2025, 12, 31)) + .roles(List.of("ADMIN", "SUPER_ADMIN")) + .includeInactifs(false) + .build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .queryParam("page", 0) + .queryParam("size", 10) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("membres", notNullValue()) + .body("totalElements", greaterThanOrEqualTo(0)) + .body("criteria.email", equalTo("@unionflow.com")) + .body("criteria.roles", hasItems("ADMIN", "SUPER_ADMIN")) + .body("criteria.includeInactifs", equalTo(false)); + } + + @Test + @Order(3) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit gérer la pagination") + void testAdvancedSearchPagination() { + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .statut("ACTIF") // Ajouter un critère valide + .includeInactifs(true) // Inclure tous les membres + .build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .queryParam("page", 0) + .queryParam("size", 2) // Petite taille pour tester la pagination + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("currentPage", equalTo(0)) + .body("pageSize", equalTo(2)) + .body("isFirst", equalTo(true)) + .body("hasPrevious", equalTo(false)); + } + + @Test + @Order(4) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit gérer le tri") + void testAdvancedSearchSorting() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .queryParam("sort", "nom") + .queryParam("direction", "desc") + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("membres", notNullValue()); + } + + @Test + @Order(5) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit retourner 400 pour critères vides") + void testAdvancedSearchWithEmptyCriteria() { + MembreSearchCriteria emptyCriteria = MembreSearchCriteria.builder().build(); + + given() + .contentType(ContentType.JSON) + .body(emptyCriteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(400) + .contentType(ContentType.JSON) + .body("message", containsString("Au moins un critère de recherche doit être spécifié")); + } + + @Test + @Order(6) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit retourner 400 pour critères invalides") + void testAdvancedSearchWithInvalidCriteria() { + MembreSearchCriteria invalidCriteria = + MembreSearchCriteria.builder() + .ageMin(50) + .ageMax(30) // Âge max < âge min + .build(); + + given() + .contentType(ContentType.JSON) + .body(invalidCriteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(400) + .contentType(ContentType.JSON) + .body("message", containsString("Critères de recherche invalides")); + } + + @Test + @Order(7) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit retourner 400 pour body null") + void testAdvancedSearchWithNullBody() { + given() + .contentType(ContentType.JSON) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(400); + } + + @Test + @Order(8) + @TestSecurity( + user = "marie.active@unionflow.com", + roles = {"MEMBRE_ACTIF"}) + @DisplayName("POST /api/membres/search/advanced doit retourner 403 pour utilisateur non autorisé") + void testAdvancedSearchUnauthorized() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(403); + } + + @Test + @Order(9) + @DisplayName( + "POST /api/membres/search/advanced doit retourner 401 pour utilisateur non authentifié") + void testAdvancedSearchUnauthenticated() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(401); + } + + @Test + @Order(10) + @TestSecurity( + user = "admin@unionflow.com", + roles = {"ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit fonctionner pour ADMIN") + void testAdvancedSearchForAdmin() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("membres", notNullValue()); + } + + @Test + @Order(11) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit inclure le temps d'exécution") + void testAdvancedSearchExecutionTime() { + MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("executionTimeMs", greaterThan(0)) + .body("executionTimeMs", lessThan(5000)); // Moins de 5 secondes + } + + @Test + @Order(12) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit retourner des statistiques complètes") + void testAdvancedSearchStatistics() { + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .statut("ACTIF") // Ajouter un critère valide + .includeInactifs(true) + .build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("statistics", notNullValue()) + .body("statistics.membresActifs", greaterThanOrEqualTo(0)) + .body("statistics.membresInactifs", greaterThanOrEqualTo(0)) + .body("statistics.ageMoyen", greaterThanOrEqualTo(0.0)) + .body("statistics.ageMin", greaterThanOrEqualTo(0)) + .body("statistics.ageMax", greaterThanOrEqualTo(0)) + .body("statistics.nombreOrganisations", greaterThanOrEqualTo(0)) + .body("statistics.ancienneteMoyenne", greaterThanOrEqualTo(0.0)); + } + + @Test + @Order(13) + @TestSecurity( + user = "superadmin@unionflow.com", + roles = {"SUPER_ADMIN"}) + @DisplayName("POST /api/membres/search/advanced doit gérer les caractères spéciaux") + void testAdvancedSearchWithSpecialCharacters() { + MembreSearchCriteria criteria = + MembreSearchCriteria.builder().query("marie-josé").nom("o'connor").build(); + + given() + .contentType(ContentType.JSON) + .body(criteria) + .when() + .post(ADVANCED_SEARCH_ENDPOINT) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("membres", notNullValue()); + } +} diff --git a/src/test/java/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.java b/src/test/java/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.java new file mode 100644 index 0000000..0368d77 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.java @@ -0,0 +1,292 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.io.ByteArrayOutputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.*; + +/** + * Tests d'intégration pour les endpoints import/export de MembreResource + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MembreResourceImportExportTest { + + private static final String BASE_PATH = "/api/membres"; + + @Inject MembreRepository membreRepository; + @Inject OrganisationRepository organisationRepository; + + private Organisation testOrganisation; + private List testMembres; + + @BeforeEach + @Transactional + void setupTestData() { + // Créer une organisation de test + testOrganisation = + Organisation.builder() + .nom("Organisation Test Import/Export") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("org-import-export-" + System.currentTimeMillis() + "@test.com") + .build(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); + + // Créer quelques membres de test + testMembres = new ArrayList<>(); + for (int i = 1; i <= 3; i++) { + Membre membre = + Membre.builder() + .numeroMembre("UF-TEST-IMPORT-" + i) + .nom("Nom" + i) + .prenom("Prenom" + i) + .email("membre" + i + "-import-" + System.currentTimeMillis() + "@test.com") + .telephone("+22170123456" + i) + .dateNaissance(LocalDate.of(1990 + i, 1, 1)) + .dateAdhesion(LocalDate.of(2023, 1, 1)) + .organisation(testOrganisation) + .build(); + membre.setDateCreation(LocalDateTime.now()); + membre.setActif(true); + membreRepository.persist(membre); + testMembres.add(membre); + } + } + + @AfterEach + @Transactional + void cleanupTestData() { + if (testMembres != null) { + testMembres.forEach( + membre -> { + if (membre.getId() != null) { + Membre m = membreRepository.findById(membre.getId()); + if (m != null) { + membreRepository.delete(m); + } + } + }); + } + + if (testOrganisation != null && testOrganisation.getId() != null) { + Organisation org = organisationRepository.findById(testOrganisation.getId()); + if (org != null) { + organisationRepository.delete(org); + } + } + } + + @Test + @Order(1) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/import/modele doit retourner un fichier Excel template") + void testTelechargerModeleImport() { + given() + .when() + .get(BASE_PATH + "/import/modele") + .then() + .statusCode(200) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .header("Content-Disposition", containsString("attachment")) + .header("Content-Disposition", containsString("modele_import_membres")); + } + + @Test + @Order(2) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/membres/import doit importer des membres depuis Excel") + void testImporterMembresExcel() throws Exception { + // Créer un fichier Excel de test + byte[] excelFile = createTestExcelFile(); + + given() + .contentType("multipart/form-data") + .multiPart("file", "test_import.xlsx", excelFile, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .formParam("organisationId", testOrganisation.getId().toString()) + .formParam("typeMembreDefaut", "ACTIF") + .formParam("mettreAJourExistants", "false") + .formParam("ignorerErreurs", "false") + .when() + .post(BASE_PATH + "/import") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("lignesTraitees", greaterThan(0)) + .body("erreurs", notNullValue()); + } + + @Test + @Order(3) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/membres/import doit retourner 400 pour fichier vide") + void testImporterMembresFichierVide() { + // Envoyer un fichier vide (tableau de bytes vide) + byte[] emptyFile = new byte[0]; + + given() + .contentType("multipart/form-data") + .multiPart("file", "empty.xlsx", emptyFile, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .formParam("organisationId", testOrganisation.getId().toString()) + .when() + .post(BASE_PATH + "/import") + .then() + .statusCode(400); + } + + @Test + @Order(4) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/export doit exporter des membres en Excel") + void testExporterMembresExcel() { + given() + .queryParam("format", "EXCEL") + .queryParam("statut", "ACTIF") + .when() + .get(BASE_PATH + "/export") + .then() + .statusCode(200) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .header("Content-Disposition", containsString("attachment")); + } + + @Test + @Order(5) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/export doit exporter en CSV") + void testExporterMembresCSV() { + given() + .queryParam("format", "CSV") + .queryParam("statut", "ACTIF") + .when() + .get(BASE_PATH + "/export") + .then() + .statusCode(200) + .contentType("text/csv") + .header("Content-Disposition", containsString("attachment")); + } + + @Test + @Order(6) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/export/count doit retourner le nombre de membres à exporter") + void testCompterMembresPourExport() { + given() + .queryParam("statut", "ACTIF") + .when() + .get(BASE_PATH + "/export/count") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("count", greaterThanOrEqualTo(0)); + } + + @Test + @Order(7) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/membres/export/selection doit exporter une sélection de membres") + void testExporterSelectionMembres() { + List membreIds = new ArrayList<>(); + testMembres.forEach(m -> membreIds.add(m.getId())); + + given() + .contentType(ContentType.JSON) + .body(membreIds) + .queryParam("format", "EXCEL") + .when() + .post(BASE_PATH + "/export/selection") + .then() + .statusCode(200) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .header("Content-Disposition", containsString("attachment")); + } + + @Test + @Order(8) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/export doit supporter inclureStatistiques") + void testExporterAvecStatistiques() { + given() + .queryParam("format", "EXCEL") + .queryParam("inclureStatistiques", "true") + .queryParam("statut", "ACTIF") + .when() + .get(BASE_PATH + "/export") + .then() + .statusCode(200) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + } + + @Test + @Order(9) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/membres/export doit supporter le chiffrement") + void testExporterAvecChiffrement() { + given() + .queryParam("format", "EXCEL") + .queryParam("motDePasse", "testPassword123") + .queryParam("statut", "ACTIF") + .when() + .get(BASE_PATH + "/export") + .then() + .statusCode(200) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + } + + /** + * Crée un fichier Excel de test avec des données de membres valides + */ + private byte[] createTestExcelFile() throws Exception { + try (Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + + Sheet sheet = workbook.createSheet("Membres"); + + // En-têtes + Row headerRow = sheet.createRow(0); + String[] headers = { + "nom", "prenom", "email", "telephone", "dateNaissance", "dateAdhesion" + }; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + } + + // Données de test + Row dataRow = sheet.createRow(1); + dataRow.createCell(0).setCellValue("TestNom"); + dataRow.createCell(1).setCellValue("TestPrenom"); + dataRow.createCell(2).setCellValue("test-import-" + System.currentTimeMillis() + "@test.com"); + dataRow.createCell(3).setCellValue("+221701234999"); + dataRow.createCell(4).setCellValue("1990-01-01"); + dataRow.createCell(5).setCellValue("2023-01-01"); + + workbook.write(out); + return out.toByteArray(); + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java b/src/test/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java new file mode 100644 index 0000000..14df72f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/resource/OrganisationResourceTest.java @@ -0,0 +1,351 @@ +package dev.lions.unionflow.server.resource; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.*; + +/** + * Tests d'intégration pour OrganisationResource + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class OrganisationResourceTest { + + private static final String BASE_PATH = "/api/organisations"; + + @Inject OrganisationRepository organisationRepository; + + private Organisation testOrganisation; + + @BeforeEach + @Transactional + void setupTestData() { + // Créer une organisation de test + testOrganisation = + Organisation.builder() + .nom("Organisation Test") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("test-org-" + System.currentTimeMillis() + "@test.com") + .telephone("+221701234567") + .ville("Dakar") + .pays("Sénégal") + .build(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); + } + + @AfterEach + @Transactional + void cleanupTestData() { + if (testOrganisation != null && testOrganisation.getId() != null) { + Organisation orgToDelete = organisationRepository.findById(testOrganisation.getId()); + if (orgToDelete != null) { + organisationRepository.delete(orgToDelete); + } + } + } + + @Test + @Order(1) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/organisations doit retourner la liste des organisations") + void testListerOrganisations() { + given() + .when() + .get(BASE_PATH) + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("$", notNullValue()); + } + + @Test + @Order(2) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/organisations/{id} doit retourner une organisation existante") + void testObtenirOrganisation() { + UUID orgId = testOrganisation.getId(); + + given() + .pathParam("id", orgId) + .when() + .get(BASE_PATH + "/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(orgId.toString())) + .body("nom", equalTo("Organisation Test")); + } + + @Test + @Order(3) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("GET /api/organisations/{id} doit retourner 404 pour un ID inexistant") + void testObtenirOrganisationInexistante() { + UUID fakeId = UUID.randomUUID(); + + given() + .pathParam("id", fakeId) + .when() + .get(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(4) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/organisations doit créer une nouvelle organisation") + void testCreerOrganisation() { + OrganisationDTO newOrg = new OrganisationDTO(); + newOrg.setNom("Nouvelle Organisation Test"); + newOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + newOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + newOrg.setEmail("nouvelle-org-" + System.currentTimeMillis() + "@test.com"); + newOrg.setTelephone("+221701234568"); + newOrg.setVille("Thiès"); + newOrg.setPays("Sénégal"); + + String location = + given() + .contentType(ContentType.JSON) + .body(newOrg) + .when() + .post(BASE_PATH) + .then() + .statusCode(201) + .contentType(ContentType.JSON) + .body("nom", equalTo("Nouvelle Organisation Test")) + .body("id", notNullValue()) + .extract() + .header("Location"); + + // Nettoyer l'organisation créée + if (location != null && location.contains("/")) { + String idStr = location.substring(location.lastIndexOf("/") + 1); + try { + UUID createdId = UUID.fromString(idStr); + Organisation created = organisationRepository.findById(createdId); + if (created != null) { + organisationRepository.delete(created); + } + } catch (Exception e) { + // Ignorer les erreurs de nettoyage + } + } + } + + @Test + @Order(5) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/organisations doit retourner 400 pour données invalides") + void testCreerOrganisationInvalide() { + OrganisationDTO invalidOrg = new OrganisationDTO(); + invalidOrg.setNom(""); // Nom vide - invalide + invalidOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + invalidOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + invalidOrg.setEmail("invalid-email"); // Email invalide + + given() + .contentType(ContentType.JSON) + .body(invalidOrg) + .when() + .post(BASE_PATH) + .then() + .statusCode(400); + } + + @Test + @Order(6) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("POST /api/organisations doit retourner 409 pour email dupliqué") + void testCreerOrganisationEmailDuplique() { + OrganisationDTO duplicateOrg = new OrganisationDTO(); + duplicateOrg.setNom("Autre Organisation"); + duplicateOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + duplicateOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + duplicateOrg.setEmail(testOrganisation.getEmail()); // Email déjà utilisé + + given() + .contentType(ContentType.JSON) + .body(duplicateOrg) + .when() + .post(BASE_PATH) + .then() + .statusCode(409); + } + + @Test + @Order(7) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("PUT /api/organisations/{id} doit mettre à jour une organisation") + void testModifierOrganisation() { + UUID orgId = testOrganisation.getId(); + OrganisationDTO updatedOrg = new OrganisationDTO(); + updatedOrg.setNom("Organisation Modifiée"); + updatedOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + updatedOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + updatedOrg.setEmail(testOrganisation.getEmail()); + updatedOrg.setTelephone("+221701234999"); + updatedOrg.setVille("Saint-Louis"); + updatedOrg.setPays("Sénégal"); + + given() + .contentType(ContentType.JSON) + .pathParam("id", orgId) + .body(updatedOrg) + .when() + .put(BASE_PATH + "/{id}") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("nom", equalTo("Organisation Modifiée")) + .body("telephone", equalTo("+221701234999")); + } + + @Test + @Order(8) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("PUT /api/organisations/{id} doit retourner 404 pour ID inexistant") + void testModifierOrganisationInexistante() { + UUID fakeId = UUID.randomUUID(); + OrganisationDTO updatedOrg = new OrganisationDTO(); + updatedOrg.setNom("Organisation Test"); + updatedOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + updatedOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + updatedOrg.setEmail("fake-" + System.currentTimeMillis() + "@test.com"); + + given() + .contentType(ContentType.JSON) + .pathParam("id", fakeId) + .body(updatedOrg) + .when() + .put(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(9) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("DELETE /api/organisations/{id} doit supprimer une organisation") + void testSupprimerOrganisation() { + // Créer une organisation temporaire pour la suppression + Organisation tempOrg = + Organisation.builder() + .nom("Organisation à Supprimer") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("temp-delete-" + System.currentTimeMillis() + "@test.com") + .build(); + tempOrg.setDateCreation(LocalDateTime.now()); + tempOrg.setActif(true); + organisationRepository.persist(tempOrg); + + UUID tempId = tempOrg.getId(); + + given() + .pathParam("id", tempId) + .when() + .delete(BASE_PATH + "/{id}") + .then() + .statusCode(204); + + // Vérifier que l'organisation a été supprimée + Organisation deleted = organisationRepository.findById(tempId); + assert deleted == null : "L'organisation devrait être supprimée"; + } + + @Test + @Order(10) + @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"}) + @DisplayName("DELETE /api/organisations/{id} doit retourner 404 pour ID inexistant") + void testSupprimerOrganisationInexistante() { + UUID fakeId = UUID.randomUUID(); + + given() + .pathParam("id", fakeId) + .when() + .delete(BASE_PATH + "/{id}") + .then() + .statusCode(404); + } + + @Test + @Order(11) + @TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"}) + @DisplayName("GET /api/organisations doit être accessible aux membres") + void testListerOrganisationsPourMembre() { + given() + .when() + .get(BASE_PATH) + .then() + .statusCode(200); + } + + @Test + @Order(12) + @DisplayName("GET /api/organisations doit être accessible publiquement") + void testListerOrganisationsPublic() { + given() + .when() + .get(BASE_PATH) + .then() + .statusCode(200); + } + + @Test + @Order(13) + @TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"}) + @DisplayName("POST /api/organisations doit être accessible aux membres") + void testCreerOrganisationPourMembre() { + OrganisationDTO newOrg = new OrganisationDTO(); + newOrg.setNom("Organisation Créée par Membre"); + newOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION); + newOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE); + newOrg.setEmail("membre-org-" + System.currentTimeMillis() + "@test.com"); + + String location = + given() + .contentType(ContentType.JSON) + .body(newOrg) + .when() + .post(BASE_PATH) + .then() + .statusCode(201) + .extract() + .header("Location"); + + // Nettoyer + if (location != null && location.contains("/")) { + String idStr = location.substring(location.lastIndexOf("/") + 1); + try { + UUID createdId = UUID.fromString(idStr); + Organisation created = organisationRepository.findById(createdId); + if (created != null) { + organisationRepository.delete(created); + } + } catch (Exception e) { + // Ignorer + } + } + } +} + diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java new file mode 100644 index 0000000..261093f --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/service/MembreImportExportServiceTest.java @@ -0,0 +1,369 @@ +package dev.lions.unionflow.server.service; + +import static org.assertj.core.api.Assertions.*; + +import dev.lions.unionflow.server.api.dto.membre.MembreDTO; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.*; + +/** + * Tests unitaires pour MembreImportExportService + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MembreImportExportServiceTest { + + @Inject MembreImportExportService importExportService; + @Inject MembreRepository membreRepository; + @Inject OrganisationRepository organisationRepository; + @Inject MembreService membreService; + + private Organisation testOrganisation; + private List testMembres; + + @BeforeEach + @Transactional + void setupTestData() { + // Créer une organisation de test + testOrganisation = + Organisation.builder() + .nom("Organisation Test Import/Export Service") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("org-service-" + System.currentTimeMillis() + "@test.com") + .build(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); + + // Créer quelques membres de test + testMembres = new ArrayList<>(); + for (int i = 1; i <= 5; i++) { + Membre membre = + Membre.builder() + .numeroMembre("UF-SERVICE-TEST-" + i) + .nom("NomService" + i) + .prenom("PrenomService" + i) + .email("service" + i + "-" + System.currentTimeMillis() + "@test.com") + .telephone("+2217012345" + (10 + i)) + .dateNaissance(LocalDate.of(1985 + i, 1, 1)) + .dateAdhesion(LocalDate.of(2022, 1, 1)) + .organisation(testOrganisation) + .build(); + membre.setDateCreation(LocalDateTime.now()); + membre.setActif(true); + membreRepository.persist(membre); + testMembres.add(membre); + } + } + + @AfterEach + @Transactional + void cleanupTestData() { + if (testMembres != null) { + testMembres.forEach( + membre -> { + if (membre.getId() != null) { + Membre m = membreRepository.findById(membre.getId()); + if (m != null) { + membreRepository.delete(m); + } + } + }); + } + + if (testOrganisation != null && testOrganisation.getId() != null) { + Organisation org = organisationRepository.findById(testOrganisation.getId()); + if (org != null) { + organisationRepository.delete(org); + } + } + } + + @Test + @Order(1) + @DisplayName("Doit générer un modèle d'import Excel valide") + void testGenererModeleImport() throws Exception { + // When + byte[] modele = importExportService.genererModeleImport(); + + // Then + assertThat(modele).isNotNull(); + assertThat(modele.length).isGreaterThan(0); + + // Vérifier que c'est un fichier Excel valide + try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(modele))) { + Sheet sheet = workbook.getSheetAt(0); + assertThat(sheet).isNotNull(); + Row headerRow = sheet.getRow(0); + assertThat(headerRow).isNotNull(); + // Vérifier la présence de colonnes essentielles + boolean hasNom = false, hasPrenom = false, hasEmail = false; + for (Cell cell : headerRow) { + String value = cell.getStringCellValue().toLowerCase(); + if (value.contains("nom")) hasNom = true; + if (value.contains("prenom")) hasPrenom = true; + if (value.contains("email")) hasEmail = true; + } + assertThat(hasNom).isTrue(); + assertThat(hasPrenom).isTrue(); + assertThat(hasEmail).isTrue(); + } + } + + @Test + @Order(2) + @DisplayName("Doit importer des membres depuis un fichier Excel valide") + void testImporterDepuisExcel() throws Exception { + // Given - Créer un fichier Excel de test + byte[] excelFile = createValidExcelFile(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(excelFile); + + // When + MembreImportExportService.ResultatImport resultat = + importExportService.importerMembres( + inputStream, + "test_import.xlsx", + testOrganisation.getId(), + "ACTIF", + false, + false); + + // Then + assertThat(resultat).isNotNull(); + assertThat(resultat.lignesTraitees).isGreaterThan(0); + assertThat(resultat.membresImportes).isNotEmpty(); + assertThat(resultat.erreurs).isEmpty(); + } + + @Test + @Order(3) + @DisplayName("Doit gérer les erreurs lors de l'import Excel") + void testImporterExcelAvecErreurs() throws Exception { + // Given - Créer un fichier Excel avec des données invalides + byte[] excelFile = createInvalidExcelFile(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(excelFile); + + // When + MembreImportExportService.ResultatImport resultat = + importExportService.importerMembres( + inputStream, + "test_invalid.xlsx", + testOrganisation.getId(), + "ACTIF", + false, + true); // Ignorer les erreurs + + // Then + assertThat(resultat).isNotNull(); + assertThat(resultat.erreurs).isNotEmpty(); + } + + @Test + @Order(4) + @DisplayName("Doit exporter des membres vers Excel") + void testExporterVersExcel() throws Exception { + // Given - Convertir les membres de test en DTOs + List membresDTO = new ArrayList<>(); + testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m))); + + // When + byte[] excelData = + importExportService.exporterVersExcel( + membresDTO, + List.of("nom", "prenom", "email", "telephone"), + true, // inclureHeaders + false, // formaterDates + false, // inclureStatistiques + null); // motDePasse + + // Then + assertThat(excelData).isNotNull(); + assertThat(excelData.length).isGreaterThan(0); + + // Vérifier que c'est un fichier Excel valide + try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(excelData))) { + Sheet sheet = workbook.getSheetAt(0); + assertThat(sheet).isNotNull(); + assertThat(sheet.getLastRowNum()).isGreaterThan(0); + } + } + + @Test + @Order(5) + @DisplayName("Doit exporter des membres vers Excel avec statistiques") + void testExporterVersExcelAvecStatistiques() throws Exception { + // Given + List membresDTO = new ArrayList<>(); + testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m))); + + // When + byte[] excelData = + importExportService.exporterVersExcel( + membresDTO, + List.of("nom", "prenom", "email"), + true, // inclureHeaders + false, // formaterDates + true, // inclureStatistiques + null); // motDePasse + + // Then + assertThat(excelData).isNotNull(); + + // Vérifier qu'il y a plusieurs feuilles (données + statistiques) + try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(excelData))) { + assertThat(workbook.getNumberOfSheets()).isGreaterThan(1); + Sheet statsSheet = workbook.getSheet("Statistiques"); + assertThat(statsSheet).isNotNull(); + } + } + + @Test + @Order(6) + @DisplayName("Doit exporter des membres vers Excel avec chiffrement") + void testExporterVersExcelAvecChiffrement() throws Exception { + // Given + List membresDTO = new ArrayList<>(); + testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m))); + + // When + byte[] excelData = + importExportService.exporterVersExcel( + membresDTO, + List.of("nom", "prenom", "email"), + true, // inclureHeaders + false, // formaterDates + false, // inclureStatistiques + "testPassword123"); // motDePasse + + // Then + assertThat(excelData).isNotNull(); + // Note: La vérification du chiffrement nécessiterait d'essayer d'ouvrir le fichier avec le mot de passe + } + + @Test + @Order(7) + @DisplayName("Doit exporter des membres vers CSV") + void testExporterVersCSV() throws Exception { + // Given + List membresDTO = new ArrayList<>(); + testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m))); + + // When - Utiliser les groupes de colonnes attendus par la méthode + byte[] csvData = + importExportService.exporterVersCSV( + membresDTO, + List.of("PERSO", "CONTACT"), // Groupes de colonnes + true, // inclureHeaders + false); // formaterDates + + // Then + assertThat(csvData).isNotNull(); + assertThat(csvData.length).isGreaterThan(0); + + // Vérifier le contenu CSV - les en-têtes sont en français avec majuscules + String csvContent = new String(csvData, java.nio.charset.StandardCharsets.UTF_8); + assertThat(csvContent).contains("Nom"); + assertThat(csvContent).contains("Prénom"); + assertThat(csvContent).contains("Email"); + } + + @Test + @Order(8) + @DisplayName("Doit rejeter un format de fichier non supporté") + void testFormatNonSupporte() { + // Given + byte[] invalidFile = "Ceci n'est pas un fichier Excel".getBytes(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(invalidFile); + + // When & Then + assertThatThrownBy( + () -> + importExportService.importerMembres( + inputStream, + "test.txt", + testOrganisation.getId(), + "ACTIF", + false, + false)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Format de fichier non supporté"); + } + + /** + * Crée un fichier Excel valide pour les tests d'import + */ + private byte[] createValidExcelFile() throws Exception { + try (Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + + Sheet sheet = workbook.createSheet("Membres"); + + // En-têtes + Row headerRow = sheet.createRow(0); + String[] headers = { + "nom", "prenom", "email", "telephone", "dateNaissance", "dateAdhesion" + }; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + } + + // Données valides + Row dataRow = sheet.createRow(1); + dataRow.createCell(0).setCellValue("TestNom"); + dataRow.createCell(1).setCellValue("TestPrenom"); + dataRow.createCell(2).setCellValue("test-valid-" + System.currentTimeMillis() + "@test.com"); + dataRow.createCell(3).setCellValue("+221701234999"); + dataRow.createCell(4).setCellValue("1990-01-01"); + dataRow.createCell(5).setCellValue("2023-01-01"); + + workbook.write(out); + return out.toByteArray(); + } + } + + /** + * Crée un fichier Excel avec des données invalides pour tester la gestion d'erreurs + */ + private byte[] createInvalidExcelFile() throws Exception { + try (Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + + Sheet sheet = workbook.createSheet("Membres"); + + // En-têtes + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("nom"); + headerRow.createCell(1).setCellValue("prenom"); + headerRow.createCell(2).setCellValue("email"); + + // Données invalides (email manquant) + Row dataRow = sheet.createRow(1); + dataRow.createCell(0).setCellValue("TestNom"); + dataRow.createCell(1).setCellValue("TestPrenom"); + // Email manquant - devrait générer une erreur + + workbook.write(out); + return out.toByteArray(); + } + } +} diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java new file mode 100644 index 0000000..4d1eb4c --- /dev/null +++ b/src/test/java/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.java @@ -0,0 +1,400 @@ +package dev.lions.unionflow.server.service; + +import static org.assertj.core.api.Assertions.*; + +import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; +import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO; +import dev.lions.unionflow.server.entity.Membre; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.repository.MembreRepository; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.*; + +/** + * Tests pour la recherche avancée de membres + * + * @author UnionFlow Team + * @version 1.0 + * @since 2025-01-19 + */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MembreServiceAdvancedSearchTest { + + @Inject MembreService membreService; + @Inject MembreRepository membreRepository; + @Inject OrganisationRepository organisationRepository; + + private Organisation testOrganisation; + private List testMembres; + + @BeforeEach + @Transactional + void setupTestData() { + // Créer et persister une organisation de test + testOrganisation = + Organisation.builder() + .nom("Organisation Test") + .typeOrganisation("ASSOCIATION") + .statut("ACTIF") + .email("test@organisation.com") + .build(); + testOrganisation.setDateCreation(LocalDateTime.now()); + testOrganisation.setActif(true); + organisationRepository.persist(testOrganisation); + + // Créer des membres de test avec différents profils + testMembres = + List.of( + // Membre actif jeune + createMembre("UF-2025-TEST001", "Dupont", "Marie", "marie.dupont@test.com", + "+221701234567", LocalDate.of(1995, 5, 15), LocalDate.of(2023, 1, 15), + "MEMBRE,SECRETAIRE", true), + + // Membre actif âgé + createMembre("UF-2025-TEST002", "Martin", "Jean", "jean.martin@test.com", + "+221701234568", LocalDate.of(1970, 8, 20), LocalDate.of(2020, 3, 10), + "MEMBRE,PRESIDENT", true), + + // Membre inactif + createMembre("UF-2025-TEST003", "Diallo", "Fatou", "fatou.diallo@test.com", + "+221701234569", LocalDate.of(1985, 12, 3), LocalDate.of(2021, 6, 5), + "MEMBRE", false), + + // Membre avec email spécifique + createMembre("UF-2025-TEST004", "Sow", "Amadou", "amadou.sow@unionflow.com", + "+221701234570", LocalDate.of(1988, 3, 12), LocalDate.of(2022, 9, 20), + "MEMBRE,TRESORIER", true)); + + // Persister tous les membres + testMembres.forEach(membre -> membreRepository.persist(membre)); + } + + private Membre createMembre(String numero, String nom, String prenom, String email, + String telephone, LocalDate dateNaissance, LocalDate dateAdhesion, + String roles, boolean actif) { + Membre membre = Membre.builder() + .numeroMembre(numero) + .nom(nom) + .prenom(prenom) + .email(email) + .telephone(telephone) + .dateNaissance(dateNaissance) + .dateAdhesion(dateAdhesion) + .organisation(testOrganisation) + .build(); + membre.setDateCreation(LocalDateTime.now()); + membre.setActif(actif); + // Note: Le champ roles est maintenant List et doit être géré via la relation MembreRole + // Pour les tests, on laisse la liste vide par défaut + return membre; + } + + @AfterEach + @Transactional + void cleanupTestData() { + // Nettoyer les données de test + if (testMembres != null) { + testMembres.forEach(membre -> { + if (membre.getId() != null) { + // Recharger l'entité depuis la base pour éviter l'erreur "detached entity" + membreRepository.findByIdOptional(membre.getId()).ifPresent(m -> { + // Utiliser deleteById pour éviter les problèmes avec les entités détachées + membreRepository.deleteById(m.getId()); + }); + } + }); + } + + if (testOrganisation != null && testOrganisation.getId() != null) { + // Recharger l'entité depuis la base pour éviter l'erreur "detached entity" + organisationRepository.findByIdOptional(testOrganisation.getId()).ifPresent(o -> { + // Utiliser deleteById pour éviter les problèmes avec les entités détachées + organisationRepository.deleteById(o.getId()); + }); + } + } + + @Test + @Order(1) + @DisplayName("Doit effectuer une recherche par terme général") + void testSearchByGeneralQuery() { + // Given + MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("marie").build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(1); + assertThat(result.getMembres()).hasSize(1); + assertThat(result.getMembres().get(0).getPrenom()).isEqualToIgnoringCase("Marie"); + assertThat(result.isFirst()).isTrue(); + assertThat(result.isLast()).isTrue(); + } + + @Test + @Order(2) + @DisplayName("Doit filtrer par statut actif") + void testSearchByActiveStatus() { + // Given + MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(3); // 3 membres actifs + assertThat(result.getMembres()).hasSize(3); + assertThat(result.getMembres()).allMatch(membre -> "ACTIF".equals(membre.getStatut())); + } + + @Test + @Order(3) + @DisplayName("Doit filtrer par tranche d'âge") + void testSearchByAgeRange() { + // Given + MembreSearchCriteria criteria = MembreSearchCriteria.builder().ageMin(25).ageMax(35).build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isGreaterThan(0); + + // Vérifier que tous les membres sont dans la tranche d'âge + result + .getMembres() + .forEach( + membre -> { + if (membre.getDateNaissance() != null) { + int age = LocalDate.now().getYear() - membre.getDateNaissance().getYear(); + assertThat(age).isBetween(25, 35); + } + }); + } + + @Test + @Order(4) + @DisplayName("Doit filtrer par période d'adhésion") + void testSearchByAdhesionPeriod() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .dateAdhesionMin(LocalDate.of(2022, 1, 1)) + .dateAdhesionMax(LocalDate.of(2023, 12, 31)) + .build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("dateAdhesion")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isGreaterThan(0); + + // Vérifier que toutes les dates d'adhésion sont dans la période + result + .getMembres() + .forEach( + membre -> { + if (membre.getDateAdhesion() != null) { + assertThat(membre.getDateAdhesion()) + .isAfterOrEqualTo(LocalDate.of(2022, 1, 1)) + .isBeforeOrEqualTo(LocalDate.of(2023, 12, 31)); + } + }); + } + + @Test + @Order(5) + @DisplayName("Doit rechercher par email avec domaine spécifique") + void testSearchByEmailDomain() { + // Given + MembreSearchCriteria criteria = MembreSearchCriteria.builder().email("@unionflow.com").build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(1); + assertThat(result.getMembres()).hasSize(1); + assertThat(result.getMembres().get(0).getEmail()).contains("@unionflow.com"); + } + + @Test + @Order(6) + @DisplayName("Doit filtrer par rôles") + void testSearchByRoles() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder().roles(List.of("PRESIDENT", "SECRETAIRE")).build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isGreaterThan(0); + + // Vérifier que tous les membres ont au moins un des rôles recherchés + result + .getMembres() + .forEach( + membre -> { + assertThat(membre.getRole()) + .satisfiesAnyOf( + role -> assertThat(role).contains("PRESIDENT"), + role -> assertThat(role).contains("SECRETAIRE")); + }); + } + + @Test + @Order(7) + @DisplayName("Doit gérer la pagination correctement") + void testPagination() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .includeInactifs(true) // Inclure tous les membres + .build(); + + // When - Première page + MembreSearchResultDTO firstPage = + membreService.searchMembresAdvanced(criteria, Page.of(0, 2), Sort.by("nom")); + + // Then + assertThat(firstPage).isNotNull(); + assertThat(firstPage.getCurrentPage()).isEqualTo(0); + assertThat(firstPage.getPageSize()).isEqualTo(2); + assertThat(firstPage.getMembres()).hasSizeLessThanOrEqualTo(2); + assertThat(firstPage.isFirst()).isTrue(); + + if (firstPage.getTotalElements() > 2) { + assertThat(firstPage.isLast()).isFalse(); + assertThat(firstPage.isHasNext()).isTrue(); + } + } + + @Test + @Order(8) + @DisplayName("Doit calculer les statistiques correctement") + void testStatisticsCalculation() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder() + .includeInactifs(true) // Inclure tous les membres + .build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getStatistics()).isNotNull(); + + MembreSearchResultDTO.SearchStatistics stats = result.getStatistics(); + assertThat(stats.getMembresActifs()).isEqualTo(3); + assertThat(stats.getMembresInactifs()).isEqualTo(1); + assertThat(stats.getAgeMoyen()).isGreaterThan(0); + assertThat(stats.getAgeMin()).isGreaterThan(0); + assertThat(stats.getAgeMax()).isGreaterThan(stats.getAgeMin()); + assertThat(stats.getAncienneteMoyenne()).isGreaterThanOrEqualTo(0); + } + + @Test + @Order(9) + @DisplayName("Doit retourner un résultat vide pour critères impossibles") + void testEmptyResultForImpossibleCriteria() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder().query("membre_inexistant_xyz").build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(0); + assertThat(result.getMembres()).isEmpty(); + assertThat(result.isEmpty()).isTrue(); + assertThat(result.getTotalPages()).isEqualTo(0); + } + + @Test + @Order(10) + @DisplayName("Doit valider la cohérence des critères") + void testCriteriaValidation() { + // Given - Critères incohérents + MembreSearchCriteria invalidCriteria = + MembreSearchCriteria.builder() + .ageMin(50) + .ageMax(30) // Âge max < âge min + .build(); + + // When & Then + assertThat(invalidCriteria.isValid()).isFalse(); + } + + @Test + @Order(11) + @DisplayName("Doit avoir des performances acceptables (< 500ms)") + void testSearchPerformance() { + // Given + MembreSearchCriteria criteria = MembreSearchCriteria.builder().includeInactifs(true).build(); + + // When & Then - Mesurer le temps d'exécution + long startTime = System.currentTimeMillis(); + + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 20), Sort.by("nom")); + + long executionTime = System.currentTimeMillis() - startTime; + + // Vérifications + assertThat(result).isNotNull(); + assertThat(executionTime).isLessThan(500L); // Moins de 500ms + + // Log pour monitoring + System.out.printf( + "Recherche avancée exécutée en %d ms pour %d résultats%n", + executionTime, result.getTotalElements()); + } + + @Test + @Order(12) + @DisplayName("Doit gérer les critères avec caractères spéciaux") + void testSearchWithSpecialCharacters() { + // Given + MembreSearchCriteria criteria = + MembreSearchCriteria.builder().query("marie-josé").nom("o'connor").build(); + + // When + MembreSearchResultDTO result = + membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom")); + + // Then + assertThat(result).isNotNull(); + // La recherche ne doit pas échouer même avec des caractères spéciaux + assertThat(result.getTotalElements()).isGreaterThanOrEqualTo(0); + } +} diff --git a/target/classes/META-INF/beans.xml b/target/classes/META-INF/beans.xml new file mode 100644 index 0000000..1ba4e60 --- /dev/null +++ b/target/classes/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + diff --git a/target/classes/application-minimal.properties b/target/classes/application-minimal.properties new file mode 100644 index 0000000..309e021 --- /dev/null +++ b/target/classes/application-minimal.properties @@ -0,0 +1,56 @@ +# Configuration UnionFlow Server - Mode Minimal +quarkus.application.name=unionflow-server-minimal +quarkus.application.version=1.0.0 + +# Configuration HTTP +quarkus.http.port=8080 +quarkus.http.host=0.0.0.0 + +# Configuration CORS +quarkus.http.cors=true +quarkus.http.cors.origins=* +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization + +# Configuration Base de données H2 (en mémoire) +quarkus.datasource.db-kind=h2 +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.datasource.jdbc.url=jdbc:h2:mem:unionflow_minimal;DB_CLOSE_DELAY=-1;MODE=PostgreSQL + +# Configuration Hibernate +quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity + +# Désactiver Flyway +quarkus.flyway.migrate-at-start=false + +# Désactiver Keycloak temporairement +quarkus.oidc.tenant-enabled=false + +# Chemins publics (tous publics en mode minimal) +quarkus.http.auth.permission.public.paths=/* +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI +quarkus.smallrye-openapi.info-title=UnionFlow Server API - Minimal +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union (mode minimal) +quarkus.smallrye-openapi.servers=http://localhost:8080 + +# Configuration Swagger UI +quarkus.swagger-ui.always-include=true +quarkus.swagger-ui.path=/swagger-ui + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=DEBUG +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO diff --git a/target/classes/application-prod.properties b/target/classes/application-prod.properties new file mode 100644 index 0000000..d1dc9c8 --- /dev/null +++ b/target/classes/application-prod.properties @@ -0,0 +1,77 @@ +# Configuration UnionFlow Server - PRODUCTION +# Ce fichier est utilisé avec le profil Quarkus "prod" + +# Configuration HTTP +quarkus.http.port=8085 +quarkus.http.host=0.0.0.0 + +# Configuration CORS - Production (strict) +quarkus.http.cors=true +quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev} +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization +quarkus.http.cors.allow-credentials=true + +# Configuration Base de données PostgreSQL - Production +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=${DB_USERNAME:unionflow} +quarkus.datasource.password=${DB_PASSWORD} +quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow} +quarkus.datasource.jdbc.min-size=5 +quarkus.datasource.jdbc.max-size=20 + +# Configuration Hibernate - Production (IMPORTANT: update, pas drop-and-create) +quarkus.hibernate-orm.database.generation=update +quarkus.hibernate-orm.log.sql=false +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity +quarkus.hibernate-orm.metrics.enabled=false + +# Configuration Flyway - Production (ACTIVÉ) +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0.0 + +# Configuration Keycloak OIDC - Production +quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow} +quarkus.oidc.client-id=unionflow-server +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc.tls.verification=required +quarkus.oidc.application-type=service + +# Configuration Keycloak Policy Enforcer +quarkus.keycloak.policy-enforcer.enable=false +quarkus.keycloak.policy-enforcer.lazy-load-paths=true +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE + +# Chemins publics (non protégés) +quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI - Production (Swagger désactivé ou protégé) +quarkus.smallrye-openapi.info-title=UnionFlow Server API +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak +quarkus.smallrye-openapi.servers=https://api.lions.dev/unionflow + +# Configuration Swagger UI - Production (DÉSACTIVÉ pour sécurité) +quarkus.swagger-ui.always-include=false + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging - Production +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=INFO +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO +quarkus.log.category."org.jboss.resteasy".level=WARN + +# Configuration Wave Money - Production +wave.api.key=${WAVE_API_KEY:} +wave.api.secret=${WAVE_API_SECRET:} +wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1} +wave.environment=${WAVE_ENVIRONMENT:production} +wave.webhook.secret=${WAVE_WEBHOOK_SECRET:} diff --git a/target/classes/application-test.properties b/target/classes/application-test.properties new file mode 100644 index 0000000..173d6db --- /dev/null +++ b/target/classes/application-test.properties @@ -0,0 +1,31 @@ +# Configuration UnionFlow Server - Profil Test +# Ce fichier est chargé automatiquement quand le profil 'test' est actif + +# Configuration Base de données H2 pour tests +quarkus.datasource.db-kind=h2 +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL + +# Configuration Hibernate pour tests +quarkus.hibernate-orm.database.generation=drop-and-create +# Désactiver complètement l'exécution des scripts SQL au démarrage +quarkus.hibernate-orm.sql-load-script-source=none +# Empêcher Hibernate d'exécuter les scripts SQL automatiquement +# Note: Ne pas définir quarkus.hibernate-orm.sql-load-script car une chaîne vide peut causer des problèmes + +# Configuration Flyway pour tests (désactivé complètement) +quarkus.flyway.migrate-at-start=false +quarkus.flyway.enabled=false +quarkus.flyway.baseline-on-migrate=false +# Note: Ne pas définir quarkus.flyway.locations car une chaîne vide cause une erreur de configuration + +# Configuration Keycloak pour tests (désactivé) +quarkus.oidc.tenant-enabled=false +quarkus.keycloak.policy-enforcer.enable=false + +# Configuration HTTP pour tests +quarkus.http.port=0 +quarkus.http.test-port=0 + + diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..c81a866 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,103 @@ +# Configuration UnionFlow Server +quarkus.application.name=unionflow-server +quarkus.application.version=1.0.0 + +# Configuration HTTP +quarkus.http.port=8085 +quarkus.http.host=0.0.0.0 + +# Configuration CORS +quarkus.http.cors=true +quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:8086,https://unionflow.lions.dev,https://security.lions.dev} +quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS +quarkus.http.cors.headers=Content-Type,Authorization + +# Configuration Base de données PostgreSQL (par défaut) +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=${DB_USERNAME:unionflow} +quarkus.datasource.password=${DB_PASSWORD} +quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow} +quarkus.datasource.jdbc.min-size=2 +quarkus.datasource.jdbc.max-size=10 + +# Configuration Base de données PostgreSQL pour développement +%dev.quarkus.datasource.username=skyfile +%dev.quarkus.datasource.password=${DB_PASSWORD_DEV:skyfile} +%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow + +# Configuration Hibernate +quarkus.hibernate-orm.database.generation=update +quarkus.hibernate-orm.log.sql=false +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity +# Désactiver l'avertissement PanacheEntity (nous utilisons BaseEntity personnalisé) +quarkus.hibernate-orm.metrics.enabled=false + +# Configuration Hibernate pour développement +%dev.quarkus.hibernate-orm.database.generation=drop-and-create +%dev.quarkus.hibernate-orm.sql-load-script=import.sql +%dev.quarkus.hibernate-orm.log.sql=true + +# Configuration Flyway pour migrations +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0.0 + +# Configuration Flyway pour développement (désactivé) +%dev.quarkus.flyway.migrate-at-start=false + +# Configuration Keycloak OIDC (par défaut) +quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow +quarkus.oidc.client-id=unionflow-server +quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET} +quarkus.oidc.tls.verification=none +quarkus.oidc.application-type=service + +# Configuration Keycloak pour développement +%dev.quarkus.oidc.tenant-enabled=false +%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow + +# Configuration Keycloak Policy Enforcer (temporairement désactivé) +quarkus.keycloak.policy-enforcer.enable=false +quarkus.keycloak.policy-enforcer.lazy-load-paths=true +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE + +# Chemins publics (non protégés) +quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico,/auth/callback,/auth/* +quarkus.http.auth.permission.public.policy=permit + +# Configuration OpenAPI +quarkus.smallrye-openapi.info-title=UnionFlow Server API +quarkus.smallrye-openapi.info-version=1.0.0 +quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak +quarkus.smallrye-openapi.servers=http://localhost:8085 + +# Configuration Swagger UI +quarkus.swagger-ui.always-include=true +quarkus.swagger-ui.path=/swagger-ui + +# Configuration santé +quarkus.smallrye-health.root-path=/health + +# Configuration logging +quarkus.log.console.enable=true +quarkus.log.console.level=INFO +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n +quarkus.log.category."dev.lions.unionflow".level=INFO +quarkus.log.category."org.hibernate".level=WARN +quarkus.log.category."io.quarkus".level=INFO + +# Configuration logging pour développement +%dev.quarkus.log.category."dev.lions.unionflow".level=DEBUG +%dev.quarkus.log.category."org.hibernate.SQL".level=DEBUG + +# Configuration Jandex pour résoudre les warnings de réflexion +quarkus.index-dependency.unionflow-server-api.group-id=dev.lions.unionflow +quarkus.index-dependency.unionflow-server-api.artifact-id=unionflow-server-api + +# Configuration Wave Money +wave.api.key=${WAVE_API_KEY:} +wave.api.secret=${WAVE_API_SECRET:} +wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1} +wave.environment=${WAVE_ENVIRONMENT:sandbox} +wave.webhook.secret=${WAVE_WEBHOOK_SECRET:} diff --git a/target/classes/db/migration/V1.2__Create_Organisation_Table.sql b/target/classes/db/migration/V1.2__Create_Organisation_Table.sql new file mode 100644 index 0000000..7329794 --- /dev/null +++ b/target/classes/db/migration/V1.2__Create_Organisation_Table.sql @@ -0,0 +1,143 @@ +-- Migration V1.2: Création de la table organisations +-- Auteur: UnionFlow Team +-- Date: 2025-01-15 +-- Description: Création de la table organisations avec toutes les colonnes nécessaires + +-- Création de la table organisations +CREATE TABLE organisations ( + id BIGSERIAL PRIMARY KEY, + + -- Informations de base + nom VARCHAR(200) NOT NULL, + nom_court VARCHAR(50), + type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION', + statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + description TEXT, + date_fondation DATE, + numero_enregistrement VARCHAR(100) UNIQUE, + + -- Informations de contact + email VARCHAR(255) NOT NULL UNIQUE, + telephone VARCHAR(20), + telephone_secondaire VARCHAR(20), + email_secondaire VARCHAR(255), + + -- Adresse + adresse VARCHAR(500), + ville VARCHAR(100), + code_postal VARCHAR(20), + region VARCHAR(100), + pays VARCHAR(100), + + -- Coordonnées géographiques + latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90), + longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180), + + -- Web et réseaux sociaux + site_web VARCHAR(500), + logo VARCHAR(500), + reseaux_sociaux VARCHAR(1000), + + -- Hiérarchie + organisation_parente_id UUID, + niveau_hierarchique INTEGER NOT NULL DEFAULT 0, + + -- Statistiques + nombre_membres INTEGER NOT NULL DEFAULT 0, + nombre_administrateurs INTEGER NOT NULL DEFAULT 0, + + -- Finances + budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0), + devise VARCHAR(3) DEFAULT 'XOF', + cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE, + montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0), + + -- Informations complémentaires + objectifs TEXT, + activites_principales TEXT, + certifications VARCHAR(500), + partenaires VARCHAR(1000), + notes VARCHAR(1000), + + -- Paramètres + organisation_publique BOOLEAN NOT NULL DEFAULT TRUE, + accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE, + + -- Métadonnées + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(100), + modifie_par VARCHAR(100), + version BIGINT NOT NULL DEFAULT 0, + + -- Contraintes + CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')), + CONSTRAINT chk_organisation_type CHECK (type_organisation IN ( + 'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE', + 'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE' + )), + CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')), + CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10), + CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0), + CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0) +); + +-- Création des index pour optimiser les performances +CREATE INDEX idx_organisation_nom ON organisations(nom); +CREATE INDEX idx_organisation_email ON organisations(email); +CREATE INDEX idx_organisation_statut ON organisations(statut); +CREATE INDEX idx_organisation_type ON organisations(type_organisation); +CREATE INDEX idx_organisation_ville ON organisations(ville); +CREATE INDEX idx_organisation_pays ON organisations(pays); +CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id); +CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement); +CREATE INDEX idx_organisation_actif ON organisations(actif); +CREATE INDEX idx_organisation_date_creation ON organisations(date_creation); +CREATE INDEX idx_organisation_publique ON organisations(organisation_publique); +CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres); + +-- Index composites pour les recherches fréquentes +CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif); +CREATE INDEX idx_organisation_type_ville ON organisations(type_organisation, ville); +CREATE INDEX idx_organisation_pays_region ON organisations(pays, region); +CREATE INDEX idx_organisation_publique_actif ON organisations(organisation_publique, actif); + +-- Index pour les recherches textuelles +CREATE INDEX idx_organisation_nom_lower ON organisations(LOWER(nom)); +CREATE INDEX idx_organisation_nom_court_lower ON organisations(LOWER(nom_court)); +CREATE INDEX idx_organisation_ville_lower ON organisations(LOWER(ville)); + +-- Ajout de la colonne organisation_id à la table membres (si elle n'existe pas déjà) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'membres' AND column_name = 'organisation_id' + ) THEN + ALTER TABLE membres ADD COLUMN organisation_id BIGINT; + ALTER TABLE membres ADD CONSTRAINT fk_membre_organisation + FOREIGN KEY (organisation_id) REFERENCES organisations(id); + CREATE INDEX idx_membre_organisation ON membres(organisation_id); + END IF; +END $$; + +-- IMPORTANT: Aucune donnée fictive n'est insérée dans ce script de migration. +-- Les données doivent être insérées manuellement via l'interface d'administration +-- ou via des scripts de migration séparés si nécessaire pour la production. + +-- Mise à jour des statistiques de la base de données +ANALYZE organisations; + +-- Commentaires sur la table et les colonnes principales +COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.)'; +COMMENT ON COLUMN organisations.nom IS 'Nom officiel de l''organisation'; +COMMENT ON COLUMN organisations.nom_court IS 'Nom court ou sigle de l''organisation'; +COMMENT ON COLUMN organisations.type_organisation IS 'Type d''organisation (LIONS_CLUB, ASSOCIATION, etc.)'; +COMMENT ON COLUMN organisations.statut IS 'Statut actuel de l''organisation (ACTIVE, SUSPENDUE, etc.)'; +COMMENT ON COLUMN organisations.organisation_parente_id IS 'ID de l''organisation parente pour la hiérarchie'; +COMMENT ON COLUMN organisations.niveau_hierarchique IS 'Niveau dans la hiérarchie (0 = racine)'; +COMMENT ON COLUMN organisations.nombre_membres IS 'Nombre total de membres actifs'; +COMMENT ON COLUMN organisations.organisation_publique IS 'Si l''organisation est visible publiquement'; +COMMENT ON COLUMN organisations.accepte_nouveaux_membres IS 'Si l''organisation accepte de nouveaux membres'; +COMMENT ON COLUMN organisations.version IS 'Version pour le contrôle de concurrence optimiste'; diff --git a/target/classes/db/migration/V1.3__Convert_Ids_To_UUID.sql b/target/classes/db/migration/V1.3__Convert_Ids_To_UUID.sql new file mode 100644 index 0000000..c921d22 --- /dev/null +++ b/target/classes/db/migration/V1.3__Convert_Ids_To_UUID.sql @@ -0,0 +1,419 @@ +-- Migration V1.3: Conversion des colonnes ID de BIGINT vers UUID +-- Auteur: UnionFlow Team +-- Date: 2025-01-16 +-- Description: Convertit toutes les colonnes ID et clés étrangères de BIGINT vers UUID +-- ATTENTION: Cette migration supprime toutes les données existantes pour simplifier la conversion +-- Pour une migration avec préservation des données, voir V1.3.1__Convert_Ids_To_UUID_With_Data.sql + +-- ============================================ +-- ÉTAPE 1: Suppression des contraintes de clés étrangères +-- ============================================ + +-- Supprimer les contraintes de clés étrangères existantes +DO $$ +BEGIN + -- Supprimer FK membres -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_membre_organisation' + AND table_name = 'membres' + ) THEN + ALTER TABLE membres DROP CONSTRAINT fk_membre_organisation; + END IF; + + -- Supprimer FK cotisations -> membres + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_cotisation%' + AND table_name = 'cotisations' + ) THEN + ALTER TABLE cotisations DROP CONSTRAINT IF EXISTS fk_cotisation_membre CASCADE; + END IF; + + -- Supprimer FK evenements -> organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_evenement%' + AND table_name = 'evenements' + ) THEN + ALTER TABLE evenements DROP CONSTRAINT IF EXISTS fk_evenement_organisation CASCADE; + END IF; + + -- Supprimer FK inscriptions_evenement -> membres et evenements + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_inscription%' + AND table_name = 'inscriptions_evenement' + ) THEN + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_membre CASCADE; + ALTER TABLE inscriptions_evenement DROP CONSTRAINT IF EXISTS fk_inscription_evenement CASCADE; + END IF; + + -- Supprimer FK demandes_aide -> membres et organisations + IF EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name LIKE 'fk_demande%' + AND table_name = 'demandes_aide' + ) THEN + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_demandeur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_evaluateur CASCADE; + ALTER TABLE demandes_aide DROP CONSTRAINT IF EXISTS fk_demande_organisation CASCADE; + END IF; +END $$; + +-- ============================================ +-- ÉTAPE 2: Supprimer les séquences (BIGSERIAL) +-- ============================================ + +DROP SEQUENCE IF EXISTS membres_SEQ CASCADE; +DROP SEQUENCE IF EXISTS cotisations_SEQ CASCADE; +DROP SEQUENCE IF EXISTS evenements_SEQ CASCADE; +DROP SEQUENCE IF EXISTS organisations_id_seq CASCADE; + +-- ============================================ +-- ÉTAPE 3: Supprimer les tables existantes (pour recréation avec UUID) +-- ============================================ + +-- Supprimer les tables dans l'ordre inverse des dépendances +DROP TABLE IF EXISTS inscriptions_evenement CASCADE; +DROP TABLE IF EXISTS demandes_aide CASCADE; +DROP TABLE IF EXISTS cotisations CASCADE; +DROP TABLE IF EXISTS evenements CASCADE; +DROP TABLE IF EXISTS membres CASCADE; +DROP TABLE IF EXISTS organisations CASCADE; + +-- ============================================ +-- ÉTAPE 4: Recréer les tables avec UUID +-- ============================================ + +-- Table organisations avec UUID +CREATE TABLE organisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Informations de base + nom VARCHAR(200) NOT NULL, + nom_court VARCHAR(50), + type_organisation VARCHAR(50) NOT NULL DEFAULT 'ASSOCIATION', + statut VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + description TEXT, + date_fondation DATE, + numero_enregistrement VARCHAR(100) UNIQUE, + + -- Informations de contact + email VARCHAR(255) NOT NULL UNIQUE, + telephone VARCHAR(20), + telephone_secondaire VARCHAR(20), + email_secondaire VARCHAR(255), + + -- Adresse + adresse VARCHAR(500), + ville VARCHAR(100), + code_postal VARCHAR(20), + region VARCHAR(100), + pays VARCHAR(100), + + -- Coordonnées géographiques + latitude DECIMAL(9,6) CHECK (latitude >= -90 AND latitude <= 90), + longitude DECIMAL(9,6) CHECK (longitude >= -180 AND longitude <= 180), + + -- Web et réseaux sociaux + site_web VARCHAR(500), + logo VARCHAR(500), + reseaux_sociaux VARCHAR(1000), + + -- Hiérarchie + organisation_parente_id UUID, + niveau_hierarchique INTEGER NOT NULL DEFAULT 0, + + -- Statistiques + nombre_membres INTEGER NOT NULL DEFAULT 0, + nombre_administrateurs INTEGER NOT NULL DEFAULT 0, + + -- Finances + budget_annuel DECIMAL(14,2) CHECK (budget_annuel >= 0), + devise VARCHAR(3) DEFAULT 'XOF', + cotisation_obligatoire BOOLEAN NOT NULL DEFAULT FALSE, + montant_cotisation_annuelle DECIMAL(12,2) CHECK (montant_cotisation_annuelle >= 0), + + -- Informations complémentaires + objectifs TEXT, + activites_principales TEXT, + certifications VARCHAR(500), + partenaires VARCHAR(1000), + notes VARCHAR(1000), + + -- Paramètres + organisation_publique BOOLEAN NOT NULL DEFAULT TRUE, + accepte_nouveaux_membres BOOLEAN NOT NULL DEFAULT TRUE, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + -- Contraintes + CONSTRAINT chk_organisation_statut CHECK (statut IN ('ACTIVE', 'SUSPENDUE', 'DISSOUTE', 'EN_ATTENTE')), + CONSTRAINT chk_organisation_type CHECK (type_organisation IN ( + 'ASSOCIATION', 'LIONS_CLUB', 'ROTARY_CLUB', 'COOPERATIVE', + 'FONDATION', 'ONG', 'SYNDICAT', 'AUTRE' + )), + CONSTRAINT chk_organisation_devise CHECK (devise IN ('XOF', 'EUR', 'USD', 'GBP', 'CHF')), + CONSTRAINT chk_organisation_niveau CHECK (niveau_hierarchique >= 0 AND niveau_hierarchique <= 10), + CONSTRAINT chk_organisation_membres CHECK (nombre_membres >= 0), + CONSTRAINT chk_organisation_admins CHECK (nombre_administrateurs >= 0), + + -- Clé étrangère pour hiérarchie + CONSTRAINT fk_organisation_parente FOREIGN KEY (organisation_parente_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table membres avec UUID +CREATE TABLE membres ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_membre VARCHAR(20) UNIQUE NOT NULL, + prenom VARCHAR(100) NOT NULL, + nom VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + mot_de_passe VARCHAR(255), + telephone VARCHAR(20), + date_naissance DATE NOT NULL, + date_adhesion DATE NOT NULL, + roles VARCHAR(500), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_membre_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table cotisations avec UUID +CREATE TABLE cotisations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + numero_reference VARCHAR(50) UNIQUE NOT NULL, + membre_id UUID NOT NULL, + type_cotisation VARCHAR(50) NOT NULL, + montant_du DECIMAL(12,2) NOT NULL CHECK (montant_du >= 0), + montant_paye DECIMAL(12,2) NOT NULL DEFAULT 0 CHECK (montant_paye >= 0), + code_devise VARCHAR(3) NOT NULL DEFAULT 'XOF', + statut VARCHAR(30) NOT NULL, + date_echeance DATE NOT NULL, + date_paiement TIMESTAMP, + description VARCHAR(500), + periode VARCHAR(20), + annee INTEGER NOT NULL CHECK (annee >= 2020 AND annee <= 2100), + mois INTEGER CHECK (mois >= 1 AND mois <= 12), + observations VARCHAR(1000), + recurrente BOOLEAN NOT NULL DEFAULT FALSE, + nombre_rappels INTEGER NOT NULL DEFAULT 0 CHECK (nombre_rappels >= 0), + date_dernier_rappel TIMESTAMP, + valide_par_id UUID, + nom_validateur VARCHAR(100), + date_validation TIMESTAMP, + methode_paiement VARCHAR(50), + reference_paiement VARCHAR(100), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_cotisation_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT chk_cotisation_statut CHECK (statut IN ('EN_ATTENTE', 'PAYEE', 'EN_RETARD', 'PARTIELLEMENT_PAYEE', 'ANNULEE')), + CONSTRAINT chk_cotisation_devise CHECK (code_devise ~ '^[A-Z]{3}$') +); + +-- Table evenements avec UUID +CREATE TABLE evenements ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description VARCHAR(2000), + date_debut TIMESTAMP NOT NULL, + date_fin TIMESTAMP, + lieu VARCHAR(255) NOT NULL, + adresse VARCHAR(500), + ville VARCHAR(100), + pays VARCHAR(100), + code_postal VARCHAR(20), + latitude DECIMAL(9,6), + longitude DECIMAL(9,6), + type_evenement VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + url_inscription VARCHAR(500), + url_informations VARCHAR(500), + image_url VARCHAR(500), + capacite_max INTEGER, + cout_participation DECIMAL(12,2), + devise VARCHAR(3), + est_public BOOLEAN NOT NULL DEFAULT TRUE, + tags VARCHAR(500), + notes VARCHAR(1000), + + -- Clé étrangère vers organisations + organisation_id UUID, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_evenement_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE SET NULL +); + +-- Table inscriptions_evenement avec UUID +CREATE TABLE inscriptions_evenement ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + membre_id UUID NOT NULL, + evenement_id UUID NOT NULL, + date_inscription TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + statut VARCHAR(20) DEFAULT 'CONFIRMEE', + commentaire VARCHAR(500), + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_inscription_membre FOREIGN KEY (membre_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_inscription_evenement FOREIGN KEY (evenement_id) + REFERENCES evenements(id) ON DELETE CASCADE, + CONSTRAINT chk_inscription_statut CHECK (statut IN ('CONFIRMEE', 'EN_ATTENTE', 'ANNULEE', 'REFUSEE')), + CONSTRAINT uk_inscription_membre_evenement UNIQUE (membre_id, evenement_id) +); + +-- Table demandes_aide avec UUID +CREATE TABLE demandes_aide ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + titre VARCHAR(200) NOT NULL, + description TEXT NOT NULL, + type_aide VARCHAR(50) NOT NULL, + statut VARCHAR(50) NOT NULL, + montant_demande DECIMAL(10,2), + montant_approuve DECIMAL(10,2), + date_demande TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_evaluation TIMESTAMP, + date_versement TIMESTAMP, + justification TEXT, + commentaire_evaluation TEXT, + urgence BOOLEAN NOT NULL DEFAULT FALSE, + documents_fournis VARCHAR(500), + + -- Clés étrangères + demandeur_id UUID NOT NULL, + evaluateur_id UUID, + organisation_id UUID NOT NULL, + + -- Métadonnées (héritées de BaseEntity) + actif BOOLEAN NOT NULL DEFAULT TRUE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_modification TIMESTAMP, + cree_par VARCHAR(255), + modifie_par VARCHAR(255), + version BIGINT NOT NULL DEFAULT 0, + + CONSTRAINT fk_demande_demandeur FOREIGN KEY (demandeur_id) + REFERENCES membres(id) ON DELETE CASCADE, + CONSTRAINT fk_demande_evaluateur FOREIGN KEY (evaluateur_id) + REFERENCES membres(id) ON DELETE SET NULL, + CONSTRAINT fk_demande_organisation FOREIGN KEY (organisation_id) + REFERENCES organisations(id) ON DELETE CASCADE +); + +-- ============================================ +-- ÉTAPE 5: Recréer les index +-- ============================================ + +-- Index pour organisations +CREATE INDEX idx_organisation_nom ON organisations(nom); +CREATE INDEX idx_organisation_email ON organisations(email); +CREATE INDEX idx_organisation_statut ON organisations(statut); +CREATE INDEX idx_organisation_type ON organisations(type_organisation); +CREATE INDEX idx_organisation_ville ON organisations(ville); +CREATE INDEX idx_organisation_pays ON organisations(pays); +CREATE INDEX idx_organisation_parente ON organisations(organisation_parente_id); +CREATE INDEX idx_organisation_numero_enregistrement ON organisations(numero_enregistrement); +CREATE INDEX idx_organisation_actif ON organisations(actif); +CREATE INDEX idx_organisation_date_creation ON organisations(date_creation); +CREATE INDEX idx_organisation_publique ON organisations(organisation_publique); +CREATE INDEX idx_organisation_accepte_membres ON organisations(accepte_nouveaux_membres); +CREATE INDEX idx_organisation_statut_actif ON organisations(statut, actif); + +-- Index pour membres +CREATE INDEX idx_membre_email ON membres(email); +CREATE INDEX idx_membre_numero ON membres(numero_membre); +CREATE INDEX idx_membre_actif ON membres(actif); +CREATE INDEX idx_membre_organisation ON membres(organisation_id); + +-- Index pour cotisations +CREATE INDEX idx_cotisation_membre ON cotisations(membre_id); +CREATE INDEX idx_cotisation_reference ON cotisations(numero_reference); +CREATE INDEX idx_cotisation_statut ON cotisations(statut); +CREATE INDEX idx_cotisation_echeance ON cotisations(date_echeance); +CREATE INDEX idx_cotisation_type ON cotisations(type_cotisation); +CREATE INDEX idx_cotisation_annee_mois ON cotisations(annee, mois); + +-- Index pour evenements +CREATE INDEX idx_evenement_date_debut ON evenements(date_debut); +CREATE INDEX idx_evenement_statut ON evenements(statut); +CREATE INDEX idx_evenement_type ON evenements(type_evenement); +CREATE INDEX idx_evenement_organisation ON evenements(organisation_id); + +-- Index pour inscriptions_evenement +CREATE INDEX idx_inscription_membre ON inscriptions_evenement(membre_id); +CREATE INDEX idx_inscription_evenement ON inscriptions_evenement(evenement_id); +CREATE INDEX idx_inscription_date ON inscriptions_evenement(date_inscription); + +-- Index pour demandes_aide +CREATE INDEX idx_demande_demandeur ON demandes_aide(demandeur_id); +CREATE INDEX idx_demande_evaluateur ON demandes_aide(evaluateur_id); +CREATE INDEX idx_demande_organisation ON demandes_aide(organisation_id); +CREATE INDEX idx_demande_statut ON demandes_aide(statut); +CREATE INDEX idx_demande_type ON demandes_aide(type_aide); +CREATE INDEX idx_demande_date_demande ON demandes_aide(date_demande); + +-- ============================================ +-- ÉTAPE 6: Commentaires sur les tables +-- ============================================ + +COMMENT ON TABLE organisations IS 'Table des organisations (Lions Clubs, Associations, Coopératives, etc.) avec UUID'; +COMMENT ON TABLE membres IS 'Table des membres avec UUID'; +COMMENT ON TABLE cotisations IS 'Table des cotisations avec UUID'; +COMMENT ON TABLE evenements IS 'Table des événements avec UUID'; +COMMENT ON TABLE inscriptions_evenement IS 'Table des inscriptions aux événements avec UUID'; +COMMENT ON TABLE demandes_aide IS 'Table des demandes d''aide avec UUID'; + +COMMENT ON COLUMN organisations.id IS 'UUID unique de l''organisation'; +COMMENT ON COLUMN membres.id IS 'UUID unique du membre'; +COMMENT ON COLUMN cotisations.id IS 'UUID unique de la cotisation'; +COMMENT ON COLUMN evenements.id IS 'UUID unique de l''événement'; +COMMENT ON COLUMN inscriptions_evenement.id IS 'UUID unique de l''inscription'; +COMMENT ON COLUMN demandes_aide.id IS 'UUID unique de la demande d''aide'; + diff --git a/target/classes/de/lions/unionflow/server/auth/AuthCallbackResource.class b/target/classes/de/lions/unionflow/server/auth/AuthCallbackResource.class new file mode 100644 index 0000000000000000000000000000000000000000..1e4a40685476745db1328c51844c245db7b4ecc9 GIT binary patch literal 5120 zcmbtYNpl;=6@J47ASq}uZN;XXNNq?IK@q@!q(~_sK#8JANik)~;^NSb!!$4rV8p=; zJTo8(D~gjtPD!eAOgW`Wd`kJ2O^ixa&c#(a~x-}~O{ zw>AIrkLQ0TqAdL=Og$P+8$!2C$M*E9jaR{Pc6Cp@6b=meeAXs{^!Ix~$Dv6U8vMT>jh-1y6750XZ& zM5v!a8l7A*ZShXEoD=RE&sjpF;T0#(tqty)avl&v83lESdg*HsdW{AE*Dsl#Ml&n_ zgV~_|i4wOBOEe6sQLSBvRo{iN=GwISzbNBiw9Q?g>${%rdV1b*g^n|<;Isw6j6A5? zzF8I1If?n__L=+&;hx0cqh8q{^M2vFj*Bx@?=3@kdDpB+_yZKv zXe*n|vc-j!l^Y9-Z?l_AH`Z@4MFP__8};IBRq_pnb=K{qCCR=1RSntAmHe~Fd+vNb=HX}knC=7$)5GQR0s zV*Y_JOc$vFhs2O!Jhm=#&N6aNMz61^uZE{^mM?MF6MiyeQFOUsf=g_2dd3jNI6FT(dqoJ&Qll7OnZ2CjQ*1Jo8XfOs zm3J)Zw!0+8XuWqn3yPP_Ju*_ZL@ia>@6a}ZnSlO)&y<5!j z*woBSyz!SxPIe(|+F|;Dl$_%l!c9okt6m!FwHvBU2d`q<2Q{eg2K>q%^Bl`G*db`9 zyU+w8eW%jCvCDi)%T8=FsnJd=C})$lYsV}%mV{X>`G^r1C*V23io!hPlJ? zvT|pmO~(wvQ)-+kP0fGx>o30gW*u=OqrF{iyHGxRzXuJdyVF%{{)clcIRd38v#tGBJ7yN7H4By zp0EmIT@n76L63YXCc5>MOyG1#6#~i@V`J^rsm$%Fuq?ssFF((tJK(5W2RJvifHkJ; z){snbf~plkA=K->4j)y8Q5N%S5xPzbXqIF&a^Dw*Mq{zV+JuAEcZe2gDNHvtdh>|Z zE<($6OQRlVTcd^cLSOIG(Ig+S`9h;XyiqkRWaGI3x&yE>#FY|5^fuiM(~3qD{{@!_ zy+ik8_gt%>mFoc2)@!`9Sv>$sqIfq#4`>zLS56|;=v?eb#h}Q#rd>a>R|(n_G<>4! zGGh!O)RA-=T}nqPV=BL8qX(OXATxG01;`BTjXD&4&%-1?@2 zF1$$ZMd;hKsZmI_;#IVhso0$(ZGY(beGsAVNIwn9wC>l!^j%y#S`FvYUS6nnUS7^x zD~_{WtsH%8?AW-5AUtIG zzezy>3#s+0Y)wvv$QmFV{(yE(-qf<%099c_?V|JwHQkldYs#qdD{d&KwTo)neqGCI zILte;p;LEw0hTAbl2Di)3eQ-vITjN9TxPaNT)3^5N{%^5zEggPE``a_x7zyvw`^_)VhUioJC4C4BNz*@o zsR`CN22-5FJ4&N;kzzE7^O~beD1Q@lpAtw?Ny?K>C6rx{X6X^0KY=zsqbj)V(i8fa zenmC_%dEbbvwxyCVo3Ebi# nZR5ikEcggSS3Q08+)>Xx=ti(s4=Uv%J?a~z@6!+Hhcxmp*!O+2 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/UnionFlowServerApplication.class b/target/classes/dev/lions/unionflow/server/UnionFlowServerApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..9f1e39982c5bebb1af4ac56379a001d1d05f13f7 GIT binary patch literal 1477 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 P^f`TrtL>}kpU3|OsP8>0 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/dto/EvenementMobileDTO.class b/target/classes/dev/lions/unionflow/server/dto/EvenementMobileDTO.class new file mode 100644 index 0000000000000000000000000000000000000000..c30235064b0fb9ad4429e7b8521ec2ab8d0b1dd7 GIT binary patch literal 21856 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 MgfMV^^vvG>0%vM)*8l(j literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Adhesion$AdhesionBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Adhesion$AdhesionBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..123bfbff2c4371593a299e621a45acaefec248f5 GIT binary patch literal 5429 zcmd5=>v9xD7(JcahA=S+cLWi1!6YDy2#O>ixp0YWNK8TyP;_>;lT0!@6J}-?#qR*F;JXak1TL3k zHD_C{MZ-_n{7Jj+=g5jZkeA%0m-IaSNI3bq_`95*lnWs6#3 zyXAtrmb)n(>6w8naYxoJ)ZWQZl3CM|WyVb4aGZq; zw3nOrm1VF()Gh4+uO6u_Sx?ywtfe`*sw^?wg35?`Xm*=w>q?anp1><=>qXPH*fVrA zbJTLI;Hp6T;LxH##{@U)K|9_w@H&pufV}0%JC$-ldJ87Qu41jcxM+Hon%9IK!HUH^ z7)=qay31P=ly5sb7)`yQoj~pav(G>$x&&4RQy|sSaL1dzZD1FA1e`RRHs$Y*w?1cJ zHw=OKnloE6f5-Um8tBC<0t+?#){5Ej2Jac@LqF@dp~zGqKQJ(Wel;2~um^k9=!$`T z=u)E(4eZyWF#`vbf%jq^5Ej;sZJac45Qo&hHw+xs``$9}svdo0;D{dO4II^@I|g3U zqgeyTlzp8AbutT_9sDQejPzguiy7QyW+xx3^I zZ;hqe>w!869t!a|ra04AR0_?eGKf19N8*j#P8wcywM$IvMI|i-61Q`hG%s5pMf`V? z7CY2&+&dqb#kFa3Jv?SJc*GFK1xlbhaP@0aqaMvhGkQ=!DT87Y2hb}FNGu6t!n-K&ETFDtZ>BZ&k1}XE;NEAM4}P>zxMY3 zXRjF7l1t3H6|X37SnA5{t(Wmx6?}$p+;sy#@XYn;kRJFwaPc^Ub%E>_ce9+TyC!qm z=Q=hw%wphr8!Bid13xx~rlzd{Z;?CM($D5ycdfEstGkzYtZCuG#=4AAf7lSsn1rf9 zOqjMkZ;_q#b;0RIMTn!DFJ2$i*z8~nYrP`Q9M^PHjT$MWnk&*%BZO+&#QegQtqe~I zx5%8DX*Q=u3ao+7)kSqSrsgfAO^G6`E!5kjZEDuY+tj>)xT$F~bGssjNcE;1((MV` z^nFS8)z{*r3^oK#MiL?}#2I`oaBO>3l0}^lJZDMds^77&q<0Yqfv0udQmK}1G=5Rs87L}a|#BQoCc5gBj$h>Z7s zL`DV>k&z2TWMl;`vc2>nX*m7Z=+zUH=BO#ycMd<{~&swc(0zH!&R3{6WAI>I$ z=2C;|Y~vw{calKQrv}yO%96u{B+!egL3O?hydMjCjT@;z87fehhC80(^0vsvX0!|< zs;VJ1NKS##Z9y}epczHHL(u)npjT6a>Kf(ecyhF^r3TeSPL_i4q-af~2GyqoKi`vI z|9Warear}cNF>(k&D5a!oZ^2t$)HoILG?i=_<56P>Fv~@`lJ;6Qc47!P7SJ$T3&C- zDK(QCRG-xXA7d^irGDV|sjAhx!@uHEoWFTC$q{|GR-c`iRUgZ&&rhk{4e=-XGx&`2&+$c! z!QY3vz$5Mv8uuI~hdJGy*WE?kUDDmAxq3RGyI7<7=mnO6_{&A#Q;#++U!%osu$wNG hvVIHre1ia0&Xj}sCj_4G3i=XX;Tsm>x8cf#zX9!MwHW{a literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Adhesion.class b/target/classes/dev/lions/unionflow/server/entity/Adhesion.class new file mode 100644 index 0000000000000000000000000000000000000000..82bb1881784bb37ea6f42ead5a5ab1d7d643f9d9 GIT binary patch literal 14348 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Adresse$AdresseBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Adresse$AdresseBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..52b08d100b290a15148ca9b760a829b88e113614 GIT binary patch literal 5371 zcmds5>vGdZ6#h0h1tTDYYfDSH7@CBODWw!JwO3=u97=574LSbo#AVPUOhQ>SX$-Ki0cC+WpSibIzXq-vyM~=I$ z7g&L^R$#c`c$K2JoVzJq>05y;bBPfmsn=_^%>};gF7oyEl2@rZvcjFMnwTkhWqHS| z1(qYw->@A=3Jmyik!l3`s@4jX4LVdC)XRLeTLV|DU@13YFJ6-+yJ9&6-{g4iVmN82 zN{uDEYB@67u$(&A?T8pO;dzdE>LuC)uEPmA?c|q*!>pHC<`EXuEcBS)g}h^Z`9N zNk3=Mi^C?~!2yA71>2P~^-59t_bo|ug~qH;;td*f!Nd@@D}yeY7{+#i%vyR2%tl?%IszM1dDX-Y>{KczOzgsLHM(YE zkKXSM6MNNuoy7QeIxrhF>z0W(@TR~*8pYvh+n~yViG6rWz)qub?VN5<>#T{lv7d#r zsICPTiIIN+n9pDi9~-zYFq&{rv>!4P4=~Sink~x(tL_BZXo~Hk`n9^>$}y$pp{hEM zgm{+dp5#&xfeugbe=S*mK()Tk(&jbX+Hxwxy8FEC5=l`A!&FW&Gm?S49Jb5_R%vi8 z2_c6`mKIGc!4}w4v6khe=a#Hs-VT;HB5QDinh=MgVByMCAz0hw|7ukSNAfb5^Xh&{ z-mukOZ-xZyr1Ajc#l#J2fp1l(Ww7LxYZ^_N`70_y zUkUsejYBwF15R0P*^#wu!Sj~u)qG-b+7>EK+F|_a*|5~0nP#PI``X2fb3Vu zpn0bnHi@SiM46|0);v;;R=MFNG0K46a>>Id9gE)znwX5_CMF}ziOI-7Vlq;Mn2ZnR}= zCx>z6hY61H-FoZVK#!*e)rrAx*Y2PvQ-kWv;<@M!dMY)jPEFpI-9b;M2GzOEBikMH zOlnYlIQW&<9rSE!P<^_1Ti{%`=$}sws*fkXKD&eFQ-kXBE%-mnBV t-y`#@CO@G8Jy>ol`i?dqKFzD4&Zl9{5&Yh3@QQef6@1I|`>wfi?jIu|$<6=( literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Adresse.class b/target/classes/dev/lions/unionflow/server/entity/Adresse.class new file mode 100644 index 0000000000000000000000000000000000000000..d12704a0855e68a55ea574405d3ae1ca8ee2581c GIT binary patch literal 13522 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 C160ER literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/AuditLog.class b/target/classes/dev/lions/unionflow/server/entity/AuditLog.class new file mode 100644 index 0000000000000000000000000000000000000000..17aadbaab67ed5fefc5bb6619364e2b191df85cb GIT binary patch literal 4965 zcma)8TXz&i5U$=_HrY(F5E3y26~#*kmsPwW0bxl31UB3f0)n_qcACwQ*_m}_HW2Uk zyZ=ScfuqLd)DAsQGr2eS(SjOHkgDp=mqta9b^MW=7tzU0dz4dvW&q zqM&}eS+*=+2CypF&8n#cUgRLq7GhkkBf>y=Ts4)@A7BPnnzI13$5k6bzXd1H!cDj| zrUJ=fD^C6CIgoL0J*si*w3=PY3T`7a|n#n5-w#6!rf#nX|gr7-R$$e1*NOq-P z4I@tqYP^W3BRXw>&w%#^+umawi#G*zP3&7jJf}TN7O0C}GUy;35>%|%uDsA{EK7fp z4~i3mH?d^;Hp^{gcerB1sRI>##>E+m?e}@^v@a0?L3_5$o=d*Gq>^c>AL;RiK~K{& z1)8Gcc{(O&XOyDp$Qtw}oxqk{@1CIXiGBZb0)q>oSzDbqo9&GZ)@8`SipyXmp1rvc z#@mH3=dNv03d0r0Yb<-Kr8Cl%KK5rt?w>ZOhk9`gSYx4`+PtkfgNm#lg8u48iZfI( zXgfoBKv$6Jth;E?4%YQUHy`CaYp)nI5|hkq6ahn54H{*rfC)3I7+8JXpq;EXpq`8K zpMjeO?TUe`aq2VhjzLevz(Sn*444Kz6$6~!?bI*wf{CPNQ)$q%Y?{P4gBe>f=y}FC zK(pMGkj|`MHE4qMd^k$aN5qJ0&}4)Jn~!aDLY;Z?)HuHfU>AoT0f&?bIKm7ChZ(`e zNCj7!B|;k2dK^fE50;(qDX|kYC3Zrk#6*C0x7|#q@DzJn1a&fgn>DKLc}H=q5TTBw zD!#DbwfveqZS$T69g(f0I>)yx$*4}ab(wtAR2{F5y3gCLCGT@v+IrF) z!ood8jH0;H=)QPe6nGll&*%Z#0|fUJz9pPy34eiq&*U%k;?Ef5=rF!K zooJk1#@A2=^a{NSsheKIQEBKu7+d!sM@X^dTK#%C3lVD9WV@ zCe9;WE+(jpZR(Setr!ANnNERk8VX)x2LH{@KA_p3piDYDmSF5@GYUGRB|4jp=$w}5 zd^VzaEzyN+M3=Ngm$MNqXo(iH5iMznu4N;7OG|Vk8`0ZZqFdRBs#>Dk*@%|ugeJ3( zWFx9+J+iV9-O&=&vk}=^qPy9M94%2J8<9uNErNX%x*WHb$^8#$_PYXBJUC((tE2nzh2zrm+*VR#U(*eXQqfWaU(Ey&QcKy@x)H z9%faX)@P7E#~&~3FX+pg)N_))QsUS2O+b4);AIMNZnXR*)`e_*lrFKMRr AfdBvi literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/BaseEntity.class b/target/classes/dev/lions/unionflow/server/entity/BaseEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..248c5351fde97a6c5a18280f62c62c96424a2b02 GIT binary patch literal 3658 zcmb7GT~icC6g>kAyDZCxAo#6oLKIP0O(IeGh=6D?APBn{O^g|K8yFmBCNr}rR`O(C z%s=rhsglx4s*(pEtjdo{a(a5$9d?IGd|+mK?z!ilzJ2d){`&j3KLCv3NfHejE|kPp z&NOVx%atv@R!#d!&J*sIaC5@)4Szc~se58N_?bjPL+6IRrRPlDTFcF^Y>1+-p}{Ee z`)pt+`-Yh-6lQLWXgIQ1rr)MmHaug+6cd(Z`?@cKX*f*yW8L-joFiP%@O)tvMQ)}< zx4u}@Enx{)_eE)0H_L*sl;_L!+V+2VFkXJ!ksb-DOT+P4{wdomZ(0&%>6=1BOW7&u zzE0hZ%oiBXDx0QUYskF7_dn%`o^k;?ueR6+ct$RQ`S6cScX5Yp|StmGBY@3mqZ$k zIGe$T=u6`q&L=UT;Xt)^Lgx%FU{FJHOCIVx1N8PkI2RM6g$Iz!;50r+;}Ske;$sbm z0=D|BXYeU5Q;TIk(QvFkq^rk`NYc+T=t6fIS22>rH4W`mi83`(-zc};e> z&)_6ZY3P_WEODp2xgy-WDv_?B5SDeylk9F;CrrKpv z#mx+w&`g-8VBP&HgH@C~l@5}~!mQS92l0Eq91lHXk|im+LyBnqN&$B==s`E_Az{?c znxq$ls+v5wyM<^$OOB-s4&gA-i z^7Z25Io%11CuLg&Ig;poYCmrUpmQ>us!13c&Q@!>V0mT7v0cv5c|w8-H2t(F966R# zc+8S6aDp42c$PIq))kKJ8NTgqXLZMMZO1j8(xh*UAdVNX0ZxggE8EcmAmt7{{)@c6P`}vR83&`h3QeZn?`JX#>MR8rY z@-NHDo0V5ddWoqhikpVK6*{Un<&ey{hBL9}#3OY?X{pQ%7ERsrMiQr4z;b}|N|v3> zRg$YdeoOH!%R4R5iVWKLsof){;{iHxKt2+3euW-Ae20*O${OGjvO7aaYXIS#^UzCQ zwp?iF3=9p@a0EwZaEw-VRK4}Yu9cH7w02N;mP*^WrvFL0a#Gj3o>roy7Tybdenh7g z?n&djZ{Rh~3?$y5e+NSYZ*Xx3!4Ek}s0? z60XydQJk2-Bv%@ya3hSD<6FjS8hC~2U-&~2%B%yIBhF0~=Uez9bRH+Vfw<;@^E;TX z%1H-bukvKB1q{6k6*3SXo~wo##!wrN3K^~yG8QZ3HolA#GF&TUg5f6j5u(aP!)&aO zxmqF9u|nqKh0N6onW2!|`v_4*sbK+kV=}T>D`YNKNFIf_j4aj)xl197`v_4@pkX;y z$h}%2g;*i?^_K9_%AT^_xcf*D)Cxs}=GvR>(Ja7?+Vq@2FzcDP(0IA*!`C ze2YghLWXLEh*%-tL5~wMRGSflLN@jhqQ*#qLi^ZJaw^I`E%Gf#$Fp#B46>Dn-yyv_ zHC|!p9a<~H;}yo_GBQINgBfx%Ahh{(&<>YV(!&Xq8ET9F_C1YkYA|f%5`#9OvAw;W zgGcPqv>Iv}rcOCYQc6r+NBqEWKSroil_|4U8HuCezS6&A{eIWRYnXqMQO&7#+JNVb x{}XfZJi?R?nOb1MCfW&nz7I0Bm(h)3d_l&a$@pRqqr8n|WIuS8D<`l8{0q<2-|7GW literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/CompteComptable$CompteComptableBuilder.class b/target/classes/dev/lions/unionflow/server/entity/CompteComptable$CompteComptableBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..1efd80cb4b775945510f697a2a1cb54ae7947dab GIT binary patch literal 5414 zcmc&%TT>KA6#g1m24NILK#9?4f`*H_F^L*M39w#35ses((P(CO8yK0L!JS?Vx!>=J zxxA$O6-iYrEmM`}RQ^bQLEfxX&Y79rot;@1Vo{Zc>C2hZ-?{YZ)2ILZ=l<^ij^YOm zbpqpAxs8clM0RJCIa`z#R&301ENR#zYEzWk z!|3v z2kqjdY0IfXE-mvHICwdloQz@37mGPB>aa1u*RAubu^GP0Z06IjRvq70>hjxE^8v{O2|Iovi>o=Jf#fs@OrU=5WPxD|zelvJ{Y8nG|V>Ua!W zdGwNw?d-;`$`nJ_OOR#~X}&834)i{P4nxhD!7Car3hb|O$yL9!bzB17Go3wIIcpRw z)l+#kwva;Jy&OFpEc1$v*LZP9wFO`2>cyRJV7>&=jSy_o+033j;rq8TGR?p zg%FF*3y~`9DcjFC;`(W2WERdCi(Uw7;7h#PPHR}CGNfFa1yVHR>6Clq60Sg#a(s~l zR&=|kZlFjrmhgs#n-vA2|4+v<-lX!)E0oU83hWMVj9)Yk1;H4GY^R#_Z5{96T?&Gn zu|QRjoiWr!Q_a)JRfetT0{Fk!s$_y3P0qpJ@14pr^c*roH4W$fWNM-)$$1jB@hJ?9WdHehPan;PwH> z4LfT|w`bCE77B|)H62&6kZbBH?3Y6bRrWf7NEwzjO_ljjYa>2v!WRO6MeUEwgEvTT z_^okN?AZ}K5_njCCGad#+%qYh5&fdLL=6|_-s+$Rtf<4wKgy}Aj1wxOnXxi^71{cz zXqm9-k(2V?9u&}B)eY6~jlizh7FXLe4Yvfg*Jc%H^mmw!_LOwhxZ^79q#LcCPF*YN zxXPrNMl!Fqff}FmK zTHR;BZahshLGJIZ0yIRlI{L5m58Odt|Iak6LlmmZX9=K)IoL~jp{)!$uxe007$|FD zFA0pAT%qZ{RfGD`L}voetOUA$)u4Vj3OtABtALIWjeV-KPY?7r+`*xm$a=}KIw5kf z3LIV;d61EhtQz@nfIN&BSJHWek;hhzJnS6^fnzHpA7|u~t42N+Aa}1EW)n3*wUUc9 zesp*)K8;DrP5xOt6AQQV;Z2-kwaK}4C!kc)SPPxE&4gu=9J&kRi%xPSRJ72UA zMNP0Db%D+dr^pZ#OTS=uX^)$%nonX4bH;QsB4*^86$vBRY}hrze@Q zi%jICS&xGl<@X(-j8kgjB$p_e?^CuB#UeR7ka&PL4Y>zsEw;`BY}DYlH%NjD6?YPH zlo!~UDbl-S%smv?9=|)_cY}U6;&;dWZi3jRXm}rQ0oAjdzgMY}!S9i hiPZRvzR&R`&Dx1a&>P_^%7tI!TYN|H^}V-p=3h0iLE-=a literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/CompteComptable.class b/target/classes/dev/lions/unionflow/server/entity/CompteComptable.class new file mode 100644 index 0000000000000000000000000000000000000000..75afa8be23a1c6b770e589d2b23bc03f26db8f1f GIT binary patch literal 11543 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 Sd&JdZAL(L0`ooB*vtI*qHiGK_ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/CompteWave$CompteWaveBuilder.class b/target/classes/dev/lions/unionflow/server/entity/CompteWave$CompteWaveBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..6f6c365e2655352ae47db9c6d0e273ed38e2b7a6 GIT binary patch literal 4996 zcmc&&?Ni)D9DbI!P#OxwzPHv^DZ&+dSgqAkXbbcO3Pk~_wZ4VB1yXX!ndGkG2fz9+ zIQ|ii&e&zl z&0D5pd-;mZX4P^Y=RN6GrJI+wZ~B}0NvBly<$a?n`)l2aifI+4tD#Mx_mNRG@|Iz* z<>yx($$~G?V^>Pjb(W+h%j=FU1^Q-dvle~VwAV%jPA*iqM@dfGRns$9EP2DW9pCVk zK5B{OlvbRL{B3DV*YIVLOZq&1ulS)R{Z+%N5d7+F9W#uwNmD8%FaMY}l~Kg( zO<;PG28d4uy4Cy}g@QvRr;7qz!MJSRm7Dx1sk7_Ywk*+40>_F}a7wziDP6fNU31ke z1cnG44^3Bw=4YLPVNG%B5=Y#6f59m!0K;@?hwiv*hHZM$!jXDrMbVj$tsq`UNtRX= zRIZ7&bHQBYRNpmh&nN_r-*ReisCLCSt^BO%`JA_J(Ok3Xezn2P9iQvQAX0XzwoTyr zabor+_70rian-aqF0|r|j#GG**)?n0@?NF1BHbl}S))=#`E}WF zO*M{$ZT`B+G`ik|Cu)hE&T;CvG?>obq20pngTR|Q+R?$l$1_miyTN+kjS_lKG&`?j zAG(x|Z|T?%UG?%hy3wI}Z|gV^_TJIaqbztBxhNyc_e7ufboAn&+WUPSeQHOyu9LFL zV^0(r)$s}r30Tc+A63|%Y&@>xFpe-+;sqnH5IMe9r@P)w;QO7R|E;=rt%-{Fq>iIF zrg}GZoP;iLBw6j7<*kZuUs2bRz{udg+j+DLGnmzIS75l#O%ml)?Xb`>hkF8t`-^hb zs91h~vW&;;tI?>Al~Nz-_y`N}Et1ji;}*fiS_{8gwxmwVkA*muad7SCXh|P~%}X*) zhK$9H-JiJe>KPM-E2_brZc;Z(_o8nUHs*|SaARorMBqvt1sViQ14+YyWHbaieJA8Q z0*AJO6>z191$E&Ie5K*b1dj^8=`gS&pam>vepO%~9?p%5&0RcZyyYHc--F3G~;@GwrIgq4=R9r=iDgkrFxXrgEh%@I%~T(d-cLj$s!q z>GjV#&PJs?TGvB~g-blUGNwO_A!LL(f|xWcYti)OXioeG*zhb7>x)E0&+`-Am z#PFH;c@^?LB~9?&Z9zbM)hNMymEdL6SA8qg_^leo39cH4bXE1@=h~JkL%i*#NwM8Y z%kVtOq&=82OG8EATq=xNfzj|-;B@_|q|jj$S-JP5=ihNWA7^>_w6dslv?#vDk;ai~ zJK44S?@pjX5F7wFOOfR;SOXBl>^1;xrAwy4GcpWHKi&L*L0C<>GfyJKN9!R9#%K=zpv rI&y4V^);&1g7rk$Ov*Z`oWrLKm!m$;5v1ZC4fRpQHw^b?uyW=f6~N;i literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/CompteWave.class b/target/classes/dev/lions/unionflow/server/entity/CompteWave.class new file mode 100644 index 0000000000000000000000000000000000000000..becd9063d69cbd36f4dccd4eb74c53a4bd3fa7bf GIT binary patch literal 10925 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/ConfigurationWave$ConfigurationWaveBuilder.class b/target/classes/dev/lions/unionflow/server/entity/ConfigurationWave$ConfigurationWaveBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..02a337af256aa1aa472862c2606842d53b65cbae GIT binary patch literal 2354 zcmcImYi|=r6g}g_aWG3m!!r+_3E-H=($WI02^4|?NNFlHC^9cba(C;HB3^2ZQ7wAGJ9o6v?k-KK%q$BG&e^6aV*uYq zID~HmMi#VDi|tljrB5ZT@K4)@OqOM;{kp3hbgPLg(WQDNADfx4`^W(ZoQzP! zke9|I90<~6gmREhM>rUyvk@W;2^8x-uL8F!`!c3h!nDAReK4$yDO~iaxg_wiPi*qh z+MmXAVc6NgMuDUBGsn?UR?FoGS8!F}L`$wIvcDnSGwoI>WlkDLN|^uC%Dhpe`;u*^ z4K=SlS&!!`@|yR9#rVKlm$_7~|Ek=oZDwVBj~`07Auzq=sWu(Ers5EW;NHA!IGb*G zzkd|?v)8-s=>Ygxnr5Q1=>==o+UvEvr9GN$7oW0P`y7AKb3U|>T5nFe5OXp~mb6o~ zks*9HjN1Y_AAy{v+odY(Mzi9|+0aJ3)YC`&a;uT})wWJDD`o_uC+sWfTq3hfG4&_^ z>!A|v2wcd8xnJN)U~fA8<)iFkAn0gF%OWTMHGB(b5-KX&%>MxzRGht z)_YFJ%FpT819s>Xhz-4%3f&!8cAthL=iC-w{)qR+3_?x3gk z3>wl$YVHntX3wA@|AHJl&v@_5_&hZ}xsx9-^}PCqxq5>!jtP!a9B1C)LjUXqB20cJ zLIpE@@EDP8C2uH Nqk9;`FPk@~{smE35f=ae literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/ConfigurationWave.class b/target/classes/dev/lions/unionflow/server/entity/ConfigurationWave.class new file mode 100644 index 0000000000000000000000000000000000000000..0d8542ab2a4fb814a1b3d5fc0f4da3af98966291 GIT binary patch literal 5341 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Cotisation.class b/target/classes/dev/lions/unionflow/server/entity/Cotisation.class new file mode 100644 index 0000000000000000000000000000000000000000..4bb3945836c4db106edc0de52eea27007f07be85 GIT binary patch literal 19719 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/DemandeAide$DemandeAideBuilder.class b/target/classes/dev/lions/unionflow/server/entity/DemandeAide$DemandeAideBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..d681058c55bba08ab087a10967107815cffbc39b GIT binary patch literal 5968 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 E0W2*9wEzGB literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Document$DocumentBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Document$DocumentBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..8adaf1ce88a1235cd0855ff800e80601085c88ca GIT binary patch literal 5107 zcmdT|>vGdZ6h51qlAw?f2$Vu8w1FljjS2-yF{FT#kWf1baS8=`Q{;^;B1^6$hlXD0 zV|4ly?Mx{Qo#_MgSvq|Jrqge=k|RgPcI#jLvDWUA8^BGG-x~JdDAGF++{SH-I?Jd(Y*DZd{wjE#hg&Jas z<&@W*P3;D^xvTrUfF*kXy6l=o)7C9UdyNt=n|9uJj7`1B8TI?y6`lhmBh}YU%i_z1 z&wZec#1zOn!Ss-)5oqem0XOlTG09jkF(rM)yP z?0-+C5oz`ND(#cKv`VAG{;`I^&gfR$FHvrEtkVZ7?WY4mr)w&W(LqLc!hITKOvk&; zs&t56VKh`1|2t=1OR987TQPbZ8c_pmCm-swv?C3KLl;##Os|UGhbp}$TzjxSVNpH9 zr0rc77YU>3iGQPW z@|{XWD&fKoZr$Y#MyKLOBIyfKa7U34auj^&fl8YM=5eU3Z}N;|8@hkT^h@Y@zK*M$ z(Vua!aAgwf|0rqT5|Ev|Q*{kKZ;Cs4D7sXqh4bM0>yG1lzN=Sq+%Gu=Z;-wuPazP( z@g4V?_Lj5BHR&tq(|O(S9d}ze8!YFCxJ$#?5^zDc3l_*r)^Rqgm2^|D)-4p|t-T&a_jQZpWBkz~E=Px*(PbYA7%3QuXFr}@f@39Q zU|R_pcvnIO!jzDKy(DDdF$ozMO+p54laPUVBxK+q2^ly;LWZjq79I)gJi5znT(-OB z4`Uu!G*-ktbR1F*GZ~(}si$<}XLP#gP5cUF2@W2|uPP_dNjilQgR9?a0F;KT3-6WG z=_fRi`U%}6)Im8Vgu>+T-0+Ff8Z^~5s62R-rq-ZSZG+0l!S`Hi(CM~8W#}QB=pDFb z_uOjULjhux6-MJJe&^_DuxE~z8W{2`!ci3K)zmLE zlf}=~A4z#e%c8rG!_UoN{88>VTDgni1X>Dh3T+zg4B9!g3uqV7E}h0tX6h;36{V&p zq49CKTj-Ns%83s>=r@J~l*KdBT}qBZ=TCry%j5-(DD>$I8dm6Ytk7w_fjv%W_Z-wn z==zKdh|kGKO=E~=q$ndF3wS&ylc;gEl#ZRNnNWs$U1ZlCqXSt#l&qp{(N`FaVh;Yn Tpl#IDNAwMS2Xh|>D_8yj>szyE literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Document.class b/target/classes/dev/lions/unionflow/server/entity/Document.class new file mode 100644 index 0000000000000000000000000000000000000000..bcb4b67d5475ccfb5f5535ebba8f1c1a2b01e1e0 GIT binary patch literal 12466 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 E58Z7RLI3~& literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/EcritureComptable$EcritureComptableBuilder.class b/target/classes/dev/lions/unionflow/server/entity/EcritureComptable$EcritureComptableBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..9f15da86adb4679d1ccae84534c88b31baa9b022 GIT binary patch literal 6117 zcmdT|ZF3w)5q?HGX=QmGOL8nb2?QJDAj?k91ml2Y8%L7g!dY@8VdDhi?A?u|wf1(! z-kyTNkN_d!{Vlwt@++Wp5ECz?cJVrTQ2JZA9iP^ult#v?w;Q6 z`}e0G{|mr5ylbiSoMN8igg`s>9DB%$c^q6FIGY~YJ_?&sBK10 z+0zpl%51~+syeh#P#C%6Y&k{G@z;ur3#0V$S(isQ}m zXR=wITh^Y(jrW9lRfpOaC5Aj5MWM6Csl6GvexxV19IwIUfs}f)LEve}C(3ZbRM$L@ zdW9<00zY#6XkM4ys3|*cWNS{eUYvE;=5@ubIUdDF+PGto0|{v^)YWZ-(ROOgxwjJ3 zYIKI-n7?liyXgApdh39MxU{FCP+jU%;UD)wUHuhjx_WrVsy%?0) zS8Uj3_JWOlqR*hYWeSzF9h$dr7u1@u@fh}tGUsfJV3ZM>zNreUi6Wg>dKYwg(Z(1K zh%R$B9v5BqwIu`j<&uqqhUbcnaf}M>B^!stcyCYU2$W1d*oA{!vvC+tD7=+Np|;er z3(8!#@g$y7xR*znR;JnoMV4)R2A^e&n{=e`dJ>l*-YD5#Tx68mP~s_Ioy=&OerGl+PEXa_wd~@m!ZT^7n|gdE9l41+$Ji45#KuqYw!)#B zv%zBDuQ<_7H(KYcE`=`3olnzXc>>}8n5T=8SV}IloUZ6gt`vsjnab`t@g%*%_>b!B zE;g^}Xg#Rbhw%&i%EB)dCboma^|yizT{I>#=Dy@qq9D90e)zS*`)O+@+y?N9<5xXa z(WM~RXl%~xcwgHDSD073u)mr@=n^9d#GK=K%PxDW{e$@V5PqldpR5Hkl%zGBOtpaL z)X5|1-c8m?lP1$vmlo5LHcjT~K26r?ylOg%6gAye2|3k6$ueo8WSVY=WZi6TWS+DR z@pU?azo#^PGBJ^F|zg(0MJqr*JIe?QZ_F@MndmcV=bTV4ThDu%PSFm7pHs zD8HZmytt$2lX9Lbiz^w2IQB`wouRSx&d^wdXJ{zb0oIW(GHuj80p|2As<^qN~EoOjMO4I6~}?8 zkMPXjW4KAaMOhI!&bMtCFohFbDZ1%o7oZu^`uMvwb>c%jJM}kC`_KtBZdej1GABM~ z%S=tO+O>h6$_;9QhX2|0*7W(@peE{gdwPSO&JAk9mfzsspl5P}nz&c^D!$gmrWZ&P zN7>@2>8ZkpIJ+aV-SU=AM7cc*=kfJk$QJ~8HaGJ5_#P?D_eQ=b$d_{?&nL)-dYky_ zj-Xb{5SBEnu^|rQ8(j?17Ph6_pe7$E_IeAwKo@g^nuMa*x4qm8bSXEe$w&$-_$IUW zk@&9P$mEtKZrF;no8q-w@echD$P4L6TVZ}vcva$*qr6w*Z-v6Q@trOQ`7a}LfPD3- ze_*=A*VzwX-N$P(y>N}M*?3)*>BR-UF30P2nckc(@O|q84Bf}Zbm5=4eT(a7`JCeO zJfG8i&YU&Ysd}g|6h9xuH+GA!_v-KEK89 axf&ynVt@AsegOW6KjAO@B>pv)y!}6)&+Q!m literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/EcritureComptable.class b/target/classes/dev/lions/unionflow/server/entity/EcritureComptable.class new file mode 100644 index 0000000000000000000000000000000000000000..81bd4bbc1a6e5d69fadc42b900680f14440b004e GIT binary patch literal 15702 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Evenement$StatutEvenement.class b/target/classes/dev/lions/unionflow/server/entity/Evenement$StatutEvenement.class new file mode 100644 index 0000000000000000000000000000000000000000..7a8f79b8bb5568a866d7a3d1b0e132fe9a949f66 GIT binary patch literal 1853 zcmbtU-*XdH6#j1WV>jti>Izi~0%Fx9rCkwg2`PV$g*&pG#ed(L;xxqtor+cN+w_*}sR z!}1N@GhCB|EIkUK4EHJE;#z0>tsC>m-FILM0r54OD^ObtlVo+*@YPo2VF`hHcdWjB7 zwXjvG*2Kwmwz=za2EFt$Gb{8RZ9jFKU>=VTQvc$Kz(Vz0Liay*(hl)3V)$?K?u3 z#v~%REb6Bg%=}x~gCbWnXgEYHhF>`WLvojgr5Eiyl1d-shS==cZkN;LwN!e%ZYFUB z>k4Rwrbh;6F9>;C!&Ph$>$dOPd#%o`J%-F^6_xFqycI5wHt&V0D5gS@Nmh0ku8oEb zr!qa(5DIEb8j2`0ERCY)T{{St4{|n2qu5w#Nz$TuY4LSOv~OyEzZV4$RJP^pdbIdH zIV&T0A~-u%?yK}|t>}5&m&x)#L5*Sle>@sAf3G}BG_WQQ!i`Q4uAQSfjnHGKt{oHG z1CXRmk&@OzO4YwE8@^dpZk z^9Zw2JrUL4lIkg`z8%xgNOexC@5S}^rJ9!NyrQ3#>Y`LXQ1x?CJwF|PO4yM;X02wl zk5g7N*2kRHjQ5eYno1vwR#WZcy!8l6KT|3b_=x^bQhC5Pl=8Q9{tgSci!AP86Zi22 z?%SfJphCHvb2SFhlWlAFGloaS~)j z2=5cD4N(n}jBdp;zaaaQ{H#bYE{#7*7;SK22$Mh#pOQuJdy)7ReVgfHsks@+JZokJ z8|BFHC&-_D6^-qem7q2FY>1JeBt-&Aon@Y2^G~qE4_F$xga7~l literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Evenement$TypeEvenement.class b/target/classes/dev/lions/unionflow/server/entity/Evenement$TypeEvenement.class new file mode 100644 index 0000000000000000000000000000000000000000..6a987bd053a562acef60480a0cb38973b3b68d76 GIT binary patch literal 2194 zcmbtUTXWk~5dKbmk?bgm5pAFsN}vI3r-^ByEhTPg)Xt#N@?)G;1_+oF@>+Sls%i~};so@kJVu)t@-pS;_TJr~0snr;HKdd2w zs0u~JqYP@Lw=1VFOcNRuVNz8*AzI$@n|46V5Vn*CRoG%G&Qc$8#22cFdkrqE(;8yJ z8dq^n%p~Bh!+l|SN<&;&hE!Z+h+DngrsLU;FWsKeFeHqc3Tm$2`+S=&DhW4kwHl7? ziU?OUXyVYYia9Yx+jBN}JJ_#mUc<1kkEoyl4(EEi)U&$3Z(r0fBJ86oUSNnAonS9{ zLBlAbB11`oj{6x#3y#N2oo1c;RlB}Nf>y(JS8d-BSSsTPp&}+g1Q`3L#_zRxMMDDj z(M*GDPMa>i$%DdOY8_3c4o-*Wxox{0PKRfcslzZUj@R&pg4byc1}MDK4tP_;8s4PQ z+P-h^G+H-y7}A4xDA%`mBbXT^&s|ksFjSExmNyv6gKqu(NF8DwL|fN2*r3(BFc>@M z+U@qt!F~<4P&`0Hpt#lQ_zk|`h|!JgKUdSD3*vonHa$D&_+%^qchLsULXnYFy< zabK3o+X`HU$^Y=~5o`bQzS3Hjh~Z1Eb})C2_!**CTbnyB_7)&XODZKTq?ELJQqrPH zNjoJat&Nnl9#Ya8NJ+G(h`JyskT@a8PdGvgeiX+j-x$Cq35(zFKZDiu2SWOTw{YSn zPD?l*)*q4ZjD(Lx^v5MkN|=u7lM+rzm{Ig6B|I4# z9;GK{u&pdTiN8kH<(iHkH#!J)A( zinZlX`c5q?I#&#xyp5}8{~nF)Un@rISntP3Q<0*8xX#kI(fAeY-~GbakQHv?nv@PT Rjy%bq;X0bcU{C6={|Vv<1(W~) literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Evenement.class b/target/classes/dev/lions/unionflow/server/entity/Evenement.class new file mode 100644 index 0000000000000000000000000000000000000000..117316bc7ce92bd791255d3aa038dd518286fd2e GIT binary patch literal 22924 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 bJ!!oN>8mW)x`rHU9r(le2Z)*OwyyYJf%u@7 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$InscriptionEvenementBuilder.class b/target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$InscriptionEvenementBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..bf1250295a41f26132f15c825aa375e9675e54f3 GIT binary patch literal 3898 zcmcIm-BQ~|6#mxy2plC45|R*F3auf=Kt$R?+c=~I2!Ygw6ewwu{>kMFv1eX!wloGR(rJjo$vhYp7Z9vSFZugU`s)Q zVXefgnrYaUt5qx-woLm+&E-y&I~uoyA@;Oo%Pl&_uAr}XtK8yclBeQ?a}~oZaYsQb zL-#Yis%xfhJ=InV&v;QVw3m6g;Bbaa?s%nnZ*7*Lf2~3^%6!?X8m>_=`5nu$g)Z5+ z3_Us1E*I<_?H&B1zsdWz~^og`418h&(nW9~manTLw;2xi> z=lylz7}ir--+s%m3~`&GB|W;y&^m9IcoHqRqT(WY8M<GgA5;CG@UywLN^5=9DxIu1=HnB8LTcg~~c7owO6>Vr|_@x;#H6ya8 z#^-$%ok&uI>hg@iP9K?;r^aSP#Tls5PiYlragH*}&qRhlnsHccNTLaTYW&AlbfH`F zzpmoEKB+#d;udBoHER(2xsW@tT`{+vRQ0*Mr)C-cZ6?WrE24=T;^6!q zX=oEDao&xwBYH2(tN1Kse{mt)5bCI5>#KTFj%lNighwc3@Iw7>vqK{@ZalT=SXx7B1>VVLN-$^hXT8g$)c=`+!TjRWiAo%Gvf#WjEW< z#zPx7_I7#9@+TpLn6Ly8^SWuS8-i!M+VNEfzG3(&)(LUKpnj+Q25*Lq5AyvEOit=< z6~7yjAN;%GU><5>X4ojTPsXfqL`DTG zBBOdzCzHCVsU!ViHkU}M(Mm>RTjm8W|K(Baqo*VcT%{j8a#hdd^cCz?hc^@2-HHihIEM@%*ZRcdq<*;#mWKg35dpudv8 zWv)(V{>E^Q9@l<{@)94**Rec3CcOF0{6Cm_MAHHK4~>n}bMggl%LunbB0T6L1{~UO zLpF!Rp_fjE%-;m=(7tm>yhXQyxwq(2ut47zaW9791IiDXh$B+_ODstO{xIwhWBxGt z3M)W-&PnZrP-C=4vjo-;<=O_xI%wTv`ai+9G`mW6FzPbCqs&~O@%!06{D1;VWHI*; Grv3+cE-C#0 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$StatutInscription.class b/target/classes/dev/lions/unionflow/server/entity/InscriptionEvenement$StatutInscription.class new file mode 100644 index 0000000000000000000000000000000000000000..b6eca0b474071a72669ccd7a18ded2b13052115a GIT binary patch literal 1820 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/JournalComptable$JournalComptableBuilder.class b/target/classes/dev/lions/unionflow/server/entity/JournalComptable$JournalComptableBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..ada87da43452ed60fba70c8b0df89a91c4421d48 GIT binary patch literal 4150 zcmb_fZF3V<6n<{{vNUXIskEpl7L3q_!b%ZD0$ADt1yfLpATJ`DP2 z<9mTs^Y~eI{co*6R@Y_Kl3wV9Pp#{It?JqCoL{blcEOdIcEW7UaZ9plpjTk{iM?)H zuI;T@Hw#Z>F%;-6`Xwnal5hO96jmK?B`0v;R*gc+@|w5q1P+;B@;pDZLj_1(as6_^ zU$d@CPgdx9y}-C$5xk0J(2 zlr=94HKHHYuoXxv?-y-%p1&y=>dY0#6EFh;u7#O(+pQ5*I^G77x5a(JEXkl)bt+0J zfg#CtSgXolwNzGht#-8Rxi(Js1?|*P*RQADS4_mYX&)jJ=BXk%QMwSLz7;p84yYn)H1twMb8soNi))K^CGwu6NTV7&EkZzAk91L`(Y4P?aHDISN&3u#v?p7 z@QpyG5w4E6?ypHp>#A*X#V&?^^{Fxz+5FgSlK8Y{xL|uFm+d+4`)jpIZnHRuY|wI1 zO%olCKWO66!I3eGIooxYIFsat`tkKHuo!%oFhDyY9=s;hfOSDkjmMLbS~iVFYFi&@ zHa47G)KcfVoz{@emBo?2IGxpjov2J;z_}8U-mf;{1&l$mv1EyIE;gQ zoB9CW!#H<>ra9DsQI50}e>bwnp5buzS1wa%XEmme#H@HYwlkaj)=V?oW~v><0)QhF z*sgL5(xcmEsy)qs#pG5@v)g8>LxtshYo=4%W~yUN;6z88&JewFl&KtLWqY6DWLM5+ z-CHI(s=#2Y?#Q{tu&25*HR?JPlxo!39sF1hJ9ASJ{L*o*(AuCVV`EQ z2%J|+^Q+#cF&4?ZfQucR`3oaQ6@uC9@38WGo%{*L3w)}UXBYW87ws=E{(+f$+#cqW z;d7Kvme15NzD`X&$2FA(y$OWlntN=6KK^^wrC=1t)r?0(8(1|(rA?lk_8|2N!v+># zVaUKuYVZZ-JK%rFf~EAFCX70^7r3S1>Ccq@SPCbfv)sPK9iVgCYO77x@!luz6dttr g=pkuJYJpD?lDiR}5uDYQncORIu*L%9MmJ~v1rjv!_W%F@ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/JournalComptable.class b/target/classes/dev/lions/unionflow/server/entity/JournalComptable.class new file mode 100644 index 0000000000000000000000000000000000000000..ace77950dfea98193c06f5203ecf50a3173d8949 GIT binary patch literal 8907 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 HYyJNPfoBPR literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/LigneEcriture$LigneEcritureBuilder.class b/target/classes/dev/lions/unionflow/server/entity/LigneEcriture$LigneEcritureBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..10dc36e06c2b5a4bbc8848e6f3df9a83984d543d GIT binary patch literal 3479 zcmb_eZEqVz5PmjqPTUJAX-I(*ng*wJ+$KjT<;_i@ahfLJCeVZ;h=g>uH_0~Vz36Uj z6d`^9U-`t(0I8^`Nc;dk@RKTLz31fYJH?0O59_@h&ptCdGds`z^YX>t09NpI1_KOh zHQuyc;mN>mNSgLt?*}{Jev|t)mrAG;yDSbQe^B*>YWO_Yv07~ix5oVph8QkBb()Ut zI`Y8Yt~}*c#c)A3>fHBQTNo~t&onlr;s@L>GF*AwATxEoDVrh?6_?+a(o>GoE-_4& zU9Vp8j_ikAa^F$BMq8}9CzT`B8m|blGHr0{jykkg#lae{in`;H-gu~2@_9`wW?WI> zuFEy-nJYWW7xIAA$9%reeJ-n{Fv`R8GTbTm4pBQgC9i(096AKSO;q)QpNbf6r$B0{ zpKT(Xk-I_)b&p{%zwm@%sN~gn7K6BK;S#3EPFYC4)u>mvzpG8_(|J|Led72+uS4OG zIusPp^%OpIq&P9*n7y5%f28OpS3}b}h`_N8zdi!YVnuRfBwb6GhTtJrL zyXfJ<0_mf{>lQ4GYlC?UuV9?vr+kXY#jC83KHao1fr|`3rtze^O8e-?l7&|>$uL~e zy@f7K{(plPGkA}|?tN`L3O$Quysz)n9fp6S`EM6@a+^6%+G>Vj`9y9@ThiOXc*awd zNh~?kHAau z@EPtiT&_Du)YN3vQQru4NMC_&;M9+PiGsBQ6LT-_aK9u8jx%I;yoO)p>%w%?DE}>K z-zZ9}o~HukJI9Z>I`nEm7VFr^;2}fq%zGi5-VwLWvCQ$;ovQNu6CIaLhF_zn548>8 zh9he(4{~MCJ8B#k&pmF(#@&-+o}j)LK}g712vKrecSk5*oEX7_QIr{e?zkgM9jlQ2 z^AVO`VYg>ODe7S@80hV|zub%KCX=}aX#lfjn^ z^RW~p1vP`mL^J2B68)MC&=uO^f!go_g=^Hf2dTR00i`*LRvB99d6eb>O(roJ(Mn84 z91xQc8^mPPmt!*OkzKMZ*`@~p*^hej8cA)^WHb*Ko@4s=mW!{`uXY%i!5j2z83nwF zE3~5Si?vW$n?hS z4LX+^)MSY&4Y^*P&Zh=7X=X(9Jwb1z1~qj;uRkpG^0bf|)D#*c0>0G?bTKuksZ`=) zyqf^JMOC58F!vXhzb!nYs(OZ58gn$}Y22Vupt1NIx0CD5ly2^{bU(m{33m3V?zEj{ z!kYOLAJH^xra3d68Z*m9nqJ@@aGufS(C9E$TUVjfQQT`J8zrNk)A$09Xf;WWF?wd` QLvQ0N?9koYZS7qC4|giwC#;Y{WAC_)N)Y7g3=*?PY_UO|&NHKFG%mq)?e!Ts6+EtS*E(bRzH=%aeRS3z>e z6wwpeS5yA&+rh;c3NXsH8Q&d9prRE8=iuNkIer?y(o)~Q^MG@Z4e zLxxFMm_$>0n0}Cd6a~=<-L|z+UBS{py@U3iJDImSvU*2O7T=Mz45#DVlTKdmn6zxe zG4?4~Qk-g1&)bIW=w?Pw^;x;837H~fY7?YWy|wRkc@)G<+Np7Yjqb#_xO~~W4o;RY zU7fH@M>Cy``iS8uSm60?LUYDa8;sG7dd8U0WZm6GV`g!Y5A~Mcc-6|QWQ^Y7(|2Ug zo#X^n*4bfA(H%JjSJs@t7&RQ5wnVunva7O0l}p~2*D|t`(r3gq`*ZHidTWYL=D7^p z2efc=?2MM9>k7cWyq=ZC_XOxXYq&{36gi)EO_Kx`@)zSrO~S~FshUNlR=)pe}K zg_j0#{<$8{UiTyGbJ=5ty<3hbecZTg`7q7wv$A>|3VKz{$AUQX&by*`r-FHI5~v7d z73;8ogNFNeY)cTjK}8=nax-KWEwAsgm?$J0Fn?4G?Oi?~(`-`lHlb)8G)#T#)WnFM z-zC<`o?8s7-CEv|zAp?pV+Iq1dhpA#Kv1Au*~u zL*5xyuyEP(>c%s?ERz(P<9TlPY&*L>uW$FVO>Y8?xKYJ>u!HlDOc}YXo>#D$Q`C;m zvc+_k^)`4+T;XCrHw~BvswCz1!JIWQVvVP6&`mv0!?V3n>`}1se=&Ok!?;bw?XtGW z8pAXkhD-}x$t^A2B=ZaIEW1l23`SIh5n&jVyzlQ;TS-#bGTEZRLTZ>-#_1;qp&V1O z6jw5#6TPF58$>a#qDvG_R1{9Vg6rzqhXTH@m?ebKQqhAI3|TU|n$pQ*@~MlsVpy-x z+A6NWwe+K1;l~C3kt%cJa>igQk$&*{?wj@W5#3fo7hOwH(Y5n~)A*j1SyY0G9;F}I zE%-&j^~>rA=K{RsM&WHIQH38AtgeTzVpOlN#T?7CIYC$6qhc@aNZ_ORcoZMw(n}?c z2U76~e3IwVtuwZ;`4Y=!9^3=IHShQXwFTBGsc!JWdw8G13^RlUGS1p(2oC~ zr0Sv@ahrnIf|eC3!|M6+Led(`sGXi>MpGRjSS@5C`khl+jy^Ao{KDoB%g#Nj;!Ajh z(UsB6enBVj7`__CSIT0_8=~S10#$ek1`8OUkHv%KjL8+yCYM^LS9iLpUBMhzRUva> zev#xK?{*JYAq)ZwCwp!gvLIDmXtB;}_CUpci>oeHR!lG+ARuXWA$qaaYVi;y!8{j` zsjOn=d$<|$*qCNB{QZh#K-~MRifNpozqaof80x1(2YAWkb$cquB2z7QQgdgyFIo!y z!ir;^#nmM`1M7%)mP~HzoMBdCr!|$&=$j0$Gzq@2R!T0J?RnunspN9z5%Atb!CKNS zI;d^U38THYWCM%xtbUiyV5~lJK-9u(Wbg7GE5*UZhJr-KeclZ6wZlrR3e;ODV{n6I zIX2&ClS8^QW@YUJet};_@k>6@O1aW74_M=R%F9Atw%WwEf|WlgY3$bu9t@@gAMFut z*34{9w>t+dYkX?5_k#JVY@k~|&xG#@hzch(Ao3~tv|Mhd;pn}s5xf?|Zx!%*3%rh( z1y0>srKj+`a!*&F?*)$t{NRyhF?gg?*f?od1kYM!Im2|oWYyJ|(`;MzbY7ViehC-F z9~E3N!`s#T7sa0yTykM34~ge&UeQ~1+ZocOy0&o{)0n)G_~!&Ey7(Jq?{dmw{KnZ& zY(W#6ZM2}3y*3+j&_0a0m`D9WG|Yt{`CE={kOza$wu)>Q36sqMWJ+QYdy5CK1Q!qB z67n_*@1^J%pgot1NXRdD$;$?En)0Cy@?e-{n$8NWKg|_2r|GOR4Xa0bN_^ACukVSa zST=y=oSr4*Ol_BFyQyV4=$m+{@7I+?r*vF}Rd_p9T>RA~r^cXoPNjw=U(KPbs|{Vq z8Lq?i{Cx*)K1veV(XTIfM0&A{sh@P~#ek`aMm5xn}L+CHI`-*hbvI z-xil=GhI7C^T?dU4my1&y?di;qUz1y zn<(al(2!inU%tS64@ZdENsZWK_%>bEbr!qChF6g2;%w7M%f!WA|5F5e(=X!b0rrsc zR<89nw6n-e;tqOeBw)g2_2}DL0L9ex&L#*~6<(Qpu{WGr0R%+{@1)?{nDu9HHbW z|3Bt+kMr{!J_}qZfL9gM4*yhCWg(^fbrug*u48f4{B=7Xu0r$(Wm3-cFq=Q*o`$X- z$>b}h(chH}O=Gkx8J@=eu4H5yW>+#gjTwGh;?B3j<0+n(9{;?M+b?*eC9x^jZIArX zi5G&jBo5?$>fygDc*dk9J{Q08WjNF7hMknnVRrBC?s@{@(5dd1pgfFl*~}ZYAh=x-Ho%pf(@Wnrth8LZv#zyCW}gH|>ayPBe&#V_&Qrs82kw**$VS^ejQJ;6=71G#y8lC@;HAR zr`U>d^X|tt*^2XVdLO>URst_#ochOE2b||$eonKc;_ujmr`T%7Kd=r@v(=&`Snqy^ ztyZNCi}4IwZEme}Gm_ER8)#v0#NWh%C?4Z~G38Ax^qTSiw#jwzFBUU{jTMJNDQg9Z zGI3)t2YYVx?utUKKq#FjZdBw!K9jOxnz96$UT*7M!-IErEhF~stxMW^#~7Tkw(b*% zt~i6UcZmE@IwXo@$k)UqWIfg4bl6ofTHQghWVoO@l8(5lNKwTY_C_5|M_m=e*B=!v zsE(y$t}0ekF{r&!$J23F#d!8d#a-2s>VdV-Tc00Tjd%&)#dGvW4Bx}^Y&CGR&$2Cn zwG@xy`)q}<9{1t}w!$nF$M6HTBD@!e7&H=C53pu^iLDre>LUD*t+<4QGK!YBSX+k} z5{kko;O8yUmii4Mn2&G)P{4%BMLsMNfR&JyQXd>G2bb_w6#kIdj3K2_)ph>Bc(sA0 zv9qfE;fYy>ml!UDAPKkSoE!cxb2H#R&wj-3d2S}3X|LeNd(f~BKk>S+;-`GN6}vx^ o?rQv;cs}Cr8~*<~e#c%r;lu3xo}2jx=9RzjW$;(ZLzE5w2mgmvYybcN literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Membre$MembreBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Membre$MembreBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..e9b2721a11ff81142a470e2a3649d2a46a2f934c GIT binary patch literal 5962 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/MembreRole$MembreRoleBuilder.class b/target/classes/dev/lions/unionflow/server/entity/MembreRole$MembreRoleBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..91ee4a98c12ae62cc7545902cbb2106cb5bcc3c0 GIT binary patch literal 2827 zcmb_dYjfK~6g}(2QQ`{HhCZMqB@Irn-B6YH!%b-8kS5@?OcUB+_$1rgMv0`&NNY3s z8GPesz)YDkWriQXk777$iQ0}miGX~N?rL@S+{d|l@1K9Y_yfQSwk_lYR$H>;_?`-5 zryWu}@zoDbETfK$92sg)A357HXhia%@@28NTx)xNOGXyP1aTvG)B6XALh&CDeF-B5RQ zW-w1&raMjq|u5FkSaTdA}VrWVGutC<9|`J#r(@e5ae^ zde38&cQQoMqhXj4I^<``MuJf-|6g-cAa6GEm=M^{Vz^7gnV7w1V+sX<$EA@po{5d? zHqOB|LB4I{71#p1skQzhI1^J#HeSWFK)zw_mcXsjzk{t>s0b`)y>Etx0?Jr6W%{nb zk6Fk|t0qIybZH*R%L$tUIptlN)GE1V0vDN$UY|)XUjgso0}JmD^q}OQjSuk=Z*s@= z+w#E^fu(-ll0L=iAA+1=bNs}{r}#|ZO5h&InhKk)e(dQzu43(mnzuCU2TPq9#`ZkhlX{+d!c){%R-E%lyi#RaV5j)gA-ioFoMu%iy7llYs& zwc$3kijGXccLjdx4=dG905{#R<;%EOSL&dBSUoYafrGn8hjN7a_k9Q>$Vee-uJ7-7 zY{}CTSeeASz_)#&_m=52Dh1uCvm%z~BUZPOFPr^8AMEO8+1J)rL|MSz3OQ72z8lAq z{9YKswy-I%IF!p#*|G4Iz_pWAIX041vdHhtSZ}IW<2u{S80#;OoN1{1viN1>BxTNY zyP-XHr=dOesi8f#mScMclLLyl{Z>r_6iTkG3F_(z>L@Xp?@dZ8xPO`L=TFT?|ZZ%a0 j>f<)|CTVGnPYqx4Ylhwkj=2q1(>-kAYZhv~d$RmDKdQAQ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/MembreRole.class b/target/classes/dev/lions/unionflow/server/entity/MembreRole.class new file mode 100644 index 0000000000000000000000000000000000000000..4dc286c37ce97e45fc0bf2e811c1db27e74b251c GIT binary patch literal 6267 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 Zqh8JtER(;Y7QV)}_@4ijeh632{RhVLt%?8u literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Notification.class b/target/classes/dev/lions/unionflow/server/entity/Notification.class new file mode 100644 index 0000000000000000000000000000000000000000..d80fb740fe5d4dcca32ae4a9f5d68e492f0bb05f GIT binary patch literal 13450 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Organisation$OrganisationBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Organisation$OrganisationBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..9078ff8cddfc2f3a6db934fc19367ea25ed52d14 GIT binary patch literal 12085 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Organisation.class b/target/classes/dev/lions/unionflow/server/entity/Organisation.class new file mode 100644 index 0000000000000000000000000000000000000000..13802c6e4ecf803d53b12e1ceded93165cf3003c GIT binary patch literal 34875 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 CBL#;5 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion$PaiementAdhesionBuilder.class b/target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion$PaiementAdhesionBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..4b791424aebd5693408453ad4bc4b9e17d29094d GIT binary patch literal 3134 zcmb_eZBrXn6n<_XY?^MR#TKj}7Mnr{*ww1_C8Y{M3N?j}Fm)Wic$3_cTQ+;+?%g=} zOZ?!E(3v`Q>Wn|YALaDi?8b&OkI3#jshe~c7@{Pm@%q8yV1}_?Pm>T7Lw;9V-cA52lP*KgtC+Ak6$f z2p{0X1pJ58Q-eQGSkr&u7KzzN%tfLQiG}C*40x5N`Pfqyce>NGa%4X4k}gM{*6I2h Z_vmEio6&aMq-om1H+Vn;yWM?r=>;jME?EEo literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion.class b/target/classes/dev/lions/unionflow/server/entity/PaiementAdhesion.class new file mode 100644 index 0000000000000000000000000000000000000000..92c3a312b2395036167518e0bf696ad272f75d71 GIT binary patch literal 6499 zcmb_f`H{!z)J=q>t2zQ8A90j(NIl?6EA{)m>3o z!O-}9`kY>K_1wH(HcK8kSLBgf(@o2Da+9_@Sud9fb7OW4OiHt8wm^!|8KX;f#&%IdNP;rY+p%gl}SBtd}qE3!^G=*LrL%66&2fZb;hw;Et~h(sn;4>Ft6t-x;vZO zYtHO5ie^O@Zw)t0yNY82)(KOrjFU;=x^LZjs{*xRIF3HU{Vn50Cb%=B`$sKiTrZq#UMA@)r+*#hN{ENoe)@aQj4qZ94>DbjWY^r8V z`8iC4*r;7HQs~AE9Qs;j_Ea1_5m^G&YCRC?blG@k!)Rd)YXe~6M8j3Q%~2;a>=ME zn51%@H;oW-Fzg~BVXu!zo7V*;SFq;Ww3YtxvRyfC&*csnmQiCoN+T-vD>(H3(;_DDQ5fKe#RM;`S?tGYCc4HLw@ag-EqB{~)eMD?Tz-vs^jy)UX*?(MyBI*V+pIH%eRO zyKzv#v)7a<9b{D_E82(Nr?!c2t6>tzgmHgeFVj0skq$$0LcIM24PV6P81tfTjR|)O zkKj=iUuuf9FQVZgVO4DD%L{1`;PFYb-C60Q%cMUKhQo-0Ryb&-g5LSB# z!7J+3wl_!&_&_ampIx5Dralm`x6u1M^rmNXrH|Z1OU~*JQ>^5_lLc|i_cVM5-=(Ef zM+y^TbVY$qt{G0f?DC0^SlBbTU`*d&i^6?_ElO#FO_!&R+Vxt|*l)@!I1sk$Fg>=4 z_pdgF_`|R~KMayTw??NF@7d$&sxUy#sZdvD3J0H(@qocqd+CB4Q~Cz8^uVbQp#jjK zC`c8(>2#bk@Kw3PdljtYuL6eHD` z+jeDQuTB{5tX*NZ_R;UQ=+-;$#^L z!?aV`oC#en>qTiPq(x~dq)1xsR5mHl3Kqwe(X#G1;@9gIUl)u&Dy}HFxy7^Xe5K-b zrpO1gvd({EpL3vKrb{blE6{fW zDm01d_1xW1ph}qnwcS7-s_|hT zYp{Tuu~F!v&=9(Ph4|To!F@AImyF)DO>FJu_XhqEbrY}RJQE!Bc?X`=S!^y~3onVu z^d6|2;Q6ft_z3@wkf!i^RrWHrWnaVgcaX|n!N)IR`zs`OV+TJ)9Goa)*w0%Ja9`jL zeEvZ4MX?K?;%+zpx}o&b`FpUJxT7S4le;T)fo_{lqG0Edg}A zGoX{WyA4pDGzmr`JA4u2k`2*d`QBdRa|GU&a7!&pknX`}c(ynNnlPt2#grly%u~2` zDa`3kF{OG1Q^)D0Fuh9X#CE?n6%VMVzFZXrK10R$P8ygkFAB}CaUnsrHcyO`uH?=Lj0zTUY=poV)R5`}EFhm6-QrFmt%{wU7kDBG%) z@;B0!k?-B%?DL4nE)4$#$}@;}Ul@K9%EV^+_4D2~JA4@r`zv~Uax*{ju`TbQHywK$ zE6+pQl8#+k;IQ&>vRL^Fr@;fo^&e0uP|u=6IUTq>;jGSySm?W2R*kw>Kos1>$~_ezCvdW z;O}^h-Mx#?$E)}%Y2Db4m+&>NVz>iO;&HCx{JHixzRp#GX*P#%aHZ1G2ENHv53ka@C8su@2wnO5+*$aI8|3|Hg6^O3!;(qv8?%Pb%+Wt-tJ9 zdP&Xr)221ZxQ!M`M~h2z#Szh>_8s9jwD9?m@!~P6CXIsge=DT&Pts(Pc%pA)2NU|K zol=OC>-$D#nH0&s;qy?pT*6cL3f;=bl$P|T9;@A6xSlD+7VF2-vA>@fQlLEFx zgl&OyF*RUTo0%8*+Z+TU$rh0XsrF|Sr1?Mb66UAg>@`kj{fztPXeZ~dpW_!N(X|`D zB$?Y^;du_6jqQ2a4&c`(q3p&BKFf>v4JF7WClW3WrT9JnUd2W3`pGrM-6ht{W&8ne MAdNqg9wTk+zui0k7ytkO literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementAide$PaiementAideBuilder.class b/target/classes/dev/lions/unionflow/server/entity/PaiementAide$PaiementAideBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..7c3547f441c3400b0e412dc0ff2acff673188997 GIT binary patch literal 3069 zcmb_ee^VPp7=AVoE=^BqX{A;Wi%lT}O|pe8cen9w6Nhi& ze|`m>sZ*!U_yPP-j_>BKav@hZ9R9fM?R)P&&--iNH-G>0>@NT-*tU>ksI^7Uai#J@ zr|Z-5$W=c%p$K{+aD=a=K6D;%DLi^#m2FWQbbebpsuPi*=<8Oo+BR()S-u8o-t6Af2+O?BYh6TS$z7Hv9mu`N9A zw}mm1!O6f^KRQ6?$35k1PEK|@uKck}7?X{@OCHzzPEGFBMN4|zHL7jm?CZ7qCNW%( zRE^0_L$$bDCyia&8N*u2GtuKRAkc|Ovmd7&9Y}wVRL1T|U+TLIBjtsM45MqREs7Yy zD>g1;f(RSZ7h7GgDS};2(J(NES#bNj*OW8cH{%VGzO_o@C zJ-qPl4_|e2y9-)H7oXuO#pkGP#_gbpck#Z3_Xb*O^v%Wx_>jTsakneBA2G}&vl+Er zx`{)O6V&fNw($w*T3_?{fml;MwXUzF-lwtFJLQpo}Ong;v-CgT3j~@LY3w7z#@Ml@wA7UohMn z3UpfNEHoHqPFAHk7sZ+Gz?KO0h6*)ispXGQdFGKbeV=v~?aW%BHD@~Ukc^tEQ@oX) zy{HhT|0{&I@lFc<1InqvpChcPKX99tnP{1fmQu9LKf_1BNlbGwQyzEv)wD8DA9qPs fAf`G!pW`0wOnoz&i5pZ+oA?s literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementAide.class b/target/classes/dev/lions/unionflow/server/entity/PaiementAide.class new file mode 100644 index 0000000000000000000000000000000000000000..aa5bd10e5b3d4dbe1c9b4e2404dfe07a2ea0f422 GIT binary patch literal 6483 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 aayLM(G43w0W-j9ocpYi{k@Og8WB&yL9rQW? literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementCotisation$PaiementCotisationBuilder.class b/target/classes/dev/lions/unionflow/server/entity/PaiementCotisation$PaiementCotisationBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..ee582c4b01a547d9010be9cf0d0d7b1ad4f2af17 GIT binary patch literal 3184 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PaiementEvenement.class b/target/classes/dev/lions/unionflow/server/entity/PaiementEvenement.class new file mode 100644 index 0000000000000000000000000000000000000000..a3bf404208330e2b55a5d070b08d625f87cacd98 GIT binary patch literal 6660 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Permission.class b/target/classes/dev/lions/unionflow/server/entity/Permission.class new file mode 100644 index 0000000000000000000000000000000000000000..4406f67df94060ccaac1696353db3f3c4da6700c GIT binary patch literal 7753 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 O&wTUy3-{%fXy89^i-uDG literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PieceJointe$PieceJointeBuilder.class b/target/classes/dev/lions/unionflow/server/entity/PieceJointe$PieceJointeBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..c15ee90b2d4a7c957ac0a910a0d72a67192e87d1 GIT binary patch literal 4790 zcmcgwU2_yg6g?eCHY|f+Ku}Rc4UmMu1Q11+M3Z13m=7c|5%6nvw@HS~&ZK5$m!ira z;=6x9tFW-J%4f?TWqEpgvgysNY+G*m(CO|wx6i%ZeednF`SY*mzXO=W!vgvQKB>xv z8~9O}xb=|T!ytO>CNgfw*p*@Gr%&8FzO2aGksqdV*ci{${h%u20`?0WdE_-bH}Jv@ zce(sXR#JifD6Ym*;ON5cw|PQr$hahM@@}2rH8~$P{KPK@ayks7)Jv5k0z(TyR4YeY z?oAoW*h^)VOA3KsmO((sAp>$Hjs0+gpAS}|T8$oizOp)4jVg6D6&PRWzQr4vHgN`O zvQ}0QV;1Wco4Q=(MDd0f`bl#K0uvT%myH!fC|07hwf>^T`q`E~R}XsCO_}iY?DjjI zX>;Yls;qfoRZja=ZrZgld}DV3*B?$}FHF3Oy1I3*L5wRF7_04zh%<2A5B+pXVBg5- zn!x_qs49!thf@w-$4S~*@I$#&ua#xI>M>wc7(^8>So30E%`@Ttbkk=9%~<$g#BztY zrc3K1MxgD)?&yC4B@Edy|-#YkBool z;22&JNNo0Q)|4LEzwY2TP6#}BIkU? z362P0(DRn2yory3hvQcZd2CN-QC zqh!kSAvyhQ;3bJvR6p-%0lc*{xvS}P*acJsPQ6%_)KcFJ({V{A>8&V9@itk}K4wZ0 zeTs*1R^UwagY5ehamvX^p>i@Zo1Bc4BPSyZ$jSIL=VW{_b27eSIT>G-J+eden?#wu zn>%@fQkR;7egD`qy!mr;dvEct+AMewr}@{>3Y@*e8Q&|s+X1LVSs%X_#?JqO;jy1M z?89ED<60$yqH=b=f@gY`-}ZXf271mKR9_H(zH|p2wFcEU%oxS_Zk~=?gX$0wWTF?l zfx6b9I>uPsaH$*UWou9!q|8aY*A4WlHK>kqzJK_j8|XD_P@N`%1n;A6pe1Wioo9lK zZL%Balr^YMRQ{g@(;YzXXPGzg8)oi}J!Mio#W}W7wsAI>?K0a{wrgx9wy9^h(K+>W z7EiRY_$F?3u({nVW567-hJVF8yOv2kb#-@HcUN_HO?M^TO|g59W#C1g%w?YRW2IRt mR@;*Q3(5vrYaXzDg>N_;qPP6%j&E5izC#&R7CG5mIrBI4=-0#m literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/PieceJointe.class b/target/classes/dev/lions/unionflow/server/entity/PieceJointe.class new file mode 100644 index 0000000000000000000000000000000000000000..fccb5e9f15de8ac762e5af566080a48e8008da69 GIT binary patch literal 10165 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Role$RoleBuilder.class b/target/classes/dev/lions/unionflow/server/entity/Role$RoleBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..abc23a094a318a60768a9f2efac2870d56b9678e GIT binary patch literal 4021 zcmbtXYjYD-7=BKA*)}Y+6eu@E#E5OEU8pExpj?Wj)s$k3T)l0Q({$-}H|%cW;1~ac zpBz8>3vfmT21kE@zlm{t&h94NWMc{SL(Xl_`@Hw_zWMu~7k>e`2v0*sV5TA)hHbfy zZ`2)5YqtBn;Y+U}JwrNy6+AXpTwCVUaJ6pP6-l~&fzgL%!!&HusT#|rhq4?9^q1X= z6c{UReOe7X%c&Lwj;_=Rwka2#hUHr&TV8P-H!uUmSdrN7X35rAMIgE(E@$z~a8Z zQ1G}Wm2v`SiapxmR!l-{-Sw)bWBH*b0;hX`SWZw7VQ)=(o0jh@OEmQujr6DomTeR* zKj5|DRjcZlLEWRE*E&95=z*(J@n(xtVV001NzS1`1}|8S70e3sO-$aVL+9wwEc)<< zjzc(1ilXJn8}-eS^lq7SqVk+uHtpM{XQ_RB*dMH0^y=9j#Y)B&#aft1crgy3$(=wA zL*N}90~l1RcXbR$tM_zdF-Y0daUrmng{lDzMfI&$h7KG0P8h?8O*UHW6C_^RZF40(wUb1b*lQ+EM85h`Dm&$2#_5zgm5w z9>t0#TTdH0Rt2#8T3`*l%b=|-ZJhQeWgLSv!XYn0M8u%%< z8d zgf6a%A?8fmUbO;Q7#YO3L!gg;43$UQFEZaHCq<7 zPWB&apGe^*!6e(1MTd>moNfBPWSs0u$fDt)fYIB7lWW!_=0J0)VUxu*<*#lY(BKLj z+y1J+VB{{cxFP*u!Sw^Y%@?WsBGb;hckI%CyL zow2N?&R7h{{Xi(gc!x|Z4;}ZjyvH_*6iMli@YLWI zFW@ci!Sw)*k_Ia9EW__w{?t>P$UosSgHEXN=t>O5%dt(Fov#MX^$Z$0h57$#(33rb zMqU+|z$CTkwBu!-DfM-wem>v-6jR$G>n$7TL{uhbkBjLp$Zdu-wguH%pqgsGLeRZ9 z^D5Apo%TNZ z9&GZ!FoS}Ek-n32E_8MlvYpydWR?Vsw777fLrF{}kGia5i zhxu~R%2$h(GMS5oC96^{=SxM47!}u9;jYUW^wRFRRunj1gMxbs<);gy%lXpNg2gcP z>_5&|b9LA8oM?t2mdP$N#OM6yTtz~KPRTB9zjyf=s})Auuhw}Q4E-98Vt^r;>x5-8 zd!-fky0XhDiaw?xfussW#R*EPOGugfQQAIXwx5){7HIvt>vD z?J%6)Tbu53iAzIqY0Ga0b^gE+ZAyvTH6hZbz2!7KJ8A~xpX;I$ak+g literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/Role.class b/target/classes/dev/lions/unionflow/server/entity/Role.class new file mode 100644 index 0000000000000000000000000000000000000000..8f28c44c147eeb5896627e760bf205b0058a4f8a GIT binary patch literal 8368 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/RolePermission.class b/target/classes/dev/lions/unionflow/server/entity/RolePermission.class new file mode 100644 index 0000000000000000000000000000000000000000..a07c9d7d7c04cc05eb84b511e51c3e0ee3f6573f GIT binary patch literal 4587 zcmb7GTXz%J756|1QQ%;WE(+3ZxW_p*LF;7Lok9=RbmSich%EFp+_St)%y}x}s|N8GI zp8}Y|9}V;U=f>4rc-bnK;$7Hfy3 zP3%Z?VNCjNnD!}Jem;#coJ`>YrVU(F7}zP2#teQy&)8||`tp5+6U}NbuRhR4zt8~| zQ9hT(Ddc4JW*V>KjX~D_r8M5cqQXm6v!Z8Qr)c^&Ex*i_=bP+ZQd8}AcSGrDL@$mj zRJwsva<0|$P{j#*8J}H~gT2OhZfEI1`1qbU*4Rs(qZXpFsEq!u-$kjsYU-8Xi zWyxHRial92J;tuYYG;#J5I1xht0>a0D_0kmW@-2WA*pGvZu=xgHtiX0zC1nJa!G2% zt=Ecr&XOi_ptW0`lKlLtb{$Sz>7=jbQVEip|$SS zg~Feji!tI36~1XYCBitm=(?5qdZE1*nkX%T!E2)FFq*-4nn+v4(nN|>%$T;lV)?qT zFM*#V@t(pPtqC$F!I@GMBMq=+*OXvW>ZC$?!Qm90u}#ktw1!%hL^IjI?-X8ZL#vbj z20mcWzn+zK5Q4>6Eo;wT(lSdu9Aih2cTq2TWCHTn;O;2*Nq%L%q%`&*jeZ{O^{@{E zx3M46O_NXu7XIU6XA?!KSp+^_|ZT-9Wr17lVs#$ z5V>6xo$gK%=g}^T&UUBh+xSigMS0Q$oq=*@=w+u=GGg^N0( z;**s?Bkpr*x8JH}NbBRRAC71Lf_Uud_&=cjjCk+U@z0=^CWzGA!8SYo0@uP7F=EaEmSu;XT}DUG}glE!-ik7iUq#U9Mty8*}&} zS8?3r^YTYrC9uI}caJNB!{{*H43PRh^3%SQ$Z zWdXp zhAp00D=A`2G_f@}cT&mB>+JygYA{e1X{^J&%ey(d*75KTdM=@pUA9Bm zxC3X*^6yvpHFtyL8sY9Y%+cTCef%C7{DJfsX(RswZ4N4S literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/TemplateNotification$TemplateNotificationBuilder.class b/target/classes/dev/lions/unionflow/server/entity/TemplateNotification$TemplateNotificationBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..0f437f56f2f4079370c25be2ad259de78b21eeaa GIT binary patch literal 3891 zcmcgu>rxy=6#ja-3~YvQi$p{ti5py&%c>+QSwI7kfG(gcf=Rp$yA2EsGg~t~Yj_C% zTmJP0w8}^-R`~!vlVy2&dSov%VYgC6e)LTD>C<0-efnJb?|+~D1z;XrLxN$m#4B0L zbZjr{+w|MBoFB3tcPre@a$A_z zoull1ZgZEYlxRtZ=O1!{O%@%u>}~PKg220j!HPJx7_L+d*E9&ryJvc3$Cj`J)>|}e z!+%`x%VozEQqn1D`n38|iF-xYEXxxz3=)I01AD28VM(1nMc)^umCc);AST@fbKf?E z@6z;Q!}MIfxq)j9n_D2j{`#Fgc}(_@h|Za|DHa&o(&O7?&?Pcx5^Z=*#}Ka4(ekFv z*Zt#N?rzD0OUpS$!`coF5enPIfl0^SYJpnZHJ^9)ME)Qhz2NbS*s2>0*L8HDQ}*7{ z(XDznbtF~qZ5@|XZ(N72dJ{T&RBuYhW!1~-=tU<(Po3Qv_QIo7FZ)6c!thh$Mlawt z7eY;1_Lh!5^vmA+IsfJ4(AZ|ehs@|&H^RA4cMTTEu+&AjbFJDq+D?`7hAdKSCno$l6ui+Dh z%k_t1=n{^~uapY^KXZ_f!>pUcDn8TjX~Yas`y zvTW%19A7Y8JvNT`l4DcB*fGTc^*mwNf)dXkwO}Ecmv7Ko-qGD-4o9V?;P`HlFPk#8 z4#zp)w7di|=%V8YPq;>Tjf(@PSg0e?to*SRNF98cgnrPnr!o+J{o zqR~jBk^FbjKOt*dL`GFEBBOd2kx}J~$fyQJWK<0!GOCLa8C3&fBx5~+!3LgG9~$&^ z(xcZ2>DHd;Rgz{&lhMB;bBf{L0=Ni?Xjg~>xMNzJ34w`BiRNXW^P@98}wG66Ug(9IjXu4%k zWXqt++v(BV9CW&6P!%o=@8DeuXB8svMI3)K^jBT_>rAHo z32p~Fv^~dZ^)si{3Avx*;DfW+kdi@?Fnowv!fgz)AIWKCY|LhU$4s6+w||EA6nFA} zBKLrXqttFtOHmu6mZp}WHc4%I;uIgtXlj2BvDuJV2X4tXVhu4Rk;Im0eHYk2=+khI z#>==LLvx3sP=@^!ZF?>ynxA=!m1kH7;sagfjHxqfn*^Icq0TZ}iWDBwrjPIqjRr`E T(L3;4BJv#!6fulaurl`_C~SW+ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/TemplateNotification.class b/target/classes/dev/lions/unionflow/server/entity/TemplateNotification.class new file mode 100644 index 0000000000000000000000000000000000000000..934b3c16ba1e83e3cba41c7b77b0cddecad7b929 GIT binary patch literal 8568 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/TransactionWave$TransactionWaveBuilder.class b/target/classes/dev/lions/unionflow/server/entity/TransactionWave$TransactionWaveBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..9ec80338fbc28de956f57c2e97d13940c30599e6 GIT binary patch literal 7211 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/TransactionWave.class b/target/classes/dev/lions/unionflow/server/entity/TransactionWave.class new file mode 100644 index 0000000000000000000000000000000000000000..84634f2f6c4ff72ef43899a5adfa765f310acbc4 GIT binary patch literal 17481 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/entity/TypeOrganisationEntity.class b/target/classes/dev/lions/unionflow/server/entity/TypeOrganisationEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..8228ae82659c01e1785329a1cc06f7d563a42f7b GIT binary patch literal 1781 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.class b/target/classes/dev/lions/unionflow/server/exception/JsonProcessingExceptionMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..be8002d7c58e36cbb6b888b8301761deec319b32 GIT binary patch literal 3011 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/AdresseRepository.class b/target/classes/dev/lions/unionflow/server/repository/AdresseRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..a4a76c39a244f1e1a6dbb28afc20cbdff0457ba2 GIT binary patch literal 2881 zcmb_dZFdtz6u#4>O=x3*wpLKYD5WGtIuu`!ZeK`gkwR!n15uBkCfR8_u-RQ_XQTO7 z`bCePqd&kO{UiCzK=Gv}8)|dHg88$v~RGg_o?wsKx9PTB*O}4V8f@0(XQ%fAX1p z<$Ls0)VZ{o;?$8%>N1-(PB}dcza2CJQyan-YKuUsP^=M1KX6Pw4Jnw(K?bq}&X07oPjF@ZjEJ_gRAfff%#)0 zJ$1xA-%_uWMPRWoB#9FOBe~F(;2`4!7Icg|?TS2Mw(yuvx-QOViFqyyPhnvyBbCN2 z#4JwJWADv|sd~zrb;c@w) z?RN+_Ze!pIn^7Hl1lHd^G=vJXR1=n7c}oN4yL*bF@SmMQZQQVSu~o{I@Ai%gEzPBf z{Sb=VYefQ+!KL2Gofje6KXIiGS=-CPJ}#}$&_%fqCav$#pz8Rt!5<18^Wqy%&#Lx8 z!0YlAzCOm8_gd%W87~h6E+6^0m7BaKJb}+n$+jI8>>}j7ii}<{sTg*v;t~hbe?u4{=#pjvG>E;kMM6gPMZf0Otg>urt?9sZv~iQ<>wcXH(qnEnk96OhCIso-)R z-otY)RDfAHj}mT7xQJSFf$n?&@(A@OP+nM>TS)&6sUK1|tG~nIpYS1pUjw}k=9K_* zG1QMR(Gv0jN!+qQK<`#KnLRX6&py4iz1AT!?&LEnOH>_-Ck yp%kO<^rJr*gekP6a42?EkO&L^wICWLWGft$dH6*xVa3luk`711vMN=Jb==D?i($j3QPlSU`oL z-VATVMDRrDF+WX&bDg|zHB?%jXLloMFU2U447JciA{h#+^7t!R^4H2S9EEd+&y}LX zP#&1fM)FMMQyPEOYUlegEDzLJ3!hnH#s8f5Q@rlHs$UXydbHWjMMlEi$)R$J-rU#P zc;WMdGqg#zTVcK6R4Hw0m6JR&xC2i!8fH8zrwi8QsXh!n=KI3%(c0TM^BSYw!iv4tek?9_-SKr?_LR>NGt&>I70? z>f!bITCIGBn?~jx&xy_mOe*X_6SM1No0`@q)mnDJZ^%4`P_VEE`vr!Iv+H9Jc-q1k z@&bD+jwhEpTN^T{+4NPQQ1R=wyKV=L8k@;%blss3rz=kq9QrUdXmqFDHC%ooi(P3( zHyy8WZs$@%;4PyQAce{;Fxx;piW zNtp&!88}NuIa9v^FBf+gXH^y>MFv*A^!q^+*j{MYl{BSV|J`=WD(qzor!n78kwlY; z?TqL^2%Ov<85Sg&kWi=Hup?IA)!lltTfEWOb%H9l&RbZ-1%cr$88nIBO^5;a(rHP) z4e82ADjSz9yn~+!jMmxsY)AFTZXper`9N)6vaqal90}x>zfHtF)yS^5mh84^GZp0RT4%*$*lu^~$ zYP)jT-XgWhuA$hCY7{tLvs~a!^`V7d;3|=}?3QG^uiMf4PIR4bVPty|1sKrLoJ^Lt znNo^;WWmM;FN=C0nE~&0WUwQ!n5+b|I}rzce<+LT%dE!c+KA0C#0_lbKwl0%ey4>d zs#<|n8Ft)AY00#x`@9I_OXHma%lg9{sQN@-zKZWP6^LiSN1MvV5_(OSSFy(Ztr7pMA9w~Y-0-Kt9~b_%L|UW&-M>~ zDV$X71YtQx8EHV)4LhRuljBzj1w!$sUeTzfBRS#Q=CpbK%XuLn(mTWBHir0p?f^086S+zUSr*T#E5Q?ZJn$oO4#H# z;PsgOw1(-iKV=eY1TV#YWa2#5u|8$|&}(m!qVi)5nSbM04lj<=n^lU_E#q38Z!P60 zx2aN!%jrJWzQYxO|2ERfTR9Rbb{JzKVKwxzHxD#8|(ptue> literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/CompteComptableRepository.class b/target/classes/dev/lions/unionflow/server/repository/CompteComptableRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..ed88b90a35f595ca508a085c7fe19df5d1f94a4c GIT binary patch literal 2821 zcmcIm?Q+{h6g?Zeag(}ja2nG>DdwZWacR`Fe8x$GEolWnW)9r%iytG(m!diR?Yk_=Oi?L*sgYz!=-C0Zh7*+@@>~{?sMzwXzigfj4ugSsJje_{M;5pvh20^ zBoa86Mha&cF4lz0AA|=4x2ZE>$ke>1?QGdn=zZs5QtgX?p;UXx@`?S%NY_wOxO>G%Q4KLQsjN|@ z{3hhGT|9?3@m2~~8E(GNi)mcL+YHmYLI$eNgV0gWSY*iNhp;;)sFJA|^F|6XhOTk7 zO=jWJiv(oI52$zu?sp@Go>r?_hIidqa)VW4NXLV;-+QO6BN@YFRO@EjCyQ<8PjZj# z3yTwEU^Vrjws(Xh=v?Yo`&@p+&NUGzvhvTb3j4Gy-eg!*-P{LrnA@nYRqDCb?OdG7 zT&YpkPPNjwg#|hj4wXp!9`RYH?4eO3{}d0ltOaZ0)|QHNeX#`bBmrChhEsorZdtj$yl;KJ}q)BtYw?rVw3nka}l&yyv={8T9J+Y;- zsh21&eJKJ?4TbK1R94CN9nmzQji%@GRuS*e@JZ0;4E^^=(MrRdt`oGgXl1mXDg1%S zU+HEHX}XS^htoJu_vt7A7jThw)a>yZMP*Fvv;nzK@nf{VRLERP{*Aig)aJye)JEIpwA!A z{nill+qh%UKf=dd^iTTHKYbqh;t=%Dv1rhXSm>fJ8T3UXDlk>ZEGLL}!})-`%LX~p zajL81$}v2xqc%gfkm+#o8@`B?Jc4?Geix{b6TZR>tv#1yJLm~ijPCb3K3T_o+H3SG F@EIXe~j{X3Dl*eaZNH#!7%7GX4+_Pw? zoAw*-3e^;fE8*C#)V4b28+OCd;ya@$O0C5t1{tosGMWZA4eOBa9=sA&oy0K1lC=3x zo}pfQEmUVb{?lpkdPPk+M7_%ATSmD}8OcFkNA3spCb+t>sd<&Tv!Kva8uAi`n(( zn^~i(Wu2y4d88Y|jWljziiGs3wDSmRRn|&chq)iZ^kd~CM*Y_ATTiIt+!efs+ezGF zc=%Qu)3}4X4C8gFTwM~bXX+CH8761D2snchziedkQ*pk@+gB_q8^c7%cUK)6mUIo@c)meDxX^1w z>1r}(sb|n7C_W^Lb}XBPZqD@m!qa$!MRKM|84<&dYDZa)#nF6jw|&Yl$hKAK7-s&s zlY0t}8FKMG9T~WqZlezGvoHP9nHw{L&+%mvUtHW04M%c8C35*2>V>=KT;C0@4ffVdD5;W1UArMF(v|c$ty`8&X7L|dh6PIMwV|{@UeZE2O1gqqZTkLM z^L58DWi<#bS8Yesa(JJ*d4hgJ^l+zWr000eK!lVN;> zNjs4ZlBDY4!xs9z)9Ckmq0e+d{|K{`Il~<0Tj&o?qkr6izHm18M_tgDqTD|@qf9+g zW_mvJ=}@H1%fa99**W^$p|XJPKeZvsY=zERCA?+QW(`v``n3pkn{MegjgzxiE@HOY zi`h;LeWHavfhQ5Orx!6>?~hrDn3ajy9-XyMcqL+1A!eo6*>PqkXK!G!RkO{YW=|u3 nPR?hxLS`*R&#Bsv0e$%tD5Uj@G0iF{nZGn)IZ{~GuoqejNk literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.class b/target/classes/dev/lions/unionflow/server/repository/ConfigurationWaveRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..cbd1710ef710528502b3455ef4e7c4c898c939bc GIT binary patch literal 2061 zcmbtVYflqF6g@+M@+c3hqKJ=GK??{YK9NOCDHur_&{i?V7_)2#7+7}e>}-_}{wMvS z(L{fMKgxJ_TWOIZwSMT%>(0qN=iIsd@$>6<0QWH)M+-x`AWGbkp6m0#rD5Ii-f~~4 zl2BZUqUTHPsa>9-6S)y6OH<5qt0Z!JXK}PL^lw=ui#wLP!B^I{gstOfXSg9f{wA>0 zcHr|(xh9lrX~8|U!;6+{*_(ntE{|0-hW1(MN?Vvn^-UQZiguHe2Sm13xvA2|BAjtm2-L-cKt#H7DQ`^XH^J>)pQn+Z!LRfp&yaIYH- zZQi6SR9Lnn>bpesPiJ9qSD#kJnMj$C(xO|E%5z<@L&d-_d_=}s>1(RPmkoq9PilhU zvZ&3gKE1FscmmfkMMdaPV#IWxv6Wv}qB;w0T9<+g1pZE-3ep^8fSg4-U=-)WvM79D9vp?TXYih_xY)Hq`F zYoq^^bdA#e W6;)!m8)-LP(fS@{XdOCZ0>1#rFf!Nx literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/CotisationRepository.class b/target/classes/dev/lions/unionflow/server/repository/CotisationRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..0a52216d0765164a62413ef82a127e0c19cc3fd1 GIT binary patch literal 15637 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 C8w0ff literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/DocumentRepository.class b/target/classes/dev/lions/unionflow/server/repository/DocumentRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..8f13a00b2161e7adc10ed276378932f645fecb67 GIT binary patch literal 2320 zcmb_dT~pge6g`U_Oo-D^NJ-Krjrv6lY1o98G{Hb(a5~A13oRzhWcsv73*JPQ)YXdC zGyPHROFQXI`vdx;I=xy6Y!cf*rw`U@<-14soO|xo-~asa7l0MiN*G~y;K>8-t1yUp z640{ehd=UI>I12{mQffh6Y37Hhi=lAf!R8GR6>zq;-xqc+!w(<-`ssET~op+!-5L= z4~fvPl9;#DuGE1rl83s@BM}IiIs|<&Y5J@n)_3b;^431bYG z8Y++*Nt@ud#jYOLOL^sit;P$BwEe}hTBv+;@e|5DwhW8k*D$iMQ9^G?l z8+AvxM(xqo=!6twxKhSdTq6ln8Voym7Pd-FqgAk9{XR>+r*OpQv{m~qk-Ag8fDdq^ zgzF4<&UCPh4{?)WYES8y>=Y-yIh2oKx-vl45k*WUdL*|sV7QoaT;TAqy7UV@bA4tQZnUz5`WK0zl>eWaxZ6T`adHD@teP#cuy}tAw;7!DP_`e0JKhap z7|*oX?nLCpjmj{aiAZr7B<+}cd8706*kKPGGkZhDhK&C6{|b0!k?%3An4T2-E8=W! z)t_uRYcCv+M$o#JB5hQs{-n8{`uB@6z680Z=o2TZ#WDBgJd5dD7=|Wzl6<*OYrbDg zdzI$Xbc^;qbF zoskN|wXKBaTwCs_SkWl21wm+RrAVoHO6ir*Mvy^`)R9)PSwCRSC z^s4xXCSZYn=jbChN-KRl=z5-ZoK{Bbxw+pl{wv*#pqvK*Q@BX?VqUkH(^-6uNhjghBpE)&Cr9X? zzKQWd&c`1!_pBP$Pt-;Net{F3ALjZg%7DEXoJ} zlYY^o=hz?MkMel;CCR2qAWi*1W@otb+SwxY=;pDydvwZ6 zGJzq66=A7Ay1M7PB7tFst2trv9k)tw&%8(&CURCuHx6`Lc=K>^K%NMPA(MOK1T}`66=9a!IM_Jf zE;G!|<^1GbDGYUge>I+jx2D;HDsMfhUvL3{Kf=jl8SKe7lYxNm*d7c zNru~2Uhop}Ki47QF0kMCXxe|VW*P39&FDH;jbSWeZV9#w~*Z|C>w`LnHog>oGIDOVrEf^WUZu&8BbGc z5+7oj)N4@uL`91t!kk7zrydDY0WG`sv2F@S_fF(?c^IEC>{*eu|0O<#@hGO}J{7pn z>w;J5)OxNTGOX6Zc|UF$M(f)>4_87D+!oX=2|FyP6jiSJ-7!nVYJp^4S&Yg)-swPTriOPG># zx>mN^B-el5PZ%E7d}KQL72R-nugTGIAh`v#&v7Yj{g{e>FRWg)nRh7i^B&ClPwFd5 z$_PX1+|Vv47{hRVFA2R!-J?W>@UD8L-W)nSM3%gU$nZ;~F$QlDt>{=APAGnmbkX3w2Kv2M z(cixeeYOkw2biPC8RoIjK)>&!=bN!FwxBOXV;?8_K$pchmN7}A$4h$-D`5^FwR!or z>E+|@UT%>Upi!Kw6x$lc=zARN z?I3(?&{vGUGsJv7hO8gMBVW=$YGGnCPBHk3Yw;aq_k-^vP;n~s!SE%v8hG12UWBoy emoawQg|VEkvpBw{*kjlUPsuLwH20#}1O5YHO?VUl literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/EvenementRepository.class b/target/classes/dev/lions/unionflow/server/repository/EvenementRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..8e8028b2d7b241ecb42f68e05caf019057aad3ca GIT binary patch literal 16094 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/LigneEcritureRepository.class b/target/classes/dev/lions/unionflow/server/repository/LigneEcritureRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..acffeed0826d9cbe77846035ff49eeb6f71e60a6 GIT binary patch literal 1729 zcmbtU+foxj5IvIw0wE%Vn+R&WfLt?x`V=n3&628=U?Qoad|EaG3~YAe>@1qU(ibhQ z(hu;XEPFQ_LP&v#4?8{6)pNG{bf5Y4`}13a95~P zp|}zi$CcVqRi2ZFw%9b4)SeRgGk+X0hMr^N)Zmt3AM)+}V`1tz+8Gw4!{2*`I`Le7 zB=?1~4K28%%DiIOhIu6TPR%yZ7}^)5E%h=(bYil|5L&;dV~i;B^XjU$26>>p`?EfoiTkRU51t1e-)Qy7&wr2sj2q0>Q;$id?FVV?P+Q8-QBH? z?BqGec107DhD89K1tQMyNn_Z%)}_U(k`}g>`a(Cd%h?VLFvMjky_8;=Pk>=4i8~l3 z0{#3Mis7Gw9u>5b_F?v2E#Y%*qZ+Q;vp1el)od3=a4(KghS`6*n8bZNVCXxL%GG({ zdX}zJ#xOW>36&8^mGqpWIpCdKevNC@`f&Q~d2}VyIq7O@&F)`=Zq|x%hDi}BB@^}} zy`A6K%%|7((zaI?$_Yj)y;@lJDV#`R5>wPHixQ)WZXzH|Hc?0P%NwdyyzZ1MS_G>< z`&*?BWEgU0v%58gI#i}aimcO?-qDFJ)1bt|Wq1ZQId?FHL$IGL+Yep|rtCQ$kggbOkpZ+JDr1 zwNkO98H5&0ry@#OJS0^`>1(66Ge(x4BU(G@#K|(UZBt*7_)MD!lC-u5&J=FZK3NMu zH*V93Mi#vkl?r0}0+3e}AE9&qRH{Gr1JS+cbm0pIzu_*!ry#C@ITBzF*5X4=N|p5PhZveW#K8?B(d)M4wMqH|Ees*5{?Q1|AYU7t}l- Oa6^?mUHiW^X6$AN%>5_ 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 K`R-SG8Tc14$cvr; literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/OrganisationRepository.class b/target/classes/dev/lions/unionflow/server/repository/OrganisationRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..aecd7726496c86e2e3b47301cc72d12df3d465f4 GIT binary patch literal 12986 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 Xt;gTQrXU-95SAK5^7sOqv#Ngua`a_j literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/PaiementRepository.class b/target/classes/dev/lions/unionflow/server/repository/PaiementRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..9557d7a9bd00a30c39e24b2fb0112645de057caa GIT binary patch literal 5433 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 XiC47t>U5Xvcl7gGPM%_W{bcw*_P;8Z literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/PieceJointeRepository.class b/target/classes/dev/lions/unionflow/server/repository/PieceJointeRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..1d8ae59ca336d22962852cd22e0053e042096234 GIT binary patch literal 2523 zcmbuATT>G;6vs~}Qm7TNBHp!LxT?hat)P~of>Q-sg>ifeyAh(>P0eOezLmb{=!|{< zKa}H1cA<+-x2O-@Y|i}i+jGn5kDp(^1Hd%QXQ7Kg$>rvwLV`H~o69Iwg>VJe*BC!9J@ZCh*GaK_RfswJQ4LH=oqtBnu&z8m$ zhJ~ia95N4&_tnKJ1|2GNb9qOHrqUedz7~309ZGjfy>Ng)R=CCK;@wL*AaF1bhu|=# z&iGDXDgLglPF*YEua;g$?j{O`5$v>DPeZOYO1t4G9M8fr0vG=3U>;7uNt;*|=o$|~ zPj4oSz|dHmM0N>`WFV1bn{_mq7-u$Sr4u$VJEl0NQ}bLEfyTnrJBt3L5F-T6xUnmf zaYc#?wb}c%;>?SpRIcL1a=l_wHbxxc;QlKnoGHB6R|JoxrfOWl06* zTRMY_3tD#9co6${8?(ydeJU!qsWKpYbFnjcgZVBm3)dzM=gNrNGNt17B!q@|FpBh< zVt&9Jlf7lOZmBZ_CiHf5DtX^;Vnff>WvCqfK$wwsBr$SY!*S9K9RlzEyu3TUyDiw? z_cwvVwGbyzgD;6d;HWM8zSOpjaMDhqS8tfojN(1ys;NZ4sUz|Iy{6@6(-V%B)*ad8 zZV67|q{`rL7yb|R;fli@pMAKaxDs4gDUJs%06ANJx7y9V~5 zs$jMIEo2V$UAP~dC=7OggUpM}x%wv<`T|D?e6YF}^QdJGMf#YDC!uJa#EAlDw&-Vn zrw?zVkN!y?ZbLs8)6b{TFLa{QHgp=(FQ(Bib)w&DL%$N!ucpzjb)r|=&~L=_n`!i0 zo#>C-(4*EPFqKB1?nGa1L%$pQelLw)?nGy8=oL%Pz^v`y`*FuVfQPs@_hSy=7swP+ A5&!@I literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/RolePermissionRepository.class b/target/classes/dev/lions/unionflow/server/repository/RolePermissionRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..94f522e49c84bfaa29652b676ee24a214c78cdf7 GIT binary patch literal 2032 zcmbtVT~pIQ6g^7|MW~_$@dMGKf(4aT{H6$kjw3T2Knk5fpF+F9YMRu|2E|{cFFHD- zGC(erC$3U z-;k?9IhGdORaIWI9ILz`_(C|wXbf!=(vf8G6^GQ`yOWW^1;>FqA6b|06UmWPr#C~ZDM@zliko;u69VZ!1%S!fPcIsw!dhSWq zDr>n$ORap;kl|Dsr*VcvHIx`iQ7(aRMXjW>o_`rqXt0u49n{C?zEE5FPV}Kag>wwo z4|On&^SHp!y(X2X7lr5BdRs(tA=gCQ7D-HwHwYg%!H_l_XSR%S#;l9`>RynZCZo0m z`Lm-Y=pT(4V(3?t$!IfojApqshA~3!*z_X0);kzS8MYY|!zUBciz{-r(vDGvOSO89 zHhme16Gs`aQ?XXlP9?@^fQ+U8|W1oWmW92Y(6kT7H#ZzgzNx!)^5z8a2hv@P2QB9JoGWuz$Ko3}+U7 zD*mb{Nl#L*n|2&mTc*!Ys~e@nURz3AoM;MFQ_>T>?9%+5=F_#BEz3b@vFz4FC657W z2nqVN(zl?MM(VEgJVGl@BQv`rpV9G=CM`(Qvn|-n;3&=0VE~Syi&j*CI6+aFAhtUI zc}DRqwC)+n^d!F_v68r2{Dj^wILq)Mh>J1%0?ghpKEkAv$OcJL@#0b){qk<~!5!#V z_M#6qLBATIU)vA;`rpub6LcP--@r}M$H=-uiX>?48_A5dMvAN?uZ61IX83{oyM-AK iz_a18x-fkRQzt0$YJj9kU_6jCA6YO#U-!_kN#HlqupOuX literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/RoleRepository.class b/target/classes/dev/lions/unionflow/server/repository/RoleRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..7a24851d8bd89bd612e51acf0328dae77a8b447d GIT binary patch literal 3044 zcmb_eZC4vb7`;PM7HCRqKm-ejN-3n4m0Iy-p(VUj(3H>wuvqOm%VvOSH@oTVESi6% z{h+qz=nwEmd3<)0EDIzwQ9mTJvuEac?!C|3{`L3kKLIS_sfI3wyCr_C+ro7`-FIj? zu-zYZkIQ2&b;&EPCzLCzdcn1MA=+w4GF*FR9-F#tI){3G{~5QGhHi#C!qs2+rhM*u z`jOb@(lHg+U0K#Erej)1T;Hm#C)OCc7lb3!J%+^e%q~N6#VzqZByc$e4LuB3bHd@9 zewoZ|oBK9rNatM3w0BJ@f_?pEQXL78VLtb7I2jD71L2ecrpwih62sJVE~MxyVe30P z8>_}l>_xt!NG{W+d%Z<*NHl#(z_!}Dv~Uqb?kJ&Zk{kDo9=y#kEJ_oL6ZhvPvYV?D zrlrIIZI$#%C5CrW7{&<67QkoNts@8{Q&dtohsKi{@K~^jOS_SJkIb z?&x9)<9Lr@;6O-E6}acysv#Z2&~yt)y96=07Lz;FpP?_{xLgeqgkib~(-!s$Z656? zFjJ(t6(UK;^sP%y@Ehsm6|0`&PwZenMZJs1Om?hVa zIOe9mF1R$MbtGQ+ycZwij)pl>G8WQComy_W>^geUm1=TTNGfp3_Y^+CUCQ{ZXK|-Q zsl||qC(?zNjXuoda}A#n;)@-o@CCkP=-RI`JZ%!X_FbMz^CswwUM%3AhD8Sbg0>N> z$;s`y*eTq{5`$LUD{iknTw|DPv#KKtBnZQDP`)b+$DQ;1zv_A|g$J0SY_=&Q83t=b zmY0X7BRn&Rd(aO*r~_=?59`UCd*h*wlKUHm8*U88Y^z4d&>ue+;Jp&i$l7)`=$Bp( z^3>tuy))`2K|mQ+raKN_yxG+Gm;bw!4g_N8X_P*(DZ0M2_yZ9P$br)ndp4M443$`) zsts);UP^5a?2Z7A6nvUzWxgvsL8ChBIIap)iYE0e!Fg^X`1k9!)P(2#DgXi2C>*;4n4+8N%b0!+~F5`7YBw9@AW z{rA#Nr_BK_oz8dBelU|B zO#XqyUgB2qR}8(vy9_^v=T2;ng*Jz3=j%4&TcIGpcqV--efuT!-|=b9%S#xaKvH@k0KU@vn9^mqtkSdHk?6@(+K2Jj(z8 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/TemplateNotificationRepository.class b/target/classes/dev/lions/unionflow/server/repository/TemplateNotificationRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..576a631fbc083b5f1c327567961f405bbfa6ad84 GIT binary patch literal 2088 zcmb_dT~8B16g|@-Rj7(p5d^UcXsOZ>{6ZEn1tgL*pe>LX6XUWSU|`v;v$H5~{v>_T zXre#BA7#9|t!;r;YVe^uJJY)-=i~0(pTEBS0Pq-7F+>>Viei`B(sexUJG89Z?mO-Y zwJQ`?qU3r~yK0Xwi=C2fYO&~Qxh^eJQ)#~XcMNR|U0dd^$!*iw;7e;;!qPFcGmJ}@ zzx7SE?R$Jvt_kH33U}2GFPV;MZ3_Opyw=bd+NY!=^)y2?HL}XkHtQBe2cqbVBZiX< zXLHgKi~bJ5Et_k$U`XU#%d}TbC4+tCuuX4Dk6}LdKQtN){p->xHgaNSZ=uL=Bb5s+ z@U^u0%F4o=F;aK1R0=#ZZNfQKkQ-!!uZZ}YmL8h=n*`x#sT)UROdBV0k)cNxlaG^6 z#**2^xuj`PhP2hnCs!FR#nFRavN@2)uv%dhCRxx*Ivd8TvXHuPNn5MQzVL3vljQ-PuaSY%p!})coJe?PwZ|gr+GIXb!SR9e0q^r*UFb{@~K;z6_&`=Di21=XY zt+iNste|c%T(*c=0hqs5_dPPN*X2!sh-KQqn^rDT{VO6K^C z=6M802&^zmdFC4SQ`b>W*)g?EgHy$_%Sy#Cjr=K9cKxxAMqp%(nT&4`2t^kT2!s<> zfsR86u1FApQ3BV}%%nSR8R7Pg;}Rt>k+$+i>ADS{FFHa;_3exUW~vyMP?RS z;@_&K3IuLP(_Y{%XC)<@O(kOs4HKD)V9O{W#F)-bFnHHScaQp7wARgWYI4TgauVB! zjl$amBCH^<%bUyNfWAv`6K0S`5d(pPU~ex|I=7j55E5;PZwRd#Y5O%)8qW?iys2xKIi+@3Z8y@m*GY% zY4E!f-@m+R%t6`SBB1bQpL=s17Ax=`zJwAl;)TFX!LvqP$6?fItnX2HNMO5O2(|Kx z?H8{c5{Hf(q>S&bXJtwYoyvHB5 z)%Kt{+9hDN$DG=cqnpe3GDoCokn8hwr^|l>Ou#` z;(9_5rb(cSSVz>vb|$NesjLJCUij*x-IS&#u~2l&we$1=6J5=Gi}33`(vr9l2>je| zGx$wctnX{P4mKE#1ZFfBn~*X+U=G6+#ZA)^Oko>IU76dVbT!;62y`0w~I1QPy_c#~6b z6`v*F0N#RY_=b%E+(4@d&vw!S*+%;ze4kpJm->g z-#);^l^}ag;x!2$*3ds{MgO=L`kfBwcOZ(M6IkemzSs+WxdZyW0R28J)qGc4egC8h zeWjh5SO@ga17^N}FKg&uwW5FB3tjDiz6R?aGaIm3L;t1~J>CmF(*ZpJNe^9ZW2P8r zW_EGn!Em6NT=)fS`^stJXC4I$kVB*&(6b}l>oICDhpKr3GdK#B`ih#ZDQXs8^Y%Tg z)*{>SBKtAW_uS${#^3t|?CE0n{f@gAarYBkeTr4UU=oVB;~BO{MQ?Y%M&^RuLvZM+ Q{!vgRIrQ7FsUv{@0MO3Z-2eap literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/TypeOrganisationRepository.class b/target/classes/dev/lions/unionflow/server/repository/TypeOrganisationRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..5ca086d3f84e85b7833fb53f7082197a2f29e24b GIT binary patch literal 2163 zcmbtV+fEx-6kW%5Vgr+#Ny9BA1LR^8$J0xCAqEoL8PrN9CN?Fi)R)8f*g45C}}t;$hY9z$*zR#PkK zbxQddX-`Yns&_+zxl4W%P1*6d?kRF>UC?cP9N@9sl{QNgV7Oyo2D1#;V&iS@@s3ao zD}?bqSDIU0p#m9b;W@&JX&)GsGMH!hzG>I&>aMBHtww#@{7^cv2i?B;&TiP|4?8<{ zBd3cFk&`z!2K#5xb_#c~n8v3JOXub`a1SQKl%oXK;&o4`BL@2n(oT!w%)&z0ttbpt zwM^m+5l!Jf9;WetVeu?LCq4!qA;(|@Lho?J4}=aW+!_z3zW+`*IugTaqEBL%LKLe8 z9-}~N?g-rwLC@7BCBY9E7IMRC3Zhdhz9i2gK1;)5n2A~=65NQ1fzR;;DKAv)mQ;Zz zL5u_(l4o{^N=gt4nc>RMG4zEjwDagSu)7&lA+> z*^w%1WI@HeI(2xd)ETTZbU&&+bdiTZsWG(b3A5gKX*bNuk7l)AuG!71O`YSbmJ;Q5 zTRLy4Qw!!A{qhA<7hSn8TsIQ!rh(^JqozKVOIRUIrl#Lhj@Xjn9K82otDem2Vkj2| zHEy^JO~cVae<{uw<{CYk?FV8{29oA;+4FoIeT3*=D;_I_rj<}#B?DnOKCOS%R=L}C zr4yMp9iQ5{ge4kqN&5VOo}^V=rDvLUUm-y&AibK+{DsLsXqmt@dQQ?C$dchSuG7kh z&2R%ZX~%Gj?hu^(CTiK+l=E5a#IE@<4eMD9e40FD&cF27b1*Lz9CHX zEyda~!e>#I*Ayo#b~-{>8X)XrBL)-~K2I<}e5J5LtA_+=nE*Yaa+e}7F%i=PFiE^1 R>jcUJDk`YbK4kYL@IRDBalHTl literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/repository/WebhookWaveRepository.class b/target/classes/dev/lions/unionflow/server/repository/WebhookWaveRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..48986ab4eef61abcd8853a3aaa7eb5359fd23084 GIT binary patch literal 3458 zcmb_eZC4vb7`;O%;U#T>QnYAwTeS_eWlL+VH9#9eD%hCNBtSWS@N70yn3C+q-Ps`L z_{~40e$b=m*dO4J^7zc|k{2K$#C{;NvpjR>KKHrL?ELfZUvB{{VJC_KhD4dy^@^}9 zSNAL$URUe`-Q`Z5JG#SbwkxFV9O~P=v}fD<+eV$|8*if+WEg*A)D692Si5?z^oE-< ziXnyv!q$KA3}@eS^*vGIj%7%$+fG%l8J1!0aeX5gn`jI}i^39enIRIN-C`I_*=0V8 z2*zTFVwmA_Mp!)SRSB+Wlq#HIGGm)YWy^4cnujk3<(_aEmNNe%wFJYJ*TO2dk47^dSLgd7l*$#{#%zUmAUD#w*WWyy4%Dz;B7D%~<(G$HtmT!mZnJU1(op?^oQjTNxrV8{q|#HF-Rx?ijBq7+u5e~h zKiS-4cqrQ$&4-@Ta{1MCUR&AG%GBR^ZgRf?wAFMWrCg%Na32fg#|jaooZD&~lAbt* zt+E{tbl5Niull7O#v_KiwQ%0c_6(y(;R-PpAQmLGBr$mr@78QaP3YY6s;;g|rU#3{ zqoA$d!xVmccWC_-h$_F}`iYTog!CozJ=d)~ble7O7@qX41&JIw_OM3hT>d;d6lsZS zT%T?iaesd2p#5mP!Z0se8TTOYpjyE2>>To*Dhk6zrE=OzN=e;7kf{@m)K5-p4EN9F zn`9w1f^U$C;swLwE}z$K4HUz-SR&)3v)?C+#dNlqro-RQ&)l;;?&i5lQhm?eVQet$ zo=!2DUDe{2T`f7hsN$ulb5~n0XxYt7hK#SB_}`-TXtq;Fgqh`IC@6A6>C{?olZcHL zY|kcXdmaXKV zD;;^3SnV5*H0V5&+^IRj<+^Fp^N&(b)@l`D`k@8WuJLjLH|P=(p}z}sMI51#?p^dd zLNlF4M&pIKKQQ_$Jq#d5zeE1ZDO{xISP+0qxJ)xT#W6uqlYZG^e(~d)nAd&G=^#GDq?OQolJrHyCk^yZkE4Hf7W(ZD=(iCk<_xo# zYoOmbj()cdef}i(g%0QsLhcW{p?`iBdZGjR7a{s%eA(ds`S;Z+dR>>j3>XtOu@XQ~nYe7kEXk+rd>Dn~t1r zI5K+zy%;)}&~r!VFHb?Ap*WyK98F?XCiD{R#A&C}DLX|_^xeD}x_=AXG*`Xy3iuDQ C+o(AJ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/AdhesionResource.class b/target/classes/dev/lions/unionflow/server/resource/AdhesionResource.class new file mode 100644 index 0000000000000000000000000000000000000000..b6bd25c812b7c90c988416196df89d0a47b3e140 GIT binary patch literal 21078 zcmd5^33ycH)jr=%!VJlzMYMR*(GyQ@j~JT1P17T~W+XFuREyMiZ_y0A z?63*~R4Qn6pSn$r#MMM^qlL ztY2$RY>OGO&2g^rHa$*qw!T0+y+wAPI#qf~3yPE02j)iBwXBD3X5 zqn0ySs}(w$V2!;o&&VgV^r>4_J*`G!30_2`S$?2ZaCtNyOT^MEu&(Nwje<%WlHFQ4 zjiIUlRSG&GdzGuzXgaA6glH^{6EwV6OPi|~G@}}$*urXvtA??xCRa$q37v+hjD}(D zEwO~wmg(QD>0K(MC8Ziw;~P~y#?Myau;p@wM0H^hX-~24fe17W7@7WlRUZ&Exg}<#H9ee8X40Awj^iJIabzC@ z60@u5){LkgOL03vr*$0J7tN%O?A5h!Tn)#$X*i|!#u7*NY2j|IP%ll#Hd6>tuH1;! zuWz=+U|`y1+thdl+G>0k`a^didmx4$aJXRJJVA4%K(H9EAi}!2@O}8y!s<3H8a6V~ z=#jk;Q6QR3q_qSLbosGOt6%GmsgVY=HKsFM7IY1yFqhG4Dix1KrM@D41`B_f5#6Ho ztAdst&oG^`F-92~>rchCeoR<)&M+!s_6(x#bTZNtOQ;Et`8Rg8qpy;MEU}Zx=AAK&n3Va>%RCHNK`KQ9v>A3I7vS_0qSe$beOEKfrH3T|?>uWr zORw2jPBGdVpgy>6+e7A|{t(5fUyzdADyZJuvn9f!e&ZCIIag(3@ot0(<&>mL0+bR| zTV%}aW+BoE=3BOzdl7U}bS z8_f2uHy1=*TlD01_SMT6H9P6D0KFF!Qfy(JV7!bGqW97J1&wQN>uTy~t8clev#Dca zQ^!S39Ubi*USrLpk5kMpAjuegAVgQtl}yXBTS1*XVKdk%(zK}bvSklx?g-NVz&86e z!vNQm)75lMfIftfz_q8Q9Z^j>=n#FFJ|d_bF-EIdpKX9gv`x>FV+DPbJ|3Wt2^!&| zx>Zeu=o54u;|(OR$t(YYChO_DU>$1$(sVG0#Nj7H^eKk+F!-TZ8fq8KaDaA53cQUr zdv@#%JSsr7<2(a8>&CnBElF?u1|Mq0aBH*rS$@3UZUsge0sTj}VhvX3Q~Vx_UANC! z$!O2wF#vGgVe!ZIBg5t8s&RF+lg2E1yA&ASsqdL(W zynr5VZWMH?RYgW^?4pnCOT)w9%PfBbBZVj@))TvgW2{QVsAd=#PV33cHUvJ>;V*=+ z-B*cZ!t;7!b2sa=C#ELS>{{cqoY^4a*R@MBFPUn`dmKvhbAC0n-F;zZ`??(0Vjo zLs%-6Hw1mZ*);hxz(0boz8{w#|c?RhvNIsA0c$OxO=#>yXL$3;I$t$SiHly!jQ|TH7 zX3pEo++7wd1QN34W!lplUD_wb`nlz!q_>eiI`zVfgYv7ew~(n(1wrP8RoGx1cmNy%;OR^ z@)i?L;C}_Ru7?P7_d~p7b3{iF=K9RVF`mL&+BY|I)ATm|IY56xzUTM=9-_a{Uy($S z0U3*tLx4&B*lz_Rs%n6q#f*~=SU0$G}qp7~DsWB)j z1T_o}qOfPf!H}``8ZN5gV8sYHSbOj&-WSF?Yh9+>{a9SdM{R+NIOoAS9?N8oXD%KSXJM6QAg9+8=5-1g7ll9 zm;^4!-AQ={&Wf-qfNtpwk)46Jw9Ol;vs@%+A_kig2xw#`)$OCeUQ1LiV*gu|n4Rfd zzkwNXa;ug`c8Qsw&!i|22TZau73;D!FQ8 zFmUMLj2;w7UM?;q9nVhFX#roP#t=K3q}*&pbTT6c8`|4e zw=_3&1;qw1#t;Ut#90v_`W`4U7oy%}`C}Y&_fm9Tz~LAi3h-7ODRGC=?X;kWn%xBs zaU(NI<`R%awt5HR>+SE^c&@eI$QStnM=cRn(|jNTlJ(%;>OB|t)h(`n4kLk)(lTk_ z+Xx@|40FEQ4&AjS`^~%pFPEc8K1)DW!LEfNE35_@atT3!cq^PICImUV%SC)8dP3q{ zfnE3cIdo}FyxFoG z=Z(BS9t?M$M2qY=7j-j>qt0#xH_@6zMvD)sWEs+{{ahet6BNifPc48iSzXf^UGEDC zL-Yp4<-RSeJw_qrU*Q8wE=$VxjuwOBD(q9dn$ye~!*>kEjzNJO^Rz+RR^s#=N=Nqj zWS`P4(d1Yb41OP_GGZo9B0J5`W zH%{c4gjGH4JCB%}_g)5YZB7BX1an1U4^hIK_)a9;%0YYLxRT>Zo$_IOP&@*u9zQRprw`+2R*&EkgwSS#W95V5{{g6CFOuMC>{uk2P-MS3DSvv$avFI@Ou$& zE-$jv`y>HTT^JCWFV`mM%pu{z-^veQ~)+s);59b+3c5mU{IK(Iunpv%8 z`TEp7$7bl=b2~98{)Plm9^vvZ2D_|(u*(|B!u(fA{88YDVT|v5f-h_Y#b1Jo0Pu$- zD(?z#$I|~e%JhQLp>fwZkMfr+_qfU2u-|f=d>5m<6Ir~dwi?%6fplB&xbq~7=W-B) z&71#f9Ats+y=S`X^<;M@s`2I0>{opDjbuH)T6Aoe4%2F{5$RHU(Xupw>xY=E6Jr7p zbJl2p3sPH*1#2hEmoZfPkWRiZqYYFq-GCcPNawFgCezqTsVQW;5$PM{${3|8pj0Aq za5FwQr$}zqBBtcbbA0FG^b^WhK{tB2-KuSNcovQTfwfawlF6-^RPpVXuOVN1$x}UF zz~fP_)kT9Ek9Wq>+Oko@l+i)u1PFTwD9f{>6+vZ!pq0no7~JL_ETfl6z+`SOMbgWb zZ$;B7I5Ns@S~%_Jwmb<;kloVDqku&IHBZalALQSRyCa>PZ=}K-3b)}5+(FEq;$U3q zgy(KII4x;D=5De>F}J%KL936oJ2(D7Kg^_=^wKc+E!3RA?<3?d2>8bGsH_W=;B;Bp z2q-ufnUQ03`OGMw;3#Bj@v7+8v^koFlUBYRrQpW~_@M%x0(j!jAU=uj#o~K~RTjeM zXuKIgBT+gEPc``D|AtqJgEaaP8vhU;6gm-qxf7z9G(nc&0v7+C!D<%4C%-M5{TNNk zZc;6qgv<&wg{Gn+ygG%E0pyuznDI1xPRBqe8*~cIFfhz4)Tus?N>*Y{B{bWhS}vbM zb?1>-Npn#*V$eLAzmgW9=BZIy$TbzNc^cO|-FaNZkBeyuhB!yTm`hO$CCg~JL1)kk z9><_FX{Gt)465fZYiX5RwUMhf*;QBDRh#UpYgl$re;Jw(UGOlCuWX)lkk(Dcze99( zDb3zb=S-S>fHt-9y9?_M(Zzz~ORa?J4$_tblz33SOXJJwVrUEliH+bXtUaoTXAP3y9U&OqH~bCQ~a#s10w|W5s7v1FnNL<6dzmb3=F)ho%3$wC`N{41Lxd{(kx#=E7>MgIw5zwzH}ps69kb5$mt^06o~s zWsjI;b1b#VvPV(&7_05DskSdwe!14w8CSvMR}RxRi01ee{AA^>IerBz7@YBcRDQ07 zSWBd$*FwdFyVMnw#!r~G!GgA<)&N!0rF1H;`7NRMLUETt;X7$9?kKOL_d}_dQNccKi~CfSlX?$@I8*+hNi|Bd}{ojqs9f68q=(@ z7kz4cS*q-{JZgMBM~!c0tMM@X@ITOD2c%1oV+GxcZ?{8-cR&tzLDF|ahr6M}d!WNT z(BW?A@LtIMKA7`fnDBkL!?TYrhK~Ax*?u55g|!+ebB#Ie=rT>;qqhp`vSz5d{1N@w z(&d%-{0XN1Q#P0LNHOSV2L0UD=r35KzqB=4$|{tc{VOj2^-ye9a`tchy8o^7=%H}j zicxG6{++4&-(y~ExsO^Zf0R}J2iWT$@$YXA0psDHQuFW3IUof7mC9ESivVmEHYn+p zD^!W{Ln5R&)?6h`?}JTV$B!o7pbycT^bvf&mVSVn{XYak-vUwp zm>$5Tv4^1e{di`atpL>%J?h}>Q85`zfh{V9#T3aI%fVeHXGCc>-6W<$T^|K9T=p1A zB|E_$JpnPcFN;A~lLFUW?9kv6DiiD@vQU@hGQ@PiOq}e4pF=3(6bD85FqYR%EKnfi zB5uM4pqSwY;LXqrM*w>zh-P|72SBN2|6V0(%~G$^uM%}RyyJBrX2VEr9ivUmFU3`K z58AlYMH|1Wj^jQ&Gq%o>vFct2TYD{k1bIr7DKwtOiqnCz5(I-MOO%!1o_eiB zSqZ#eokW=#k^p5u*U>0YhM!59p~+}7G=ZPusKf-@BH-K(NXo=w%)a&V6YUV`OoS>nq%NcrVC3@v zsRdByLJ-GkG)pX^Y6yQJ1iTdTT?2ux#rJh$IkiB1=Zh5}#52hdD*>k!v|ZHG4zY@^ z5e@Wl(L}e4M!;z`;M5?YWX86894PIvpj2(aM!}C688(|hPCG2vn4t||!=Vje)9%2= z8{XiO$9WcBKu-BwqDfBTe9JeA3t0IV=0FDHqx>QV6iiOyV&~D1AO!Sqq~;!GDCC6A zr6E*n!y-0I?|7Y$oT45W0R)=KFfRvITr4&$f2}mgk zQqn<621qFlQrZR-Wq_jXAf>cKQZZ8645uhbNrub0q_hb{Rfv=dpvxE{y3#hfGOX!s z1<*-k&qE;G^c{(La|zz|?|Wj>hg zDh#s^=ECgieSq1$fZ1NaY|o%DyYyXwncw%xtr{+3e0WG)_7NLx*D~5T znvGd}zB)qK%_4Np#oSME6OUqgD1PyX*!@ zeuqJR9J%h7{_@jaRMt%>Js@scS|;DzYQC9JW^L}R;@gX2)2ei5?S!&i61=-|mn?Su zsjO}~)O&~~l=<=jRbr29;7j>%lr|TM9gfoGQkQh8bFr(pU8;=I#5aM^qjaKpiYAJ0 z(@gO+MZ_~SUpxzhK1U7WdHhEIyZG__bLim({Mi0`_>tg?_zCUH_}%kMxUBjLU4iE- z#jCVSyhb;Q@58OWL7x+^(}UtodIZlLrJav`2ju9u*8NVDwqINaWOMdnKP=@fXqLa- zfN!Np3XPK_Sw{0M7hHx^6yi0w;4<2UwekL-OlBA0f|GE;-EhH){E?f9-o0Xt`9R_uFtSaR4b@@8_IH#;16|dGz?&bcusc@p%)1X1+)~C?RMk6U9UCb~14n z;93R&?v}88c-WVyWK&6P=cZE5DRyouo5CN5#C|I`HF?Sb@z~N*S^4k;Y-}%<`9;7M z*~bZ`Hk!=}U-YB7N_@FWe8rRJko}kd{VD*8-m)^1uoxdcS?B8zQZYW)Atzy{Cfkrn zBJmDQQ*cR2DWQ2vDXqX~gHlE=p%n$xJn%5HgbnXATQpnB%Pwb)z>+`=dT#;@C8Ji;jZ zMgcHB?x0VC@d@X#02sgN1LKqbWia051LIRe0pqtUFg`sP7@x@o<8xKw`TSt~?tcWv zK9CS8^QlBxK$Gz~O*xHfm4yK2A^>wSovJLs|8;9A0J;bOT?T-j0f4RmK+gm~R|25* z04Vk#$|?Y~fp#d3bR|BoR+<3RH2~^r0QD>YwW$D5pZxy-)EB_}=6*MeHD9!W`Vs^6 zMOe^eZ; zwTHz|hJ-<;BPK+f2uHMeDVnd`KueSxX{mA(tyXS^(r?2db@W9>$43!dENuFfL28Z`+#v6KnR* z1vLAYY|SDGk6dz@gr+IqzaA2Q#|{Cb;Q}jQY}U70$WywNx3y(wWc*F0R%I0TZC-F zJOncCcN~`~6&%;hFc8>S>BSQB1jSHBBEaFqdbu)=gfd?KJyHIhDF05Df2Yd7VflBu o{CkT0J5&CxR%WA*iC9{aVoGf(|6f01%AA!GlsY8>UpL|Z0NbZL1poj5 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/AnalyticsResource.class b/target/classes/dev/lions/unionflow/server/resource/AnalyticsResource.class new file mode 100644 index 0000000000000000000000000000000000000000..9b86ab76248a47e0ec9e7e99528459b8c780969d GIT binary patch literal 12322 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 DhB~Ca literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/CotisationResource.class b/target/classes/dev/lions/unionflow/server/resource/CotisationResource.class new file mode 100644 index 0000000000000000000000000000000000000000..6ad70dbef235a5b060229239c8e0947181f51967 GIT binary patch literal 21779 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/ExportResource.class b/target/classes/dev/lions/unionflow/server/resource/ExportResource.class new file mode 100644 index 0000000000000000000000000000000000000000..e0e3fc3aadee7b664b97d86551792dcb112d2405 GIT binary patch literal 5786 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/MembreResource.class b/target/classes/dev/lions/unionflow/server/resource/MembreResource.class new file mode 100644 index 0000000000000000000000000000000000000000..a0520c8cffecd42d5fa519f83c588124d310d66e GIT binary patch literal 25949 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/NotificationResource$NotificationGroupeeRequest.class b/target/classes/dev/lions/unionflow/server/resource/NotificationResource$NotificationGroupeeRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..7230ea622124bcaf10b7bff988bdac0d867a6b9c GIT binary patch literal 765 zcmcIi$xa(V5Pj_x9J4uO!3ju290G^*H4@8(D2b(!BjK2Pdt8{d#@*11li%WmltVrs z-@_TDX5vT_j!3!G_PX9HRrRm?A3p&cW2cFVz+g;snQ3dhjE3DsX1_^KZca`*@;17Z zWZwpTp;Hz3!P%m?S1x{XHeL~((RCyrny3k^Pt{z>Oqq%NGMZ8v1eRws8#(HYJ;OMrgPEV^CPyT1XXNYNJn literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/NotificationResource.class b/target/classes/dev/lions/unionflow/server/resource/NotificationResource.class new file mode 100644 index 0000000000000000000000000000000000000000..6b5aaabae76ac141dd88d23ece8f4b2302aecd46 GIT binary patch literal 8117 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 WPoIf@;Xn8vX0pH%5uk+Q_q+l3vDNPY literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/OrganisationResource.class b/target/classes/dev/lions/unionflow/server/resource/OrganisationResource.class new file mode 100644 index 0000000000000000000000000000000000000000..fe7e9f8c64af7b0c46a03037bcd698f6afbde35d GIT binary patch literal 15398 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 VGbA6C=lEM&oB7-PU4D_8{|r{zBSZiI literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/PaiementResource$ErrorResponse.class b/target/classes/dev/lions/unionflow/server/resource/PaiementResource$ErrorResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..3fcd47fdf5ae7108cdfa0cef567acb560d1d3a67 GIT binary patch literal 591 zcmbtR%T5A85Ug1i7H|+Cet&!du#YtdmcZeFrd}XJWxR>vOC!WZPt- zlh8z1XCqSlje>*SnC%>78Rr-^VQ~QY%?BP^_bA*kvQXrCKB;_xjaSZnLy5WSXEqj4 bX2f;Lt+B=;BMVEYBy6l##R}K0CVce+Z1RKyKy+mI_RE56L8xopfe45D@V~ zTYGO+D4?y{J6c){G*GOz6|2_X{+j;Ur|+4a-OOIH0-EQ^%$YOieCPY#@BJ>bpZxE} zdjKB8e}V`o*cunps-c?+TTLc-J7SneRa;oo!cr|^n@KAs)ILoYlOo~tdlNyFDySOQ zrZv^j5~FJG$he3(3QD?qcPUuYWm==^_=suSs$q_f>Ri#qyRaG+R8D!a26%=Z6V#xq zhze2@uF7cO?V_O7{mF!*Pl{pP)<+D{oJg3C=14;ntn3=sCN#^@R6QXzQ#;)oca=BA z3_YPcJE&}J-LQhvRx>U_sKi}CR4G`skm_MXuuMV4sBluYDA-)fW6}_`8k%i4)-7y$ z1r#g~V-d;-LYJNpJ;})tVGU|jUO}YGjA_QOX6f?Tn=ET`RSls8Yr~-hn|**Jq}+jLS&4Skls+8MWUugxzeAgd)zxi>5UrNq81Dw{>^+C@AZ0?{4XD zSFj=5`+b_FO$tX?Su|LaIf`QRxXTW8SPmPdH#UY5M0p5x*c3#)f`@LOhheNmg97KX zpjhP8qWD)}3mynUB^X5L2z-~RYe`@moj-3nTY&~L#+QbS`*D-CuCy?8u`J_XC>6>2{g6H_uB zhtZD#1tBKRZeiQnD8sqhMJz}62&>Xi7{l1d1U{)vh*mQZ)0};}GsY3gm2jB(?)O9C zSuNqu5f(xho{%~7E6louwlMWCPGin&rr?n~PK0z6kxCjYfC`OU?$aiN0*SkXkrd;VUTjL$;U7WS_CJph2ao>>4 z-+5_WbH-$*R!`{hlPt@*oA}zz&Ri4F<1*WhhcSW)1*bnxQ;N{IXcmGZb@YM$FZjYvyeZm6ClNrC6XIEB*+_7;$Jr=BdwHx$(7 z13X{*v*8V3h~}{gNg96hJY~IksqA;ccmb!&@x20Be51QAFN;UmvQc{2Wr>t-b+S<8 z2Gp+hK^C?;ycot0X|?-4Bcj{ zM^L-P0-xBwkjt#^jT&+851@IR%ugHhR(9f%B_yuW+H#JEXHJpTwOXe(B98Y zEvD(%j-^d?3unxX+aX-VTo8X?n>n9h^u)9|A=H!`Q?Ba8ku1?T(>@>Zt= z?$#1E7M)9^2deErk=9yWuYdbXp)Kx$ECAuzrCI_O=6|5`1lyXxtYzOSu4dQ-s{S7VA*zYOP{#97x@6aJ{srrT!}J-!zBX?pbWUjxRRM;gLCf`7$4?8zQYG@1kT5 z?X&3QyLZs_1mE}Ez#wo9`@N-<3L3SNoqUOF+yODlnkZAPG!t?-$So4;1I*rTb zIXGD_sy9cD=Ap+`L~kYwJ?VwJ_yf^#_g=@IP(HV0(A>j!z)te3nWGjgLn~IJjcT`} zmg72f5T#wjbvM!Ngu&IzLW&l0N{YE(&%-{&+;3)KAE4kWqAjtnBARIi zZ*ybhNveI2Zx0do!?a(ciX+&_aV-f~N1|1H6j3?JOz|{-e>p|opXBam$mnM& z<2elBdE$8*6C6vlceuIO$e?W$MLWge0HyhHCx<=UQyf;6t7{i?j4aV)+uWe3@9DA(m%}<&TNw8De>kSe_?KFOa3L5WiQ+(hHv} zOV9GGRCwngbuN#V=iQ*njpqxW4XK~p22yVlskeyKZ;8|;BK13}_noZqMVH#z$Oq4*oK@`udUA7MK_MkB{Euu{djDHB*tc*89dDYJjWu!v-W z=pxt4mL&D=O};zK8J25?RWH-wTYL%-c3A};rXF4}U7`ovE=95Fhs(aPSH4S9-HT1n z%yMf3E7x_d`lEY&$GyJmUjOV~|LVKmq*>C~QdT{MCROk^;H~(mRQ^V@6(8?hjlbg` P_!lebzqziIYr6gqRJa$$ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/PreferencesResource.class b/target/classes/dev/lions/unionflow/server/resource/PreferencesResource.class new file mode 100644 index 0000000000000000000000000000000000000000..53203f520d83470c6c3a1a0749ffa8d93f8f269a GIT binary patch literal 4127 zcmbtXSyvQC7`+83i=!eM1x(^-TySkQt{F5zM8$v*2FPL()AST$W7FMM^`OR>?U&>+ zzGXYclZU+UmLHXqTir85_lzu_@IZBKU)}rd^UuG3{SDv>er!jJz{QeWvOG8NwN>#s z%zMEjOG~vRm8GN(D$0@8q>}SeN#Burs=C&WHi7O1d&#yu+b>%aa|_am1X{-?ZVL2| z1*&W<%mrFoUQjN(JTb;ms)9gIxFW8|mt02*43Dh=#NgqnG>$t#8 zyRaQQ+R-i0yBe7sdazTVvn->8oxsTfK2uZ5uxD#sIJ&wrIw9~z4x6x9VEdTs%R80D zIjN>?f~TU!0>}1dZRMJIb#e1x47Cfb=+B`SIl_(ZyCisdEfub1OJkE@E|R{hQkE95 zs$43O>sd21qt{8Lro%_tQrdC?B`vx{NdJ)(v$J9U=EyWxcQvd|+R=S3Z(6dw3Tbv* zPt6Oiwj)_lqorp8$Lcrix(Vr(PDQ!V6Kg8)q#p9_r(#m5GqPy4Dlx8au@&sf?{D zj-#axyen{Fs`g;4u{^OgC?{n4$a_apSM!IoiL3KDT);)z%=PDkd4VJ8s3}H@`JzBD zwKz(WOX29OiI>Ycw4PB1;+mVSGqef82r|_9TvLq0j{-UNMP?8 zagf6>t_!pT4+t(ztVW|j&6yxNSaX4E71t{~947HOQ^q=KVmO+3_%erk z_=-i)4~7GuMTAX&wYIx8IxDr(tE4GwuFWzav$k)l9uq~Q$G01+2HTaC2r8QCDYdYn z+wG z&MA|YOcP;Bc_w4^^2wy(tClkrlqzgtDYe@UL$BIBS_|3~)5g+BIPvF&&4`jwLw?B;)S0qDb?cmoPxz$?2H9YCs)tJ z92Dgm-u=H}SI^nwFL3VIAGpNvz3~$-@%{^p{KDPVnAEoTc zNMwdtpJDb%o!N;O`0ORVcm=W31agMJ&!&j=)rs|0i5aVAi6tuuF*{?ZxfMdr>V_(1 z2+36pHBB-tl-FzM%-89>!2K0AiSH$5Dv-?}cJNoN@xk56-8!4zDjUI^w2Etat0P{l zA&y)uA5Y nF(uvCUigMro@sn*JnB1q&y_BoxWQWfLtE#69@~Ra literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/TypeOrganisationResource.class b/target/classes/dev/lions/unionflow/server/resource/TypeOrganisationResource.class new file mode 100644 index 0000000000000000000000000000000000000000..47cbbaa1260ace947edc3e940627a85fd8fb92fe GIT binary patch literal 6805 zcmcgw_j?rA6+NT2l2`tY;ZzGyCX4TcV<0BAeXqM z$4-xJ+*2G|sWx_G;>1o-s?&S_zWgQma_*bi)oRtiABld@%)YtrzWeUE_uTv5TmO0e zO#u7wuNW#c?9G@Hde(AmPY-N9MzhWd-80<@)74$ma{@PQ>I0K`v)3InY|Arz3ZHf?+oB;RYglQM z7IZCPLL@cMg8pze6|GMNwr}OkAJ;OspqV;>*QT$)XJK=lQ(T6Z|TM?PI_f(ikBw`u9JGXDw zuthnmWV>$Yj43|NgfU^J6JC%`&%DTe#nO)LbIX!?66R-(Q_jp-hTa~|rX_W1K%$2% zVC3^zE3IJE$2|e0#!DYFbB2bNx3f&Yn#@%itXw`sMkyCumLLp?t&H#JrI9j^Jvs(@ zskn02_8m05$FUPe;2)z$tWj%bN`1Iq!^-G`<654KZk$UreTHk~Oa|y2#%Vvdt>z0# zJiVxomaXfua+but5uA*|w-A&u^Q0mE5DIW5sy38m;H`n_PAX+oRPWf?$+jq-FD;Yo zFh`9b>kmnEw$!4PkveK*J+j~dbi~j;huT9vh$D$xIBwniEmROy$=LdHp6;Wk0IO^!?1>HmsqoWS{%3IFugt^ zT1a?J%du@^xK~wcBZ6iT2}7g5z+W0PQK~4DP?ft!!$osz4AVENGs9)FOIGHyPPF}u z!xo?V5(qu*o!?mt7e>9pKT|YFpVG*Gi;AV zEkUj{*cJt5qA5)#Ph=d+PnajeqL)!Zn@u~&ZBZOgrdlUVn@DrfnW-{LCzY`XG6& zIUy}~ONl{|M~;*X&2h=oaXcd~S=-q&kWBTobsg|;W^+PS&*p?sFq!r;#j!cdE9#ruJCkU4v8XC3 zn~kIIlG>#^Tu;ZB(n=qvRx8isa(;A-oUqNs_c)1p5G~66TZApsJC9^;* z&Rw;5T|;Xz=od=Xv(PlRZBbFmdPK7dEjhBYZC0PJ6E0&zqf5J>8r5y3=QW;D*OkH4=Q^1nZOU=F z{1bh_I|KFU#&bJ;`No*14;W)qT4nPvM?#Xso`2D>vAx)1;SMqFB$(y(eY>?X^zCjL39IfM94WISuS&J2j^VdrL+QO&&)ir8o z(C`Y@yv!dJxQPE{0+7JPY6Seniz5D)X*!?swR-DoSYJNnay3OnDS%6GDWAj+m(x12 z#N_h|J`-H%N)H=xm4{8ZnljfML1im8^Lvv=(>T5r*B*h^ie}1g^Kc#XR&1x_jx=_P zWQ9mxFOs{8KX=Q|8*n3+xJo>76QwwEGxm6B!CqO%<8|f!@TEm<=n=|PiA{~~xON(C zrx2@r0aaD|w!VskmrkRzXWJR{PGj&T%2X-$G*C0JhmAlB9kW;Md26U6+B3Cr8JaJb zfVczi<{ImW$9r&;bKlF^;-hfH;K&F^s!_4BReYO9hT`I`hp5!#bCjcWP=$vv5624L zvt(=I1@BcAy?0#3vuESIoJiV5?=9?1g06{&WY2eqQ|Kk9`!w7$IN3and&D*Txu)?x z{(4{wiEvP7i0@zKgRP&tjqPva>i)A;PH1UC)(_hPoJ^4x>Zb4>}y zJ!s(ne!66UUkB;8Ax6zGBjh%MeLLYDrp#fYaRi6x?H>C3Hv0Mq-z9{1(YLg{h|L}N zB6iZHb=3PMz6mxP@jSlFw-)Bo^LT-8f=N9phGTeeBt}~_R@^CCB)~jLjbGuj44AJ9 zP9DBiK@LIaB)vJwuXo`>+)Wm^hkm&iJ9yOD z#rNH~k67N1c07PiYUpS15Aj{_+pidHxQO2{Ucq;`HxPG0vVxjx@m;=EQg?`CCAEb} zhS^0Vd682<@(sKhA!!rI?@?2Pq{b9OIgH;Ium7L`qe?VIK0lQ4nIalz4G!2noGZ44vPk|k6_V&Yl>Lhpg8YQ`s*=%eH^QCiaGWKz4jzG zc#4uwkz1c8CeJXZo<#?q!$H0a4k5=KD&lY`!eKQvgxRyXJbQZS^C8KeO8k}@D)2j% zKkf8-=I-;_z literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/resource/WaveResource$ErrorResponse.class b/target/classes/dev/lions/unionflow/server/resource/WaveResource$ErrorResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..64f1d05b893a529446f8272db7d48410c8525f8b GIT binary patch literal 575 zcmbtR+e*Vg5Ixgdnl!Z+ZN0n+KC}wPceMz`qEISWsn2P<)-7=(yJ`F`A4KrM5AdVJ zNi3-NBr@#foSE6PGxPTT@(SP>TNW~e&Q!!+AVX!mh2rBT2p>EnbS$)|g$WmWBD`xq z7MDw~bE$k zX$U9%{}st!c7|yudNR?feecJ9QXs)SS4!w^;2R^1g>}NgUquP!AH5|M2f{>Wp^31~ z#bE_&K z(%_fd%6wW{z+URh{s>Mmvn!i%%^{=Lbj*!uqbZZIT-}w%3mn!qpbzS{t7~RPTBNnA z3rEEolW8+!y31%}-Mn6b%H>wdsK#VWjbe(xjIm6PqXvfvRP`CI=Yzn)I({bS`tr2y zI1Tg0cDV`yhsH4u;{_(SnHi%aJG9ZT6FQ9-sA;p3db(G)P5JCMj&D?^R-*#5;+TOr z&2{@tI(R}kyh?_OzE4ithG8$ahK5~3Mp2-*uG~EOu&Je7OOuh(Mu)9Pbkffpz?&V- zcC@5r8yZRY-7Q*|l{TEFG)ZTq*m>NDp3c(9 zaZM}QTRQ~Cx3{!6ceRw5TzC7~KSnF0YlLS`Avf0OwmxLIhFw6CS$SjlAv2}8qxH($ z1{te)aYPZT#(XS{Vu8T%Z;qjH9Esxvh7TU$GT$o`nJO&82~jL22xUSvx0|_=b>cV? zCkaHCceOMnT2=_m&0|s`uKu8gYAnSmQJgGrc$rVcaVkz@)ExCCfo_>CUQ(8syDYKh z<`~wTHPb1|NHrRi_wkUT+KuCOJ(pvUY9SvYpzLQJdClllHzHD!*z|*;+44Yo!87-R+HI9nO*A zkREIjI6XLZqTjYQOG#`Xdy4GY5NOJUt+yKMrRy664jCPg_qB%A?z}kOh4Wb?hV(&W zxs^%k?mE-$=N+kFE>nbvxG zT30k2qs1|{iSt)>_H?X>;R3SILtLdpZN|#fy0(=a*|#Hx z3%SAwr_zw=7`6LuuN`2pin6JORqMPc2-VSrcy?IsN-LX5Kl+@vyuSH!UmR|>S2nDpRc5ySgfy~9WnWa$gjpCTB7 zGJ^92rmJ8Cy(QsVDelFLJ*y=_d7O_A$MFGN8N)|Q%Ajo|`wg4_LSL(pUpunY*AhnB z81A<+l!e7UBxlAk@e zSk$0WdRpVLFX%`VHw!E|s9B@9rC=B0K_gI`HXSbdyaaQkNHcn!cAuUx9TjvceI6dJ z`}6JE*5FgC6rV|jEfSwk#jzcqW{EAqRHff4x!Pz$Lkyo4s0&k28OR#x_(joCGEl=! z0znFGkrww`hHq|6P8oWOZR=(Z3RuPb(YUg5vs~Rw z4ZV}0{#qROVXwe7{~zn$5(BP)hYXF5d-n>DtT$|enm)SaRa52yVl zfHvS*nQ(8pFJQfty(@dVTe$a`(rj6-!w?R)8*aaqqFnt1Ka1k0T)B@f+|A60HE3vF zG4i$`2@;m!9cmRFVVf!MkzeS zay^~yHeI7(@_76>hF_Ijeg>qkU?+E4t%%__JjmOBB6xcyMSC^sNv>?dqspz$)Uc;Z z+85wY*uH0ZP7wZ5=_0JY@jRC#;Q;{*N^(>UeNmt}JoHV8f|}>$k|qS)9LofOcxxuZ z<&Qj=l!syyY0l^o0oQ!F(*plAh4Q2H--+VS0>_4!T#B1f{8iwn@?ANvM`WIJbQlf~ z8RWUAgW24!RB%5Y!7O@(yAFIw~)7iVRX7t6h1vT9j51?WfdJ1}-yBF^SM!V8!(Cx!`&?B#lc4Blo1I;}9N}%|s`|z6N!?pNF*sqKlw*^Uo*=w)XI@Tfw;_;3GUbt`u-52Q;4NRbPBO5L^MUS z4Zht(RQpJ(e&&-&xBA#~fT#|Vu4$g`4iW1N@l6K^+MP$V+efs@$1%d$JRBpOqllnu zb2tj_(R08}CV@Xl3;k^ND7b&5K6GG6ePj%zKK3S%x`;?!Or+jR-!36i+i2^hMCuYE zbs4?9oJd_kq^=}VR}rZz-d3cp#x*|0ijewvh*Q@p<$2ZTRIkse>)IDQjG3kB6kr=^ zH5OPmhQOM`mFap)aJ{+)b?-WYbv^C4feCdZRr@B);r%hVnLR&A4ZMX)x1GoTw-Tt^ zn5x?qSoKO+dh@jE#kCwMb#IQZdkYH4Gy{=WaZD7Sc!LEi5`~Cza{jF6nC)Xx>(&rp zw<*9L@&Rk~0lPg&EyD7dF*x+O0$8M~$ZT8B*`)H$VMYLV(1knMdKbBKH{rO4x^*wb zeeCRfGUf!wgW@3?_Ap(3gemkWVR(#;c$`)62{iFu zE*d-*H0PmcCR96>yR!Q2q`NcnP{^7`$651A)Vure)qpIIUGH`&93}bMoqGKS8p+a5At*y{D9pU2SFo~qn0dJ-$#)=?Z5*jA*TaviEL=hk`E~6H ze2+}#>Qy-SK7J6m_m^T7-m2EY5Ah>TaSA8+1$+Hc{r+10{#N}y75IHd-9M*(Ur@g< skrGmTD*2~EK}uAyFfGL&D&-$Qmg0|R&cvVa7yOO1`aAnovd^mj0c^2=bpQYW literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/security/SecurityConfig$Permissions.class b/target/classes/dev/lions/unionflow/server/security/SecurityConfig$Permissions.class new file mode 100644 index 0000000000000000000000000000000000000000..548049a6c232935aaa591b77f7fc15e004b894f6 GIT binary patch literal 1412 zcmbu9T~pIQ6o$_Ng;2_8MMOkIzCnn9_yr=NU9dB4$~2+sRVS1c!<2L;NpbvHUOD51 zKfoX5_$Eerdg*Ypo9Eqg-reju$*J-$(n5ok^UgDZ7+c&445i}gza&Z>1i7%JuAScFLUT8a8 z?E4XF@EqfL@B-sS@Dk%?@CxHq@EYSM;C03u zV4d+(@FwFe@HXRT;2p-#aq}YTLX59a0X=4-)arM7{VguZ9>%CZM2q;75@q7&6z$Vn YP&XZ5>>`5>F{k7797z=MDAsd-0PqY6_W%F@ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/security/SecurityConfig$Roles.class b/target/classes/dev/lions/unionflow/server/security/SecurityConfig$Roles.class new file mode 100644 index 0000000000000000000000000000000000000000..c7853441f3a07dc8955c8483cb91f39a4b1ae401 GIT binary patch literal 847 zcmbV~+iuf95QhIv;>K~4rfq1`a6S}zK*ADNh?8WTrC3UAWIMi%oOB6W8C&uph{xgz zi3=WpheC|4BHT;3nVoNE)-&^ufByRZ1HdahZll5Q>|Ct%NG5Tn7cm_lqU5uliF7Se zI+jH$^Dnx0>6jFkt3x-5MAk-&Vf!Lnhk6voE8SUKh-J>87~`pBGwe?O#=Jb0@#+Ob zkG_!QVVnnHR0tIc!{Cg2zUA1qVYz%h<5rpx^@OGS@i z<4d3+Kuw9MpOXlUY27dLQ|p+Awau!~O%k^12x5~U8w zGK_*Sl_g(Zw(^fMV|YIKU-d~#%}a`ODc;Jm%HZmXo|M`%bgeiRsTqY?Rxa$A;px8@ zq?J|+Cn;=^ElAofC6c6=ZHomFx5+l$cDq}4H-#cP zqp!aD!h^3m&M@PRiHtM)f)9=}iaO)J<9NQ!Hl}G>X6#JzU4Q3yzTf4fzy9;XPXI39 z!!TMToGj`qNzXe(OM)T~l+ zVs=3VaKArS}<*GQKbi?pi4NN*^+>N zD|vOj6@m8V%a&&>>C=X5%$j=2vTRTD#4{x9$t-A#n&W9n!xGGrX@8(qWVm1&mf>9_ z*@5`9gtk$;s7J8_JHzOb(7PFR88LKA=qTx4^(_*P4baV62u4lKb%)}cKfMDIcFPE% zok7SLmYypw&FapiM)DG38M~mF)0$(5b+Fk!>{E@R75ilLLMB~r-rxhD6SJmCAOjDq_!YV1_n6T<%wnod2qg&n-D>_}2 zuxlW`(U-6}CF50`pojG~%93W7;{1$^(@oBOpAvSi!InM4WVd*Xc86+|EX^xBjCX(I z`S3;-7l(wOVHvOEENjGYQ)O?S)Qy7X>7>qr^<#2LX793q!@N;kGORF0C7j)|iJBI~ z5ubYm3d5Tk%cgTtrvC_|R?w`hW@#lotBVqK!$_}d_i7d+xXNgaE9zu=B9}|0^UCe4 zk{!t_5oG!JYk5UYk14sy2qu_sRT<4Ilj4@}l3>JYUM?G!W)*Zd0+n`?w42DMl{`aG zyP~Wx4!ppI?U~4rr*dgEHK|PHZ!6PEPRY^>4{En3oRVf4_t(^*n+K@^9k|u^jA@=G zNSun|3U11H2e%}&>vzhU$qHN78C79pXNdc^WsHlsN0t~CM;Fz!P4%?GVpdxYI(7&3 zZcQrF)|@Sbr6@xeX4?&SOrI^66ic*tQ6OH5U|zz&6l=zCndGw5rv*8mGoF5VzOUaM z?8`rW%(519VRA-%4GUaT#u6+tFACH-3Fn&hbX}DNZ`rsLpN?Qz!v1<1mTj%0A{;t0 zTu|jhzQau-#SBGNyX+M7%Z3n|?zM1Ke>si|#guTIEUE&{yz0)fW4L;!*#1a5}oT-p`ejFbGqvVb&rFL zbt``=Td+?EH)jyy$lVUk+Brr!c5$Z@GFRKUigOfy9Wm(%wtt6R-*C}_9)1f0pc{Mq z9dJcW56qGr#c3$0Ps5c@iHMk5*oV+RJ;-D)+SI#G3Y~4csGGM-UwCN5=s(!96+7qC>|dX53Jzi z*A4Gmt_x7479i?%bNkmC?N^)HQ->L`bH1+!Tju-JHi8jS=~zseP(u ze}a5O9Kwm%SOu3G%|ER(57)v#(F)v;vwDR2fkgO6T$yQ&jj1zju{YJ3P%NX)w8wI4 z1=kuuKdS>hPy>Cd209Xe_9oivK=W15CU~FM;q}(=CTn=jNltA|@~b+~|43f1fnEqm zb|*r0Bs0z4`=k!4yTxmNPE2Jcc124N!ys2*_s?YK%1Yb!Edpvg2F@e)4|+B^l%+3ac=OlP{HCC z=oDLa1$TsMRNWtKKzNp_zOqzcikfg14@EO8(I$Yzk8H9 hZ^24U=k9WSkDnNKSIO*so{VsO!0`j_xBL4e{{kN_nQj08 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/AdhesionService.class b/target/classes/dev/lions/unionflow/server/service/AdhesionService.class new file mode 100644 index 0000000000000000000000000000000000000000..7f52c7b313fb5168fa357bf9ba9063973e86f0dd GIT binary patch literal 18808 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/AdresseService.class b/target/classes/dev/lions/unionflow/server/service/AdresseService.class new file mode 100644 index 0000000000000000000000000000000000000000..2cb338c77834e7fa48e4cd77cd96c1351aaa4007 GIT binary patch literal 12752 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 F{vY%K+`#|< literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/AnalyticsService.class b/target/classes/dev/lions/unionflow/server/service/AnalyticsService.class new file mode 100644 index 0000000000000000000000000000000000000000..7278b1eb959aff8aab9612710da7b779bdd769d8 GIT binary patch literal 19263 zcmd5^34D~*wLj;}GD)~VNC=pK7%*x`0&znjxFwST29imfnFIoAoMgTvLnbqEmMCD| zm+EtAZJ)N%0xwPgkeIlNYViE z>!a~xBo$BWVmhyF7Mv3fU}ML${V{T-g-rJ}s4Q;V85#~HQlZ93OsHz~8U=clj_4zO z2|X8+wTFpG`=p*2ZDD^rH4Sa&9Oj7bX?kPSim*NsiiP#&NO%IG=N{&;tKg>}*Q#%U>hDRgrP_#cC4MDD!YB8;wZBS86(a0^G-YH#W$W%g)g$;F0 zMr>_8%prr*bj6U~n!r|X(7mZQ*xb_Qse$Iz7}iqbSWAs*Ej6LhNR1v#k0fhCw!PE@ zca7@pdMXjQEUhzXJG@Lq=k!M-u}JDX*h}roZl-*9Jgk?{3AEfr)l7>gThE42zv#W9 z6?7s~@t~eEt%~WSS}3b+gzji4nOwhes__*QAQcr*A=CV}NKE&oNBZNWojq_OI>p~(wXcpz{XNc0!izMEH_@Pooy^|v(eyY zqr=U{TyD;)d((C9)Dsaf+EfmGy~(+BDiUq%>hiXNakDrT884iQ=b++{*cwWOT7w-R z@kGaSq!OW65>kV6YYb+eL(%m`bQ)8Osk+r+y*e7I9(m>gQ$MQrysdgPo=!;P=ra!( z*h-t8s*awL>kH91ht8liE;XD z(FM}2#Ok&o5-Wk)wkWD3H;AcSxx-sR9@^-l4NU8g5W`%(QM8F*g|2ua9D$h{Mu?%A zwkT?&cA#o&st-$20@f>klg*Ik0Q7&m#m}tb%t+UaFDzD!gi=F|Es?=iy+1Mn-4G-? z6m<#`$4`rdHzrCokQ7M2qQC^mU?_csI}wj0lc89@jkZ8`De4x;N_))q_%1yb)0+o# zp|&aN5vUb5R1nK^G`q|s-SKD?>!Ge2)9+Apk$^9?!E+!zvF20?O9h-7(}1@J^0 zQ1JBY{qgiz!dOspItqM9QJ=t{V8dJ3X2DE}2y9r9F0jjOSW{%qa+9=n04BqvMipHq zV2+I2Ty!~BKVvbNq*jV{ z(Qa7Kc8|XUdn!{YC&3u1ccr4Y(N#>x+1Pi7qIx>fnb7<7ut<~XWB+0vdpddzTCP!a zEnSzxxK8-heh?8F)IDSID6BUgV`{aGc``euXVQ#jdjs9%q8ktGIA>o$6x~d3hhyqX zN1|ap!PEnvc?d^0`-nBo_CCZ~q*~0O!J=E~HW&RHe8VxBK+)}V2iAbpgx$~GN2fi< zk{Rz%^iH~qY2N6BO#;(_jyd zR*z#vQxQFeRc+mx)u)zV0enKDl+Pgkoel%+ZEvL~6@5-ru{;tBNBSY5bYcTSo=7wz z`;@2Y85ez?X+f4^FP1#yE);!%o`qIGz0-Qf02o;7wN?UyObg8Lsb5s|CHgW%9gB?V zp>!v<@Cgjh6wO7;?s2R*Q$jD$i!S=g1ZrC1@u(h(2{kVX4cl{RuwEK8d`;2U>19}C zN*@_b`gI*!4~N>9`Vljeg;xpEI33%SOmfI!%`$QuIsuVNQC(WgKE|0()AeGg9Gjkg`%fJAL?D0-9r0pH%A(Dj7L6T`xO@-Jm! zleNf;e@AF_(+GYJGv~P&#|87UoE4@j&gTMbfkI*E^n}INPAqK)%h{#4NLao!*b!`Q z>veYoy@BSSx5F2}b6cCo9rSp>c@LK;o+E%uEZKOvJw8vn#}^FrcKA0o`@8`YZm!~a z0=LkDYxT4@`&vba-c}Dp$fb%GhzJ)1yxV$PJ$|3p;}00ZEaGx(9FT&QV4N!yFXF`* zC`YUt6QR)|Gh_Rwe|h2NSj9iL!uEC;p7 z$m}`EWQ2d#;uOfyOM!hWd=cC@S1X!J^GbLH*SHwj@v~-e<2ffolh0!mpTs9)yMe4! zEHx?P7o5S-PK^gFe>BaD+|5>794XimuH`xxue1%y@L}>saXmM{(?{`)1dN;-;FMr9 z_PD$ndt7dWU}`tZu|a`>tRn_z1dP3c&rp1(P=r&aEJfM$;r)D;;K2N;1cq z0*H;~7+;{cnOhLOh9hIhd@*gb7(F=~~3)RW+McfLeop!1O2E0M>M&5)|myvWd z6&a1<_;B?M^UN?DL^R^$fYW*uY+7J7IKeL3O8TALs|cW7v5%2(E3 zJrw`}5YB0}M=A!eWW-4{%Q@3zoYsp0U#e(6m6mWHhh5yy)O2_ToXrMj&m$EZ$>7pt zdc?}8CObpBbjYS4CAn;wWJqyDGO7!6fO68+V>~P?+9qMbh~gN>VT%3nkx}@V0;hex!NY|f>57w<+WZkW0A z9wd9-3KiQdZVGwfFYw#2r*eEl*3}g8)li$W*Q{P6#9XWRI?;LO1ZA3pIoyM#6~c%c z6)m8$626&laWRsgvmNZ@xbJCCsTJQUte9)C!p^zN(6=eRU1VOKBXcXwo|N}@Dt?Fb zTs)ztk!DYiArp@=4gImlgf558WAbDc2!~kwZpHsDXG=x0UB)II8;o^)kK*_6d(Fl! z9Q(qSmQOeglQ=t`2t^y+Ly34SE->(q_w#*<-^cHV@){l*0c!2$9M|mh3R>NbewGWH z#(9H$C&lKtE#R`i%64VjME5_e_#=kbOs4yAm??KV=9&3DoP)s!M)eZDpT}MNANB&~ zM6HTH&JTdxRNUN-z}9SX4ZX28DJ8s@Kk4F6Oz>6aJH-$2!!RMtoiq+CjhxGbq>D93 z5q}CZMPmK&SS+oV5%GS-kMgG>%aPEq?vBTh=-C=c4dE#%M=X%#$(CW{B4e>Fw@LPf z2NbQM)g}BGKjGrX5#rBcriT+hA;ZteQC)nb*@~$rPUP;B?gfNk+vTz{)y2x6iQ2G1lJz;XNq654cZ;YH5wE2 z7m9x=O1=Oqmw8qTUx>48xMy_wAH}~iTrs?W7cLS$`)U5Q;@=oX8Zlbn<$kC5_xuNA z4-vzqzf>gQiER#a_$IkZJD$(k#yPJ1Y5tSqKg+Ol!*In|GqV==SH*wh|C5_($n&)7 ziC9F(r3yQn%QR+5W12PVSgX+MNF(zbOzxw1$OwuUD}O$SM9h51T=4AYwpjjI(-i#| z{jdxI+FzstKPb-rvJXO#~ZK2XCv_jX z7vNUUp=O?(HJ^~3M#e}xPHB~r2|7;Fcal)*3VPclyX5Ke_ipa!@(a)sr7bmp+?%@m zzFt4x+W90i4JAO!ly-suEy_(#d3?Ri!5|V)$SQfX!yuG8xRXnQJLf{rJZe{V!zL_$V#6I z)HF#X&VG)sRd&t6s)u3xrQ;9Rg9->G5yq@Ix<*G;)jA6zhdb^ULc>zsDfi zP=JjdUp5mhg9Mes=3GYUS6WzLj-L!O zflUHAptM1OtTG^5y@AdSpBGcXl5!;)QQFSzfR1t)oN%C{hBUyn z+(ffX9aq|@KrVpVK(r7590ruV(<^TiN=wSyg*Kyw3xQyVdkd_=rKN##HihAf2h#mR z8zOo%>`5fz2^Upivl%e1Ic$hX#-zfzePdm1UM;cBUNDzSRS9fZl6oV&cuKz_ z)rf#H8bQzkUmoa>j~e&jOL5|wLL?L##Zl5wJe(}iuGFq_Y4Eu@y z5&75`hxt(rVLGm|Oo28B+=LtBQ9W7H7LO07M{`eAtd(M>j^d1`0pF1WKh3tm!W2h& z(E#G%_45n0Jw@7eh$M3_Bvf;eb_4DZJ#&kFUrSnUg2ULeLe{nwO)Gds!wOu@97S-4 z^Y#@@sYF@_QV5tsGOrQvSWNr%NQmv%dFnjI8t zp2=HUj+3N3e0@<&RdOL4H5Y197NL}Oto<~{YEwoV65LbTkTaXohD^$oHl&%Rv{lfe zDQ!3lo!C~oS(a_gUj@vT&{DiT9<`MyeBWqey`#8`?^(?`IQA3+B{Alv7de8bhrI{;W*uD zKL^L@D*JisI3?`o?c+3HKVLjf+wJE|$H`|u_m5MH{X8&EXW7q@ajLVQqvN#9evXe* znf;s?r^0=dZYr#+Z&;O~Jx#8<%EC%lhOTZZI!M=pJGX#UcUBf==v~r$Pi0Yt?ybzv z(EEk74vq37}r((@Vms#&~}p>I6` z_T(8_k%1tIT2b>rm>ZDn-H6=hCaOSN6>X+!+5$Kq-9Q)8J8+}%)4-r><3f;oMO13w;SJG zG~u4|d-3D~ZaYBZZWz^nVus_X^bNYfMfc;sITQZ1xNxh9B?Opi1#}~wmZ(4Ty(>h{r}_tD?#8}jlB zp=qx_hB$(@qM4zvXfB-GTx>L#PH9(Wd+{WAjL!2hdO>NStDMU+yfAkLc~x3yzB?}a z&QV<60EHAkkcUfs1@sAU{F7kcgQVzT_|$!1+J5-aN8u$u4L6s8C=bvwI*8AB9)nUp z4p09CKG@k0X6*wrpM+QY98BRUI+dQrw;P|QGwB&xgWFhV(X(_mJx6Qli@164Ieb0w zB(>1<HUwRY045691pSzj^b=ez|0!KgKZ82HMtkVzbS3=)cffy1 zS3$7XK(yCFv^N0mX29Q0zo9!|f_KvI;3a<##r^}`O@D;y{0XY`XQGQ4J${1I*xz-{v> z#qYkCT=4skIJ_~Mb#TaN5g0tX_VKz5pD#`2>=vKSo~FXeLdOiS2xAGxT?@sfREjGV zOHeN3`S5q;w3-*u1-zI7d>mcM71Re|inuqKh&r6s9Zu^Gr*((ZJxH7gr>l{&+Ki_H zsBa#8H~gmIBj*smL91O{i~k@Fe9B^pePntKXxtIffu%E70~!Kcc>r^>-+9uI(bIruEX zQexqQPj)PPZkmG6;EedpgGCUprV?HSoEqO^obYv!h0}|`NmSfz?p3xn<(m)Nq13X` z;+>+K(CQ3FE%iow;dDwoSSW{{8eU~-CM=b)ynV1A#bNJ4r zN?6a`Fpw@7$W}O%ZEz?((6g;M^dG)Mxqe26g73?wk;n~(L~c5yL@t3udUG&3a*5!> zHEaG{2V&0uZ-;OoI`|U?e+F_ecq^P5K9RF<`mtqYJrH6pPn_xTl+E z8f)P)TF-Y)w8{o&1;kf+CcOrzc$M2E=55IEyKcmvyEA;R8UIdbabn@U=zxD0;U^X) zyD*8kWt_G6NyOcXUnzc_b`*&S*NHR{y*iO5B2y>QL3luyL^;YgB2bf$-`J$qqz+e)|Gnj?n4c;uPjD5 z#dpv=z7zD_Mdkc1T8tp_1l$5?KwZRNZBF3Ov5G&6l|y*G$_NNG15<1>=gB@7ALv-9 z^`f6_NUQ3fqtf~ee=M)GD8mne#pV3SFIv z7PPo{FF4v;O|wqjQW38AgO@%AECofGyHQD|DECgt*b1~@NYBxN69AeUo!l6!QOP< znQ$3yq`AnqjVvE8k&@$ z5FemHB%^kszi@oOB*meA*wLI}M{|ZP%>l_q{2_KQjDzAgp*iyO13Za64C8e6O=C+k zj5FCcVe-Y;;wbFZgv|C)VQg{V&LN%^h>y?foVt2MeOl~i28OoyB(hHc6hqm;puLNr@I}V z?zVUezG)@e9PqT5F4L4Y7ZTLuOxm)AwJ60F^2@b(S-mmRjr{!_jn8-R%ZH4RA1y6H zK<73`$@>c|3U>& z&9!Q*q*iLbJ5OrM?RUjen`71J|wfX%2B*p*g+tFmhwrB-RzHcRbzyVfqX6YW|j zYDIP>Ag_+IYu!?tZ`XRHR%X{Ol3Imb>qV{5uJp;PC3a1hTD4splA5w>!%}l5k4fzW z>m4JD*;?w1d70E!nD2V_YYQHs6S1sS(eYXeshV1f=L;yGMY`r?l&@ep z`XS06A)WGTl)piW UjXuqfX}3_ZcB^)qb_Z4dKPGJ5ivR!s literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/AuditService.class b/target/classes/dev/lions/unionflow/server/service/AuditService.class new file mode 100644 index 0000000000000000000000000000000000000000..3b42cfb719ecfaac534f8db79162132eea7af314 GIT binary patch literal 12333 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/CotisationService.class b/target/classes/dev/lions/unionflow/server/service/CotisationService.class new file mode 100644 index 0000000000000000000000000000000000000000..4466e94389b8e52bf14e6d2a9aa9be83d832f9c9 GIT binary patch literal 20265 zcmch934B!5_5V5dC7EHkKu7>V5C%lHfI&e7LXagvgGoRFDBw0EFEB8fiL)TMRBhdB zwYIkIR%>0Lwsn0Fv@S)dR;zVwwY6$%ZL5E6YZqHv%kO*cdo%MU2?N#k&*#J3_wIW3 zbMCqKo?H&w@?iFm3y6Gzb=OPpSvvXh(aq$#4UcJ;zUI+}{4 z(b|Fr)C8!6seD6ZQ=~c;iLa|(vUY>rnr5Qtj?{AkQN%mZX)FJK(dxFNDJl1xXcqjABgy1^6}Ro-EDtWDbanV4qo zCmc=PMq;+$oYq)09!<{$$tFx($yBm1(PoFJk_HE85YqwO3AiZID#&XZLI*JQS!btR z!ZIB)0WfFzurL-$rD`Vjq<&Ljce(q6h06@o}-w7M;wsBVwOBk=wrgu^rgPU; zh#Mq3+-9f3d4WrXr-VBr>+IR#GtZtJPDRf!Pa&E>lL9mmsN{*>5Kr5XaZOb;nJKU- z63f_2+L?w-$RcKR#Oqemmky<=0jh>fb?36C!{~6P(l&c-26{Bh&)SxBG8$i3<72j~ zbiLF}Tq*3HuBi`&LNtSB2Ixqp*`Ho(G|i&ffEVnmhZ3i^G)3Z|O;V&!c;~82r=3iJ zdA3lzpd08thv~?c`o{W&%fqeVMaz~nh4VsynkCEX>X(HVtO~at+6LvXZ(VN-seu+V^-tO9MbTs`z06K!Vz8zMy8_gcy4@CO)YK#s^uq*A zkdS4{YH=E83j+B5N1QPS!~-UEnx5}gvDlVL0K z;1&R*=~RlqzIH@9VXHlk8mxNG=3lE|l9~{;65L%I@b0nB!bB_vCy_{|G_?~Hwk(}+ z={vGF8s)kh;G>#0&_>vcR(BSrseTweDDq=G5Pj|zRJi#RTgY%0Mi=% zG+nWxp$^Wmh$$)VIGDy1#tus($w-Htwvz?1j4bKuEL=ep_Dlq~6#-T{8HvWzsp{rL zx;Ybz!7QH1RMo=`g<}z)BML`0*{$J*I&nneI?fg+@j0gP9%e=$TK47~Cax3*aIU8F z==0z)9AcXgW=W?26^Sv8>xDphLC^MsVl$nu=>obC8U;EtRTmwYQ?Zr|tUweIV2CcJ zO9ONXj8D(`HGPpTgG45h^|6$_e0?%;I@4kP6?#tCi$a6|<>i{Lpf59NP#iIAdU*mq zpJ_u;E7VgaKa-fKC`8Y%P)72YLV1m2I<%M|_5_IOphDpjT`rKjq21(vncZ$D#Tzp< z_aZEx0mC3&i}+%{1UI>BYdbq zK#a@s{BtA*V|rk&m(K#N6k`ONb0-4$gNKo2raC<39U;x2SzxxTOIVR{5& znX_E=CbHd^@%jwn*3-;{sFohn^aI+3J($FkX7kE4r6{2aaH$ceQ%g^1+D$(Mcfso( zIzA{HAo*1-L{HJv0s0YC&QR9Zrs)~_F@mLNs=lK$ji?ga=GC&wbDDl4s|;Bui32;? zZ6)dQf~FVgB>)0+x;4WH^=51UKhrCkeo8+>eCg?soi_l>GBFz=6CBjO`I{eQ=rV&| z)AV!t1#t29OZz#`f=J4C6h3Z=BEy~LZNqUwcTJLm1%9=b*JoL-LPSD7^b?= z3fIz`n%)vsECqWS+C<-eE&7J-XTR25TxeY9>|$6J7e0i<9i9M9DmF`@`~ZZObfk4- zQ>4=iILkAgvMh`nM>) z}*-JqVLDT0qH?L?k zV6)cTPmGb+Ru}9b55O)UPq6G7dsSy+?0h?&)PgNpD?PRcse4VXow^_0if+J)s}mAz zJP0`(Se;@Cwzf<`Y;iFU(R_ed`99IqQiuhNLV$-EKDpbe(^J(+vwK>U$i&-nd=kD7 zNA`_JEom6^>{L}4B5%gySM)oa$hOH8*k-!o0X-0uIPVi-moVQ<%X^G%sbOI1doy^HwyK1&`@yj}F}i ze+p6zPTJ7sr2O}-#1!u-%{{C{PrKo($({}LQq@z&u)*8*|8>F7DAYLKI&@i`$0#JN$E3$

mNO~gsnKd;9 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/DashboardServiceImpl.class b/target/classes/dev/lions/unionflow/server/service/DashboardServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..10dc048a61237c7dacf6f237818cd031fa33f0ca GIT binary patch literal 18448 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/DocumentService.class b/target/classes/dev/lions/unionflow/server/service/DocumentService.class new file mode 100644 index 0000000000000000000000000000000000000000..8dfc79d4c331c4960c49c7bfdd22c31de219f731 GIT binary patch literal 14781 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/EvenementService.class b/target/classes/dev/lions/unionflow/server/service/EvenementService.class new file mode 100644 index 0000000000000000000000000000000000000000..f19d816b76323202786b6362db4e1f52de5d8b8a GIT binary patch literal 14106 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 W@GU%ffo~Do!_Y4F9qxM_E%+CI>%mL_ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/ExportService.class b/target/classes/dev/lions/unionflow/server/service/ExportService.class new file mode 100644 index 0000000000000000000000000000000000000000..625dde77fcbc247c55941064a90a87362ede4cf2 GIT binary patch literal 15236 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 A$N&HU literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/KPICalculatorService.class b/target/classes/dev/lions/unionflow/server/service/KPICalculatorService.class new file mode 100644 index 0000000000000000000000000000000000000000..4552c5be16a706a1bffa788efffb578bccf8cd73 GIT binary patch literal 11923 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 GqTd3sDS;yZ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/KeycloakService.class b/target/classes/dev/lions/unionflow/server/service/KeycloakService.class new file mode 100644 index 0000000000000000000000000000000000000000..2e87a5478c76d642ce13b820f2e8f4e61779848b GIT binary patch literal 6454 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 OzLoOnxA;96Gyef~xXoz* literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/MatchingService$ResultatMatching.class b/target/classes/dev/lions/unionflow/server/service/MatchingService$ResultatMatching.class new file mode 100644 index 0000000000000000000000000000000000000000..9f7bdf13cde67d5ce1c419b250e8065b42297035 GIT binary patch literal 745 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/MembreImportExportService$ResultatImport.class b/target/classes/dev/lions/unionflow/server/service/MembreImportExportService$ResultatImport.class new file mode 100644 index 0000000000000000000000000000000000000000..fc27610453de06174ee2c0286cc993ec89f69ec1 GIT binary patch literal 811 zcmbtS%WhLK5Is)Pq)9^?%9}z_r7nO){Q(VKPzprcwi1cDcakA><=B<&oAO(%kXY~m zd=z5rn*|S139*?oXU6mPx9?xR0(gNZEz|_wO=%&Gb~eb;GJG)ZN(S-^@)`7m#wW+@1Wfxx=S zWaO2Ol#JcVaO6FeUZ6!;VMnjX`O2t`EOdzb0_`COt5|vx*!|7juXrv$j9%N>{sD(< z%<~AL>N6F5^GDz{i%^$ZPNS1mmgPQPjZSm!W?x$!4+QE@pPdOb4&9VGsN+@_8|ZQE zLR&g1=VS6CH8$MzX5l8voGGspUm6?nTytN?#Wj7TyZp;Rc=FEBm3~66bt=65*GulD zE%O-$){;LoDg+8GJQC>tPdj|*Uw??e>IsGT)`f^2@XGint}w2k$r|5%iK|O&qr=uU z_SMj3?Z!`hojGq8J(f55h0L7uJ^zf&PdVf^%O;yqe~CM+rxdB+UEE`=<37t89^fHk PDkQLl$7o|a+qZrI9`fGS literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/MembreImportExportService.class b/target/classes/dev/lions/unionflow/server/service/MembreImportExportService.class new file mode 100644 index 0000000000000000000000000000000000000000..bcb4d5177dfcf14d8f4a54eee25c70916c094f81 GIT binary patch literal 31515 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/MembreService.class b/target/classes/dev/lions/unionflow/server/service/MembreService.class new file mode 100644 index 0000000000000000000000000000000000000000..0f993fac0cccd78e0b2b02ed13a97ba145df026e GIT binary patch literal 33415 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 Cp&~T^ literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/NotificationHistoryService$NotificationHistoryEntry.class b/target/classes/dev/lions/unionflow/server/service/NotificationHistoryService$NotificationHistoryEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..19db07f79319898bc7e9439572710067751ad2d6 GIT binary patch literal 3207 zcmcJQ-%lJ>6vw}JVRzYeYH?ez6_B=2+y!JTQd@LRTtJ4wK^|;lImTyW!)CG=9ZMRO0Ht;+Jc?M~*3DfFQ~iH8L) zwPqiUwG70ONI{^Vmpo=58Mw#TJ#L@_DIPt}Zq`6LaI@@=7`QKRN7x-RUkmd&+#ku+_vnRoIoJ5Bv}9gd#~l+N*C)1Y3~cBuQF28~wv2IcJv?hVN_u?kRng|JdkU7%W#qVz^j zdv;Zo3hCvkfbfyBO&2Ovw`8A_TqF85}AOL78- zG;6AoOiw+OIGy~G?fLT+&&N>`MH~GOQi!n_sTE@tDogTHmUO2q2~Js3nzAG@Wl3Ag zlBmdb(Tr3x>3fholdK>cAHIpM8!GHU`X*H8RUD$W5d`2c9#Z2O^iU5cUnQ6r*+ots z9;ujI!^CEj8O>z-bFjE z5yX46a6yc`GPKMZ76$5m9ioT0C zF?C&SYiHXZtC@IXblsQ^vvy9Zh9l9GTbh#+cpA@y$Pg2QA3+dXUj5 z$d3f_QxgytK7+iV1u>%_KWjnqdJr=T@(Y3d+606v4}&aXiN1U5OAX^ukl(Z*=ka2g zyyuOKM?rokkUyG$aOq=^m+-O%Q*5SQ8K~{`@O>MQ!~8E|Seq^$}RbYjkaW)OwxlB{jbKA9*QW Ax&QzG literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/NotificationHistoryService.class b/target/classes/dev/lions/unionflow/server/service/NotificationHistoryService.class new file mode 100644 index 0000000000000000000000000000000000000000..6f074540a778f1eb4a8060ef4a80b21fd31cb57c GIT binary patch literal 11120 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 e?M;H_Tk-?>5z1sbzXtrcut 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/OrganisationService.class b/target/classes/dev/lions/unionflow/server/service/OrganisationService.class new file mode 100644 index 0000000000000000000000000000000000000000..9868e8c0ae0a00207d99a63153a2a679960acfb7 GIT binary patch literal 15772 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/PaiementService.class b/target/classes/dev/lions/unionflow/server/service/PaiementService.class new file mode 100644 index 0000000000000000000000000000000000000000..07d1b40a044797f0633dbc478dfa0c70a7c038f0 GIT binary patch literal 11483 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/PermissionService.class b/target/classes/dev/lions/unionflow/server/service/PermissionService.class new file mode 100644 index 0000000000000000000000000000000000000000..ac4c2380322a0f53e8ac8b8147ab2c620e7f1b33 GIT binary patch literal 6084 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/PreferencesNotificationService.class b/target/classes/dev/lions/unionflow/server/service/PreferencesNotificationService.class new file mode 100644 index 0000000000000000000000000000000000000000..1fe9b2fa91af73def9a5ea7556574acc4274c2bc GIT binary patch literal 6023 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 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/PropositionAideService.class b/target/classes/dev/lions/unionflow/server/service/PropositionAideService.class new file mode 100644 index 0000000000000000000000000000000000000000..d533071ff94b26edf4227ade12934491bd3e6bb4 GIT binary patch literal 18294 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 WH@8TAq&`;2Bl%-oTlh40!2beW>A8Xc literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/RoleService.class b/target/classes/dev/lions/unionflow/server/service/RoleService.class new file mode 100644 index 0000000000000000000000000000000000000000..17391fdd66fa1ec5733d396dd5372872df73e8f7 GIT binary patch literal 6454 zcmcIod3+Sr9sj-r*v&eDB?^iMt_mjMS|pwXsNs^}5|j;5&>oZBNixY~CeF+TQd@i4 z)*kk50kOr_w$jssE(opIrl;EbzU+P9_hoIr@6GJ)B-zCHN0U!7GjD#s-}}Db@B90` z-YjT!poBldF zw5hpEV8#X~ZzOOk=EZQDz!{UdSHpaqE>PEN_<_F#mN(H8r7&%cP=^bLmIA5o;>f~H_lR8Zi8GB12rw>>>4`I7Fe-0N0r9E$}~8{E;u ziX*9fW5G_@PGQ*43n_Q>h-Iu!E%p*JnBs%_fU&``bGm<>>G$!@^L5)N z@h+Sks;X9HSFgh+N!iT;^Qx&Rhi=hOkJ$-qMJ9%;1y)Vr&`DBS!!{ZDQ)T1}uFD1O zq~xYS-Lxc&wrjXXVn}#=PySz{yE1+|G-T09!VDR%XL|J17?9y+Mjd-4h=gS;O zaGe>m+hBf6(HCfVVTt;Y;TLOoiIkpA>5Yb$b4``~QsY()FOwRxX?)zPS7^8muOvKb zb(peRZ=sWPH`iz0R3HcEl~}HdK)dZ5vLlg3U#;OaxSbL-T1|6)GyzJA&0SLUbsAoe zH_-i@v~y(02==ZPPBlfHi0ny=N=gdIn>4%`Z=p2;DZO9~pKkV4j0NEKY1oeg+{y_q z+P1KYO$%UDN8vlD;ZD4b28l2FQlMoz7^5oOucZGDX}C+qvz`{WIeD|kl=Ux;cM2>H zYTBUbm2X##hY%#RMx#$!~5`l z=0uQnm7I^X!|Y2cJ6f1!ByI&qu(7U*k}KSrrj3QoR!%h&4UxG*BdqHd_l=;R^0Fhm z9b_7eE%q5o$i^#c_S(8%a4E2+lqbrWQee$A$=te*&S^nW!%^JNn3T$5W%79eA_gJK z1@8S-!cplSLXerK6ndIKKceBIc#xe~(C9=VNsi2RBCWn0NCxOTor2fKQryGMv;`m2 z@Nv1?dTFH1vVu2~(Ja$0&nMxob?QX)U2%L$;4E3s*$sK@pT_Sp4Q#9XWuAOi!y~eD zsy97ZV!~*~@R-246_$m46L(PGC^t=Ue4Y(L`yh{8>Y$X$8*C0jY$^7(s;5nPB*^GJ zgRIMsVp9TN!dGJWvcTMm_4N*C*16$)6<=d{AJ(mcvAu^!s;1phs)u5#V0=TvH}NeV zX_(&1F;Y50)sk#8UGW{Z=RwMc=&E-zG-m08-Fbb{N>z3$GzrtVbn@HdrCl%G;rN@K zf}JnBgT3kZmD7Ur;UrRWJN%(s>5rZ|x5IpEzzn$oK8{Tlh!-{RFxAF=FUuApXKXfQ zGn7;j432**Bz&H~>5~8t_XfiyZ#*NNb9lekPq)eKo^3Aqk$wyi*DyF!&{Vb zoPoknYb0jjaw8*S6y?5EMf`?{e5rSh4232(=$4f=eWP{GEc`l-KhgO+O9WNLKgTAQ z1utdnm8C6}A3fYZdT4b@B=8sfEr!32IYxv=Gm0);mfllZ{NM2pZV>D&OG5&W{r_Dt zrJu&U47?w8llX6- zDQY$RRBDPEj>SbxU}2E9HtUz#-AajTHOgLDaPw`qZNpWc9}F)h5^Qy!W_!exDm6qg zQP1XJ0?Dh4wJi4WpC(Ic*Q+@*B9pqt}qjkKS#cM*1odRvb7|)2~ z#x!cqL6W0$snyJ@{HbdY$I$Qq&bW^cHJm6g44lPZIRXmuD@v%A=2a@rTyhlWj8|Ep zR3sJ(;9R7X@_9I)W=e=J;Fz5M99QmrF&N39HjZbg(s9t z(8lix*Wz*ocYXjPgiAnp7M{%|JV#j(D|oA;d*TYjrJQ62g$_ypIZj@JZ!A3y6DVTv zVKjCuC;VkQyzJT1hl;3imi~}DmF3>SMmG77RI2RF4>HQ$Y2@22dqU0f3xU9 zCmpa8J-oAV9fol|ZsFtT`KYi@oqY>hL&$puB=tA zBx|2QM~t0=z6}2r_dOa}wCc+3z4757IGr zl8y&RnnPHOyO^tY<1(&yJ;}0_bjjkdT0<(pR`yC~NP&gSXqmS)6|$vCwq!zXDj_v4 zqOL4Wb5G#$T}zJP2gmVanR7oG@2++=6=Oto?ktn5|E#L|oh5aq$j>=n4SrGf@-O-P zEB>uu`The9{}KNrs(<520xLfYA!dk~@>wV;F-yd$!w(vw4vpfJD;Ee&%oa&3_#byi B7|Z|w literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/TrendAnalysisService$StatistiquesDTO.class b/target/classes/dev/lions/unionflow/server/service/TrendAnalysisService$StatistiquesDTO.class new file mode 100644 index 0000000000000000000000000000000000000000..90d6cca3317ddf5151269e9175555ef251df6404 GIT binary patch literal 1170 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 T=keV6N??;>XrtP!IxywGe7@eD literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/service/WaveService.class b/target/classes/dev/lions/unionflow/server/service/WaveService.class new file mode 100644 index 0000000000000000000000000000000000000000..05f3555eb8ba388569782930d9db45eca38934d8 GIT binary patch literal 15654 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 Qz)%jSZc`sqx6_pW2g#0l1poj5 literal 0 HcmV?d00001 diff --git a/target/classes/dev/lions/unionflow/server/util/IdConverter.class b/target/classes/dev/lions/unionflow/server/util/IdConverter.class new file mode 100644 index 0000000000000000000000000000000000000000..6c68d2bc295af8cdc71212377913f742b1953a0e GIT binary patch literal 3395 zcmb7GYjYD-7=8}DB%}+JMaxA+ZbE5@Noav;RkQ`PB?Y7f6cpHGPur!*Zrt4z>s|4J zig&#G(V2eG4>~%v!#Fzd#ToyAGyWDweanMvMr-t(U4Jn!YaC%^xF?H2&| zcEZg!Q5NPV$c~YQxz%gY4O<1m>1+4q`4b#@zG3@xt~Uy{ICwQpjc*h_fi2h;!&ZTnLGOn7@_C8!XAWW8ru8k7U$VK&2cEvjT!nJvOzvK9x?;7P{=}$POm%tUg8^b${VEA-d z_LMUz)765hHtR!1!FSwg)y3Zv_$*rYHR~$iBP`US^tR<3XR=a`zJ9f?7wL8jHmWD8 zf%%z;ef@$(R5geJqgWiVeA&0E6>rD!A+N7Ui8!vZ#|pZw4cazud(rSb>BaD=z+H>H zZNxu@&-peiZOV2~brxBch>9~1$1?TjgYy_?-IRi|rRyiezvo~X68ue2g3YHH8_>p) zRyCqr!P#Teag1pSV&6GQmzoA8Z*ENSV!(GWati6UifFrwRu{m`O zJAheq&SBSgG;InF?I158$<4b6nBl=$bYOQtpsNZ=ZI(6!r(SVy!|mw79v<02B73>o zgne9Zf@merUflDaJTDPa@!i-}42Iw7{(D&Y-hBNa@0i2UnionFlow", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": true, + "rememberMe": true, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": true, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRoles": ["offline_access", "uma_authorization", "default-roles-unionflow"], + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "supportedLocales": ["fr", "en"], + "defaultLocale": "fr", + "internationalizationEnabled": true, + "clients": [ + { + "clientId": "unionflow-server", + "name": "UnionFlow Server API", + "description": "Client pour l'API serveur UnionFlow", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "dev-secret", + "redirectUris": ["http://localhost:8080/*"], + "webOrigins": ["http://localhost:8080", "http://localhost:3000"], + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "given_name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "family_name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ], + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + }, + { + "clientId": "unionflow-mobile", + "name": "UnionFlow Mobile App", + "description": "Client pour l'application mobile UnionFlow", + "enabled": true, + "publicClient": true, + "redirectUris": ["unionflow://callback", "http://localhost:3000/callback"], + "webOrigins": ["*"], + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "fullScopeAllowed": true, + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] + } + ], + "roles": { + "realm": [ + { + "name": "ADMIN", + "description": "Administrateur système avec tous les droits", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "PRESIDENT", + "description": "Président de l'union avec droits de gestion complète", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "SECRETAIRE", + "description": "Secrétaire avec droits de gestion des membres et événements", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "TRESORIER", + "description": "Trésorier avec droits de gestion financière", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "GESTIONNAIRE_MEMBRE", + "description": "Gestionnaire des membres avec droits de CRUD sur les membres", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "ORGANISATEUR_EVENEMENT", + "description": "Organisateur d'événements avec droits de gestion des événements", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + }, + { + "name": "MEMBRE", + "description": "Membre standard avec droits de consultation", + "composite": false, + "clientRole": false, + "containerId": "unionflow" + } + ] + }, + "users": [ + { + "username": "admin", + "enabled": true, + "emailVerified": true, + "firstName": "Administrateur", + "lastName": "Système", + "email": "admin@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "admin123", + "temporary": false + } + ], + "realmRoles": ["ADMIN", "PRESIDENT"], + "clientRoles": {} + }, + { + "username": "president", + "enabled": true, + "emailVerified": true, + "firstName": "Jean", + "lastName": "Dupont", + "email": "president@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "president123", + "temporary": false + } + ], + "realmRoles": ["PRESIDENT", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "secretaire", + "enabled": true, + "emailVerified": true, + "firstName": "Marie", + "lastName": "Martin", + "email": "secretaire@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "secretaire123", + "temporary": false + } + ], + "realmRoles": ["SECRETAIRE", "GESTIONNAIRE_MEMBRE", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "tresorier", + "enabled": true, + "emailVerified": true, + "firstName": "Pierre", + "lastName": "Durand", + "email": "tresorier@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "tresorier123", + "temporary": false + } + ], + "realmRoles": ["TRESORIER", "MEMBRE"], + "clientRoles": {} + }, + { + "username": "membre1", + "enabled": true, + "emailVerified": true, + "firstName": "Sophie", + "lastName": "Bernard", + "email": "membre1@unionflow.dev", + "credentials": [ + { + "type": "password", + "value": "membre123", + "temporary": false + } + ], + "realmRoles": ["MEMBRE"], + "clientRoles": {} + } + ], + "groups": [ + { + "name": "Administration", + "path": "/Administration", + "realmRoles": ["ADMIN"], + "subGroups": [] + }, + { + "name": "Bureau", + "path": "/Bureau", + "realmRoles": ["PRESIDENT", "SECRETAIRE", "TRESORIER"], + "subGroups": [] + }, + { + "name": "Gestionnaires", + "path": "/Gestionnaires", + "realmRoles": ["GESTIONNAIRE_MEMBRE", "ORGANISATEUR_EVENEMENT"], + "subGroups": [] + }, + { + "name": "Membres", + "path": "/Membres", + "realmRoles": ["MEMBRE"], + "subGroups": [] + } + ] +} diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..5572190 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,161 @@ +dev\lions\unionflow\server\entity\RolePermission$RolePermissionBuilder.class +dev\lions\unionflow\server\resource\ComptabiliteResource.class +dev\lions\unionflow\server\resource\NotificationResource.class +dev\lions\unionflow\server\resource\DocumentResource$ErrorResponse.class +dev\lions\unionflow\server\service\AnalyticsService.class +dev\lions\unionflow\server\service\DocumentService.class +dev\lions\unionflow\server\entity\MembreRole.class +dev\lions\unionflow\server\entity\WebhookWave$WebhookWaveBuilder.class +dev\lions\unionflow\server\resource\AnalyticsResource.class +dev\lions\unionflow\server\resource\EvenementResource.class +dev\lions\unionflow\server\service\NotificationHistoryService.class +dev\lions\unionflow\server\service\PermissionService.class +de\lions\unionflow\server\auth\AuthCallbackResource.class +dev\lions\unionflow\server\entity\PaiementEvenement$PaiementEvenementBuilder.class +dev\lions\unionflow\server\entity\Role$RoleBuilder.class +dev\lions\unionflow\server\entity\Evenement$EvenementBuilder.class +dev\lions\unionflow\server\service\MatchingService$ResultatMatching.class +dev\lions\unionflow\server\repository\RoleRepository.class +dev\lions\unionflow\server\entity\Organisation$OrganisationBuilder.class +dev\lions\unionflow\server\service\AuditService.class +dev\lions\unionflow\server\dto\EvenementMobileDTO$EvenementMobileDTOBuilder.class +dev\lions\unionflow\server\service\MatchingService.class +dev\lions\unionflow\server\security\SecurityConfig$Permissions.class +dev\lions\unionflow\server\entity\Adresse$AdresseBuilder.class +dev\lions\unionflow\server\entity\PaiementEvenement.class +dev\lions\unionflow\server\entity\CompteWave.class +dev\lions\unionflow\server\repository\CompteWaveRepository.class +dev\lions\unionflow\server\entity\Permission$PermissionBuilder.class +dev\lions\unionflow\server\entity\Membre.class +dev\lions\unionflow\server\repository\AdresseRepository.class +dev\lions\unionflow\server\entity\Adhesion.class +dev\lions\unionflow\server\service\NotificationHistoryService$NotificationHistoryEntry.class +dev\lions\unionflow\server\entity\Organisation.class +dev\lions\unionflow\server\service\AdhesionService.class +dev\lions\unionflow\server\service\DashboardServiceImpl.class +dev\lions\unionflow\server\service\AnalyticsService$1.class +dev\lions\unionflow\server\entity\Paiement$PaiementBuilder.class +dev\lions\unionflow\server\entity\Paiement.class +dev\lions\unionflow\server\service\PreferencesNotificationService.class +dev\lions\unionflow\server\entity\ConfigurationWave.class +dev\lions\unionflow\server\entity\PieceJointe$PieceJointeBuilder.class +dev\lions\unionflow\server\entity\Notification$NotificationBuilder.class +dev\lions\unionflow\server\repository\TypeOrganisationRepository.class +dev\lions\unionflow\server\repository\MembreRepository.class +dev\lions\unionflow\server\entity\DemandeAide.class +dev\lions\unionflow\server\repository\EcritureComptableRepository.class +dev\lions\unionflow\server\service\WaveService.class +dev\lions\unionflow\server\repository\PaiementRepository.class +dev\lions\unionflow\server\service\ComptabiliteService.class +dev\lions\unionflow\server\resource\WaveResource.class +dev\lions\unionflow\server\resource\AuditResource.class +dev\lions\unionflow\server\repository\LigneEcritureRepository.class +dev\lions\unionflow\server\security\SecurityConfig$Roles.class +dev\lions\unionflow\server\resource\MembreResource.class +dev\lions\unionflow\server\repository\AuditLogRepository.class +dev\lions\unionflow\server\service\TrendAnalysisService$TendanceDTO.class +dev\lions\unionflow\server\service\KPICalculatorService.class +dev\lions\unionflow\server\entity\TransactionWave.class +dev\lions\unionflow\server\entity\Role.class +dev\lions\unionflow\server\entity\CompteComptable$CompteComptableBuilder.class +dev\lions\unionflow\server\resource\DashboardResource.class +dev\lions\unionflow\server\entity\JournalComptable.class +dev\lions\unionflow\server\service\NotificationService.class +dev\lions\unionflow\server\entity\PaiementAide.class +dev\lions\unionflow\server\resource\NotificationResource$NotificationGroupeeRequest.class +dev\lions\unionflow\server\entity\TemplateNotification$TemplateNotificationBuilder.class +dev\lions\unionflow\server\entity\InscriptionEvenement.class +dev\lions\unionflow\server\service\DemandeAideService$1.class +dev\lions\unionflow\server\entity\Role$TypeRole.class +dev\lions\unionflow\server\entity\PaiementCotisation.class +dev\lions\unionflow\server\repository\TemplateNotificationRepository.class +dev\lions\unionflow\server\resource\PreferencesResource.class +dev\lions\unionflow\server\resource\TypeOrganisationResource.class +dev\lions\unionflow\server\service\TrendAnalysisService$1.class +dev\lions\unionflow\server\entity\CompteComptable.class +dev\lions\unionflow\server\entity\Cotisation.class +dev\lions\unionflow\server\entity\PaiementAide$PaiementAideBuilder.class +dev\lions\unionflow\server\resource\AdhesionResource.class +dev\lions\unionflow\server\entity\Document$DocumentBuilder.class +dev\lions\unionflow\server\service\MembreImportExportService$ResultatImport.class +dev\lions\unionflow\server\entity\PieceJointe.class +dev\lions\unionflow\server\service\PropositionAideService.class +dev\lions\unionflow\server\entity\WebhookWave.class +dev\lions\unionflow\server\entity\Notification.class +dev\lions\unionflow\server\entity\InscriptionEvenement$StatutInscription.class +dev\lions\unionflow\server\entity\LigneEcriture.class +dev\lions\unionflow\server\resource\ComptabiliteResource$ErrorResponse.class +dev\lions\unionflow\server\entity\LigneEcriture$LigneEcritureBuilder.class +dev\lions\unionflow\server\service\TrendAnalysisService.class +dev\lions\unionflow\server\entity\CompteWave$CompteWaveBuilder.class +dev\lions\unionflow\server\exception\JsonProcessingExceptionMapper.class +dev\lions\unionflow\server\entity\Document.class +dev\lions\unionflow\server\repository\DocumentRepository.class +dev\lions\unionflow\server\entity\MembreRole$MembreRoleBuilder.class +dev\lions\unionflow\server\service\CotisationService.class +dev\lions\unionflow\server\entity\Cotisation$CotisationBuilder.class +dev\lions\unionflow\server\repository\ConfigurationWaveRepository.class +dev\lions\unionflow\server\repository\OrganisationRepository.class +dev\lions\unionflow\server\repository\PieceJointeRepository.class +dev\lions\unionflow\server\entity\AuditLog.class +dev\lions\unionflow\server\security\SecurityConfig.class +dev\lions\unionflow\server\resource\NotificationResource$ErrorResponse.class +dev\lions\unionflow\server\resource\OrganisationResource.class +dev\lions\unionflow\server\entity\InscriptionEvenement$InscriptionEvenementBuilder.class +dev\lions\unionflow\server\entity\PaiementCotisation$PaiementCotisationBuilder.class +dev\lions\unionflow\server\service\ExportService.class +dev\lions\unionflow\server\service\RoleService.class +dev\lions\unionflow\server\service\EvenementService.class +dev\lions\unionflow\server\entity\Evenement$StatutEvenement.class +dev\lions\unionflow\server\repository\AdhesionRepository.class +dev\lions\unionflow\server\resource\HealthResource.class +dev\lions\unionflow\server\repository\NotificationRepository.class +dev\lions\unionflow\server\entity\TemplateNotification.class +dev\lions\unionflow\server\entity\PaiementAdhesion$PaiementAdhesionBuilder.class +dev\lions\unionflow\server\service\AdresseService.class +dev\lions\unionflow\server\repository\RolePermissionRepository.class +dev\lions\unionflow\server\resource\PaiementResource.class +dev\lions\unionflow\server\repository\WebhookWaveRepository.class +dev\lions\unionflow\server\resource\DocumentResource.class +dev\lions\unionflow\server\entity\PaiementAdhesion.class +dev\lions\unionflow\server\service\NotificationHistoryService$NotificationHistoryEntry$Builder.class +dev\lions\unionflow\server\entity\Permission.class +dev\lions\unionflow\server\resource\PaiementResource$ErrorResponse.class +dev\lions\unionflow\server\entity\TypeOrganisationEntity.class +dev\lions\unionflow\server\repository\BaseRepository.class +dev\lions\unionflow\server\resource\WaveResource$ErrorResponse.class +dev\lions\unionflow\server\entity\BaseEntity.class +dev\lions\unionflow\server\service\MembreImportExportService$1.class +dev\lions\unionflow\server\service\MembreImportExportService.class +dev\lions\unionflow\server\service\PaiementService.class +dev\lions\unionflow\server\service\KeycloakService.class +dev\lions\unionflow\server\entity\DemandeAide$DemandeAideBuilder.class +dev\lions\unionflow\server\util\IdConverter.class +dev\lions\unionflow\server\repository\CotisationRepository.class +dev\lions\unionflow\server\repository\DemandeAideRepository.class +dev\lions\unionflow\server\entity\EcritureComptable$EcritureComptableBuilder.class +dev\lions\unionflow\server\resource\CotisationResource.class +dev\lions\unionflow\server\entity\Adresse.class +dev\lions\unionflow\server\entity\TransactionWave$TransactionWaveBuilder.class +dev\lions\unionflow\server\entity\Evenement$TypeEvenement.class +dev\lions\unionflow\server\dto\EvenementMobileDTO.class +dev\lions\unionflow\server\entity\Adhesion$AdhesionBuilder.class +dev\lions\unionflow\server\entity\Membre$MembreBuilder.class +dev\lions\unionflow\server\repository\MembreRoleRepository.class +dev\lions\unionflow\server\resource\ExportResource.class +dev\lions\unionflow\server\service\DemandeAideService.class +dev\lions\unionflow\server\entity\RolePermission.class +dev\lions\unionflow\server\repository\TransactionWaveRepository.class +dev\lions\unionflow\server\repository\CompteComptableRepository.class +dev\lions\unionflow\server\repository\EvenementRepository.class +dev\lions\unionflow\server\entity\EcritureComptable.class +dev\lions\unionflow\server\service\MembreService.class +dev\lions\unionflow\server\service\OrganisationService.class +dev\lions\unionflow\server\service\TrendAnalysisService$StatistiquesDTO.class +dev\lions\unionflow\server\service\TypeOrganisationService.class +dev\lions\unionflow\server\entity\ConfigurationWave$ConfigurationWaveBuilder.class +dev\lions\unionflow\server\entity\Evenement.class +dev\lions\unionflow\server\entity\JournalComptable$JournalComptableBuilder.class +dev\lions\unionflow\server\repository\JournalComptableRepository.class +dev\lions\unionflow\server\repository\PermissionRepository.class +dev\lions\unionflow\server\UnionFlowServerApplication.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..70c75ae --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,109 @@ +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\AnalyticsResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\HealthResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\DashboardServiceImpl.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\NotificationService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\PieceJointe.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\CompteWaveRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\RoleRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\MembreRole.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\CompteComptable.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\de\lions\unionflow\server\auth\AuthCallbackResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\WebhookWave.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\MembreImportExportService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\EcritureComptableRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\ConfigurationWave.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\UnionFlowServerApplication.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\TrendAnalysisService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Paiement.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\AdhesionRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\AuditService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\ConfigurationWaveRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\AuditResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\PaiementAdhesion.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\CotisationResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\NotificationHistoryService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\DemandeAide.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\RolePermissionRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\exception\JsonProcessingExceptionMapper.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\InscriptionEvenement.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\MembreRoleRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\DemandeAideRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Membre.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\PermissionRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\EvenementResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\OrganisationService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\OrganisationRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\PaiementResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\PropositionAideService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\OrganisationResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\AdresseService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\AuditLog.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\EvenementService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\ComptabiliteResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\PieceJointeRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\BaseEntity.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Permission.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\security\SecurityConfig.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\RoleService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\PreferencesNotificationService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\PaiementCotisation.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Organisation.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\PaiementAide.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Notification.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\PaiementRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\AdhesionService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\RolePermission.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\CompteWave.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\BaseRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\TransactionWaveRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\CotisationRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\PaiementService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\DocumentRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\PreferencesResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Adhesion.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\LigneEcriture.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\ExportResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\TemplateNotificationRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\MembreResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\PaiementEvenement.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\AuditLogRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\DocumentResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\DemandeAideService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Role.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\WebhookWaveRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\AnalyticsService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\CompteComptableRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Adresse.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\AdhesionResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\DocumentService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\AdresseRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\LigneEcritureRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\JournalComptable.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\TypeOrganisationService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\TypeOrganisationRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\MatchingService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\ComptabiliteService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\NotificationResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Document.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\TypeOrganisationResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\KPICalculatorService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\dto\EvenementMobileDTO.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\PermissionService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Evenement.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\DashboardResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\TransactionWave.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\MembreService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\EcritureComptable.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\TemplateNotification.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\JournalComptableRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\CotisationService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\WaveService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\resource\WaveResource.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\TypeOrganisationEntity.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\KeycloakService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\EvenementRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\util\IdConverter.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\service\ExportService.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\MembreRepository.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\entity\Cotisation.java +C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-server-impl-quarkus\src\main\java\dev\lions\unionflow\server\repository\NotificationRepository.java diff --git a/target/test-classes/dev/lions/unionflow/server/resource/EvenementResourceTest.class b/target/test-classes/dev/lions/unionflow/server/resource/EvenementResourceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..9b086f3d8c1796651a3e319318928a9a31a70e25 GIT binary patch literal 14393 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 bRZ|d~TKpIMUP=r2uQ0m5@jv)qH0A#Rx~SNg literal 0 HcmV?d00001 diff --git a/target/test-classes/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.class b/target/test-classes/dev/lions/unionflow/server/resource/MembreResourceAdvancedSearchTest.class new file mode 100644 index 0000000000000000000000000000000000000000..40c6eb3b9e00fdf35eed9c7b74ebf00e356009ae GIT binary patch literal 10597 zcmcIq3wRXQb^fm;*hLH=Rv562VKFZOMhlpSv0`E&345_#VP#<;HW}>>(!lP_GBYa> zN!&*g*RdVfkLK0-l{j%snzXT#$`Ub78k*LzvD+q1-8j#tX%pw&HqFy^`rnz^)vR_` z7Q%e~(9GPqbI&>ddEEb=^)s)Ydr-J1X@$tU@Wbhh8@cpyc|rMM`N~T4Qf`* z(rhzlCAC8Tb=cQi^DxNvvQaP*AU ztJ^wtX*LYgQ61?E+TCti{jo#ziNn8H`H72!gqs2`sqnhXN@*6~PaBj7R@2c!U{$+r z#*XAv>u}DFNzI63GDkIrVsg2J|G>{`a&$fYX6Le+rKU2vadjao8k1&*3tMJdqkc0J z*LSz=KG59S5$~kE71U|f?QB{d>QpltSC4mhC3>P0IW(FwbtihzG?EeqL$jjlpq7j# zsq5)S8IDn!rKdEz7E7@#gi8gsPv9{bIVs2VqsNb8FKY8nP_P^;1VVi|Jx$~UZfaOP zQ4maG$>m4H*i_CU+d5ffg?dX^E1z@p;C?Q3f@>yLp>&knicFo3n6d=Okl@3 zhZhZQ!!;r76u5|K&7^Qt+s;{9D#ilsG<)|7u0@<;`}IMMcx;)Zq-s*Yxf03`J3eaKnk3bbnZVjwtk$uY*OBtmJi zjw^U8-bVMib5Z)

CC0cok%T65{PfP=}hjUc(+WN+r|}QKZMJF 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 DxAkL7 literal 0 HcmV?d00001 diff --git a/target/test-classes/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.class b/target/test-classes/dev/lions/unionflow/server/resource/MembreResourceImportExportTest.class new file mode 100644 index 0000000000000000000000000000000000000000..09f955478f202fa0b79f46a0b103dce79874a250 GIT binary patch literal 13756 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 WN@;T;#s-?F&BLtowUe|us`)=6%zY*R literal 0 HcmV?d00001 diff --git a/target/test-classes/dev/lions/unionflow/server/resource/OrganisationResourceTest.class b/target/test-classes/dev/lions/unionflow/server/resource/OrganisationResourceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..91af3bfe33790db4a9592309492f0d780a3b1e50 GIT binary patch literal 11981 zcmcIq34B!5z5f2mBr}A|Hjzz4heb(9VA!`H5R!nwgdhn)P?4LN8!|AN8E5VUs9JHW zN?WzowrV%GinvszI)c`vif>UlbJi2nGpQ>#LV2~ zEdTZUf9ITe_5Ej_2XL-D;zo&JdC=(dheOdw!k>)rt2-Rs?N1o-J|pgr8;NK#9x(iE z@f~_3l+aB!*qDCUVI)j9T!PU%^*-Gn)+0OoZCyK!fGHTZy1u>X(hc<;=Lx(m+4t>c zJQUecCm7Md2~9m>cIx4z;lVJr^XsvYKkAq%AsFYp-)O|5iI5qM?-i_Q8C356h65z; ze@qD`ZcIkZP_NM$N`$(?Mtvj_H7)%(>v)R1OOKnnKNL|4`J1gT_8M(cYo|2?a|V;d z;2h@O94RL>P~^HAsRF)Fb)&k7%v!= zwPiz?gJ_tD(`lxlkqE_&V156v?bIbnO*qKbVGx|5VKm0De@iH0tWWlK8SxG^t#VH^ zpocs4cu3u+AC6%Ez^+z3mcB1vZV6R^QcTt0LnQ?>dqSMxBz`za{HRDs7-ll2f~iqA zb-~mYy4=5$v1sx$7BU%!Dlk?X-O;$wqz8J~X%T+DZ4o?!K< z$ysO;4GXZ4ZjVHJ1s7K3r`|`T$zs9q`u6s=hUWT?=C<`phou^pskuj+dt-)EOAnR{ z%IX_Bn%AgyD>STB?aG<|W|HdHqrr{UIXKe3H(?sR8XD0=lLeCTIQ`VYY;6sN!z%0; z*Xz7Qv9%h`Lo+kBSKnn2Qv$lVIb`k^FG%A2l4K``$v_|QpXnc7g+qNG) z7&&@yhaOf9wraRYHE_lBy$SW;5)IpMDaWt^Ofc`1!>dlEw60+!MpYmH!wtg6cnca7 zr1`CsW;z+lWE#4$!wQMrf=QgKNINTGLK=2r7jc4FsRn3>8x|-MLDIpLKS5n}X9ao@ zbt6J&WdUk+G#WPah=v$0V>{cWo| zl?8TdsKD?F?8W77eC%YUtRMSxz&Kk{G+cozDQ3)wCqfCrjQ z0!m{ioJ}bLXZ76#HVe%ZPF=0x8eGee*kPE>^rg4oO-VBp_HWwM+(`56#q}CKfg5O^ z?ocGSdar7^D(#kR!=YrjLu@tfO&azq<;#N%a+5`2I5D^*+L*o@uZms^jbg-|dClZH zqlk72*9g;-i@a`&6Ic1Z%!-Lp=CKH7w1lGm%aVG0S2E#OGm?cc^Hup&!?mYx*v*w> z@njX%gT0~1stjwH9Z;ZG7H8B^w6glf)@G*kv?4OF?#9CU-t~H~!68?zZR+sl6A)i8 z8Zv!x!z2xg7;#@%_Yr3kCzUanXg43x6iId6doMcJ%lP{U9%j#Og zm+)ofcMihJC+n-O}-oi*I61aR%{ zF(PW5`!#$G4-gy}cY+NC>cvHl?6cWjU7pa?Hby36C{?YCwwOh2>fCrx;2R)iUhCmF zfQK|Zj7Kb1p)9i~-!hqQWQ?I8sRG$b{(N5-)D)YqK*!;D6pw3o0*3?@Ry|56z9m>( z)m&W70t5AHJPc1F<;GKjvj&LKW12BP>yoJ|87riQr*W7HuWN5(luj8K2m_nb?T_La z4bS46OwT}e9Kq(QlXm`4x_IzBN$K iUerxcBYxQW$LAxv8ry%EX* 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 C8VGp+ literal 0 HcmV?d00001 diff --git a/target/test-classes/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.class b/target/test-classes/dev/lions/unionflow/server/service/MembreServiceAdvancedSearchTest.class new file mode 100644 index 0000000000000000000000000000000000000000..5b8f4ec4de14f15923511db03a587b510f409673 GIT binary patch literal 17211 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 literal 0 HcmV?d00001