Files
unionflow-mobile-apps/unionflow-mobile-apps/lib/features/contributions/bloc/contributions_bloc.dart
2025-11-17 16:02:04 +00:00

598 lines
18 KiB
Dart

/// 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<ContributionsEvent, ContributionsState> {
ContributionsBloc() : super(const ContributionsInitial()) {
on<LoadContributions>(_onLoadContributions);
on<LoadContributionById>(_onLoadContributionById);
on<CreateContribution>(_onCreateContribution);
on<UpdateContribution>(_onUpdateContribution);
on<DeleteContribution>(_onDeleteContribution);
on<SearchContributions>(_onSearchContributions);
on<LoadContributionsByMembre>(_onLoadContributionsByMembre);
on<LoadContributionsPayees>(_onLoadContributionsPayees);
on<LoadContributionsNonPayees>(_onLoadContributionsNonPayees);
on<LoadContributionsEnRetard>(_onLoadContributionsEnRetard);
on<RecordPayment>(_onRecordPayment);
on<LoadContributionsStats>(_onLoadContributionsStats);
on<GenerateAnnualContributions>(_onGenerateAnnualContributions);
on<SendPaymentReminder>(_onSendPaymentReminder);
}
/// Charger la liste des contributions
Future<void> _onLoadContributions(
LoadContributions event,
Emitter<ContributionsState> 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<void> _onLoadContributionById(
LoadContributionById event,
Emitter<ContributionsState> 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<void> _onCreateContribution(
CreateContribution event,
Emitter<ContributionsState> 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<void> _onUpdateContribution(
UpdateContribution event,
Emitter<ContributionsState> 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<void> _onDeleteContribution(
DeleteContribution event,
Emitter<ContributionsState> 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<void> _onSearchContributions(
SearchContributions event,
Emitter<ContributionsState> 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<void> _onLoadContributionsByMembre(
LoadContributionsByMembre event,
Emitter<ContributionsState> 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<void> _onLoadContributionsPayees(
LoadContributionsPayees event,
Emitter<ContributionsState> 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<void> _onLoadContributionsNonPayees(
LoadContributionsNonPayees event,
Emitter<ContributionsState> 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<void> _onLoadContributionsEnRetard(
LoadContributionsEnRetard event,
Emitter<ContributionsState> 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<void> _onRecordPayment(
RecordPayment event,
Emitter<ContributionsState> 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<void> _onLoadContributionsStats(
LoadContributionsStats event,
Emitter<ContributionsState> 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<double>(0, (sum, c) => sum + c.montant),
'montantPaye': contributions.fold<double>(0, (sum, c) => sum + (c.montantPaye ?? 0)),
'montantRestant': contributions.fold<double>(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<void> _onGenerateAnnualContributions(
GenerateAnnualContributions event,
Emitter<ContributionsState> 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<void> _onSendPaymentReminder(
SendPaymentReminder event,
Emitter<ContributionsState> 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<ContributionModel> _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,
),
];
}
}