diff --git a/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java b/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java index be96016..f97182e 100644 --- a/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java +++ b/src/main/java/dev/lions/unionflow/server/service/TypeReferenceService.java @@ -11,6 +11,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import java.text.Normalizer; +import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -157,6 +158,9 @@ public class TypeReferenceService { .modulesRequis(request.modulesRequis()) .build(); + validerLbcFtSiFinancierSolidaire( + entity.getCategorie(), entity.getModulesRequis()); + if (request.organisationId() != null) { Organisation org = organisationRepository .findById(request.organisationId()); @@ -211,6 +215,8 @@ public class TypeReferenceService { } appliquerMiseAJour(entity, request); + validerLbcFtSiFinancierSolidaire( + entity.getCategorie(), entity.getModulesRequis()); entity.setModifiePar( keycloakService.getCurrentUserEmail()); repository.update(entity); @@ -353,6 +359,26 @@ public class TypeReferenceService { } } + /** + * Réglementation BCEAO : LCB_FT obligatoire pour FINANCIER_SOLIDAIRE. + * Appelé avant persist et avant update pour couvrir les deux flux. + */ + private void validerLbcFtSiFinancierSolidaire( + String categorie, String modulesRequis) { + if (!"FINANCIER_SOLIDAIRE".equals(categorie)) { + return; + } + boolean contientLbcFt = modulesRequis != null + && Arrays.asList(modulesRequis.split(",")) + .contains("LCB_FT"); + if (!contientLbcFt) { + throw new IllegalArgumentException( + "Le module LCB_FT (anti-blanchiment) est obligatoire " + + "pour les types d'organisation de catégorie " + + "FINANCIER_SOLIDAIRE (réglementation BCEAO)"); + } + } + /** * Valide l'unicité du code dans un domaine. * diff --git a/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java index a0c5272..181f8a1 100644 --- a/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java +++ b/src/test/java/dev/lions/unionflow/server/service/TypeReferenceServiceTest.java @@ -506,4 +506,128 @@ class TypeReferenceServiceTest { assertThat(updated.getOrdreAffichage()).isEqualTo(10); assertThat(updated.getEstDefaut()).isTrue(); } + + // ================================================================ + // validerLbcFtSiFinancierSolidaire — règle BCEAO + // ================================================================ + + @Test + @TestTransaction + @DisplayName("creer FINANCIER_SOLIDAIRE sans LCB_FT lance IllegalArgumentException") + void creer_financierSolidaire_sansLbcFt_throws() { + String domaine = "TYPE_ORGANISATION"; + CreateTypeReferenceRequest request = CreateTypeReferenceRequest.builder() + .domaine(domaine) + .code("FS_" + UUID.randomUUID().toString().substring(0, 8)) + .libelle("Mutuelle test") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis("COTISATIONS,EPARGNE,CREDIT") + .organisationId(null) + .build(); + + assertThatThrownBy(() -> typeReferenceService.creer(request)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("LCB_FT"); + } + + @Test + @TestTransaction + @DisplayName("creer FINANCIER_SOLIDAIRE avec modulesRequis null lance IllegalArgumentException") + void creer_financierSolidaire_modulesNull_throws() { + CreateTypeReferenceRequest request = CreateTypeReferenceRequest.builder() + .domaine("TYPE_ORGANISATION") + .code("FS_NULL_" + UUID.randomUUID().toString().substring(0, 6)) + .libelle("Mutuelle sans modules") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis(null) + .organisationId(null) + .build(); + + assertThatThrownBy(() -> typeReferenceService.creer(request)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("LCB_FT"); + } + + @Test + @TestTransaction + @DisplayName("creer FINANCIER_SOLIDAIRE avec LCB_FT réussit") + void creer_financierSolidaire_avecLbcFt_succes() { + CreateTypeReferenceRequest request = CreateTypeReferenceRequest.builder() + .domaine("TYPE_ORGANISATION") + .code("FS_OK_" + UUID.randomUUID().toString().substring(0, 6)) + .libelle("Mutuelle conforme") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis("COTISATIONS,EPARGNE,CREDIT,LCB_FT") + .organisationId(null) + .build(); + + TypeReferenceResponse created = typeReferenceService.creer(request); + assertThat(created).isNotNull(); + assertThat(created.getModulesRequis()).contains("LCB_FT"); + } + + @Test + @TestTransaction + @DisplayName("creer catégorie ASSOCIATIF sans LCB_FT réussit (pas de contrainte)") + void creer_autreCategorie_sansLbcFt_succes() { + CreateTypeReferenceRequest request = CreateTypeReferenceRequest.builder() + .domaine("TYPE_ORGANISATION") + .code("ASSOC_" + UUID.randomUUID().toString().substring(0, 6)) + .libelle("Association simple") + .categorie("ASSOCIATIF") + .modulesRequis("COTISATIONS,EVENEMENTS") + .organisationId(null) + .build(); + + TypeReferenceResponse created = typeReferenceService.creer(request); + assertThat(created).isNotNull(); + } + + @Test + @TestTransaction + @DisplayName("modifier FINANCIER_SOLIDAIRE en retirant LCB_FT lance IllegalArgumentException") + void modifier_financierSolidaire_suppressionLbcFt_throws() { + TypeReferenceResponse created = typeReferenceService.creer( + CreateTypeReferenceRequest.builder() + .domaine("TYPE_ORGANISATION") + .code("FS_MOD_" + UUID.randomUUID().toString().substring(0, 6)) + .libelle("Mutuelle à modifier") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis("COTISATIONS,LCB_FT") + .organisationId(null) + .build()); + + UpdateTypeReferenceRequest update = new UpdateTypeReferenceRequest( + null, null, null, null, null, null, null, null, null, + null, // categorie inchangée → reste FINANCIER_SOLIDAIRE + "COTISATIONS,EPARGNE"); // LCB_FT retiré + + assertThatThrownBy(() -> typeReferenceService.modifier(created.getId(), update)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("LCB_FT"); + } + + @Test + @TestTransaction + @DisplayName("modifier FINANCIER_SOLIDAIRE sans changer modulesRequis réussit") + void modifier_financierSolidaire_sansChangerModules_succes() { + TypeReferenceResponse created = typeReferenceService.creer( + CreateTypeReferenceRequest.builder() + .domaine("TYPE_ORGANISATION") + .code("FS_NOCHG_" + UUID.randomUUID().toString().substring(0, 5)) + .libelle("Mutuelle initiale") + .categorie("FINANCIER_SOLIDAIRE") + .modulesRequis("COTISATIONS,LCB_FT") + .organisationId(null) + .build()); + + UpdateTypeReferenceRequest update = new UpdateTypeReferenceRequest( + null, "Mutuelle renommée", null, null, null, null, null, null, null, + null, // categorie inchangée + null); // modulesRequis inchangé → LCB_FT conservé en entité + + TypeReferenceResponse updated = typeReferenceService.modifier(created.getId(), update); + assertThat(updated.getLibelle()).isEqualTo("Mutuelle renommée"); + assertThat(updated.getModulesRequis()).contains("LCB_FT"); + } }