import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; import '../../../../core/models/cotisation_model.dart'; import '../../../../core/models/payment_model.dart'; import '../../../../core/services/payment_service.dart'; import '../../../../core/services/notification_service.dart'; import '../../domain/repositories/cotisation_repository.dart'; import 'cotisations_event.dart'; import 'cotisations_state.dart'; /// BLoC pour la gestion des cotisations /// Gère l'état et les événements liés aux cotisations @injectable class CotisationsBloc extends Bloc { final CotisationRepository _cotisationRepository; final PaymentService _paymentService; final NotificationService _notificationService; CotisationsBloc( this._cotisationRepository, this._paymentService, this._notificationService, ) : super(const CotisationsInitial()) { // Enregistrement des handlers d'événements on(_onLoadCotisations); on(_onLoadCotisationById); on(_onLoadCotisationByReference); on(_onCreateCotisation); on(_onUpdateCotisation); on(_onDeleteCotisation); on(_onLoadCotisationsByMembre); on(_onLoadCotisationsByStatut); on(_onLoadCotisationsEnRetard); on(_onSearchCotisations); on(_onLoadCotisationsStats); on(_onRefreshCotisations); on(_onResetCotisationsState); on(_onFilterCotisations); on(_onSortCotisations); // Nouveaux handlers pour les paiements et fonctionnalités avancées on(_onInitiatePayment); on(_onCheckPaymentStatus); on(_onCancelPayment); on(_onScheduleNotifications); on(_onSyncWithServer); on(_onApplyAdvancedFilters); on(_onExportCotisations); } /// Handler pour charger la liste des cotisations Future _onLoadCotisations( LoadCotisations event, Emitter emit, ) async { try { if (event.refresh || state is CotisationsInitial) { emit(CotisationsLoading(isRefreshing: event.refresh)); } final cotisations = await _cotisationRepository.getCotisations( page: event.page, size: event.size, ); List allCotisations = []; // Si c'est un refresh ou la première page, remplacer la liste if (event.refresh || event.page == 0) { allCotisations = cotisations; } else { // Sinon, ajouter à la liste existante (pagination) if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; allCotisations = [...currentState.cotisations, ...cotisations]; } else { allCotisations = cotisations; } } emit(CotisationsLoaded( cotisations: allCotisations, filteredCotisations: allCotisations, hasReachedMax: cotisations.length < event.size, currentPage: event.page, )); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement des cotisations: ${error.toString()}', originalError: error, )); } } /// Handler pour charger une cotisation par ID Future _onLoadCotisationById( LoadCotisationById event, Emitter emit, ) async { try { emit(const CotisationsLoading()); final cotisation = await _cotisationRepository.getCotisationById(event.id); emit(CotisationDetailLoaded(cotisation)); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement de la cotisation: ${error.toString()}', originalError: error, )); } } /// Handler pour charger une cotisation par référence Future _onLoadCotisationByReference( LoadCotisationByReference event, Emitter emit, ) async { try { emit(const CotisationsLoading()); final cotisation = await _cotisationRepository.getCotisationByReference(event.numeroReference); emit(CotisationDetailLoaded(cotisation)); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement de la cotisation: ${error.toString()}', originalError: error, )); } } /// Handler pour créer une nouvelle cotisation Future _onCreateCotisation( CreateCotisation event, Emitter emit, ) async { try { emit(const CotisationOperationLoading('create')); final nouvelleCotisation = await _cotisationRepository.createCotisation(event.cotisation); emit(CotisationCreated(nouvelleCotisation)); // Recharger la liste des cotisations add(const LoadCotisations(refresh: true)); } catch (error) { emit(CotisationsError( 'Erreur lors de la création de la cotisation: ${error.toString()}', originalError: error, )); } } /// Handler pour mettre à jour une cotisation Future _onUpdateCotisation( UpdateCotisation event, Emitter emit, ) async { try { emit(CotisationOperationLoading('update', cotisationId: event.id)); final cotisationMiseAJour = await _cotisationRepository.updateCotisation( event.id, event.cotisation, ); emit(CotisationUpdated(cotisationMiseAJour)); // Mettre à jour la liste si elle est chargée if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; final updatedList = currentState.cotisations.map((c) { return c.id == event.id ? cotisationMiseAJour : c; }).toList(); emit(currentState.copyWith( cotisations: updatedList, filteredCotisations: updatedList, )); } } catch (error) { emit(CotisationsError( 'Erreur lors de la mise à jour de la cotisation: ${error.toString()}', originalError: error, )); } } /// Handler pour supprimer une cotisation Future _onDeleteCotisation( DeleteCotisation event, Emitter emit, ) async { try { emit(CotisationOperationLoading('delete', cotisationId: event.id)); await _cotisationRepository.deleteCotisation(event.id); emit(CotisationDeleted(event.id)); // Retirer de la liste si elle est chargée if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; final updatedList = currentState.cotisations .where((c) => c.id != event.id) .toList(); emit(currentState.copyWith( cotisations: updatedList, filteredCotisations: updatedList, )); } } catch (error) { emit(CotisationsError( 'Erreur lors de la suppression de la cotisation: ${error.toString()}', originalError: error, )); } } /// Handler pour charger les cotisations d'un membre Future _onLoadCotisationsByMembre( LoadCotisationsByMembre event, Emitter emit, ) async { try { if (event.refresh || event.page == 0) { emit(CotisationsLoading(isRefreshing: event.refresh)); } final cotisations = await _cotisationRepository.getCotisationsByMembre( event.membreId, page: event.page, size: event.size, ); List allCotisations = []; if (event.refresh || event.page == 0) { allCotisations = cotisations; } else { if (state is CotisationsByMembreLoaded) { final currentState = state as CotisationsByMembreLoaded; allCotisations = [...currentState.cotisations, ...cotisations]; } else { allCotisations = cotisations; } } emit(CotisationsByMembreLoaded( membreId: event.membreId, cotisations: allCotisations, hasReachedMax: cotisations.length < event.size, currentPage: event.page, )); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement des cotisations du membre: ${error.toString()}', originalError: error, )); } } /// Handler pour charger les cotisations par statut Future _onLoadCotisationsByStatut( LoadCotisationsByStatut event, Emitter emit, ) async { try { if (event.refresh || event.page == 0) { emit(CotisationsLoading(isRefreshing: event.refresh)); } final cotisations = await _cotisationRepository.getCotisationsByStatut( event.statut, page: event.page, size: event.size, ); List allCotisations = []; if (event.refresh || event.page == 0) { allCotisations = cotisations; } else { if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; allCotisations = [...currentState.cotisations, ...cotisations]; } else { allCotisations = cotisations; } } emit(CotisationsLoaded( cotisations: allCotisations, filteredCotisations: allCotisations, hasReachedMax: cotisations.length < event.size, currentPage: event.page, currentFilter: event.statut, )); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement des cotisations par statut: ${error.toString()}', originalError: error, )); } } /// Handler pour charger les cotisations en retard Future _onLoadCotisationsEnRetard( LoadCotisationsEnRetard event, Emitter emit, ) async { try { if (event.refresh || event.page == 0) { emit(CotisationsLoading(isRefreshing: event.refresh)); } final cotisations = await _cotisationRepository.getCotisationsEnRetard( page: event.page, size: event.size, ); List allCotisations = []; if (event.refresh || event.page == 0) { allCotisations = cotisations; } else { if (state is CotisationsEnRetardLoaded) { final currentState = state as CotisationsEnRetardLoaded; allCotisations = [...currentState.cotisations, ...cotisations]; } else { allCotisations = cotisations; } } emit(CotisationsEnRetardLoaded( cotisations: allCotisations, hasReachedMax: cotisations.length < event.size, currentPage: event.page, )); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement des cotisations en retard: ${error.toString()}', originalError: error, )); } } /// Handler pour la recherche de cotisations Future _onSearchCotisations( SearchCotisations event, Emitter emit, ) async { try { if (event.refresh || event.page == 0) { emit(CotisationsLoading(isRefreshing: event.refresh)); } final cotisations = await _cotisationRepository.rechercherCotisations( membreId: event.membreId, statut: event.statut, typeCotisation: event.typeCotisation, annee: event.annee, mois: event.mois, page: event.page, size: event.size, ); final searchCriteria = { if (event.membreId != null) 'membreId': event.membreId, if (event.statut != null) 'statut': event.statut, if (event.typeCotisation != null) 'typeCotisation': event.typeCotisation, if (event.annee != null) 'annee': event.annee, if (event.mois != null) 'mois': event.mois, }; List allCotisations = []; if (event.refresh || event.page == 0) { allCotisations = cotisations; } else { if (state is CotisationsSearchResults) { final currentState = state as CotisationsSearchResults; allCotisations = [...currentState.cotisations, ...cotisations]; } else { allCotisations = cotisations; } } emit(CotisationsSearchResults( cotisations: allCotisations, searchCriteria: searchCriteria, hasReachedMax: cotisations.length < event.size, currentPage: event.page, )); } catch (error) { emit(CotisationsError( 'Erreur lors de la recherche de cotisations: ${error.toString()}', originalError: error, )); } } /// Handler pour charger les statistiques Future _onLoadCotisationsStats( LoadCotisationsStats event, Emitter emit, ) async { try { emit(const CotisationsLoading()); final statistics = await _cotisationRepository.getCotisationsStats(); emit(CotisationsStatsLoaded(statistics)); } catch (error) { emit(CotisationsError( 'Erreur lors du chargement des statistiques: ${error.toString()}', originalError: error, )); } } /// Handler pour rafraîchir les données Future _onRefreshCotisations( RefreshCotisations event, Emitter emit, ) async { add(const LoadCotisations(refresh: true)); } /// Handler pour réinitialiser l'état Future _onResetCotisationsState( ResetCotisationsState event, Emitter emit, ) async { emit(const CotisationsInitial()); } /// Handler pour filtrer les cotisations localement Future _onFilterCotisations( FilterCotisations event, Emitter emit, ) async { if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; List filteredList = currentState.cotisations; // Filtrage par recherche textuelle if (event.searchQuery != null && event.searchQuery!.isNotEmpty) { final query = event.searchQuery!.toLowerCase(); filteredList = filteredList.where((cotisation) { return cotisation.numeroReference.toLowerCase().contains(query) || (cotisation.nomMembre?.toLowerCase().contains(query) ?? false) || cotisation.typeCotisation.toLowerCase().contains(query) || (cotisation.description?.toLowerCase().contains(query) ?? false); }).toList(); } // Filtrage par statut if (event.statutFilter != null && event.statutFilter!.isNotEmpty) { filteredList = filteredList.where((cotisation) { return cotisation.statut == event.statutFilter; }).toList(); } // Filtrage par type if (event.typeFilter != null && event.typeFilter!.isNotEmpty) { filteredList = filteredList.where((cotisation) { return cotisation.typeCotisation == event.typeFilter; }).toList(); } emit(currentState.copyWith( filteredCotisations: filteredList, searchQuery: event.searchQuery, currentFilter: event.statutFilter ?? event.typeFilter, )); } } /// Handler pour trier les cotisations Future _onSortCotisations( SortCotisations event, Emitter emit, ) async { if (state is CotisationsLoaded) { final currentState = state as CotisationsLoaded; List sortedList = [...currentState.filteredCotisations]; switch (event.sortBy) { case 'dateEcheance': sortedList.sort((a, b) => event.ascending ? a.dateEcheance.compareTo(b.dateEcheance) : b.dateEcheance.compareTo(a.dateEcheance)); break; case 'montantDu': sortedList.sort((a, b) => event.ascending ? a.montantDu.compareTo(b.montantDu) : b.montantDu.compareTo(a.montantDu)); break; case 'statut': sortedList.sort((a, b) => event.ascending ? a.statut.compareTo(b.statut) : b.statut.compareTo(a.statut)); break; case 'nomMembre': sortedList.sort((a, b) => event.ascending ? (a.nomMembre ?? '').compareTo(b.nomMembre ?? '') : (b.nomMembre ?? '').compareTo(a.nomMembre ?? '')); break; case 'typeCotisation': sortedList.sort((a, b) => event.ascending ? a.typeCotisation.compareTo(b.typeCotisation) : b.typeCotisation.compareTo(a.typeCotisation)); break; default: // Tri par défaut par date d'échéance sortedList.sort((a, b) => b.dateEcheance.compareTo(a.dateEcheance)); } emit(currentState.copyWith(filteredCotisations: sortedList)); } } /// Handler pour initier un paiement Future _onInitiatePayment( InitiatePayment event, Emitter emit, ) async { try { // Valider les données de paiement if (!_paymentService.validatePaymentData( cotisationId: event.cotisationId, montant: event.montant, methodePaiement: event.methodePaiement, numeroTelephone: event.numeroTelephone, )) { emit(PaymentFailure( cotisationId: event.cotisationId, paymentId: '', errorMessage: 'Données de paiement invalides', errorCode: 'INVALID_DATA', )); return; } // Initier le paiement final payment = await _paymentService.initiatePayment( cotisationId: event.cotisationId, montant: event.montant, methodePaiement: event.methodePaiement, numeroTelephone: event.numeroTelephone, nomPayeur: event.nomPayeur, emailPayeur: event.emailPayeur, ); emit(PaymentInProgress( cotisationId: event.cotisationId, paymentId: payment.id, methodePaiement: event.methodePaiement, montant: event.montant, )); } catch (e) { emit(PaymentFailure( cotisationId: event.cotisationId, paymentId: '', errorMessage: e.toString(), )); } } /// Handler pour vérifier le statut d'un paiement Future _onCheckPaymentStatus( CheckPaymentStatus event, Emitter emit, ) async { try { final payment = await _paymentService.checkPaymentStatus(event.paymentId); if (payment.isSuccessful) { // Récupérer la cotisation mise à jour final cotisation = await _cotisationRepository.getCotisationById(payment.cotisationId); emit(PaymentSuccess( cotisationId: payment.cotisationId, payment: payment, updatedCotisation: cotisation, )); // Envoyer notification de succès await _notificationService.showPaymentConfirmation(cotisation, payment.montant); } else if (payment.isFailed) { emit(PaymentFailure( cotisationId: payment.cotisationId, paymentId: payment.id, errorMessage: payment.messageErreur ?? 'Paiement échoué', )); // Envoyer notification d'échec final cotisation = await _cotisationRepository.getCotisationById(payment.cotisationId); await _notificationService.showPaymentFailure(cotisation, payment.messageErreur ?? 'Erreur inconnue'); } } catch (e) { emit(CotisationsError('Erreur lors de la vérification du paiement: ${e.toString()}')); } } /// Handler pour annuler un paiement Future _onCancelPayment( CancelPayment event, Emitter emit, ) async { try { final cancelled = await _paymentService.cancelPayment(event.paymentId); if (cancelled) { emit(PaymentCancelled( cotisationId: event.cotisationId, paymentId: event.paymentId, )); } else { emit(const CotisationsError('Impossible d\'annuler le paiement')); } } catch (e) { emit(CotisationsError('Erreur lors de l\'annulation du paiement: ${e.toString()}')); } } /// Handler pour programmer les notifications Future _onScheduleNotifications( ScheduleNotifications event, Emitter emit, ) async { try { await _notificationService.scheduleAllCotisationsNotifications(event.cotisations); emit(NotificationsScheduled( notificationsCount: event.cotisations.length * 2, cotisationIds: event.cotisations.map((c) => c.id).toList(), )); } catch (e) { emit(CotisationsError('Erreur lors de la programmation des notifications: ${e.toString()}')); } } /// Handler pour synchroniser avec le serveur Future _onSyncWithServer( SyncWithServer event, Emitter emit, ) async { try { emit(const SyncInProgress('Synchronisation en cours...')); // Recharger les données final cotisations = await _cotisationRepository.getCotisations(); emit(SyncCompleted( itemsSynced: cotisations.length, syncTime: DateTime.now(), )); // Émettre l'état chargé avec les nouvelles données emit(CotisationsLoaded( cotisations: cotisations, filteredCotisations: cotisations, )); } catch (e) { emit(CotisationsError('Erreur lors de la synchronisation: ${e.toString()}')); } } /// Handler pour appliquer des filtres avancés Future _onApplyAdvancedFilters( ApplyAdvancedFilters event, Emitter emit, ) async { try { emit(const CotisationsLoading()); final cotisations = await _cotisationRepository.rechercherCotisations( membreId: event.filters['membreId'], statut: event.filters['statut'], typeCotisation: event.filters['typeCotisation'], annee: event.filters['annee'], mois: event.filters['mois'], ); emit(CotisationsSearchResults( cotisations: cotisations, searchCriteria: event.filters, )); } catch (e) { emit(CotisationsError('Erreur lors de l\'application des filtres: ${e.toString()}')); } } /// Handler pour exporter les cotisations Future _onExportCotisations( ExportCotisations event, Emitter emit, ) async { try { final cotisations = event.cotisations ?? []; emit(ExportInProgress( format: event.format, totalItems: cotisations.length, )); // TODO: Implémenter l'export réel selon le format await Future.delayed(const Duration(seconds: 2)); // Simulation emit(ExportCompleted( format: event.format, filePath: '/storage/emulated/0/Download/cotisations.${event.format}', itemsExported: cotisations.length, )); } catch (e) { emit(CotisationsError('Erreur lors de l\'export: ${e.toString()}')); } } }