/// BLoC pour la gestion des contributions library contributions_bloc; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/utils/logger.dart'; import '../data/models/contribution_model.dart'; import 'contributions_event.dart'; import 'contributions_state.dart'; /// BLoC pour gérer l'état des contributions class ContributionsBloc extends Bloc { ContributionsBloc() : super(const ContributionsInitial()) { on(_onLoadContributions); on(_onLoadContributionById); on(_onCreateContribution); on(_onUpdateContribution); on(_onDeleteContribution); on(_onSearchContributions); on(_onLoadContributionsByMembre); on(_onLoadContributionsPayees); on(_onLoadContributionsNonPayees); on(_onLoadContributionsEnRetard); on(_onRecordPayment); on(_onLoadContributionsStats); on(_onGenerateAnnualContributions); on(_onSendPaymentReminder); } /// Charger la liste des contributions Future _onLoadContributions( LoadContributions event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'LoadContributions', data: { 'page': event.page, 'size': event.size, }); emit(const ContributionsLoading(message: 'Chargement des contributions...')); // Simuler un délai réseau await Future.delayed(const Duration(milliseconds: 500)); // Données mock final contributions = _getMockContributions(); final total = contributions.length; final totalPages = (total / event.size).ceil(); // Pagination final start = event.page * event.size; final end = (start + event.size).clamp(0, total); final paginatedContributions = contributions.sublist( start.clamp(0, total), end, ); emit(ContributionsLoaded( contributions: paginatedContributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('ContributionsBloc', 'ContributionsLoaded', data: { 'count': paginatedContributions.length, 'total': total, }); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement des contributions', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors du chargement des contributions', error: e, )); } } /// Charger une contribution par ID Future _onLoadContributionById( LoadContributionById event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'LoadContributionById', data: { 'id': event.id, }); emit(const ContributionsLoading(message: 'Chargement de la contribution...')); await Future.delayed(const Duration(milliseconds: 300)); final contributions = _getMockContributions(); final contribution = contributions.firstWhere( (c) => c.id == event.id, orElse: () => throw Exception('Contribution non trouvée'), ); emit(ContributionDetailLoaded(contribution: contribution)); AppLogger.blocState('ContributionsBloc', 'ContributionDetailLoaded'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement de la contribution', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Contribution non trouvée', error: e, )); } } /// Créer une nouvelle contribution Future _onCreateContribution( CreateContribution event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'CreateContribution'); emit(const ContributionsLoading(message: 'Création de la contribution...')); await Future.delayed(const Duration(milliseconds: 500)); final newContribution = event.contribution.copyWith( id: 'cont_${DateTime.now().millisecondsSinceEpoch}', dateCreation: DateTime.now(), ); emit(ContributionCreated(contribution: newContribution)); AppLogger.blocState('ContributionsBloc', 'ContributionCreated'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la création de la contribution', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors de la création de la contribution', error: e, )); } } /// Mettre à jour une contribution Future _onUpdateContribution( UpdateContribution event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'UpdateContribution', data: { 'id': event.id, }); emit(const ContributionsLoading(message: 'Mise à jour de la contribution...')); await Future.delayed(const Duration(milliseconds: 500)); final updatedContribution = event.contribution.copyWith( id: event.id, dateModification: DateTime.now(), ); emit(ContributionUpdated(contribution: updatedContribution)); AppLogger.blocState('ContributionsBloc', 'ContributionUpdated'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la mise à jour de la contribution', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors de la mise à jour de la contribution', error: e, )); } } /// Supprimer une contribution Future _onDeleteContribution( DeleteContribution event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'DeleteContribution', data: { 'id': event.id, }); emit(const ContributionsLoading(message: 'Suppression de la contribution...')); await Future.delayed(const Duration(milliseconds: 500)); emit(ContributionDeleted(id: event.id)); AppLogger.blocState('ContributionsBloc', 'ContributionDeleted'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la suppression de la contribution', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors de la suppression de la contribution', error: e, )); } } /// Rechercher des contributions Future _onSearchContributions( SearchContributions event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'SearchContributions'); emit(const ContributionsLoading(message: 'Recherche en cours...')); await Future.delayed(const Duration(milliseconds: 500)); var contributions = _getMockContributions(); // Filtrer par membre if (event.membreId != null) { contributions = contributions .where((c) => c.membreId == event.membreId) .toList(); } // Filtrer par statut if (event.statut != null) { contributions = contributions .where((c) => c.statut == event.statut) .toList(); } // Filtrer par type if (event.type != null) { contributions = contributions .where((c) => c.type == event.type) .toList(); } // Filtrer par année if (event.annee != null) { contributions = contributions .where((c) => c.annee == event.annee) .toList(); } final total = contributions.length; final totalPages = (total / event.size).ceil(); // Pagination final start = event.page * event.size; final end = (start + event.size).clamp(0, total); final paginatedContributions = contributions.sublist( start.clamp(0, total), end, ); emit(ContributionsLoaded( contributions: paginatedContributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('ContributionsBloc', 'ContributionsLoaded (search)'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la recherche de contributions', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors de la recherche', error: e, )); } } /// Charger les contributions d'un membre Future _onLoadContributionsByMembre( LoadContributionsByMembre event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'LoadContributionsByMembre', data: { 'membreId': event.membreId, }); emit(const ContributionsLoading(message: 'Chargement des contributions du membre...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions() .where((c) => c.membreId == event.membreId) .toList(); final total = contributions.length; final totalPages = (total / event.size).ceil(); emit(ContributionsLoaded( contributions: contributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('ContributionsBloc', 'ContributionsLoaded (by membre)'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement des contributions du membre', error: e, stackTrace: stackTrace, ); emit(ContributionsError( message: 'Erreur lors du chargement', error: e, )); } } /// Charger les contributions payées Future _onLoadContributionsPayees( LoadContributionsPayees event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Chargement des contributions payées...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions() .where((c) => c.statut == ContributionStatus.payee) .toList(); final total = contributions.length; final totalPages = (total / event.size).ceil(); emit(ContributionsLoaded( contributions: contributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Charger les contributions non payées Future _onLoadContributionsNonPayees( LoadContributionsNonPayees event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Chargement des contributions non payées...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions() .where((c) => c.statut == ContributionStatus.nonPayee) .toList(); final total = contributions.length; final totalPages = (total / event.size).ceil(); emit(ContributionsLoaded( contributions: contributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Charger les contributions en retard Future _onLoadContributionsEnRetard( LoadContributionsEnRetard event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Chargement des contributions en retard...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions() .where((c) => c.statut == ContributionStatus.enRetard) .toList(); final total = contributions.length; final totalPages = (total / event.size).ceil(); emit(ContributionsLoaded( contributions: contributions, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Enregistrer un paiement Future _onRecordPayment( RecordPayment event, Emitter emit, ) async { try { AppLogger.blocEvent('ContributionsBloc', 'RecordPayment'); emit(const ContributionsLoading(message: 'Enregistrement du paiement...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions(); final contribution = contributions.firstWhere((c) => c.id == event.contributionId); final updatedContribution = contribution.copyWith( montantPaye: event.montant, datePaiement: event.datePaiement, methodePaiement: event.methodePaiement, numeroPaiement: event.numeroPaiement, referencePaiement: event.referencePaiement, statut: event.montant >= contribution.montant ? ContributionStatus.payee : ContributionStatus.partielle, dateModification: DateTime.now(), ); emit(PaymentRecorded(contribution: updatedContribution)); AppLogger.blocState('ContributionsBloc', 'PaymentRecorded'); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur lors de l\'enregistrement du paiement', error: e)); } } /// Charger les statistiques Future _onLoadContributionsStats( LoadContributionsStats event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Chargement des statistiques...')); await Future.delayed(const Duration(milliseconds: 500)); final contributions = _getMockContributions(); final stats = { 'total': contributions.length, 'payees': contributions.where((c) => c.statut == ContributionStatus.payee).length, 'nonPayees': contributions.where((c) => c.statut == ContributionStatus.nonPayee).length, 'enRetard': contributions.where((c) => c.statut == ContributionStatus.enRetard).length, 'partielles': contributions.where((c) => c.statut == ContributionStatus.partielle).length, 'montantTotal': contributions.fold(0, (sum, c) => sum + c.montant), 'montantPaye': contributions.fold(0, (sum, c) => sum + (c.montantPaye ?? 0)), 'montantRestant': contributions.fold(0, (sum, c) => sum + c.montantRestant), 'tauxRecouvrement': 0.0, }; if (stats['montantTotal']! > 0) { stats['tauxRecouvrement'] = (stats['montantPaye']! / stats['montantTotal']!) * 100; } emit(ContributionsStatsLoaded(stats: stats)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Générer les contributions annuelles Future _onGenerateAnnualContributions( GenerateAnnualContributions event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Génération des contributions...')); await Future.delayed(const Duration(seconds: 1)); // Simuler la génération de 50 contributions emit(const ContributionsGenerated(nombreGenere: 50)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Envoyer un rappel de paiement Future _onSendPaymentReminder( SendPaymentReminder event, Emitter emit, ) async { try { emit(const ContributionsLoading(message: 'Envoi du rappel...')); await Future.delayed(const Duration(milliseconds: 500)); emit(ReminderSent(contributionId: event.contributionId)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(ContributionsError(message: 'Erreur', error: e)); } } /// Données mock pour les tests List _getMockContributions() { final now = DateTime.now(); return [ ContributionModel( id: 'cont_001', membreId: 'mbr_001', membreNom: 'Dupont', membrePrenom: 'Jean', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: ContributionStatus.payee, montantPaye: 50000, datePaiement: DateTime(now.year, 1, 15), methodePaiement: PaymentMethod.virement, ), ContributionModel( id: 'cont_002', membreId: 'mbr_002', membreNom: 'Martin', membrePrenom: 'Marie', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: ContributionStatus.nonPayee, ), ContributionModel( id: 'cont_003', membreId: 'mbr_003', membreNom: 'Bernard', membrePrenom: 'Pierre', montant: 50000, dateEcheance: DateTime(now.year - 1, 12, 31), annee: now.year - 1, statut: ContributionStatus.enRetard, ), ContributionModel( id: 'cont_004', membreId: 'mbr_004', membreNom: 'Dubois', membrePrenom: 'Sophie', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: ContributionStatus.partielle, montantPaye: 25000, datePaiement: DateTime(now.year, 2, 10), methodePaiement: PaymentMethod.especes, ), ContributionModel( id: 'cont_005', membreId: 'mbr_005', membreNom: 'Petit', membrePrenom: 'Luc', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: ContributionStatus.payee, montantPaye: 50000, datePaiement: DateTime(now.year, 3, 5), methodePaiement: PaymentMethod.mobileMoney, ), ]; } }