/// BLoC pour la gestion des cotisations library cotisations_bloc; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/utils/logger.dart'; import '../data/models/cotisation_model.dart'; import 'cotisations_event.dart'; import 'cotisations_state.dart'; /// BLoC pour gérer l'état des cotisations class CotisationsBloc extends Bloc { CotisationsBloc() : super(const CotisationsInitial()) { on(_onLoadCotisations); on(_onLoadCotisationById); on(_onCreateCotisation); on(_onUpdateCotisation); on(_onDeleteCotisation); on(_onSearchCotisations); on(_onLoadCotisationsByMembre); on(_onLoadCotisationsPayees); on(_onLoadCotisationsNonPayees); on(_onLoadCotisationsEnRetard); on(_onEnregistrerPaiement); on(_onLoadCotisationsStats); on(_onGenererCotisationsAnnuelles); on(_onEnvoyerRappelPaiement); } /// Charger la liste des cotisations Future _onLoadCotisations( LoadCotisations event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'LoadCotisations', data: { 'page': event.page, 'size': event.size, }); emit(const CotisationsLoading(message: 'Chargement des cotisations...')); // Simuler un délai réseau await Future.delayed(const Duration(milliseconds: 500)); // Données mock final cotisations = _getMockCotisations(); final total = cotisations.length; final totalPages = (total / event.size).ceil(); // Pagination final start = event.page * event.size; final end = (start + event.size).clamp(0, total); final paginatedCotisations = cotisations.sublist( start.clamp(0, total), end, ); emit(CotisationsLoaded( cotisations: paginatedCotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('CotisationsBloc', 'CotisationsLoaded', data: { 'count': paginatedCotisations.length, 'total': total, }); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement des cotisations', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors du chargement des cotisations', error: e, )); } } /// Charger une cotisation par ID Future _onLoadCotisationById( LoadCotisationById event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'LoadCotisationById', data: { 'id': event.id, }); emit(const CotisationsLoading(message: 'Chargement de la cotisation...')); await Future.delayed(const Duration(milliseconds: 300)); final cotisations = _getMockCotisations(); final cotisation = cotisations.firstWhere( (c) => c.id == event.id, orElse: () => throw Exception('Cotisation non trouvée'), ); emit(CotisationDetailLoaded(cotisation: cotisation)); AppLogger.blocState('CotisationsBloc', 'CotisationDetailLoaded'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement de la cotisation', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Cotisation non trouvée', error: e, )); } } /// Créer une nouvelle cotisation Future _onCreateCotisation( CreateCotisation event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'CreateCotisation'); emit(const CotisationsLoading(message: 'Création de la cotisation...')); await Future.delayed(const Duration(milliseconds: 500)); final newCotisation = event.cotisation.copyWith( id: 'cot_${DateTime.now().millisecondsSinceEpoch}', dateCreation: DateTime.now(), ); emit(CotisationCreated(cotisation: newCotisation)); AppLogger.blocState('CotisationsBloc', 'CotisationCreated'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la création de la cotisation', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors de la création de la cotisation', error: e, )); } } /// Mettre à jour une cotisation Future _onUpdateCotisation( UpdateCotisation event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'UpdateCotisation', data: { 'id': event.id, }); emit(const CotisationsLoading(message: 'Mise à jour de la cotisation...')); await Future.delayed(const Duration(milliseconds: 500)); final updatedCotisation = event.cotisation.copyWith( id: event.id, dateModification: DateTime.now(), ); emit(CotisationUpdated(cotisation: updatedCotisation)); AppLogger.blocState('CotisationsBloc', 'CotisationUpdated'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la mise à jour de la cotisation', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors de la mise à jour de la cotisation', error: e, )); } } /// Supprimer une cotisation Future _onDeleteCotisation( DeleteCotisation event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'DeleteCotisation', data: { 'id': event.id, }); emit(const CotisationsLoading(message: 'Suppression de la cotisation...')); await Future.delayed(const Duration(milliseconds: 500)); emit(CotisationDeleted(id: event.id)); AppLogger.blocState('CotisationsBloc', 'CotisationDeleted'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la suppression de la cotisation', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors de la suppression de la cotisation', error: e, )); } } /// Rechercher des cotisations Future _onSearchCotisations( SearchCotisations event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'SearchCotisations'); emit(const CotisationsLoading(message: 'Recherche en cours...')); await Future.delayed(const Duration(milliseconds: 500)); var cotisations = _getMockCotisations(); // Filtrer par membre if (event.membreId != null) { cotisations = cotisations .where((c) => c.membreId == event.membreId) .toList(); } // Filtrer par statut if (event.statut != null) { cotisations = cotisations .where((c) => c.statut == event.statut) .toList(); } // Filtrer par type if (event.type != null) { cotisations = cotisations .where((c) => c.type == event.type) .toList(); } // Filtrer par année if (event.annee != null) { cotisations = cotisations .where((c) => c.annee == event.annee) .toList(); } final total = cotisations.length; final totalPages = (total / event.size).ceil(); // Pagination final start = event.page * event.size; final end = (start + event.size).clamp(0, total); final paginatedCotisations = cotisations.sublist( start.clamp(0, total), end, ); emit(CotisationsLoaded( cotisations: paginatedCotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('CotisationsBloc', 'CotisationsLoaded (search)'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors de la recherche de cotisations', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors de la recherche', error: e, )); } } /// Charger les cotisations d'un membre Future _onLoadCotisationsByMembre( LoadCotisationsByMembre event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'LoadCotisationsByMembre', data: { 'membreId': event.membreId, }); emit(const CotisationsLoading(message: 'Chargement des cotisations du membre...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations() .where((c) => c.membreId == event.membreId) .toList(); final total = cotisations.length; final totalPages = (total / event.size).ceil(); emit(CotisationsLoaded( cotisations: cotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); AppLogger.blocState('CotisationsBloc', 'CotisationsLoaded (by membre)'); } catch (e, stackTrace) { AppLogger.error( 'Erreur lors du chargement des cotisations du membre', error: e, stackTrace: stackTrace, ); emit(CotisationsError( message: 'Erreur lors du chargement', error: e, )); } } /// Charger les cotisations payées Future _onLoadCotisationsPayees( LoadCotisationsPayees event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Chargement des cotisations payées...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations() .where((c) => c.statut == StatutCotisation.payee) .toList(); final total = cotisations.length; final totalPages = (total / event.size).ceil(); emit(CotisationsLoaded( cotisations: cotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Charger les cotisations non payées Future _onLoadCotisationsNonPayees( LoadCotisationsNonPayees event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Chargement des cotisations non payées...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations() .where((c) => c.statut == StatutCotisation.nonPayee) .toList(); final total = cotisations.length; final totalPages = (total / event.size).ceil(); emit(CotisationsLoaded( cotisations: cotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Charger les cotisations en retard Future _onLoadCotisationsEnRetard( LoadCotisationsEnRetard event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Chargement des cotisations en retard...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations() .where((c) => c.statut == StatutCotisation.enRetard) .toList(); final total = cotisations.length; final totalPages = (total / event.size).ceil(); emit(CotisationsLoaded( cotisations: cotisations, total: total, page: event.page, size: event.size, totalPages: totalPages, )); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Enregistrer un paiement Future _onEnregistrerPaiement( EnregistrerPaiement event, Emitter emit, ) async { try { AppLogger.blocEvent('CotisationsBloc', 'EnregistrerPaiement'); emit(const CotisationsLoading(message: 'Enregistrement du paiement...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations(); final cotisation = cotisations.firstWhere((c) => c.id == event.cotisationId); final updatedCotisation = cotisation.copyWith( montantPaye: event.montant, datePaiement: event.datePaiement, methodePaiement: event.methodePaiement, numeroPaiement: event.numeroPaiement, referencePaiement: event.referencePaiement, statut: event.montant >= cotisation.montant ? StatutCotisation.payee : StatutCotisation.partielle, dateModification: DateTime.now(), ); emit(PaiementEnregistre(cotisation: updatedCotisation)); AppLogger.blocState('CotisationsBloc', 'PaiementEnregistre'); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur lors de l\'enregistrement du paiement', error: e)); } } /// Charger les statistiques Future _onLoadCotisationsStats( LoadCotisationsStats event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Chargement des statistiques...')); await Future.delayed(const Duration(milliseconds: 500)); final cotisations = _getMockCotisations(); final stats = { 'total': cotisations.length, 'payees': cotisations.where((c) => c.statut == StatutCotisation.payee).length, 'nonPayees': cotisations.where((c) => c.statut == StatutCotisation.nonPayee).length, 'enRetard': cotisations.where((c) => c.statut == StatutCotisation.enRetard).length, 'partielles': cotisations.where((c) => c.statut == StatutCotisation.partielle).length, 'montantTotal': cotisations.fold(0, (sum, c) => sum + c.montant), 'montantPaye': cotisations.fold(0, (sum, c) => sum + (c.montantPaye ?? 0)), 'montantRestant': cotisations.fold(0, (sum, c) => sum + c.montantRestant), 'tauxRecouvrement': 0.0, }; if (stats['montantTotal']! > 0) { stats['tauxRecouvrement'] = (stats['montantPaye']! / stats['montantTotal']!) * 100; } emit(CotisationsStatsLoaded(stats: stats)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Générer les cotisations annuelles Future _onGenererCotisationsAnnuelles( GenererCotisationsAnnuelles event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Génération des cotisations...')); await Future.delayed(const Duration(seconds: 1)); // Simuler la génération de 50 cotisations emit(const CotisationsGenerees(nombreGenere: 50)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Envoyer un rappel de paiement Future _onEnvoyerRappelPaiement( EnvoyerRappelPaiement event, Emitter emit, ) async { try { emit(const CotisationsLoading(message: 'Envoi du rappel...')); await Future.delayed(const Duration(milliseconds: 500)); emit(RappelEnvoye(cotisationId: event.cotisationId)); } catch (e, stackTrace) { AppLogger.error('Erreur', error: e, stackTrace: stackTrace); emit(CotisationsError(message: 'Erreur', error: e)); } } /// Données mock pour les tests List _getMockCotisations() { final now = DateTime.now(); return [ CotisationModel( id: 'cot_001', membreId: 'mbr_001', membreNom: 'Dupont', membrePrenom: 'Jean', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: StatutCotisation.payee, montantPaye: 50000, datePaiement: DateTime(now.year, 1, 15), methodePaiement: MethodePaiement.virement, ), CotisationModel( id: 'cot_002', membreId: 'mbr_002', membreNom: 'Martin', membrePrenom: 'Marie', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: StatutCotisation.nonPayee, ), CotisationModel( id: 'cot_003', membreId: 'mbr_003', membreNom: 'Bernard', membrePrenom: 'Pierre', montant: 50000, dateEcheance: DateTime(now.year - 1, 12, 31), annee: now.year - 1, statut: StatutCotisation.enRetard, ), CotisationModel( id: 'cot_004', membreId: 'mbr_004', membreNom: 'Dubois', membrePrenom: 'Sophie', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: StatutCotisation.partielle, montantPaye: 25000, datePaiement: DateTime(now.year, 2, 10), methodePaiement: MethodePaiement.especes, ), CotisationModel( id: 'cot_005', membreId: 'mbr_005', membreNom: 'Petit', membrePrenom: 'Luc', montant: 50000, dateEcheance: DateTime(now.year, 12, 31), annee: now.year, statut: StatutCotisation.payee, montantPaye: 50000, datePaiement: DateTime(now.year, 3, 5), methodePaiement: MethodePaiement.mobileMoney, ), ]; } }