From 62318476f8d16aed9d5924f8f6237b1b0e000086 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 15 Mar 2026 02:46:41 +0000 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20am=C3=A9lioration=20gestion=20e?= =?UTF-8?q?rreurs=20LCB-FT=20(T021)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 Mobile - Section 4.1 Épargne Nouveau fichier : - ErrorFormatter : utilitaire central pour formater les erreurs backend - Détecte et formate spécialement les erreurs LCB-FT (origine fonds manquante) - Détecte erreurs KYC, réseau, 400/401/403/404/500 - Messages conviviaux avec emojis - Durée d'affichage adaptée (6s pour LCB-FT, 3s sinon) Modifications 3 dialogs (dépôt, retrait, transfert) : - Remplacement affichage erreur brut par ErrorFormatter.format() - Messages explicites : "L'origine des fonds est obligatoire (conformité LCB-FT)" - Durée snackbar conditionnelle selon type erreur Impact UX : - Messages d'erreur clairs et professionnels - Utilisateur comprend POURQUOI l'origine fonds est requise (anti-blanchiment) - Temps de lecture suffisant pour messages importants Spec : specs/001-mutuelles-anti-blanchiment/spec.md Progression : 18/27 tâches (67%) Signed-off-by: lions dev Team --- .../lib/core/utils/error_formatter.dart | 95 +++++++++++++++++++ .../widgets/depot_epargne_dialog.dart | 11 ++- .../widgets/retrait_epargne_dialog.dart | 9 +- .../widgets/transfert_epargne_dialog.dart | 4 +- 4 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart diff --git a/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart b/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart new file mode 100644 index 0000000..628f9db --- /dev/null +++ b/unionflow/unionflow-mobile-apps/lib/core/utils/error_formatter.dart @@ -0,0 +1,95 @@ +/// Utilitaire pour formater les messages d'erreur venant du backend. +/// Gère notamment les erreurs LCB-FT (anti-blanchiment). +class ErrorFormatter { + /// Formate une erreur en message utilisateur convivial. + /// + /// Détecte et formate spécialement les erreurs LCB-FT (origine des fonds manquante). + /// Supprime les préfixes techniques comme "Exception: " ou "DioException: ". + static String format(dynamic error) { + if (error == null) return 'Une erreur inconnue est survenue'; + + final errorString = error.toString(); + + // Erreur LCB-FT : origine des fonds manquante + if (errorString.contains('origine des fonds') || + errorString.contains('LCB-FT') || + errorString.contains('au-dessus du seuil')) { + return '🛡️ L\'origine des fonds est obligatoire pour cette opération (conformité LCB-FT anti-blanchiment).\n\nVeuillez préciser d\'où proviennent les fonds.'; + } + + // Erreur KYC + if (errorString.contains('KYC') || errorString.contains('vérification identité')) { + return '🛡️ Votre identité doit être vérifiée pour cette opération (conformité KYC).\n\nContactez votre administrateur.'; + } + + // Erreur solde insuffisant + if (errorString.contains('solde') && errorString.contains('insuffisant')) { + return '💳 Solde insuffisant pour effectuer cette opération.'; + } + + // Erreur réseau / timeout + if (errorString.contains('SocketException') || + errorString.contains('timeout') || + errorString.contains('network')) { + return '📡 Erreur de connexion. Vérifiez votre connexion internet et réessayez.'; + } + + // Erreur 400 générique (validation backend) + if (errorString.contains('400') || errorString.contains('Bad Request')) { + // Essayer d'extraire le message du backend + final match = RegExp(r'message["\s:]+([^"}\n]+)', caseSensitive: false) + .firstMatch(errorString); + if (match != null && match.group(1) != null) { + return match.group(1)!.trim(); + } + return 'Données invalides. Vérifiez les informations saisies.'; + } + + // Erreur 401 / 403 (authentification / autorisation) + if (errorString.contains('401') || errorString.contains('403')) { + return '🔒 Vous n\'avez pas les autorisations nécessaires pour cette opération.'; + } + + // Erreur 404 (ressource non trouvée) + if (errorString.contains('404')) { + return 'Ressource non trouvée. Elle a peut-être été supprimée.'; + } + + // Erreur 500 (erreur serveur) + if (errorString.contains('500') || errorString.contains('Internal Server')) { + return '🔧 Erreur serveur. Nos équipes ont été notifiées. Réessayez plus tard.'; + } + + // Nettoyer les préfixes techniques + String cleaned = errorString + .replaceFirst('Exception: ', '') + .replaceFirst('DioException: ', '') + .replaceFirst('DioError: ', '') + .replaceFirst('Error: ', '') + .trim(); + + // Si le message est trop long, le tronquer + if (cleaned.length > 200) { + cleaned = '${cleaned.substring(0, 197)}...'; + } + + return cleaned.isNotEmpty ? cleaned : 'Une erreur est survenue'; + } + + /// Détermine si une erreur est critique (nécessite intervention admin). + static bool isCritical(dynamic error) { + final errorString = error.toString().toLowerCase(); + return errorString.contains('kyc') || + errorString.contains('vérification identité') || + errorString.contains('401') || + errorString.contains('403'); + } + + /// Détermine si une erreur est liée au LCB-FT. + static bool isLcbFtError(dynamic error) { + final errorString = error.toString().toLowerCase(); + return errorString.contains('origine des fonds') || + errorString.contains('lcb-ft') || + errorString.contains('anti-blanchiment'); + } +} 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 c9dfe3d..c76ba87 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 @@ -4,6 +4,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; @@ -113,7 +114,10 @@ class _DepotEpargneDialogState extends State { } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Wave: ${e.toString().replaceFirst('Exception: ', '')}')), + SnackBar( + content: Text(ErrorFormatter.format(e)), + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), + ), ); } finally { if (mounted) setState(() => _waveLoading = false); @@ -158,7 +162,10 @@ class _DepotEpargneDialogState extends State { } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur: $e')), + SnackBar( + content: Text(ErrorFormatter.format(e)), + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), + ), ); } finally { if (mounted) setState(() => _loading = false); 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 84a0b22..eafbee8 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 @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; @@ -105,17 +106,21 @@ class _RetraitEpargneDialogState extends State { _showSnack('Retrait enregistré', isError: false); } catch (e) { if (!mounted) return; - _showSnack('Erreur: ${e.toString().replaceFirst('Exception: ', '')}'); + _showSnack( + ErrorFormatter.format(e), + duration: ErrorFormatter.isLcbFtError(e) ? 6 : 3, + ); } finally { if (mounted) setState(() => _loading = false); } } - void _showSnack(String msg, {bool isError = true}) { + void _showSnack(String msg, {bool isError = true, int duration = 3}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: isError ? ColorTokens.error : ColorTokens.success, + duration: Duration(seconds: duration), ), ); } diff --git a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart index 24e0ef3..10ba13c 100644 --- a/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart +++ b/unionflow/unionflow-mobile-apps/lib/features/epargne/presentation/widgets/transfert_epargne_dialog.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import '../../../../core/constants/lcb_ft_constants.dart'; import '../../../../core/data/repositories/parametres_lcb_ft_repository.dart'; +import '../../../../core/utils/error_formatter.dart'; import '../../data/models/compte_epargne_model.dart'; import '../../data/models/transaction_epargne_request.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; @@ -130,8 +131,9 @@ class _TransfertEpargneDialogState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Erreur: ${e.toString().replaceFirst('Exception: ', '')}'), + content: Text(ErrorFormatter.format(e)), backgroundColor: ColorTokens.error, + duration: ErrorFormatter.isLcbFtError(e) ? const Duration(seconds: 6) : const Duration(seconds: 3), ), ); } finally {