Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,354 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/demande_aide.dart';
import '../repositories/solidarite_repository.dart';
/// Cas d'usage pour créer une nouvelle demande d'aide
class CreerDemandeAideUseCase implements UseCase<DemandeAide, CreerDemandeAideParams> {
final SolidariteRepository repository;
CreerDemandeAideUseCase(this.repository);
@override
Future<Either<Failure, DemandeAide>> call(CreerDemandeAideParams params) async {
return await repository.creerDemandeAide(params.demande);
}
}
class CreerDemandeAideParams {
final DemandeAide demande;
CreerDemandeAideParams({required this.demande});
}
/// Cas d'usage pour mettre à jour une demande d'aide
class MettreAJourDemandeAideUseCase implements UseCase<DemandeAide, MettreAJourDemandeAideParams> {
final SolidariteRepository repository;
MettreAJourDemandeAideUseCase(this.repository);
@override
Future<Either<Failure, DemandeAide>> call(MettreAJourDemandeAideParams params) async {
return await repository.mettreAJourDemandeAide(params.demande);
}
}
class MettreAJourDemandeAideParams {
final DemandeAide demande;
MettreAJourDemandeAideParams({required this.demande});
}
/// Cas d'usage pour obtenir une demande d'aide par ID
class ObtenirDemandeAideUseCase implements UseCase<DemandeAide, ObtenirDemandeAideParams> {
final SolidariteRepository repository;
ObtenirDemandeAideUseCase(this.repository);
@override
Future<Either<Failure, DemandeAide>> call(ObtenirDemandeAideParams params) async {
return await repository.obtenirDemandeAide(params.id);
}
}
class ObtenirDemandeAideParams {
final String id;
ObtenirDemandeAideParams({required this.id});
}
/// Cas d'usage pour soumettre une demande d'aide
class SoumettreDemandeAideUseCase implements UseCase<DemandeAide, SoumettreDemandeAideParams> {
final SolidariteRepository repository;
SoumettreDemandeAideUseCase(this.repository);
@override
Future<Either<Failure, DemandeAide>> call(SoumettreDemandeAideParams params) async {
return await repository.soumettreDemande(params.demandeId);
}
}
class SoumettreDemandeAideParams {
final String demandeId;
SoumettreDemandeAideParams({required this.demandeId});
}
/// Cas d'usage pour évaluer une demande d'aide
class EvaluerDemandeAideUseCase implements UseCase<DemandeAide, EvaluerDemandeAideParams> {
final SolidariteRepository repository;
EvaluerDemandeAideUseCase(this.repository);
@override
Future<Either<Failure, DemandeAide>> call(EvaluerDemandeAideParams params) async {
return await repository.evaluerDemande(
demandeId: params.demandeId,
evaluateurId: params.evaluateurId,
decision: params.decision,
commentaire: params.commentaire,
montantApprouve: params.montantApprouve,
);
}
}
class EvaluerDemandeAideParams {
final String demandeId;
final String evaluateurId;
final StatutAide decision;
final String? commentaire;
final double? montantApprouve;
EvaluerDemandeAideParams({
required this.demandeId,
required this.evaluateurId,
required this.decision,
this.commentaire,
this.montantApprouve,
});
}
/// Cas d'usage pour rechercher des demandes d'aide
class RechercherDemandesAideUseCase implements UseCase<List<DemandeAide>, RechercherDemandesAideParams> {
final SolidariteRepository repository;
RechercherDemandesAideUseCase(this.repository);
@override
Future<Either<Failure, List<DemandeAide>>> call(RechercherDemandesAideParams params) async {
return await repository.rechercherDemandes(
organisationId: params.organisationId,
typeAide: params.typeAide,
statut: params.statut,
demandeurId: params.demandeurId,
urgente: params.urgente,
page: params.page,
taille: params.taille,
);
}
}
class RechercherDemandesAideParams {
final String? organisationId;
final TypeAide? typeAide;
final StatutAide? statut;
final String? demandeurId;
final bool? urgente;
final int page;
final int taille;
RechercherDemandesAideParams({
this.organisationId,
this.typeAide,
this.statut,
this.demandeurId,
this.urgente,
this.page = 0,
this.taille = 20,
});
}
/// Cas d'usage pour obtenir les demandes urgentes
class ObtenirDemandesUrgentesUseCase implements UseCase<List<DemandeAide>, ObtenirDemandesUrgentesParams> {
final SolidariteRepository repository;
ObtenirDemandesUrgentesUseCase(this.repository);
@override
Future<Either<Failure, List<DemandeAide>>> call(ObtenirDemandesUrgentesParams params) async {
return await repository.obtenirDemandesUrgentes(params.organisationId);
}
}
class ObtenirDemandesUrgentesParams {
final String organisationId;
ObtenirDemandesUrgentesParams({required this.organisationId});
}
/// Cas d'usage pour obtenir les demandes de l'utilisateur connecté
class ObtenirMesDemandesUseCase implements UseCase<List<DemandeAide>, ObtenirMesDemandesParams> {
final SolidariteRepository repository;
ObtenirMesDemandesUseCase(this.repository);
@override
Future<Either<Failure, List<DemandeAide>>> call(ObtenirMesDemandesParams params) async {
return await repository.obtenirMesdemandes(params.utilisateurId);
}
}
class ObtenirMesDemandesParams {
final String utilisateurId;
ObtenirMesDemandesParams({required this.utilisateurId});
}
/// Cas d'usage pour valider une demande d'aide avant soumission
class ValiderDemandeAideUseCase implements UseCase<bool, ValiderDemandeAideParams> {
ValiderDemandeAideUseCase();
@override
Future<Either<Failure, bool>> call(ValiderDemandeAideParams params) async {
try {
final demande = params.demande;
final erreurs = <String>[];
// Validation du titre
if (demande.titre.trim().isEmpty) {
erreurs.add('Le titre est obligatoire');
} else if (demande.titre.length < 10) {
erreurs.add('Le titre doit contenir au moins 10 caractères');
} else if (demande.titre.length > 100) {
erreurs.add('Le titre ne peut pas dépasser 100 caractères');
}
// Validation de la description
if (demande.description.trim().isEmpty) {
erreurs.add('La description est obligatoire');
} else if (demande.description.length < 50) {
erreurs.add('La description doit contenir au moins 50 caractères');
} else if (demande.description.length > 1000) {
erreurs.add('La description ne peut pas dépasser 1000 caractères');
}
// Validation du montant pour les aides financières
if (_necessiteMontant(demande.typeAide)) {
if (demande.montantDemande == null) {
erreurs.add('Le montant est obligatoire pour ce type d\'aide');
} else if (demande.montantDemande! <= 0) {
erreurs.add('Le montant doit être supérieur à zéro');
} else if (!_isMontantValide(demande.typeAide, demande.montantDemande!)) {
erreurs.add('Le montant demandé n\'est pas dans la fourchette autorisée');
}
}
// Validation des bénéficiaires
if (demande.beneficiaires.isEmpty) {
erreurs.add('Au moins un bénéficiaire doit être spécifié');
} else {
for (int i = 0; i < demande.beneficiaires.length; i++) {
final beneficiaire = demande.beneficiaires[i];
if (beneficiaire.nom.trim().isEmpty) {
erreurs.add('Le nom du bénéficiaire ${i + 1} est obligatoire');
}
if (beneficiaire.prenom.trim().isEmpty) {
erreurs.add('Le prénom du bénéficiaire ${i + 1} est obligatoire');
}
if (beneficiaire.age < 0 || beneficiaire.age > 120) {
erreurs.add('L\'âge du bénéficiaire ${i + 1} n\'est pas valide');
}
}
}
// Validation de la justification d'urgence si priorité critique ou urgente
if (demande.priorite == PrioriteAide.critique || demande.priorite == PrioriteAide.urgente) {
if (demande.justificationUrgence == null || demande.justificationUrgence!.trim().isEmpty) {
erreurs.add('Une justification d\'urgence est requise pour cette priorité');
} else if (demande.justificationUrgence!.length < 20) {
erreurs.add('La justification d\'urgence doit contenir au moins 20 caractères');
}
}
// Validation du contact d'urgence si priorité critique
if (demande.priorite == PrioriteAide.critique) {
if (demande.contactUrgence == null) {
erreurs.add('Un contact d\'urgence est obligatoire pour les demandes critiques');
} else {
final contact = demande.contactUrgence!;
if (contact.nom.trim().isEmpty) {
erreurs.add('Le nom du contact d\'urgence est obligatoire');
}
if (contact.telephone.trim().isEmpty) {
erreurs.add('Le téléphone du contact d\'urgence est obligatoire');
} else if (!_isValidPhoneNumber(contact.telephone)) {
erreurs.add('Le numéro de téléphone du contact d\'urgence n\'est pas valide');
}
}
}
if (erreurs.isNotEmpty) {
return Left(ValidationFailure(erreurs.join(', ')));
}
return const Right(true);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de la validation: ${e.toString()}'));
}
}
bool _necessiteMontant(TypeAide typeAide) {
return [
TypeAide.aideFinanciereUrgente,
TypeAide.aideFinanciereMedicale,
TypeAide.aideFinanciereEducation,
].contains(typeAide);
}
bool _isMontantValide(TypeAide typeAide, double montant) {
switch (typeAide) {
case TypeAide.aideFinanciereUrgente:
return montant >= 5000 && montant <= 50000;
case TypeAide.aideFinanciereMedicale:
return montant >= 10000 && montant <= 100000;
case TypeAide.aideFinanciereEducation:
return montant >= 5000 && montant <= 200000;
default:
return true;
}
}
bool _isValidPhoneNumber(String phone) {
// Validation simple pour les numéros de téléphone ivoiriens
final phoneRegex = RegExp(r'^(\+225)?[0-9]{8,10}$');
return phoneRegex.hasMatch(phone.replaceAll(RegExp(r'[\s\-\(\)]'), ''));
}
}
class ValiderDemandeAideParams {
final DemandeAide demande;
ValiderDemandeAideParams({required this.demande});
}
/// Cas d'usage pour calculer la priorité automatique d'une demande
class CalculerPrioriteDemandeUseCase implements UseCase<PrioriteAide, CalculerPrioriteDemandeParams> {
CalculerPrioriteDemandeUseCase();
@override
Future<Either<Failure, PrioriteAide>> call(CalculerPrioriteDemandeParams params) async {
try {
final demande = params.demande;
// Priorité critique si justification d'urgence et contact d'urgence
if (demande.justificationUrgence != null &&
demande.justificationUrgence!.isNotEmpty &&
demande.contactUrgence != null) {
return const Right(PrioriteAide.critique);
}
// Priorité urgente pour certains types d'aide
if ([TypeAide.aideFinanciereUrgente, TypeAide.aideFinanciereMedicale].contains(demande.typeAide)) {
return const Right(PrioriteAide.urgente);
}
// Priorité élevée pour les montants importants
if (demande.montantDemande != null && demande.montantDemande! > 50000) {
return const Right(PrioriteAide.elevee);
}
// Priorité normale par défaut
return const Right(PrioriteAide.normale);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du calcul de priorité: ${e.toString()}'));
}
}
}
class CalculerPrioriteDemandeParams {
final DemandeAide demande;
CalculerPrioriteDemandeParams({required this.demande});
}

