Version propre - Dashboard enhanced

This commit is contained in:
DahoudG
2025-09-13 19:05:06 +00:00
parent 3df010add7
commit 73459b3092
70 changed files with 15317 additions and 1498 deletions

View File

@@ -21,6 +21,12 @@ import 'package:unionflow_mobile_apps/core/network/auth_interceptor.dart'
as _i772;
import 'package:unionflow_mobile_apps/core/network/dio_client.dart' as _i978;
import 'package:unionflow_mobile_apps/core/services/api_service.dart' as _i238;
import 'package:unionflow_mobile_apps/features/cotisations/data/repositories/cotisation_repository_impl.dart'
as _i991;
import 'package:unionflow_mobile_apps/features/cotisations/domain/repositories/cotisation_repository.dart'
as _i961;
import 'package:unionflow_mobile_apps/features/cotisations/presentation/bloc/cotisations_bloc.dart'
as _i919;
import 'package:unionflow_mobile_apps/features/members/data/repositories/membre_repository_impl.dart'
as _i108;
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart'
@@ -47,6 +53,8 @@ extension GetItInjectableX on _i174.GetIt {
() => _i238.ApiService(gh<_i978.DioClient>()));
gh.singleton<_i772.AuthInterceptor>(
() => _i772.AuthInterceptor(gh<_i394.SecureTokenStorage>()));
gh.lazySingleton<_i961.CotisationRepository>(
() => _i991.CotisationRepositoryImpl(gh<_i238.ApiService>()));
gh.lazySingleton<_i930.MembreRepository>(
() => _i108.MembreRepositoryImpl(gh<_i238.ApiService>()));
gh.factory<_i41.MembresBloc>(
@@ -57,6 +65,8 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i772.AuthInterceptor>(),
gh<_i978.DioClient>(),
));
gh.factory<_i919.CotisationsBloc>(
() => _i919.CotisationsBloc(gh<_i961.CotisationRepository>()));
gh.singleton<_i635.AuthBloc>(() => _i635.AuthBloc(gh<_i423.AuthService>()));
return this;
}

View File

