From eb729bdc5681641d9e9aedf8deedc6d03214de35 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 15 Mar 2026 02:31:35 +0000 Subject: [PATCH] feat(backend): Phase 3 - Service et Resource LCB-FT (Spec 001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémentation backend pour conformité anti-blanchiment : 1. ParametresLcbFtService - getParametres() : récupération params par org/devise - getSeuilJustification() : seuil rapide (avec cache) - saveOrUpdateParametres() : CRUD admin - Cache Quarkus pour performance - Fallback 500k XOF par défaut 2. ParametresLcbFtResource - GET /api/parametres-lcb-ft : params complets (@PermitAll) - GET /api/parametres-lcb-ft/seuil-justification : seuil léger - POST /api/parametres-lcb-ft : CRUD admin (@RolesAllowed) - OpenAPI/Swagger documentation complète 3. Validation existante confirmée - TransactionEpargneService.validerLcbFtSiSeuilAtteint() - Audit LCB-FT via AuditService.logLcbFtSeuilAtteint() Phase 3 : 67% complété (4/6 tâches, 2 optionnelles skip) - T012 ✅ Service paramètres - T013 ✅ Validation seuils (existante) - T014 ✅ Audit opérations (existant) - T017 ✅ Endpoint REST mobile - T015 ⏩ Optionnel (KYC crédit) - T016 ⏩ Optionnel (alertes) Spec : specs/001-mutuelles-anti-blanchiment/spec.md Branche : 001-mutuelles-anti-blanchiment Signed-off-by: lions dev Team --- .../resource/ParametresLcbFtResource.java | 130 +++++++++++++++ .../service/ParametresLcbFtService.java | 154 ++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 src/main/java/dev/lions/unionflow/server/resource/ParametresLcbFtResource.java create mode 100644 src/main/java/dev/lions/unionflow/server/service/ParametresLcbFtService.java diff --git a/src/main/java/dev/lions/unionflow/server/resource/ParametresLcbFtResource.java b/src/main/java/dev/lions/unionflow/server/resource/ParametresLcbFtResource.java new file mode 100644 index 0000000..019e71c --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/resource/ParametresLcbFtResource.java @@ -0,0 +1,130 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest; +import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse; +import dev.lions.unionflow.server.service.ParametresLcbFtService; +import jakarta.annotation.security.PermitAll; +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 lombok.extern.slf4j.Slf4j; +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 java.math.BigDecimal; +import java.util.UUID; + +/** + * Resource REST pour les paramètres LCB-FT (seuils anti-blanchiment). + * + * @author lions dev Team + * @version 1.0 + * @since 2026-03-13 + */ +@Path("/api/parametres-lcb-ft") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Paramètres LCB-FT", description = "Gestion des seuils anti-blanchiment (LCB-FT)") +@Slf4j +public class ParametresLcbFtResource { + + @Inject + ParametresLcbFtService parametresService; + + /** + * Récupère les paramètres LCB-FT pour une organisation et une devise. + * Endpoint utilisé par le mobile pour valider côté client. + * + * @param organisationId ID de l'organisation (optionnel, null pour paramètres plateforme) + * @param codeDevise Code devise ISO 4217 (XOF par défaut) + * @return Paramètres LCB-FT complets + */ + @GET + @PermitAll + @Operation( + summary = "Récupérer les paramètres LCB-FT", + description = "Retourne les seuils anti-blanchiment pour une organisation ou la plateforme" + ) + @APIResponse(responseCode = "200", description = "Paramètres récupérés") + @APIResponse(responseCode = "404", description = "Paramètres non configurés") + public Response getParametres( + @QueryParam("organisationId") + @Parameter(description = "ID de l'organisation (optionnel)") + String organisationId, + + @QueryParam("codeDevise") + @DefaultValue("XOF") + @Parameter(description = "Code devise (XOF par défaut)") + String codeDevise) { + + log.info("GET /api/parametres-lcb-ft?organisationId={}&codeDevise={}", + organisationId, codeDevise); + + UUID orgId = organisationId != null && !organisationId.isBlank() ? + UUID.fromString(organisationId) : null; + + ParametresLcbFtResponse params = parametresService.getParametres(orgId, codeDevise); + return Response.ok(params).build(); + } + + /** + * Récupère uniquement le seuil de justification (endpoint léger pour mobile). + * + * @param organisationId ID de l'organisation + * @param codeDevise Code devise (XOF par défaut) + * @return Montant seuil + */ + @GET + @Path("/seuil-justification") + @PermitAll + @Operation( + summary = "Récupérer le seuil de justification uniquement", + description = "Endpoint léger pour récupérer juste le montant seuil (utilisé par mobile)" + ) + @APIResponse(responseCode = "200", description = "Seuil récupéré") + public Response getSeuilJustification( + @QueryParam("organisationId") String organisationId, + @QueryParam("codeDevise") @DefaultValue("XOF") String codeDevise) { + + log.debug("GET /api/parametres-lcb-ft/seuil-justification"); + + UUID orgId = organisationId != null && !organisationId.isBlank() ? + UUID.fromString(organisationId) : null; + + BigDecimal seuil = parametresService.getSeuilJustification(orgId, codeDevise); + return Response.ok().entity(new SeuilResponse(seuil, codeDevise)).build(); + } + + /** + * Crée ou met à jour les paramètres LCB-FT (admin uniquement). + * + * @param request Paramètres à créer/mettre à jour + * @return Paramètres créés/mis à jour + */ + @POST + @RolesAllowed({ "ADMIN", "SUPER_ADMIN" }) + @Operation( + summary = "Créer ou mettre à jour les paramètres LCB-FT", + description = "Admin uniquement - Configure les seuils pour une organisation ou la plateforme" + ) + @APIResponse(responseCode = "200", description = "Paramètres sauvegardés") + public Response saveOrUpdateParametres(@Valid ParametresLcbFtRequest request) { + log.info("POST /api/parametres-lcb-ft - org={}", request.getOrganisationId()); + + ParametresLcbFtResponse saved = parametresService.saveOrUpdateParametres(request); + return Response.ok(saved).build(); + } + + /** + * DTO léger pour retourner uniquement le seuil. + */ + public record SeuilResponse( + BigDecimal montantSeuil, + String codeDevise + ) {} +} diff --git a/src/main/java/dev/lions/unionflow/server/service/ParametresLcbFtService.java b/src/main/java/dev/lions/unionflow/server/service/ParametresLcbFtService.java new file mode 100644 index 0000000..3e233f8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/service/ParametresLcbFtService.java @@ -0,0 +1,154 @@ +package dev.lions.unionflow.server.service; + +import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest; +import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse; +import dev.lions.unionflow.server.entity.Organisation; +import dev.lions.unionflow.server.entity.ParametresLcbFt; +import dev.lions.unionflow.server.repository.OrganisationRepository; +import dev.lions.unionflow.server.repository.ParametresLcbFtRepository; +import io.quarkus.cache.CacheResult; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.NotFoundException; +import org.jboss.logging.Logger; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.UUID; + +/** + * Service métier pour la gestion des paramètres LCB-FT (seuils anti-blanchiment). + * + * @author lions dev Team + * @version 1.0 + * @since 2026-03-13 + */ +@ApplicationScoped +public class ParametresLcbFtService { + + private static final Logger LOG = Logger.getLogger(ParametresLcbFtService.java); + private static final String CODE_DEVISE_DEFAULT = "XOF"; + + @Inject + ParametresLcbFtRepository parametresRepository; + + @Inject + OrganisationRepository organisationRepository; + + /** + * Récupère les paramètres LCB-FT pour une organisation et une devise. + * Avec cache pour performance (les seuils changent rarement). + * + * @param organisationId ID de l'organisation (null pour paramètres plateforme) + * @param codeDevise Code devise ISO 4217 (XOF par défaut) + * @return Paramètres LCB-FT ou paramètres plateforme par défaut + */ + @CacheResult(cacheName = "parametres-lcb-ft") + public ParametresLcbFtResponse getParametres(UUID organisationId, String codeDevise) { + if (codeDevise == null || codeDevise.isBlank()) { + codeDevise = CODE_DEVISE_DEFAULT; + } + + LOG.infof("Récupération paramètres LCB-FT pour organisation=%s, devise=%s", + organisationId, codeDevise); + + Optional params = parametresRepository + .findByOrganisationAndDevise(organisationId, codeDevise); + + if (params.isEmpty()) { + LOG.warnf("Aucun paramètre LCB-FT trouvé pour organisation=%s, devise=%s", + organisationId, codeDevise); + throw new NotFoundException( + "Paramètres LCB-FT non configurés pour cette organisation/devise"); + } + + return toDTO(params.get()); + } + + /** + * Récupère uniquement le seuil de justification (pour validation rapide). + * + * @param organisationId ID de l'organisation + * @param codeDevise Code devise (XOF par défaut) + * @return Montant seuil au-dessus duquel origine des fonds est obligatoire + */ + @CacheResult(cacheName = "seuil-justification-lcb-ft") + public BigDecimal getSeuilJustification(UUID organisationId, String codeDevise) { + if (codeDevise == null || codeDevise.isBlank()) { + codeDevise = CODE_DEVISE_DEFAULT; + } + + LOG.debugf("Récupération seuil justification LCB-FT org=%s, devise=%s", + organisationId, codeDevise); + + return parametresRepository.getSeuilJustification(organisationId, codeDevise) + .orElse(BigDecimal.valueOf(500000)); // Fallback 500k XOF + } + + /** + * Crée ou met à jour les paramètres LCB-FT pour une organisation. + */ + @Transactional + public ParametresLcbFtResponse saveOrUpdateParametres(ParametresLcbFtRequest request) { + LOG.infof("Sauvegarde paramètres LCB-FT pour organisation=%s", + request.getOrganisationId()); + + Organisation organisation = null; + if (request.getOrganisationId() != null) { + organisation = organisationRepository.findByIdOptional( + UUID.fromString(request.getOrganisationId())) + .orElseThrow(() -> new NotFoundException( + "Organisation non trouvée: " + request.getOrganisationId())); + } + + // Chercher paramètre existant + String codeDevise = request.getCodeDevise() != null ? + request.getCodeDevise() : CODE_DEVISE_DEFAULT; + + UUID orgId = organisation != null ? organisation.getId() : null; + Optional existing = parametresRepository + .findByOrganisationAndDevise(orgId, codeDevise); + + ParametresLcbFt params; + if (existing.isPresent()) { + // Mise à jour + params = existing.get(); + params.setMontantSeuilJustification(request.getMontantSeuilJustification()); + params.setMontantSeuilValidationManuelle(request.getMontantSeuilValidationManuelle()); + LOG.infof("Paramètres LCB-FT mis à jour : %s", params.getId()); + } else { + // Création + params = ParametresLcbFt.builder() + .organisation(organisation) + .codeDevise(codeDevise) + .montantSeuilJustification(request.getMontantSeuilJustification()) + .montantSeuilValidationManuelle(request.getMontantSeuilValidationManuelle()) + .build(); + parametresRepository.persist(params); + LOG.infof("Nouveaux paramètres LCB-FT créés : %s", params.getId()); + } + + return toDTO(params); + } + + /** + * Convertit l'entité en DTO de réponse. + */ + private ParametresLcbFtResponse toDTO(ParametresLcbFt params) { + return ParametresLcbFtResponse.builder() + .id(params.getId().toString()) + .organisationId(params.getOrganisation() != null ? + params.getOrganisation().getId().toString() : null) + .organisationNom(params.getOrganisation() != null ? + params.getOrganisation().getNom() : null) + .montantSeuilJustification(params.getMontantSeuilJustification()) + .montantSeuilValidationManuelle(params.getMontantSeuilValidationManuelle()) + .codeDevise(params.getCodeDevise()) + .estParametrePlateforme(params.getOrganisation() == null) + .dateCreation(params.getDateCreation()) + .dateModification(params.getDateModification()) + .actif(params.getActif()) + .build(); + } +}