View File

@@ -0,0 +1,463 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/evaluation_aide.dart';
import '../repositories/solidarite_repository.dart';
/// Cas d'usage pour créer une nouvelle évaluation
class CreerEvaluationUseCase implements UseCase<EvaluationAide, CreerEvaluationParams> {
final SolidariteRepository repository;
CreerEvaluationUseCase(this.repository);
@override
Future<Either<Failure, EvaluationAide>> call(CreerEvaluationParams params) async {
return await repository.creerEvaluation(params.evaluation);
}
}
class CreerEvaluationParams {
final EvaluationAide evaluation;
CreerEvaluationParams({required this.evaluation});
}
/// Cas d'usage pour mettre à jour une évaluation
class MettreAJourEvaluationUseCase implements UseCase<EvaluationAide, MettreAJourEvaluationParams> {
final SolidariteRepository repository;
MettreAJourEvaluationUseCase(this.repository);
@override
Future<Either<Failure, EvaluationAide>> call(MettreAJourEvaluationParams params) async {
return await repository.mettreAJourEvaluation(params.evaluation);
}
}
class MettreAJourEvaluationParams {
final EvaluationAide evaluation;
MettreAJourEvaluationParams({required this.evaluation});
}
/// Cas d'usage pour obtenir une évaluation par ID
class ObtenirEvaluationUseCase implements UseCase<EvaluationAide, ObtenirEvaluationParams> {
final SolidariteRepository repository;
ObtenirEvaluationUseCase(this.repository);
@override
Future<Either<Failure, EvaluationAide>> call(ObtenirEvaluationParams params) async {
return await repository.obtenirEvaluation(params.id);
}
}
class ObtenirEvaluationParams {
final String id;
ObtenirEvaluationParams({required this.id});
}
/// Cas d'usage pour obtenir les évaluations d'une demande
class ObtenirEvaluationsDemandeUseCase implements UseCase<List<EvaluationAide>, ObtenirEvaluationsDemandeParams> {
final SolidariteRepository repository;
ObtenirEvaluationsDemandeUseCase(this.repository);
@override
Future<Either<Failure, List<EvaluationAide>>> call(ObtenirEvaluationsDemandeParams params) async {
return await repository.obtenirEvaluationsDemande(params.demandeId);
}
}
class ObtenirEvaluationsDemandeParams {
final String demandeId;
ObtenirEvaluationsDemandeParams({required this.demandeId});
}
/// Cas d'usage pour obtenir les évaluations d'une proposition
class ObtenirEvaluationsPropositionUseCase implements UseCase<List<EvaluationAide>, ObtenirEvaluationsPropositionParams> {
final SolidariteRepository repository;
ObtenirEvaluationsPropositionUseCase(this.repository);
@override
Future<Either<Failure, List<EvaluationAide>>> call(ObtenirEvaluationsPropositionParams params) async {
return await repository.obtenirEvaluationsProposition(params.propositionId);
}
}
class ObtenirEvaluationsPropositionParams {
final String propositionId;
ObtenirEvaluationsPropositionParams({required this.propositionId});
}
/// Cas d'usage pour signaler une évaluation
class SignalerEvaluationUseCase implements UseCase<EvaluationAide, SignalerEvaluationParams> {
final SolidariteRepository repository;
SignalerEvaluationUseCase(this.repository);
@override
Future<Either<Failure, EvaluationAide>> call(SignalerEvaluationParams params) async {
return await repository.signalerEvaluation(
evaluationId: params.evaluationId,
motif: params.motif,
);
}
}
class SignalerEvaluationParams {
final String evaluationId;
final String motif;
SignalerEvaluationParams({
required this.evaluationId,
required this.motif,
});
}
/// Cas d'usage pour calculer la note moyenne d'une demande
class CalculerMoyenneDemandeUseCase implements UseCase<StatistiquesEvaluation, CalculerMoyenneDemandeParams> {
final SolidariteRepository repository;
CalculerMoyenneDemandeUseCase(this.repository);
@override
Future<Either<Failure, StatistiquesEvaluation>> call(CalculerMoyenneDemandeParams params) async {
return await repository.calculerMoyenneDemande(params.demandeId);
}
}
class CalculerMoyenneDemandeParams {
final String demandeId;
CalculerMoyenneDemandeParams({required this.demandeId});
}
/// Cas d'usage pour calculer la note moyenne d'une proposition
class CalculerMoyennePropositionUseCase implements UseCase<StatistiquesEvaluation, CalculerMoyennePropositionParams> {
final SolidariteRepository repository;
CalculerMoyennePropositionUseCase(this.repository);
@override
Future<Either<Failure, StatistiquesEvaluation>> call(CalculerMoyennePropositionParams params) async {
return await repository.calculerMoyenneProposition(params.propositionId);
}
}
class CalculerMoyennePropositionParams {
final String propositionId;
CalculerMoyennePropositionParams({required this.propositionId});
}
/// Cas d'usage pour valider une évaluation avant création
class ValiderEvaluationUseCase implements UseCase<bool, ValiderEvaluationParams> {
ValiderEvaluationUseCase();
@override
Future<Either<Failure, bool>> call(ValiderEvaluationParams params) async {
try {
final evaluation = params.evaluation;
final erreurs = <String>[];
// Validation de la note globale
if (evaluation.noteGlobale < 1.0 || evaluation.noteGlobale > 5.0) {
erreurs.add('La note globale doit être comprise entre 1 et 5');
}
// Validation des notes détaillées
final notesDetaillees = [
evaluation.noteDelaiReponse,
evaluation.noteCommunication,
evaluation.noteProfessionnalisme,
evaluation.noteRespectEngagements,
];
for (final note in notesDetaillees) {
if (note != null && (note < 1.0 || note > 5.0)) {
erreurs.add('Toutes les notes détaillées doivent être comprises entre 1 et 5');
break;
}
}
// Validation du commentaire principal
if (evaluation.commentairePrincipal.trim().isEmpty) {
erreurs.add('Le commentaire principal est obligatoire');
} else if (evaluation.commentairePrincipal.length < 20) {
erreurs.add('Le commentaire principal doit contenir au moins 20 caractères');
} else if (evaluation.commentairePrincipal.length > 1000) {
erreurs.add('Le commentaire principal ne peut pas dépasser 1000 caractères');
}
// Validation de la cohérence entre note et commentaire
if (evaluation.noteGlobale <= 2.0 && evaluation.commentairePrincipal.length < 50) {
erreurs.add('Un commentaire détaillé est requis pour les notes faibles');
}
// Validation des points positifs et d'amélioration
if (evaluation.pointsPositifs != null && evaluation.pointsPositifs!.length > 500) {
erreurs.add('Les points positifs ne peuvent pas dépasser 500 caractères');
}
if (evaluation.pointsAmelioration != null && evaluation.pointsAmelioration!.length > 500) {
erreurs.add('Les points d\'amélioration ne peuvent pas dépasser 500 caractères');
}
// Validation des recommandations
if (evaluation.recommandations != null && evaluation.recommandations!.length > 500) {
erreurs.add('Les recommandations ne peuvent pas dépasser 500 caractères');
}
// Validation de la cohérence de la recommandation
if (evaluation.recommande == true && evaluation.noteGlobale < 3.0) {
erreurs.add('Impossible de recommander avec une note inférieure à 3');
}
if (evaluation.recommande == false && evaluation.noteGlobale >= 4.0) {
erreurs.add('Une note de 4 ou plus devrait normalement être recommandée');
}
// Détection de contenu inapproprié
if (_contientContenuInapproprie(evaluation.commentairePrincipal)) {
erreurs.add('Le commentaire contient du contenu inapproprié');
}
if (erreurs.isNotEmpty) {
return Left(ValidationFailure(erreurs.join(', ')));
}
return const Right(true);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de la validation: ${e.toString()}'));
}
}
bool _contientContenuInapproprie(String texte) {
// Liste simple de mots inappropriés (à étendre selon les besoins)
final motsInappropries = [
'spam', 'arnaque', 'escroquerie', 'fraude',
// Ajouter d'autres mots selon le contexte
];
final texteMinuscule = texte.toLowerCase();
return motsInappropries.any((mot) => texteMinuscule.contains(mot));
}
}
class ValiderEvaluationParams {
final EvaluationAide evaluation;
ValiderEvaluationParams({required this.evaluation});
}
/// Cas d'usage pour calculer le score de qualité d'une évaluation
class CalculerScoreQualiteEvaluationUseCase implements UseCase<double, CalculerScoreQualiteEvaluationParams> {
CalculerScoreQualiteEvaluationUseCase();
@override
Future<Either<Failure, double>> call(CalculerScoreQualiteEvaluationParams params) async {
try {
final evaluation = params.evaluation;
double score = 50.0; // Score de base
// Bonus pour la longueur du commentaire
final longueurCommentaire = evaluation.commentairePrincipal.length;
if (longueurCommentaire >= 100) {
score += 15.0;
} else if (longueurCommentaire >= 50) {
score += 10.0;
} else if (longueurCommentaire >= 20) {
score += 5.0;
}
// Bonus pour les notes détaillées
final notesDetaillees = [
evaluation.noteDelaiReponse,
evaluation.noteCommunication,
evaluation.noteProfessionnalisme,
evaluation.noteRespectEngagements,
];
final nombreNotesDetaillees = notesDetaillees.where((note) => note != null).length;
score += nombreNotesDetaillees * 5.0; // 5 points par note détaillée
// Bonus pour les sections optionnelles remplies
if (evaluation.pointsPositifs != null && evaluation.pointsPositifs!.isNotEmpty) {
score += 5.0;
}
if (evaluation.pointsAmelioration != null && evaluation.pointsAmelioration!.isNotEmpty) {
score += 5.0;
}
if (evaluation.recommandations != null && evaluation.recommandations!.isNotEmpty) {
score += 5.0;
}
// Bonus pour la cohérence
if (_estCoherente(evaluation)) {
score += 10.0;
}
// Malus pour les évaluations extrêmes sans justification
if ((evaluation.noteGlobale <= 1.5 || evaluation.noteGlobale >= 4.5) &&
longueurCommentaire < 50) {
score -= 15.0;
}
// Malus pour les signalements
score -= evaluation.nombreSignalements * 10.0;
return Right(score.clamp(0.0, 100.0));
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du calcul du score de qualité: ${e.toString()}'));
}
}
bool _estCoherente(EvaluationAide evaluation) {
// Vérifier la cohérence entre la note globale et les notes détaillées
final notesDetaillees = [
evaluation.noteDelaiReponse,
evaluation.noteCommunication,
evaluation.noteProfessionnalisme,
evaluation.noteRespectEngagements,
].where((note) => note != null).cast<double>().toList();
if (notesDetaillees.isEmpty) return true;
final moyenneDetaillees = notesDetaillees.reduce((a, b) => a + b) / notesDetaillees.length;
final ecart = (evaluation.noteGlobale - moyenneDetaillees).abs();
// Cohérent si l'écart est inférieur à 1 point
return ecart < 1.0;
}
}
class CalculerScoreQualiteEvaluationParams {
final EvaluationAide evaluation;
CalculerScoreQualiteEvaluationParams({required this.evaluation});
}
/// Cas d'usage pour analyser les tendances d'évaluation
class AnalyserTendancesEvaluationUseCase implements UseCase<AnalyseTendancesEvaluation, AnalyserTendancesEvaluationParams> {
AnalyserTendancesEvaluationUseCase();
@override
Future<Either<Failure, AnalyseTendancesEvaluation>> call(AnalyserTendancesEvaluationParams params) async {
try {
// Simulation d'analyse des tendances d'évaluation
// Dans une vraie implémentation, on analyserait les données historiques
final analyse = AnalyseTendancesEvaluation(
noteMoyenneGlobale: 4.2,
nombreTotalEvaluations: 1247,
repartitionNotes: {
5: 456,
4: 523,
3: 189,
2: 58,
1: 21,
},
pourcentageRecommandations: 78.5,
tempsReponseEvaluationMoyen: const Duration(days: 3),
criteresLesMieuxNotes: [
CritereNote('Respect des engagements', 4.6),
CritereNote('Communication', 4.3),
CritereNote('Professionnalisme', 4.1),
CritereNote('Délai de réponse', 3.9),
],
typeEvaluateursPlusActifs: [
TypeEvaluateurActivite(TypeEvaluateur.beneficiaire, 67.2),
TypeEvaluateurActivite(TypeEvaluateur.proposant, 23.8),
TypeEvaluateurActivite(TypeEvaluateur.evaluateurOfficial, 6.5),
TypeEvaluateurActivite(TypeEvaluateur.administrateur, 2.5),
],
evolutionSatisfaction: EvolutionSatisfaction(
dernierMois: 4.2,
moisPrecedent: 4.0,
tendance: TendanceSatisfaction.hausse,
),
recommandationsAmelioration: [
'Améliorer les délais de réponse des proposants',
'Encourager plus d\'évaluations détaillées',
'Former les proposants à la communication',
],
);
return Right(analyse);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de l\'analyse des tendances: ${e.toString()}'));
}
}
}
class AnalyserTendancesEvaluationParams {
final String organisationId;
final DateTime? dateDebut;
final DateTime? dateFin;
AnalyserTendancesEvaluationParams({
required this.organisationId,
this.dateDebut,
this.dateFin,
});
}
/// Classes pour l'analyse des tendances d'évaluation
class AnalyseTendancesEvaluation {
final double noteMoyenneGlobale;
final int nombreTotalEvaluations;
final Map<int, int> repartitionNotes;
final double pourcentageRecommandations;
final Duration tempsReponseEvaluationMoyen;
final List<CritereNote> criteresLesMieuxNotes;
final List<TypeEvaluateurActivite> typeEvaluateursPlusActifs;
final EvolutionSatisfaction evolutionSatisfaction;
final List<String> recommandationsAmelioration;
const AnalyseTendancesEvaluation({
required this.noteMoyenneGlobale,
required this.nombreTotalEvaluations,
required this.repartitionNotes,
required this.pourcentageRecommandations,
required this.tempsReponseEvaluationMoyen,
required this.criteresLesMieuxNotes,
required this.typeEvaluateursPlusActifs,
required this.evolutionSatisfaction,
required this.recommandationsAmelioration,
});
}
class CritereNote {
final String nom;
final double noteMoyenne;
const CritereNote(this.nom, this.noteMoyenne);
}
class TypeEvaluateurActivite {
final TypeEvaluateur type;
final double pourcentage;
const TypeEvaluateurActivite(this.type, this.pourcentage);
}
class EvolutionSatisfaction {
final double dernierMois;
final double moisPrecedent;
final TendanceSatisfaction tendance;
const EvolutionSatisfaction({
required this.dernierMois,
required this.moisPrecedent,
required this.tendance,
});
}
enum TendanceSatisfaction { hausse, baisse, stable }

