From 74161dfc89b2665ba4b103fc47028c1999a28fa5 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 15 Mar 2026 02:41:05 +0000 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20r=C3=A9cup=C3=A9ration=20seuil?= =?UTF-8?q?=20LCB-FT=20depuis=20API=20(T018)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 Mobile - Section 4.1 Épargne Nouveaux fichiers : - SeuilLcbFtModel : modèle pour seuil depuis API - ParametresLcbFtRepository : appel /api/parametres-lcb-ft/seuil-justification - @lazySingleton pour injection GetIt Modifications : - DepotEpargneDialog : charge seuil au initState, fallback 500k XOF - RetraitEpargneDialog : idem - Remplace constante kSeuilOrigineFondsObligatoireXOF par valeur dynamique Impact : - Seuil LCB-FT maintenant configurable par organisation - Fallback automatique si API échoue - Messages utilisateur avec montant dynamique Spec : specs/001-mutuelles-anti-blanchiment/spec.md Progression : 16/27 tâches (59%) Signed-off-by: lions dev Team --- .../core/data/models/seuil_lcb_ft_model.dart | 26 ++++++ .../parametres_lcb_ft_repository.dart | 84 +++++++++++++++++++ .../widgets/depot_epargne_dialog.dart | 25 +++++- .../widgets/retrait_epargne_dialog.dart | 25 +++++- 4 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 unionflow/unionflow-mobile-apps/lib/core/data/models/seuil_lcb_ft_model.dart create mode 100644 unionflow/unionflow-mobile-apps/lib/core/data/repositories/parametres_lcb_ft_repository.dart diff --git a/unionflow/unionflow-mobile-apps/lib/core/data/models/seuil_lcb_ft_model.dart b/unionflow/unionflow-mobile-apps/lib/core/data/models/seuil_lcb_ft_model.dart new file mode 100644 index 0000000..ff020b4 --- /dev/null +++ b/unionflow/unionflow-mobile-apps/lib/core/data/models/seuil_lcb_ft_model.dart @@ -0,0 +1,26 @@ +/// Modèle pour le seuil LCB-FT récupéré depuis l'API. +/// Endpoint: GET /api/parametres-lcb-ft/seuil-justification +class SeuilLcbFtModel { + final double montantSeuil; + final String codeDevise; + + const SeuilLcbFtModel({ + required this.montantSeuil, + required this.codeDevise, + }); + + factory SeuilLcbFtModel.fromJson(Map json) { + return SeuilLcbFtModel( + montantSeuil: (json['montantSeuil'] as num).toDouble(), + codeDevise: json['codeDevise'] as String? ?? 'XOF', + ); + } + + /// Seuil par défaut si l'API échoue (500k XOF selon spec LCB-FT BCEAO). + factory SeuilLcbFtModel.defaultSeuil() { + return const SeuilLcbFtModel( + montantSeuil: 500000.0, + codeDevise: 'XOF', + ); + } +} diff --git a/unionflow/unionflow-mobile-apps/lib/core/data/repositories/parametres_lcb_ft_repository.dart b/unionflow/unionflow-mobile-apps/lib/core/data/repositories/parametres_lcb_ft_repository.dart new file mode 100644 index 0000000..6a99a5a --- /dev/null +++ b/unionflow/unionflow-mobile-apps/lib/core/data/repositories/parametres_lcb_ft_repository.dart @@ -0,0 +1,84 @@ +import 'package:injectable/injectable.dart'; +import 'package:unionflow_mobile_apps/core/network/api_client.dart'; +import 'package:unionflow_mobile_apps/core/utils/logger.dart'; +import '../models/seuil_lcb_ft_model.dart'; + +/// Repository pour les paramètres LCB-FT (seuils anti-blanchiment). +/// Endpoints: GET /api/parametres-lcb-ft, GET /api/parametres-lcb-ft/seuil-justification +@lazySingleton +class ParametresLcbFtRepository { + final ApiClient _apiClient; + static const String _base = '/api/parametres-lcb-ft'; + + ParametresLcbFtRepository(this._apiClient); + + /// Récupère uniquement le seuil de justification (endpoint léger). + /// Paramètres optionnels : organisationId, codeDevise (XOF par défaut). + /// Retourne le seuil par défaut (500k XOF) en cas d'erreur. + Future getSeuilJustification({ + String? organisationId, + String codeDevise = 'XOF', + }) async { + try { + final queryParams = {}; + if (organisationId != null && organisationId.isNotEmpty) { + queryParams['organisationId'] = organisationId; + } + queryParams['codeDevise'] = codeDevise; + + final response = await _apiClient.get( + '$_base/seuil-justification', + queryParameters: queryParams, + ); + + if (response.statusCode == 200 && response.data != null) { + return SeuilLcbFtModel.fromJson(response.data as Map); + } + + AppLogger.warning( + 'ParametresLcbFtRepository: getSeuilJustification status ${response.statusCode}, fallback au seuil par défaut', + ); + return SeuilLcbFtModel.defaultSeuil(); + } catch (e, st) { + AppLogger.error( + 'ParametresLcbFtRepository: getSeuilJustification échoué, fallback au seuil par défaut', + error: e, + stackTrace: st, + ); + return SeuilLcbFtModel.defaultSeuil(); + } + } + + /// Récupère les paramètres LCB-FT complets (tous les seuils + config). + /// Pour usage admin ou affichage détaillé. + Future?> getParametres({ + String? organisationId, + String codeDevise = 'XOF', + }) async { + try { + final queryParams = {}; + if (organisationId != null && organisationId.isNotEmpty) { + queryParams['organisationId'] = organisationId; + } + queryParams['codeDevise'] = codeDevise; + + final response = await _apiClient.get(_base, queryParameters: queryParams); + + if (response.statusCode == 200 && response.data != null) { + return response.data as Map; + } + + AppLogger.warning( + 'ParametresLcbFtRepository: getParametres status ${response.statusCode}', + ); + return null; + } catch (e, st) { + AppLogger.error( + 'ParametresLcbFtRepository: getParametres échoué', + error: e, + stackTrace: st, + ); + return null; + } + } +} diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart index 9a1055c..c9dfe3d 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/depot_epargne_dialog.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; +import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; @@ -34,16 +35,34 @@ class _DepotEpargneDialogState extends State { bool _waveLoading = false; _DepotMode _mode = _DepotMode.manual; late TransactionEpargneRepository _repository; + late ParametresLcbFtRepository _parametresRepository; + + /// Seuil LCB-FT récupéré depuis l'API (fallback à 500k XOF). + double _seuilLcbFt = kSeuilOrigineFondsObligatoireXOF; + bool _seuilLoaded = false; @override void initState() { super.initState(); _repository = GetIt.I(); + _parametresRepository = GetIt.I(); + _chargerSeuil(); + } + + /// Charge le seuil LCB-FT depuis l'API au chargement du dialog. + Future _chargerSeuil() async { + final seuil = await _parametresRepository.getSeuilJustification(); + if (mounted) { + setState(() { + _seuilLcbFt = seuil.montantSeuil; + _seuilLoaded = true; + }); + } } bool get _origineFondsRequis { final m = double.tryParse(_montantController.text.replaceAll(',', '.')); - return m != null && m >= kSeuilOrigineFondsObligatoireXOF; + return m != null && m >= _seuilLcbFt; } @override @@ -114,7 +133,7 @@ class _DepotEpargneDialogState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - 'L\'origine des fonds est obligatoire pour les opérations à partir de ${kSeuilOrigineFondsObligatoireXOF.toStringAsFixed(0)} XOF (LCB-FT).', + 'L\'origine des fonds est obligatoire pour les opérations à partir de ${_seuilLcbFt.toStringAsFixed(0)} XOF (LCB-FT).', ), ), ); @@ -219,7 +238,7 @@ class _DepotEpargneDialogState extends State { Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - 'Requis pour les opérations ≥ ${kSeuilOrigineFondsObligatoireXOF.toStringAsFixed(0)} XOF', + 'Requis pour les opérations ≥ ${_seuilLcbFt.toStringAsFixed(0)} XOF', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.primary, ), diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart index 2dd6a03..84a0b22 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/retrait_epargne_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; +import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; @@ -33,16 +34,34 @@ class _RetraitEpargneDialogState extends State { final _origineFondsController = TextEditingController(); bool _loading = false; late TransactionEpargneRepository _repository; + late ParametresLcbFtRepository _parametresRepository; + + /// Seuil LCB-FT récupéré depuis l'API (fallback à 500k XOF). + double _seuilLcbFt = kSeuilOrigineFondsObligatoireXOF; + bool _seuilLoaded = false; @override void initState() { super.initState(); _repository = GetIt.I(); + _parametresRepository = GetIt.I(); + _chargerSeuil(); + } + + /// Charge le seuil LCB-FT depuis l'API au chargement du dialog. + Future _chargerSeuil() async { + final seuil = await _parametresRepository.getSeuilJustification(); + if (mounted) { + setState(() { + _seuilLcbFt = seuil.montantSeuil; + _seuilLoaded = true; + }); + } } bool get _origineFondsRequis { final m = double.tryParse(_montantController.text.replaceAll(',', '.')); - return m != null && m >= kSeuilOrigineFondsObligatoireXOF; + return m != null && m >= _seuilLcbFt; } @override @@ -66,7 +85,7 @@ class _RetraitEpargneDialogState extends State { } if (_origineFondsRequis && _origineFondsController.text.trim().isEmpty) { _showSnack( - 'L\'origine des fonds est obligatoire pour les opérations à partir de ${kSeuilOrigineFondsObligatoireXOF.toStringAsFixed(0)} XOF (LCB-FT).', + 'L\'origine des fonds est obligatoire pour les opérations à partir de ${_seuilLcbFt.toStringAsFixed(0)} XOF (LCB-FT).', ); return; } @@ -160,7 +179,7 @@ class _RetraitEpargneDialogState extends State { Padding( padding: const EdgeInsets.only(top: 8), child: Text( - 'Requis pour les opérations ≥ ${kSeuilOrigineFondsObligatoireXOF.toStringAsFixed(0)} XOF', + 'Requis pour les opérations ≥ ${_seuilLcbFt.toStringAsFixed(0)} XOF', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: ColorTokens.primary), ), ),