Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
import 'package:dio/dio.dart';
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/compte_epargne_model.dart';
import '../models/transaction_epargne_request.dart';
/// Repository des comptes épargne — API /api/v1/epargne/comptes.
@lazySingleton
class CompteEpargneRepository {
final ApiClient _apiClient;
static const String _baseComptes = '/api/v1/epargne/comptes';
CompteEpargneRepository(this._apiClient);
List<dynamic> _parseListResponse(dynamic data) {
if (data is List) return data;
if (data is Map && data.containsKey('content')) {
final content = data['content'];
return content is List ? content : [];
}
return [];
}
/// Comptes épargne du membre connecté (GET /api/v1/epargne/comptes/mes-comptes).
Future<List<CompteEpargneModel>> getMesComptes() async {
try {
final response = await _apiClient.get('$_baseComptes/mes-comptes');
if (response.statusCode == 200) {
final data = _parseListResponse(response.data);
return data.map((e) => CompteEpargneModel.fromJson(e as Map<String, dynamic>)).toList();
}
AppLogger.error('CompteEpargneRepository: getMesComptes status ${response.statusCode}');
throw Exception('Impossible de charger les comptes: ${response.statusCode}');
} catch (e, st) {
AppLogger.error('CompteEpargneRepository: getMesComptes échoué', error: e, stackTrace: st);
rethrow;
}
}
Future<List<CompteEpargneModel>> getByMembre(String membreId) async {
try {
final response = await _apiClient.get('$_baseComptes/membre/$membreId');
if (response.statusCode == 200) {
final data = _parseListResponse(response.data);
return data.map((e) => CompteEpargneModel.fromJson(e as Map<String, dynamic>)).toList();
}
AppLogger.error('CompteEpargneRepository: getByMembre status ${response.statusCode}');
throw Exception('Impossible de charger les comptes du membre: ${response.statusCode}');
} catch (e, st) {
AppLogger.error('CompteEpargneRepository: getByMembre échoué', error: e, stackTrace: st);
rethrow;
}
}
Future<CompteEpargneModel?> getById(String id) async {
final response = await _apiClient.get('$_baseComptes/$id');
if (response.statusCode == 200) {
return CompteEpargneModel.fromJson(response.data as Map<String, dynamic>);
}
return null;
}
/// Crée un compte épargne pour un membre (réservé admin / admin organisation).
/// POST /api/v1/epargne/comptes
Future<CompteEpargneModel> creerCompte({
required String membreId,
required String organisationId,
required String typeCompte,
String? notesOuverture,
}) async {
final body = <String, dynamic>{
'membreId': membreId,
'organisationId': organisationId,
'typeCompte': typeCompte,
};
if (notesOuverture != null && notesOuverture.isNotEmpty) {
body['notesOuverture'] = notesOuverture;
}
final response = await _apiClient.post(_baseComptes, data: body);
if (response.statusCode == 201 || response.statusCode == 200) {
return CompteEpargneModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur création compte épargne: ${response.statusCode}');
}
}
/// Repository des transactions épargne — API /api/v1/epargne/transactions.
/// LCB-FT : le backend exige origineFonds au-dessus du seuil configuré.
@lazySingleton
class TransactionEpargneRepository {
final ApiClient _apiClient;
static const String _base = '/api/v1/epargne/transactions';
TransactionEpargneRepository(this._apiClient);
/// Exécute une transaction (dépôt, retrait, etc.).
Future<Map<String, dynamic>> executer(TransactionEpargneRequest request) async {
final response = await _apiClient.post(_base, data: request.toJson());
if (response.statusCode == 201 || response.statusCode == 200) {
return response.data as Map<String, dynamic>;
}
throw Exception('Erreur transaction épargne: ${response.statusCode}');
}
/// Transfert entre deux comptes.
Future<Map<String, dynamic>> transferer(TransactionEpargneRequest request) async {
final response = await _apiClient.post('$_base/transfert', data: request.toJson());
if (response.statusCode == 201 || response.statusCode == 200) {
return response.data as Map<String, dynamic>;
}
throw Exception('Erreur transfert: ${response.statusCode}');
}
/// Historique des transactions d'un compte.
Future<List<Map<String, dynamic>>> getByCompte(String compteId) async {
final response = await _apiClient.get('$_base/compte/$compteId');
if (response.statusCode == 200) {
final data = response.data;
if (data is List) return List<Map<String, dynamic>>.from(data.map((e) => e as Map<String, dynamic>));
return [];
}
throw Exception('Erreur chargement historique: ${response.statusCode}');
}
/// Initie un dépôt sur compte épargne via Wave (même API que cotisations).
/// Retourne l'URL à ouvrir (wave_launch_url) pour confirmer dans l'app Wave.
Future<DepotWaveResult> initierDepotEpargneEnLigne({
required String compteId,
required double montant,
required String numeroTelephone,
}) async {
final response = await _apiClient.post(
'/api/paiements/initier-depot-epargne-en-ligne',
data: {
'compteId': compteId,
'montant': montant,
'numeroTelephone': numeroTelephone.replaceAll(RegExp(r'\D'), ''),
},
);
if (response.statusCode != 201 && response.statusCode != 200) {
final msg = response.data is Map
? (response.data['message'] ?? response.data['error'] ?? response.statusCode)
: response.statusCode;
throw Exception('Impossible d\'initier le dépôt: $msg');
}
final data = response.data is Map<String, dynamic>
? response.data as Map<String, dynamic>
: Map<String, dynamic>.from(response.data as Map);
return DepotWaveResult(
waveLaunchUrl: data['waveLaunchUrl'] as String? ?? data['redirectUrl'] as String? ?? '',
redirectUrl: data['redirectUrl'] as String? ?? data['waveLaunchUrl'] as String? ?? '',
message: data['message'] as String? ?? 'Ouvrez Wave pour confirmer le dépôt.',
);
}
}
/// Résultat de l'initiation d'un dépôt Wave (épargne).
class DepotWaveResult {
final String waveLaunchUrl;
final String redirectUrl;
final String message;
const DepotWaveResult({
required this.waveLaunchUrl,
required this.redirectUrl,
required this.message,
});
}