View File

@@ -0,0 +1,391 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/demande_aide.dart';
import '../entities/proposition_aide.dart';
import '../repositories/solidarite_repository.dart';
/// Cas d'usage pour trouver les propositions compatibles avec une demande
class TrouverPropositionsCompatiblesUseCase implements UseCase<List<PropositionAide>, TrouverPropositionsCompatiblesParams> {
final SolidariteRepository repository;
TrouverPropositionsCompatiblesUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(TrouverPropositionsCompatiblesParams params) async {
return await repository.trouverPropositionsCompatibles(params.demandeId);
}
}
class TrouverPropositionsCompatiblesParams {
final String demandeId;
TrouverPropositionsCompatiblesParams({required this.demandeId});
}
/// Cas d'usage pour trouver les demandes compatibles avec une proposition
class TrouverDemandesCompatiblesUseCase implements UseCase<List<DemandeAide>, TrouverDemandesCompatiblesParams> {
final SolidariteRepository repository;
TrouverDemandesCompatiblesUseCase(this.repository);
@override
Future<Either<Failure, List<DemandeAide>>> call(TrouverDemandesCompatiblesParams params) async {
return await repository.trouverDemandesCompatibles(params.propositionId);
}
}
class TrouverDemandesCompatiblesParams {
final String propositionId;
TrouverDemandesCompatiblesParams({required this.propositionId});
}
/// Cas d'usage pour rechercher des proposants financiers
class RechercherProposantsFinanciersUseCase implements UseCase<List<PropositionAide>, RechercherProposantsFinanciersParams> {
final SolidariteRepository repository;
RechercherProposantsFinanciersUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(RechercherProposantsFinanciersParams params) async {
return await repository.rechercherProposantsFinanciers(params.demandeId);
}
}
class RechercherProposantsFinanciersParams {
final String demandeId;
RechercherProposantsFinanciersParams({required this.demandeId});
}
/// Cas d'usage pour calculer le score de compatibilité entre une demande et une proposition
class CalculerScoreCompatibiliteUseCase implements UseCase<double, CalculerScoreCompatibiliteParams> {
CalculerScoreCompatibiliteUseCase();
@override
Future<Either<Failure, double>> call(CalculerScoreCompatibiliteParams params) async {
try {
final demande = params.demande;
final proposition = params.proposition;
double score = 0.0;
// 1. Correspondance du type d'aide (40 points max)
if (demande.typeAide == proposition.typeAide) {
score += 40.0;
} else if (_sontTypesCompatibles(demande.typeAide, proposition.typeAide)) {
score += 25.0;
} else if (proposition.typeAide == TypeAide.autre) {
score += 15.0;
}
// 2. Compatibilité financière (25 points max)
if (_necessiteMontant(demande.typeAide) && proposition.montantMaximum != null) {
final montantDemande = demande.montantDemande;
if (montantDemande != null) {
if (montantDemande <= proposition.montantMaximum!) {
score += 25.0;
} else {
// Pénalité proportionnelle au dépassement
double ratio = proposition.montantMaximum! / montantDemande;
score += 25.0 * ratio;
}
}
} else if (!_necessiteMontant(demande.typeAide)) {
score += 25.0; // Pas de contrainte financière
}
// 3. Expérience du proposant (15 points max)
if (proposition.nombreBeneficiairesAides > 0) {
score += (proposition.nombreBeneficiairesAides * 2.0).clamp(0.0, 15.0);
}
// 4. Réputation (10 points max)
if (proposition.noteMoyenne != null && proposition.nombreEvaluations >= 3) {
score += (proposition.noteMoyenne! - 3.0) * 3.33;
}
// 5. Disponibilité et capacité (10 points max)
if (proposition.peutAccepterBeneficiaires) {
double ratioCapacite = proposition.placesRestantes / proposition.nombreMaxBeneficiaires;
score += 10.0 * ratioCapacite;
}
// Bonus et malus additionnels
score += _calculerBonusGeographique(demande, proposition);
score += _calculerBonusTemporel(demande, proposition);
score -= _calculerMalusDelai(demande, proposition);
return Right(score.clamp(0.0, 100.0));
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du calcul de compatibilité: ${e.toString()}'));
}
}
bool _sontTypesCompatibles(TypeAide typeAide1, TypeAide typeAide2) {
// Définir les groupes de types compatibles
final groupesCompatibles = [
[TypeAide.aideFinanciereUrgente, TypeAide.aideFinanciereMedicale, TypeAide.aideFinanciereEducation],
[TypeAide.aideMaterielleVetements, TypeAide.aideMaterielleNourriture],
[TypeAide.aideProfessionnelleFormation, TypeAide.aideSocialeAccompagnement],
];
for (final groupe in groupesCompatibles) {
if (groupe.contains(typeAide1) && groupe.contains(typeAide2)) {
return true;
}
}
return false;
}
bool _necessiteMontant(TypeAide typeAide) {
return [
TypeAide.aideFinanciereUrgente,
TypeAide.aideFinanciereMedicale,
TypeAide.aideFinanciereEducation,
].contains(typeAide);
}
double _calculerBonusGeographique(DemandeAide demande, PropositionAide proposition) {
// Simulation - dans une vraie implémentation, on utiliserait les données de localisation
if (demande.localisation != null && proposition.zonesGeographiques.isNotEmpty) {
// Logique de proximité géographique
return 5.0;
}
return 0.0;
}
double _calculerBonusTemporel(DemandeAide demande, PropositionAide proposition) {
double bonus = 0.0;
// Bonus pour demande urgente
if (demande.estUrgente) {
bonus += 5.0;
}
// Bonus pour proposition récente
final joursDepuisCreation = DateTime.now().difference(proposition.dateCreation).inDays;
if (joursDepuisCreation <= 30) {
bonus += 3.0;
}
return bonus;
}
double _calculerMalusDelai(DemandeAide demande, PropositionAide proposition) {
double malus = 0.0;
// Malus si la demande est en retard
if (demande.delaiDepasse) {
malus += 5.0;
}
// Malus si la proposition a un délai de réponse long
if (proposition.delaiReponseHeures > 168) { // Plus d'une semaine
malus += 3.0;
}
return malus;
}
}
class CalculerScoreCompatibiliteParams {
final DemandeAide demande;
final PropositionAide proposition;
CalculerScoreCompatibiliteParams({
required this.demande,
required this.proposition,
});
}
/// Cas d'usage pour effectuer un matching intelligent
class EffectuerMatchingIntelligentUseCase implements UseCase<List<ResultatMatching>, EffectuerMatchingIntelligentParams> {
final TrouverPropositionsCompatiblesUseCase trouverPropositionsCompatibles;
final CalculerScoreCompatibiliteUseCase calculerScoreCompatibilite;
EffectuerMatchingIntelligentUseCase({
required this.trouverPropositionsCompatibles,
required this.calculerScoreCompatibilite,
});
@override
Future<Either<Failure, List<ResultatMatching>>> call(EffectuerMatchingIntelligentParams params) async {
try {
// 1. Trouver les propositions compatibles
final propositionsResult = await trouverPropositionsCompatibles(
TrouverPropositionsCompatiblesParams(demandeId: params.demande.id)
);
return propositionsResult.fold(
(failure) => Left(failure),
(propositions) async {
// 2. Calculer les scores de compatibilité
final resultats = <ResultatMatching>[];
for (final proposition in propositions) {
final scoreResult = await calculerScoreCompatibilite(
CalculerScoreCompatibiliteParams(
demande: params.demande,
proposition: proposition,
)
);
scoreResult.fold(
(failure) {
// Ignorer les erreurs de calcul de score individuel
},
(score) {
if (score >= params.scoreMinimum) {
resultats.add(ResultatMatching(
proposition: proposition,
score: score,
raisonCompatibilite: _genererRaisonCompatibilite(params.demande, proposition, score),
));
}
},
);
}
// 3. Trier par score décroissant
resultats.sort((a, b) => b.score.compareTo(a.score));
// 4. Limiter le nombre de résultats
final resultatsLimites = resultats.take(params.limiteResultats).toList();
return Right(resultatsLimites);
},
);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du matching intelligent: ${e.toString()}'));
}
}
String _genererRaisonCompatibilite(DemandeAide demande, PropositionAide proposition, double score) {
final raisons = <String>[];
// Type d'aide
if (demande.typeAide == proposition.typeAide) {
raisons.add('Type d\'aide identique');
}
// Compatibilité financière
if (demande.montantDemande != null && proposition.montantMaximum != null) {
if (demande.montantDemande! <= proposition.montantMaximum!) {
raisons.add('Montant compatible');
}
}
// Expérience
if (proposition.nombreBeneficiairesAides > 5) {
raisons.add('Proposant expérimenté');
}
// Réputation
if (proposition.noteMoyenne != null && proposition.noteMoyenne! >= 4.0) {
raisons.add('Excellente réputation');
}
// Disponibilité
if (proposition.peutAccepterBeneficiaires) {
raisons.add('Places disponibles');
}
return raisons.isEmpty ? 'Compatible' : raisons.join(', ');
}
}
class EffectuerMatchingIntelligentParams {
final DemandeAide demande;
final double scoreMinimum;
final int limiteResultats;
EffectuerMatchingIntelligentParams({
required this.demande,
this.scoreMinimum = 30.0,
this.limiteResultats = 10,
});
}
/// Classe représentant un résultat de matching
class ResultatMatching {
final PropositionAide proposition;
final double score;
final String raisonCompatibilite;
const ResultatMatching({
required this.proposition,
required this.score,
required this.raisonCompatibilite,
});
}
/// Cas d'usage pour analyser les tendances de matching
class AnalyserTendancesMatchingUseCase implements UseCase<AnalyseTendances, AnalyserTendancesMatchingParams> {
AnalyserTendancesMatchingUseCase();
@override
Future<Either<Failure, AnalyseTendances>> call(AnalyserTendancesMatchingParams params) async {
try {
// Simulation d'analyse des tendances
// Dans une vraie implémentation, on analyserait les données historiques
final analyse = AnalyseTendances(
tauxMatchingMoyen: 78.5,
tempsMatchingMoyen: const Duration(hours: 6),
typesAidePlusDemandesMap: {
TypeAide.aideFinanciereUrgente: 45,
TypeAide.aideFinanciereMedicale: 32,
TypeAide.aideMaterielleNourriture: 28,
},
typesAidePlusProposesMap: {
TypeAide.aideFinanciereEducation: 38,
TypeAide.aideProfessionnelleFormation: 25,
TypeAide.aideSocialeAccompagnement: 22,
},
heuresOptimalesMatching: ['09:00', '14:00', '18:00'],
recommandations: [
'Augmenter les propositions d\'aide financière urgente',
'Promouvoir les aides matérielles auprès des proposants',
'Optimiser les notifications entre 9h et 18h',
],
);
return Right(analyse);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de l\'analyse des tendances: ${e.toString()}'));
}
}
}
class AnalyserTendancesMatchingParams {
final String organisationId;
final DateTime? dateDebut;
final DateTime? dateFin;
AnalyserTendancesMatchingParams({
required this.organisationId,
this.dateDebut,
this.dateFin,
});
}
/// Classe représentant une analyse des tendances de matching
class AnalyseTendances {
final double tauxMatchingMoyen;
final Duration tempsMatchingMoyen;
final Map<TypeAide, int> typesAidePlusDemandesMap;
final Map<TypeAide, int> typesAidePlusProposesMap;
final List<String> heuresOptimalesMatching;
final List<String> recommandations;
const AnalyseTendances({
required this.tauxMatchingMoyen,
required this.tempsMatchingMoyen,
required this.typesAidePlusDemandesMap,
required this.typesAidePlusProposesMap,
required this.heuresOptimalesMatching,
required this.recommandations,
});
}

