598 lines
18 KiB
Dart
598 lines
18 KiB
Dart
/// 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<CotisationsEvent, CotisationsState> {
|
|
CotisationsBloc() : super(const CotisationsInitial()) {
|
|
on<LoadCotisations>(_onLoadCotisations);
|
|
on<LoadCotisationById>(_onLoadCotisationById);
|
|
on<CreateCotisation>(_onCreateCotisation);
|
|
on<UpdateCotisation>(_onUpdateCotisation);
|
|
on<DeleteCotisation>(_onDeleteCotisation);
|
|
on<SearchCotisations>(_onSearchCotisations);
|
|
on<LoadCotisationsByMembre>(_onLoadCotisationsByMembre);
|
|
on<LoadCotisationsPayees>(_onLoadCotisationsPayees);
|
|
on<LoadCotisationsNonPayees>(_onLoadCotisationsNonPayees);
|
|
on<LoadCotisationsEnRetard>(_onLoadCotisationsEnRetard);
|
|
on<EnregistrerPaiement>(_onEnregistrerPaiement);
|
|
on<LoadCotisationsStats>(_onLoadCotisationsStats);
|
|
on<GenererCotisationsAnnuelles>(_onGenererCotisationsAnnuelles);
|
|
on<EnvoyerRappelPaiement>(_onEnvoyerRappelPaiement);
|
|
}
|
|
|
|
/// Charger la liste des cotisations
|
|
Future<void> _onLoadCotisations(
|
|
LoadCotisations event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationById(
|
|
LoadCotisationById event,
|
|
Emitter<CotisationsState> 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<void> _onCreateCotisation(
|
|
CreateCotisation event,
|
|
Emitter<CotisationsState> 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<void> _onUpdateCotisation(
|
|
UpdateCotisation event,
|
|
Emitter<CotisationsState> 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<void> _onDeleteCotisation(
|
|
DeleteCotisation event,
|
|
Emitter<CotisationsState> 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<void> _onSearchCotisations(
|
|
SearchCotisations event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationsByMembre(
|
|
LoadCotisationsByMembre event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationsPayees(
|
|
LoadCotisationsPayees event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationsNonPayees(
|
|
LoadCotisationsNonPayees event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationsEnRetard(
|
|
LoadCotisationsEnRetard event,
|
|
Emitter<CotisationsState> 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<void> _onEnregistrerPaiement(
|
|
EnregistrerPaiement event,
|
|
Emitter<CotisationsState> 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<void> _onLoadCotisationsStats(
|
|
LoadCotisationsStats event,
|
|
Emitter<CotisationsState> 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<double>(0, (sum, c) => sum + c.montant),
|
|
'montantPaye': cotisations.fold<double>(0, (sum, c) => sum + (c.montantPaye ?? 0)),
|
|
'montantRestant': cotisations.fold<double>(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<void> _onGenererCotisationsAnnuelles(
|
|
GenererCotisationsAnnuelles event,
|
|
Emitter<CotisationsState> 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<void> _onEnvoyerRappelPaiement(
|
|
EnvoyerRappelPaiement event,
|
|
Emitter<CotisationsState> 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<CotisationModel> _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,
|
|
),
|
|
];
|
|
}
|
|
}
|
|
|