feat(mobile): ajout validation LCB-FT au transfert épargne (T019)
Phase 4 Mobile - Section 4.1 Épargne Modifications TransfertEpargneDialog : - Import ParametresLcbFtRepository + lcb_ft_constants - Chargement seuil LCB-FT au initState (comme dépôt/retrait) - Ajout champ origineFonds avec validation conditionnelle - Validation : montant >= seuil → origine fonds obligatoire - Message clair pour utilisateur avec montant seuil dynamique - onChanged sur montant pour mise à jour UI en temps réel Impact : - Les 3 types d'opérations (dépôt, retrait, transfert) ont maintenant la validation LCB-FT - Champ origineFonds transmis dans TransactionEpargneRequest - Conformité BCEAO/OHADA sur tous les flux épargne Spec : specs/001-mutuelles-anti-blanchiment/spec.md Progression : 17/27 tâches (63%) Signed-off-by: lions dev Team
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
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/compte_epargne_model.dart';
|
||||
import '../../data/models/transaction_epargne_request.dart';
|
||||
import '../../data/repositories/transaction_epargne_repository.dart';
|
||||
@@ -27,9 +29,15 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _montantController = TextEditingController();
|
||||
final _motifController = TextEditingController();
|
||||
final _origineFondsController = TextEditingController();
|
||||
bool _loading = false;
|
||||
String? _compteDestinationId;
|
||||
late TransactionEpargneRepository _repository;
|
||||
late ParametresLcbFtRepository _parametresRepository;
|
||||
|
||||
/// Seuil LCB-FT récupéré depuis l'API (fallback à 500k XOF).
|
||||
double _seuilLcbFt = kSeuilOrigineFondsObligatoireXOF;
|
||||
bool _seuilLoaded = false;
|
||||
|
||||
List<CompteEpargneModel> get _comptesDestination {
|
||||
if (widget.compteSource.id == null) return [];
|
||||
@@ -38,17 +46,36 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
bool get _origineFondsRequis {
|
||||
final m = double.tryParse(_montantController.text.replaceAll(',', '.'));
|
||||
return m != null && m >= _seuilLcbFt;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_repository = GetIt.I<TransactionEpargneRepository>();
|
||||
_parametresRepository = GetIt.I<ParametresLcbFtRepository>();
|
||||
if (_comptesDestination.isNotEmpty) _compteDestinationId = _comptesDestination.first.id;
|
||||
_chargerSeuil();
|
||||
}
|
||||
|
||||
/// Charge le seuil LCB-FT depuis l'API au chargement du dialog.
|
||||
Future<void> _chargerSeuil() async {
|
||||
final seuil = await _parametresRepository.getSeuilJustification();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_seuilLcbFt = seuil.montantSeuil;
|
||||
_seuilLoaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_montantController.dispose();
|
||||
_motifController.dispose();
|
||||
_origineFondsController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -72,6 +99,16 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_origineFondsRequis && _origineFondsController.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'L\'origine des fonds est obligatoire pour les opérations à partir de ${_seuilLcbFt.toStringAsFixed(0)} XOF (LCB-FT).',
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
final request = TransactionEpargneRequest(
|
||||
@@ -80,6 +117,7 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
montant: montant,
|
||||
compteDestinationId: _compteDestinationId,
|
||||
motif: _motifController.text.trim().isEmpty ? null : _motifController.text.trim(),
|
||||
origineFonds: _origineFondsController.text.trim().isEmpty ? null : _origineFondsController.text.trim(),
|
||||
);
|
||||
await _repository.transferer(request);
|
||||
if (!mounted) return;
|
||||
@@ -171,6 +209,7 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
if (n > solde) return 'Solde insuffisant';
|
||||
return null;
|
||||
},
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
@@ -181,6 +220,24 @@ class _TransfertEpargneDialogState extends State<TransfertEpargneDialog> {
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _origineFondsController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Origine des fonds (LCB-FT)',
|
||||
hintText: _origineFondsRequis ? 'Obligatoire au-dessus du seuil' : 'Optionnel',
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
if (_origineFondsRequis)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
'Requis pour les opérations ≥ ${_seuilLcbFt.toStringAsFixed(0)} XOF',
|
||||
style: TypographyTokens.bodySmall?.copyWith(color: ColorTokens.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user