View File

@@ -0,0 +1,394 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/proposition_aide.dart';
import '../entities/demande_aide.dart';
import '../repositories/solidarite_repository.dart';
/// Cas d'usage pour créer une nouvelle proposition d'aide
class CreerPropositionAideUseCase implements UseCase<PropositionAide, CreerPropositionAideParams> {
final SolidariteRepository repository;
CreerPropositionAideUseCase(this.repository);
@override
Future<Either<Failure, PropositionAide>> call(CreerPropositionAideParams params) async {
return await repository.creerPropositionAide(params.proposition);
}
}
class CreerPropositionAideParams {
final PropositionAide proposition;
CreerPropositionAideParams({required this.proposition});
}
/// Cas d'usage pour mettre à jour une proposition d'aide
class MettreAJourPropositionAideUseCase implements UseCase<PropositionAide, MettreAJourPropositionAideParams> {
final SolidariteRepository repository;
MettreAJourPropositionAideUseCase(this.repository);
@override
Future<Either<Failure, PropositionAide>> call(MettreAJourPropositionAideParams params) async {
return await repository.mettreAJourPropositionAide(params.proposition);
}
}
class MettreAJourPropositionAideParams {
final PropositionAide proposition;
MettreAJourPropositionAideParams({required this.proposition});
}
/// Cas d'usage pour obtenir une proposition d'aide par ID
class ObtenirPropositionAideUseCase implements UseCase<PropositionAide, ObtenirPropositionAideParams> {
final SolidariteRepository repository;
ObtenirPropositionAideUseCase(this.repository);
@override
Future<Either<Failure, PropositionAide>> call(ObtenirPropositionAideParams params) async {
return await repository.obtenirPropositionAide(params.id);
}
}
class ObtenirPropositionAideParams {
final String id;
ObtenirPropositionAideParams({required this.id});
}
/// Cas d'usage pour changer le statut d'une proposition d'aide
class ChangerStatutPropositionUseCase implements UseCase<PropositionAide, ChangerStatutPropositionParams> {
final SolidariteRepository repository;
ChangerStatutPropositionUseCase(this.repository);
@override
Future<Either<Failure, PropositionAide>> call(ChangerStatutPropositionParams params) async {
return await repository.changerStatutProposition(
propositionId: params.propositionId,
activer: params.activer,
);
}
}
class ChangerStatutPropositionParams {
final String propositionId;
final bool activer;
ChangerStatutPropositionParams({
required this.propositionId,
required this.activer,
});
}
/// Cas d'usage pour rechercher des propositions d'aide
class RechercherPropositionsAideUseCase implements UseCase<List<PropositionAide>, RechercherPropositionsAideParams> {
final SolidariteRepository repository;
RechercherPropositionsAideUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(RechercherPropositionsAideParams params) async {
return await repository.rechercherPropositions(
organisationId: params.organisationId,
typeAide: params.typeAide,
proposantId: params.proposantId,
actives: params.actives,
page: params.page,
taille: params.taille,
);
}
}
class RechercherPropositionsAideParams {
final String? organisationId;
final TypeAide? typeAide;
final String? proposantId;
final bool? actives;
final int page;
final int taille;
RechercherPropositionsAideParams({
this.organisationId,
this.typeAide,
this.proposantId,
this.actives,
this.page = 0,
this.taille = 20,
});
}
/// Cas d'usage pour obtenir les propositions actives pour un type d'aide
class ObtenirPropositionsActivesUseCase implements UseCase<List<PropositionAide>, ObtenirPropositionsActivesParams> {
final SolidariteRepository repository;
ObtenirPropositionsActivesUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(ObtenirPropositionsActivesParams params) async {
return await repository.obtenirPropositionsActives(params.typeAide);
}
}
class ObtenirPropositionsActivesParams {
final TypeAide typeAide;
ObtenirPropositionsActivesParams({required this.typeAide});
}
/// Cas d'usage pour obtenir les meilleures propositions
class ObtenirMeilleuresPropositionsUseCase implements UseCase<List<PropositionAide>, ObtenirMeilleuresPropositionsParams> {
final SolidariteRepository repository;
ObtenirMeilleuresPropositionsUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(ObtenirMeilleuresPropositionsParams params) async {
return await repository.obtenirMeilleuresPropositions(params.limite);
}
}
class ObtenirMeilleuresPropositionsParams {
final int limite;
ObtenirMeilleuresPropositionsParams({this.limite = 10});
}
/// Cas d'usage pour obtenir les propositions de l'utilisateur connecté
class ObtenirMesPropositionsUseCase implements UseCase<List<PropositionAide>, ObtenirMesPropositionsParams> {
final SolidariteRepository repository;
ObtenirMesPropositionsUseCase(this.repository);
@override
Future<Either<Failure, List<PropositionAide>>> call(ObtenirMesPropositionsParams params) async {
return await repository.obtenirMesPropositions(params.utilisateurId);
}
}
class ObtenirMesPropositionsParams {
final String utilisateurId;
ObtenirMesPropositionsParams({required this.utilisateurId});
}
/// Cas d'usage pour valider une proposition d'aide avant création
class ValiderPropositionAideUseCase implements UseCase<bool, ValiderPropositionAideParams> {
ValiderPropositionAideUseCase();
@override
Future<Either<Failure, bool>> call(ValiderPropositionAideParams params) async {
try {
final proposition = params.proposition;
final erreurs = <String>[];
// Validation du titre
if (proposition.titre.trim().isEmpty) {
erreurs.add('Le titre est obligatoire');
} else if (proposition.titre.length < 10) {
erreurs.add('Le titre doit contenir au moins 10 caractères');
} else if (proposition.titre.length > 100) {
erreurs.add('Le titre ne peut pas dépasser 100 caractères');
}
// Validation de la description
if (proposition.description.trim().isEmpty) {
erreurs.add('La description est obligatoire');
} else if (proposition.description.length < 50) {
erreurs.add('La description doit contenir au moins 50 caractères');
} else if (proposition.description.length > 1000) {
erreurs.add('La description ne peut pas dépasser 1000 caractères');
}
// Validation du nombre maximum de bénéficiaires
if (proposition.nombreMaxBeneficiaires <= 0) {
erreurs.add('Le nombre maximum de bénéficiaires doit être supérieur à zéro');
} else if (proposition.nombreMaxBeneficiaires > 100) {
erreurs.add('Le nombre maximum de bénéficiaires ne peut pas dépasser 100');
}
// Validation des montants pour les aides financières
if (_estAideFinanciere(proposition.typeAide)) {
if (proposition.montantMaximum == null) {
erreurs.add('Le montant maximum est obligatoire pour les aides financières');
} else if (proposition.montantMaximum! <= 0) {
erreurs.add('Le montant maximum doit être supérieur à zéro');
} else if (proposition.montantMaximum! > 1000000) {
erreurs.add('Le montant maximum ne peut pas dépasser 1 000 000 FCFA');
}
if (proposition.montantMinimum != null) {
if (proposition.montantMinimum! <= 0) {
erreurs.add('Le montant minimum doit être supérieur à zéro');
} else if (proposition.montantMaximum != null &&
proposition.montantMinimum! >= proposition.montantMaximum!) {
erreurs.add('Le montant minimum doit être inférieur au montant maximum');
}
}
}
// Validation du délai de réponse
if (proposition.delaiReponseHeures <= 0) {
erreurs.add('Le délai de réponse doit être supérieur à zéro');
} else if (proposition.delaiReponseHeures > 720) { // 30 jours max
erreurs.add('Le délai de réponse ne peut pas dépasser 30 jours');
}
// Validation du contact proposant
final contact = proposition.contactProposant;
if (contact.nom.trim().isEmpty) {
erreurs.add('Le nom du contact est obligatoire');
}
if (contact.telephone.trim().isEmpty) {
erreurs.add('Le téléphone du contact est obligatoire');
} else if (!_isValidPhoneNumber(contact.telephone)) {
erreurs.add('Le numéro de téléphone n\'est pas valide');
}
// Validation de l'email si fourni
if (contact.email != null && contact.email!.isNotEmpty) {
if (!_isValidEmail(contact.email!)) {
erreurs.add('L\'adresse email n\'est pas valide');
}
}
// Validation des zones géographiques
if (proposition.zonesGeographiques.isEmpty) {
erreurs.add('Au moins une zone géographique doit être spécifiée');
}
// Validation des créneaux de disponibilité
if (proposition.creneauxDisponibilite.isEmpty) {
erreurs.add('Au moins un créneau de disponibilité doit être spécifié');
} else {
for (int i = 0; i < proposition.creneauxDisponibilite.length; i++) {
final creneau = proposition.creneauxDisponibilite[i];
if (!_isValidTimeFormat(creneau.heureDebut)) {
erreurs.add('L\'heure de début du créneau ${i + 1} n\'est pas valide (format HH:MM)');
}
if (!_isValidTimeFormat(creneau.heureFin)) {
erreurs.add('L\'heure de fin du créneau ${i + 1} n\'est pas valide (format HH:MM)');
}
if (_isValidTimeFormat(creneau.heureDebut) &&
_isValidTimeFormat(creneau.heureFin) &&
_compareTime(creneau.heureDebut, creneau.heureFin) >= 0) {
erreurs.add('L\'heure de fin du créneau ${i + 1} doit être après l\'heure de début');
}
}
}
// Validation de la date d'expiration
if (proposition.dateExpiration != null) {
if (proposition.dateExpiration!.isBefore(DateTime.now())) {
erreurs.add('La date d\'expiration ne peut pas être dans le passé');
} else if (proposition.dateExpiration!.isAfter(DateTime.now().add(const Duration(days: 365)))) {
erreurs.add('La date d\'expiration ne peut pas dépasser un an');
}
}
if (erreurs.isNotEmpty) {
return Left(ValidationFailure(erreurs.join(', ')));
}
return const Right(true);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de la validation: ${e.toString()}'));
}
}
bool _estAideFinanciere(TypeAide typeAide) {
return [
TypeAide.aideFinanciereUrgente,
TypeAide.aideFinanciereMedicale,
TypeAide.aideFinanciereEducation,
].contains(typeAide);
}
bool _isValidPhoneNumber(String phone) {
final phoneRegex = RegExp(r'^(\+225)?[0-9]{8,10}$');
return phoneRegex.hasMatch(phone.replaceAll(RegExp(r'[\s\-\(\)]'), ''));
}
bool _isValidEmail(String email) {
final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
return emailRegex.hasMatch(email);
}
bool _isValidTimeFormat(String time) {
final timeRegex = RegExp(r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$');
return timeRegex.hasMatch(time);
}
int _compareTime(String time1, String time2) {
final parts1 = time1.split(':');
final parts2 = time2.split(':');
final minutes1 = int.parse(parts1[0]) * 60 + int.parse(parts1[1]);
final minutes2 = int.parse(parts2[0]) * 60 + int.parse(parts2[1]);
return minutes1.compareTo(minutes2);
}
}
class ValiderPropositionAideParams {
final PropositionAide proposition;
ValiderPropositionAideParams({required this.proposition});
}
/// Cas d'usage pour calculer le score de pertinence d'une proposition
class CalculerScorePropositionUseCase implements UseCase<double, CalculerScorePropositionParams> {
CalculerScorePropositionUseCase();
@override
Future<Either<Failure, double>> call(CalculerScorePropositionParams params) async {
try {
final proposition = params.proposition;
double score = 50.0; // Score de base
// Bonus pour l'expérience (nombre d'aides réalisées)
score += (proposition.nombreBeneficiairesAides * 2.0).clamp(0.0, 20.0);
// Bonus pour la note moyenne
if (proposition.noteMoyenne != null && proposition.nombreEvaluations >= 3) {
score += (proposition.noteMoyenne! - 3.0) * 10.0;
}
// Bonus pour la récence (proposition créée récemment)
final joursDepuisCreation = DateTime.now().difference(proposition.dateCreation).inDays;
if (joursDepuisCreation <= 30) {
score += 10.0;
} else if (joursDepuisCreation <= 90) {
score += 5.0;
}
// Bonus pour la disponibilité
if (proposition.isActiveEtDisponible) {
score += 15.0;
}
// Malus pour l'inactivité (pas de vues)
if (proposition.nombreVues == 0) {
score -= 10.0;
}
// Bonus pour la vérification
if (proposition.estVerifiee) {
score += 5.0;
}
return Right(score.clamp(0.0, 100.0));
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du calcul du score: ${e.toString()}'));
}
}
}
class CalculerScorePropositionParams {
final PropositionAide proposition;
CalculerScorePropositionParams({required this.proposition});
}

View File

@@ -0,0 +1,428 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecases/usecase.dart';
import '../entities/demande_aide.dart';
import '../entities/proposition_aide.dart';
import '../repositories/solidarite_repository.dart';
/// Cas d'usage pour obtenir les statistiques complètes de solidarité
class ObtenirStatistiquesSolidariteUseCase implements UseCase<StatistiquesSolidarite, ObtenirStatistiquesSolidariteParams> {
final SolidariteRepository repository;
ObtenirStatistiquesSolidariteUseCase(this.repository);
@override
Future<Either<Failure, StatistiquesSolidarite>> call(ObtenirStatistiquesSolidariteParams params) async {
final result = await repository.obtenirStatistiquesSolidarite(params.organisationId);
return result.fold(
(failure) => Left(failure),
(data) {
try {
final statistiques = StatistiquesSolidarite.fromMap(data);
return Right(statistiques);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du parsing des statistiques: ${e.toString()}'));
}
},
);
}
}
class ObtenirStatistiquesSolidariteParams {
final String organisationId;
ObtenirStatistiquesSolidariteParams({required this.organisationId});
}
/// Cas d'usage pour calculer les KPIs de performance
class CalculerKPIsPerformanceUseCase implements UseCase<KPIsPerformance, CalculerKPIsPerformanceParams> {
CalculerKPIsPerformanceUseCase();
@override
Future<Either<Failure, KPIsPerformance>> call(CalculerKPIsPerformanceParams params) async {
try {
// Simulation de calculs KPI - dans une vraie implémentation,
// ces calculs seraient basés sur des données réelles
final kpis = KPIsPerformance(
efficaciteMatching: _calculerEfficaciteMatching(params.statistiques),
tempsReponseMoyen: _calculerTempsReponseMoyen(params.statistiques),
satisfactionGlobale: _calculerSatisfactionGlobale(params.statistiques),
tauxResolution: _calculerTauxResolution(params.statistiques),
impactSocial: _calculerImpactSocial(params.statistiques),
engagementCommunautaire: _calculerEngagementCommunautaire(params.statistiques),
evolutionMensuelle: _calculerEvolutionMensuelle(params.statistiques),
objectifsAtteints: _verifierObjectifsAtteints(params.statistiques),
);
return Right(kpis);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors du calcul des KPIs: ${e.toString()}'));
}
}
double _calculerEfficaciteMatching(StatistiquesSolidarite stats) {
if (stats.demandes.total == 0) return 0.0;
final demandesMatchees = stats.demandes.parStatut[StatutAide.approuvee] ?? 0;
return (demandesMatchees / stats.demandes.total) * 100;
}
Duration _calculerTempsReponseMoyen(StatistiquesSolidarite stats) {
return Duration(hours: stats.demandes.delaiMoyenTraitementHeures.toInt());
}
double _calculerSatisfactionGlobale(StatistiquesSolidarite stats) {
// Simulation basée sur le taux d'approbation
return (stats.demandes.tauxApprobation / 100) * 5.0;
}
double _calculerTauxResolution(StatistiquesSolidarite stats) {
if (stats.demandes.total == 0) return 0.0;
final demandesResolues = (stats.demandes.parStatut[StatutAide.terminee] ?? 0) +
(stats.demandes.parStatut[StatutAide.versee] ?? 0) +
(stats.demandes.parStatut[StatutAide.livree] ?? 0);
return (demandesResolues / stats.demandes.total) * 100;
}
int _calculerImpactSocial(StatistiquesSolidarite stats) {
// Estimation du nombre de personnes aidées
return (stats.demandes.total * 2.3).round(); // Moyenne de 2.3 personnes par demande
}
double _calculerEngagementCommunautaire(StatistiquesSolidarite stats) {
if (stats.propositions.total == 0) return 0.0;
return (stats.propositions.actives / stats.propositions.total) * 100;
}
EvolutionMensuelle _calculerEvolutionMensuelle(StatistiquesSolidarite stats) {
// Simulation d'évolution - dans une vraie implémentation,
// on comparerait avec les données du mois précédent
return const EvolutionMensuelle(
demandes: 12.5,
propositions: 8.3,
montants: 15.7,
satisfaction: 2.1,
);
}
Map<String, bool> _verifierObjectifsAtteints(StatistiquesSolidarite stats) {
return {
'tauxApprobation': stats.demandes.tauxApprobation >= 80.0,
'delaiTraitement': stats.demandes.delaiMoyenTraitementHeures <= 48.0,
'satisfactionMinimum': true, // Simulation
'propositionsActives': stats.propositions.actives >= 10,
};
}
}
class CalculerKPIsPerformanceParams {
final StatistiquesSolidarite statistiques;
CalculerKPIsPerformanceParams({required this.statistiques});
}
/// Cas d'usage pour générer un rapport d'activité
class GenererRapportActiviteUseCase implements UseCase<RapportActivite, GenererRapportActiviteParams> {
GenererRapportActiviteUseCase();
@override
Future<Either<Failure, RapportActivite>> call(GenererRapportActiviteParams params) async {
try {
final rapport = RapportActivite(
periode: params.periode,
dateGeneration: DateTime.now(),
resumeExecutif: _genererResumeExecutif(params.statistiques),
metriquesClees: _extraireMetriquesClees(params.statistiques),
analyseTendances: _analyserTendances(params.statistiques),
recommandations: _genererRecommandations(params.statistiques),
annexes: _genererAnnexes(params.statistiques),
);
return Right(rapport);
} catch (e) {
return Left(UnexpectedFailure('Erreur lors de la génération du rapport: ${e.toString()}'));
}
}
String _genererResumeExecutif(StatistiquesSolidarite stats) {
return '''
Durant cette période, ${stats.demandes.total} demandes d'aide ont été traitées avec un taux d'approbation de ${stats.demandes.tauxApprobation.toStringAsFixed(1)}%.
${stats.propositions.total} propositions d'aide ont été créées, dont ${stats.propositions.actives} sont actuellement actives.
Le montant total versé s'élève à ${stats.financier.montantTotalVerse.toStringAsFixed(0)} FCFA, représentant ${stats.financier.tauxVersement.toStringAsFixed(1)}% des montants approuvés.
Le délai moyen de traitement des demandes est de ${stats.demandes.delaiMoyenTraitementHeures.toStringAsFixed(1)} heures.
''';
}
Map<String, dynamic> _extraireMetriquesClees(StatistiquesSolidarite stats) {
return {
'totalDemandes': stats.demandes.total,
'tauxApprobation': stats.demandes.tauxApprobation,
'montantVerse': stats.financier.montantTotalVerse,
'propositionsActives': stats.propositions.actives,
'delaiMoyenTraitement': stats.demandes.delaiMoyenTraitementHeures,
};
}
String _analyserTendances(StatistiquesSolidarite stats) {
return '''
Tendances observées :
- Augmentation de 12.5% des demandes par rapport au mois précédent
- Amélioration du taux d'approbation (+3.2%)
- Réduction du délai moyen de traitement (-8 heures)
- Croissance de l'engagement communautaire (+5.7%)
''';
}
List<String> _genererRecommandations(StatistiquesSolidarite stats) {
final recommandations = <String>[];
if (stats.demandes.tauxApprobation < 80.0) {
recommandations.add('Améliorer le processus d\'évaluation pour augmenter le taux d\'approbation');
}
if (stats.demandes.delaiMoyenTraitementHeures > 48.0) {
recommandations.add('Optimiser les délais de traitement des demandes');
}
if (stats.propositions.actives < 10) {
recommandations.add('Encourager plus de propositions d\'aide de la part des membres');
}
if (stats.financier.tauxVersement < 90.0) {
recommandations.add('Améliorer le suivi des versements approuvés');
}
if (recommandations.isEmpty) {
recommandations.add('Maintenir l\'excellent niveau de performance actuel');
}
return recommandations;
}
Map<String, dynamic> _genererAnnexes(StatistiquesSolidarite stats) {
return {
'repartitionParType': stats.demandes.parType,
'repartitionParStatut': stats.demandes.parStatut,
'repartitionParPriorite': stats.demandes.parPriorite,
'statistiquesFinancieres': {
'montantTotalDemande': stats.financier.montantTotalDemande,
'montantTotalApprouve': stats.financier.montantTotalApprouve,
'montantTotalVerse': stats.financier.montantTotalVerse,
'capaciteFinanciereDisponible': stats.financier.capaciteFinanciereDisponible,
},
};
}
}
class GenererRapportActiviteParams {
final StatistiquesSolidarite statistiques;
final PeriodeRapport periode;
GenererRapportActiviteParams({
required this.statistiques,
required this.periode,
});
}
/// Classes de données pour les statistiques
class StatistiquesSolidarite {
final StatistiquesDemandes demandes;
final StatistiquesPropositions propositions;
final StatistiquesFinancieres financier;
final Map<String, dynamic> kpis;
final Map<String, dynamic> tendances;
final DateTime dateCalcul;
final String organisationId;
const StatistiquesSolidarite({
required this.demandes,
required this.propositions,
required this.financier,
required this.kpis,
required this.tendances,
required this.dateCalcul,
required this.organisationId,
});
factory StatistiquesSolidarite.fromMap(Map<String, dynamic> map) {
return StatistiquesSolidarite(
demandes: StatistiquesDemandes.fromMap(map['demandes']),
propositions: StatistiquesPropositions.fromMap(map['propositions']),
financier: StatistiquesFinancieres.fromMap(map['financier']),
kpis: Map<String, dynamic>.from(map['kpis']),
tendances: Map<String, dynamic>.from(map['tendances']),
dateCalcul: DateTime.parse(map['dateCalcul']),
organisationId: map['organisationId'],
);
}
}
class StatistiquesDemandes {
final int total;
final Map<StatutAide, int> parStatut;
final Map<TypeAide, int> parType;
final Map<PrioriteAide, int> parPriorite;
final int urgentes;
final int enRetard;
final double tauxApprobation;
final double delaiMoyenTraitementHeures;
const StatistiquesDemandes({
required this.total,
required this.parStatut,
required this.parType,
required this.parPriorite,
required this.urgentes,
required this.enRetard,
required this.tauxApprobation,
required this.delaiMoyenTraitementHeures,
});
factory StatistiquesDemandes.fromMap(Map<String, dynamic> map) {
return StatistiquesDemandes(
total: map['total'],
parStatut: Map<StatutAide, int>.from(map['parStatut']),
parType: Map<TypeAide, int>.from(map['parType']),
parPriorite: Map<PrioriteAide, int>.from(map['parPriorite']),
urgentes: map['urgentes'],
enRetard: map['enRetard'],
tauxApprobation: map['tauxApprobation'].toDouble(),
delaiMoyenTraitementHeures: map['delaiMoyenTraitementHeures'].toDouble(),
);
}
}
class StatistiquesPropositions {
final int total;
final int actives;
final Map<TypeAide, int> parType;
final int capaciteDisponible;
final double tauxUtilisationMoyen;
final double noteMoyenne;
const StatistiquesPropositions({
required this.total,
required this.actives,
required this.parType,
required this.capaciteDisponible,
required this.tauxUtilisationMoyen,
required this.noteMoyenne,
});
factory StatistiquesPropositions.fromMap(Map<String, dynamic> map) {
return StatistiquesPropositions(
total: map['total'],
actives: map['actives'],
parType: Map<TypeAide, int>.from(map['parType']),
capaciteDisponible: map['capaciteDisponible'],
tauxUtilisationMoyen: map['tauxUtilisationMoyen'].toDouble(),
noteMoyenne: map['noteMoyenne'].toDouble(),
);
}
}
class StatistiquesFinancieres {
final double montantTotalDemande;
final double montantTotalApprouve;
final double montantTotalVerse;
final double capaciteFinanciereDisponible;
final double montantMoyenDemande;
final double tauxVersement;
const StatistiquesFinancieres({
required this.montantTotalDemande,
required this.montantTotalApprouve,
required this.montantTotalVerse,
required this.capaciteFinanciereDisponible,
required this.montantMoyenDemande,
required this.tauxVersement,
});
factory StatistiquesFinancieres.fromMap(Map<String, dynamic> map) {
return StatistiquesFinancieres(
montantTotalDemande: map['montantTotalDemande'].toDouble(),
montantTotalApprouve: map['montantTotalApprouve'].toDouble(),
montantTotalVerse: map['montantTotalVerse'].toDouble(),
capaciteFinanciereDisponible: map['capaciteFinanciereDisponible'].toDouble(),
montantMoyenDemande: map['montantMoyenDemande'].toDouble(),
tauxVersement: map['tauxVersement'].toDouble(),
);
}
}
class KPIsPerformance {
final double efficaciteMatching;
final Duration tempsReponseMoyen;
final double satisfactionGlobale;
final double tauxResolution;
final int impactSocial;
final double engagementCommunautaire;
final EvolutionMensuelle evolutionMensuelle;
final Map<String, bool> objectifsAtteints;
const KPIsPerformance({
required this.efficaciteMatching,
required this.tempsReponseMoyen,
required this.satisfactionGlobale,
required this.tauxResolution,
required this.impactSocial,
required this.engagementCommunautaire,
required this.evolutionMensuelle,
required this.objectifsAtteints,
});
}
class EvolutionMensuelle {
final double demandes;
final double propositions;
final double montants;
final double satisfaction;
const EvolutionMensuelle({
required this.demandes,
required this.propositions,
required this.montants,
required this.satisfaction,
});
}
class RapportActivite {
final PeriodeRapport periode;
final DateTime dateGeneration;
final String resumeExecutif;
final Map<String, dynamic> metriquesClees;
final String analyseTendances;
final List<String> recommandations;
final Map<String, dynamic> annexes;
const RapportActivite({
required this.periode,
required this.dateGeneration,
required this.resumeExecutif,
required this.metriquesClees,
required this.analyseTendances,
required this.recommandations,
required this.annexes,
});
}
class PeriodeRapport {
final DateTime debut;
final DateTime fin;
final String libelle;
const PeriodeRapport({
required this.debut,
required this.fin,
required this.libelle,
});
}