598 lines
18 KiB
Dart
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,
|
|
),
|
|
];
|
|
}
|
|
}
|
|
|