@@ -0,0 +1,271 @@
import 'package:json_annotation/json_annotation.dart';
part 'cotisation_model.g.dart';
/// Modèle de données pour les cotisations
/// Correspond au CotisationDTO du backend
@JsonSerializable()
class CotisationModel {
final String id;
final String numeroReference;
final String membreId;
final String? nomMembre;
final String? numeroMembre;
final String typeCotisation;
final double montantDu;
final double montantPaye;
final String codeDevise;
final String statut;
final DateTime dateEcheance;
final DateTime? datePaiement;
final String? description;
final String? periode;
final int annee;
final int? mois;
final String? observations;
final bool recurrente;
final int nombreRappels;
final DateTime? dateDernierRappel;
final String? valideParId;
final String? nomValidateur;
final DateTime? dateValidation;
final String? methodePaiement;
final String? referencePaiement;
final DateTime dateCreation;
final DateTime? dateModification;
const CotisationModel({
required this.id,
required this.numeroReference,
required this.membreId,
this.nomMembre,
this.numeroMembre,
required this.typeCotisation,
required this.montantDu,
required this.montantPaye,
required this.codeDevise,
required this.statut,
required this.dateEcheance,
this.datePaiement,
this.description,
this.periode,
required this.annee,
this.mois,
this.observations,
required this.recurrente,
required this.nombreRappels,
this.dateDernierRappel,
this.valideParId,
this.nomValidateur,
this.dateValidation,
this.methodePaiement,
this.referencePaiement,
required this.dateCreation,
this.dateModification,
});
/// Factory pour créer depuis JSON
factory CotisationModel.fromJson(Map<String, dynamic> json) =>
_$CotisationModelFromJson(json);
/// Convertit vers JSON
Map<String, dynamic> toJson() => _$CotisationModelToJson(this);
/// Calcule le montant restant à payer
double get montantRestant => montantDu - montantPaye;
/// Vérifie si la cotisation est entièrement payée
bool get isEntierementPayee => montantRestant <= 0;
/// Vérifie si la cotisation est en retard
bool get isEnRetard {
return dateEcheance.isBefore(DateTime.now()) && !isEntierementPayee;
}
/// Retourne le pourcentage de paiement
double get pourcentagePaiement {
if (montantDu == 0) return 0;
return (montantPaye / montantDu * 100).clamp(0, 100);
}
/// Retourne la couleur associée au statut
String get couleurStatut {
switch (statut) {
case 'PAYEE':
return '#4CAF50'; // Vert
case 'EN_ATTENTE':
return '#FF9800'; // Orange
case 'EN_RETARD':
return '#F44336'; // Rouge
case 'PARTIELLEMENT_PAYEE':
return '#2196F3'; // Bleu
case 'ANNULEE':
return '#9E9E9E'; // Gris
default:
return '#757575'; // Gris foncé
}
}
/// Retourne le libellé du statut en français
String get libelleStatut {
switch (statut) {
case 'PAYEE':
return 'Payée';
case 'EN_ATTENTE':
return 'En attente';
case 'EN_RETARD':
return 'En retard';
case 'PARTIELLEMENT_PAYEE':
return 'Partiellement payée';
case 'ANNULEE':
return 'Annulée';
default:
return statut;
}
}
/// Retourne le libellé du type de cotisation
String get libelleTypeCotisation {
switch (typeCotisation) {
case 'MENSUELLE':
return 'Mensuelle';
case 'TRIMESTRIELLE':
return 'Trimestrielle';
case 'SEMESTRIELLE':
return 'Semestrielle';
case 'ANNUELLE':
return 'Annuelle';
case 'EXCEPTIONNELLE':
return 'Exceptionnelle';
case 'ADHESION':
return 'Adhésion';
default:
return typeCotisation;
}
}
/// Retourne l'icône associée au type de cotisation
String get iconeTypeCotisation {
switch (typeCotisation) {
case 'MENSUELLE':
return '📅';
case 'TRIMESTRIELLE':
return '📊';
case 'SEMESTRIELLE':
return '📈';
case 'ANNUELLE':
return '🗓️';
case 'EXCEPTIONNELLE':
return '';
case 'ADHESION':
return '🎯';
default:
return '💰';
}
}
/// Retourne le nombre de jours jusqu'à l'échéance
int get joursJusquEcheance {
final maintenant = DateTime.now();
final difference = dateEcheance.difference(maintenant);
return difference.inDays;
}
/// Vérifie si l'échéance approche (moins de 7 jours)
bool get echeanceProche {
return joursJusquEcheance <= 7 && joursJusquEcheance >= 0;
}
/// Retourne un message d'urgence basé sur l'échéance
String get messageUrgence {
final jours = joursJusquEcheance;
if (jours < 0) {
return 'En retard de ${-jours} jour${-jours > 1 ? 's' : ''}';
} else if (jours == 0) {
return 'Échéance aujourd\'hui';
} else if (jours <= 3) {
return 'Échéance dans $jours jour${jours > 1 ? 's' : ''}';
} else if (jours <= 7) {
return 'Échéance dans $jours jours';
} else {
return '';
}
}
/// Copie avec modifications
CotisationModel copyWith({
String? id,
String? numeroReference,
String? membreId,
String? nomMembre,
String? numeroMembre,
String? typeCotisation,
double? montantDu,
double? montantPaye,
String? codeDevise,
String? statut,
DateTime? dateEcheance,
DateTime? datePaiement,
String? description,
String? periode,
int? annee,
int? mois,
String? observations,
bool? recurrente,
int? nombreRappels,
DateTime? dateDernierRappel,
String? valideParId,
String? nomValidateur,
DateTime? dateValidation,
String? methodePaiement,
String? referencePaiement,
DateTime? dateCreation,
DateTime? dateModification,
}) {
return CotisationModel(
id: id ?? this.id,
numeroReference: numeroReference ?? this.numeroReference,
membreId: membreId ?? this.membreId,
nomMembre: nomMembre ?? this.nomMembre,
numeroMembre: numeroMembre ?? this.numeroMembre,
typeCotisation: typeCotisation ?? this.typeCotisation,
montantDu: montantDu ?? this.montantDu,
montantPaye: montantPaye ?? this.montantPaye,
codeDevise: codeDevise ?? this.codeDevise,
statut: statut ?? this.statut,
dateEcheance: dateEcheance ?? this.dateEcheance,
datePaiement: datePaiement ?? this.datePaiement,
description: description ?? this.description,
periode: periode ?? this.periode,
annee: annee ?? this.annee,
mois: mois ?? this.mois,
observations: observations ?? this.observations,
recurrente: recurrente ?? this.recurrente,
nombreRappels: nombreRappels ?? this.nombreRappels,
dateDernierRappel: dateDernierRappel ?? this.dateDernierRappel,
valideParId: valideParId ?? this.valideParId,
nomValidateur: nomValidateur ?? this.nomValidateur,
dateValidation: dateValidation ?? this.dateValidation,
methodePaiement: methodePaiement ?? this.methodePaiement,
referencePaiement: referencePaiement ?? this.referencePaiement,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CotisationModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
@override
String toString() {
return 'CotisationModel(id: $id, numeroReference: $numeroReference, '
'nomMembre: $nomMembre, typeCotisation: $typeCotisation, '
'montantDu: $montantDu, statut: $statut)';
}
}

View File

@@ -0,0 +1,77 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'cotisation_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CotisationModel _$CotisationModelFromJson(Map<String, dynamic> json) =>
CotisationModel(
id: json['id'] as String,
numeroReference: json['numeroReference'] as String,
membreId: json['membreId'] as String,
nomMembre: json['nomMembre'] as String?,
numeroMembre: json['numeroMembre'] as String?,
typeCotisation: json['typeCotisation'] as String,
montantDu: (json['montantDu'] as num).toDouble(),
montantPaye: (json['montantPaye'] as num).toDouble(),
codeDevise: json['codeDevise'] as String,
statut: json['statut'] as String,
dateEcheance: DateTime.parse(json['dateEcheance'] as String),
datePaiement: json['datePaiement'] == null
? null
: DateTime.parse(json['datePaiement'] as String),
description: json['description'] as String?,
periode: json['periode'] as String?,
annee: (json['annee'] as num).toInt(),
mois: (json['mois'] as num?)?.toInt(),
observations: json['observations'] as String?,
recurrente: json['recurrente'] as bool,
nombreRappels: (json['nombreRappels'] as num).toInt(),
dateDernierRappel: json['dateDernierRappel'] == null
? null
: DateTime.parse(json['dateDernierRappel'] as String),
valideParId: json['valideParId'] as String?,
nomValidateur: json['nomValidateur'] as String?,
dateValidation: json['dateValidation'] == null
? null
: DateTime.parse(json['dateValidation'] as String),
methodePaiement: json['methodePaiement'] as String?,
referencePaiement: json['referencePaiement'] as String?,
dateCreation: DateTime.parse(json['dateCreation'] as String),
dateModification: json['dateModification'] == null
? null
: DateTime.parse(json['dateModification'] as String),
);
Map<String, dynamic> _$CotisationModelToJson(CotisationModel instance) =>
<String, dynamic>{
'id': instance.id,
'numeroReference': instance.numeroReference,
'membreId': instance.membreId,
'nomMembre': instance.nomMembre,
'numeroMembre': instance.numeroMembre,
'typeCotisation': instance.typeCotisation,
'montantDu': instance.montantDu,
'montantPaye': instance.montantPaye,
'codeDevise': instance.codeDevise,
'statut': instance.statut,
'dateEcheance': instance.dateEcheance.toIso8601String(),
'datePaiement': instance.datePaiement?.toIso8601String(),
'description': instance.description,
'periode': instance.periode,
'annee': instance.annee,
'mois': instance.mois,
'observations': instance.observations,
'recurrente': instance.recurrente,
'nombreRappels': instance.nombreRappels,
'dateDernierRappel': instance.dateDernierRappel?.toIso8601String(),
'valideParId': instance.valideParId,
'nomValidateur': instance.nomValidateur,
'dateValidation': instance.dateValidation?.toIso8601String(),
'methodePaiement': instance.methodePaiement,
'referencePaiement': instance.referencePaiement,
'dateCreation': instance.dateCreation.toIso8601String(),
'dateModification': instance.dateModification?.toIso8601String(),
};

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../models/membre_model.dart';
import '../models/cotisation_model.dart';
import '../models/wave_checkout_session_model.dart';
import '../network/dio_client.dart';
@@ -164,6 +165,191 @@ class ApiService {
}
}
// ========================================
// COTISATIONS
// ========================================
/// Récupère la liste de toutes les cotisations avec pagination
Future<List<CotisationModel>> getCotisations({int page = 0, int size = 20}) async {
try {
final response = await _dio.get('/api/cotisations', queryParameters: {
'page': page,
'size': size,
});
if (response.data is List) {
return (response.data as List)
.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour la liste des cotisations');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des cotisations');
}
}
/// Récupère une cotisation par son ID
Future<CotisationModel> getCotisationById(String id) async {
try {
final response = await _dio.get('/api/cotisations/$id');
return CotisationModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération de la cotisation');
}
}
/// Récupère une cotisation par son numéro de référence
Future<CotisationModel> getCotisationByReference(String numeroReference) async {
try {
final response = await _dio.get('/api/cotisations/reference/$numeroReference');
return CotisationModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération de la cotisation');
}
}
/// Crée une nouvelle cotisation
Future<CotisationModel> createCotisation(CotisationModel cotisation) async {
try {
final response = await _dio.post(
'/api/cotisations',
data: cotisation.toJson(),
);
return CotisationModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la création de la cotisation');
}
}
/// Met à jour une cotisation existante
Future<CotisationModel> updateCotisation(String id, CotisationModel cotisation) async {
try {
final response = await _dio.put(
'/api/cotisations/$id',
data: cotisation.toJson(),
);
return CotisationModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la mise à jour de la cotisation');
}
}
/// Supprime une cotisation
Future<void> deleteCotisation(String id) async {
try {
await _dio.delete('/api/cotisations/$id');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la suppression de la cotisation');
}
}
/// Récupère les cotisations d'un membre
Future<List<CotisationModel>> getCotisationsByMembre(String membreId, {int page = 0, int size = 20}) async {
try {
final response = await _dio.get('/api/cotisations/membre/$membreId', queryParameters: {
'page': page,
'size': size,
});
if (response.data is List) {
return (response.data as List)
.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour les cotisations du membre');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des cotisations du membre');
}
}
/// Récupère les cotisations par statut
Future<List<CotisationModel>> getCotisationsByStatut(String statut, {int page = 0, int size = 20}) async {
try {
final response = await _dio.get('/api/cotisations/statut/$statut', queryParameters: {
'page': page,
'size': size,
});
if (response.data is List) {
return (response.data as List)
.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour les cotisations par statut');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des cotisations par statut');
}
}
/// Récupère les cotisations en retard
Future<List<CotisationModel>> getCotisationsEnRetard({int page = 0, int size = 20}) async {
try {
final response = await _dio.get('/api/cotisations/en-retard', queryParameters: {
'page': page,
'size': size,
});
if (response.data is List) {
return (response.data as List)
.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour les cotisations en retard');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des cotisations en retard');
}
}
/// Recherche avancée de cotisations
Future<List<CotisationModel>> rechercherCotisations({
String? membreId,
String? statut,
String? typeCotisation,
int? annee,
int? mois,
int page = 0,
int size = 20,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'size': size,
};
if (membreId != null) queryParams['membreId'] = membreId;
if (statut != null) queryParams['statut'] = statut;
if (typeCotisation != null) queryParams['typeCotisation'] = typeCotisation;
if (annee != null) queryParams['annee'] = annee;
if (mois != null) queryParams['mois'] = mois;
final response = await _dio.get('/api/cotisations/recherche', queryParameters: queryParams);
if (response.data is List) {
return (response.data as List)
.map((json) => CotisationModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour la recherche de cotisations');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la recherche de cotisations');
}
}
/// Récupère les statistiques des cotisations
Future<Map<String, dynamic>> getCotisationsStats() async {
try {
final response = await _dio.get('/api/cotisations/stats');
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des statistiques des cotisations');
}
}
// ========================================
// GESTION DES ERREURS
// ========================================