feat(mobile): amélioration gestion erreurs LCB-FT (T021)
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
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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<DepotEpargneDialog> {
|
||||
} 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<DepotEpargneDialog> {
|
||||
} 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);
|
||||
|
||||
@@ -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<RetraitEpargneDialog> {
|
||||
_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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<TransfertEpargneDialog> {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user