Clean project: remove test files, debug logs, and add documentation

This commit is contained in:
dahoud
2025-10-05 13:41:33 +00:00
parent 96a17eadbd
commit 291847924c
438 changed files with 65754 additions and 32713 deletions

View File

@@ -0,0 +1,597 @@
/// 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,
),
];
}
}

View File

@@ -0,0 +1,223 @@
/// Événements pour le BLoC des cotisations
library cotisations_event;
import 'package:equatable/equatable.dart';
import '../data/models/cotisation_model.dart';
/// Classe de base pour tous les événements de cotisations
abstract class CotisationsEvent extends Equatable {
const CotisationsEvent();
@override
List<Object?> get props => [];
}
/// Charger la liste des cotisations
class LoadCotisations extends CotisationsEvent {
final int page;
final int size;
const LoadCotisations({
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [page, size];
}
/// Charger une cotisation par ID
class LoadCotisationById extends CotisationsEvent {
final String id;
const LoadCotisationById({required this.id});
@override
List<Object?> get props => [id];
}
/// Créer une nouvelle cotisation
class CreateCotisation extends CotisationsEvent {
final CotisationModel cotisation;
const CreateCotisation({required this.cotisation});
@override
List<Object?> get props => [cotisation];
}
/// Mettre à jour une cotisation
class UpdateCotisation extends CotisationsEvent {
final String id;
final CotisationModel cotisation;
const UpdateCotisation({
required this.id,
required this.cotisation,
});
@override
List<Object?> get props => [id, cotisation];
}
/// Supprimer une cotisation
class DeleteCotisation extends CotisationsEvent {
final String id;
const DeleteCotisation({required this.id});
@override
List<Object?> get props => [id];
}
/// Rechercher des cotisations
class SearchCotisations extends CotisationsEvent {
final String? membreId;
final StatutCotisation? statut;
final TypeCotisation? type;
final int? annee;
final int page;
final int size;
const SearchCotisations({
this.membreId,
this.statut,
this.type,
this.annee,
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [membreId, statut, type, annee, page, size];
}
/// Charger les cotisations d'un membre
class LoadCotisationsByMembre extends CotisationsEvent {
final String membreId;
final int page;
final int size;
const LoadCotisationsByMembre({
required this.membreId,
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [membreId, page, size];
}
/// Charger les cotisations payées
class LoadCotisationsPayees extends CotisationsEvent {
final int page;
final int size;
const LoadCotisationsPayees({
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [page, size];
}
/// Charger les cotisations non payées
class LoadCotisationsNonPayees extends CotisationsEvent {
final int page;
final int size;
const LoadCotisationsNonPayees({
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [page, size];
}
/// Charger les cotisations en retard
class LoadCotisationsEnRetard extends CotisationsEvent {
final int page;
final int size;
const LoadCotisationsEnRetard({
this.page = 0,
this.size = 20,
});
@override
List<Object?> get props => [page, size];
}
/// Enregistrer un paiement
class EnregistrerPaiement extends CotisationsEvent {
final String cotisationId;
final double montant;
final MethodePaiement methodePaiement;
final String? numeroPaiement;
final String? referencePaiement;
final DateTime datePaiement;
final String? notes;
final String? reference;
const EnregistrerPaiement({
required this.cotisationId,
required this.montant,
required this.methodePaiement,
this.numeroPaiement,
this.referencePaiement,
required this.datePaiement,
this.notes,
this.reference,
});
@override
List<Object?> get props => [
cotisationId,
montant,
methodePaiement,
numeroPaiement,
referencePaiement,
datePaiement,
notes,
reference,
];
}
/// Charger les statistiques des cotisations
class LoadCotisationsStats extends CotisationsEvent {
final int? annee;
const LoadCotisationsStats({this.annee});
@override
List<Object?> get props => [annee];
}
/// Générer les cotisations annuelles
class GenererCotisationsAnnuelles extends CotisationsEvent {
final int annee;
final double montant;
final DateTime dateEcheance;
const GenererCotisationsAnnuelles({
required this.annee,
required this.montant,
required this.dateEcheance,
});
@override
List<Object?> get props => [annee, montant, dateEcheance];
}
/// Envoyer un rappel de paiement
class EnvoyerRappelPaiement extends CotisationsEvent {
final String cotisationId;
const EnvoyerRappelPaiement({required this.cotisationId});
@override
List<Object?> get props => [cotisationId];
}

View File

@@ -0,0 +1,172 @@
/// États pour le BLoC des cotisations
library cotisations_state;
import 'package:equatable/equatable.dart';
import '../data/models/cotisation_model.dart';
/// Classe de base pour tous les états de cotisations
abstract class CotisationsState extends Equatable {
const CotisationsState();
@override
List<Object?> get props => [];
}
/// État initial
class CotisationsInitial extends CotisationsState {
const CotisationsInitial();
}
/// État de chargement
class CotisationsLoading extends CotisationsState {
final String? message;
const CotisationsLoading({this.message});
@override
List<Object?> get props => [message];
}
/// État de rafraîchissement
class CotisationsRefreshing extends CotisationsState {
const CotisationsRefreshing();
}
/// État chargé avec succès
class CotisationsLoaded extends CotisationsState {
final List<CotisationModel> cotisations;
final int total;
final int page;
final int size;
final int totalPages;
const CotisationsLoaded({
required this.cotisations,
required this.total,
required this.page,
required this.size,
required this.totalPages,
});
@override
List<Object?> get props => [cotisations, total, page, size, totalPages];
}
/// État détail d'une cotisation chargé
class CotisationDetailLoaded extends CotisationsState {
final CotisationModel cotisation;
const CotisationDetailLoaded({required this.cotisation});
@override
List<Object?> get props => [cotisation];
}
/// État cotisation créée
class CotisationCreated extends CotisationsState {
final CotisationModel cotisation;
const CotisationCreated({required this.cotisation});
@override
List<Object?> get props => [cotisation];
}
/// État cotisation mise à jour
class CotisationUpdated extends CotisationsState {
final CotisationModel cotisation;
const CotisationUpdated({required this.cotisation});
@override
List<Object?> get props => [cotisation];
}
/// État cotisation supprimée
class CotisationDeleted extends CotisationsState {
final String id;
const CotisationDeleted({required this.id});
@override
List<Object?> get props => [id];
}
/// État paiement enregistré
class PaiementEnregistre extends CotisationsState {
final CotisationModel cotisation;
const PaiementEnregistre({required this.cotisation});
@override
List<Object?> get props => [cotisation];
}
/// État statistiques chargées
class CotisationsStatsLoaded extends CotisationsState {
final Map<String, dynamic> stats;
const CotisationsStatsLoaded({required this.stats});
@override
List<Object?> get props => [stats];
}
/// État cotisations générées
class CotisationsGenerees extends CotisationsState {
final int nombreGenere;
const CotisationsGenerees({required this.nombreGenere});
@override
List<Object?> get props => [nombreGenere];
}
/// État rappel envoyé
class RappelEnvoye extends CotisationsState {
final String cotisationId;
const RappelEnvoye({required this.cotisationId});
@override
List<Object?> get props => [cotisationId];
}
/// État d'erreur générique
class CotisationsError extends CotisationsState {
final String message;
final dynamic error;
const CotisationsError({
required this.message,
this.error,
});
@override
List<Object?> get props => [message, error];
}
/// État d'erreur réseau
class CotisationsNetworkError extends CotisationsState {
final String message;
const CotisationsNetworkError({required this.message});
@override
List<Object?> get props => [message];
}
/// État d'erreur de validation
class CotisationsValidationError extends CotisationsState {
final String message;
final Map<String, String>? fieldErrors;
const CotisationsValidationError({
required this.message,
this.fieldErrors,
});
@override
List<Object?> get props => [message, fieldErrors];
}