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,481 @@
import 'package:equatable/equatable.dart';
/// Entité représentant une demande d'aide dans le système de solidarité
///
/// Cette entité encapsule toutes les informations relatives à une demande d'aide,
/// incluant les détails du demandeur, le type d'aide, les montants et le statut.
class DemandeAide extends Equatable {
/// Identifiant unique de la demande
final String id;
/// Numéro de référence unique (format: DA-YYYY-NNNNNN)
final String numeroReference;
/// Titre de la demande d'aide
final String titre;
/// Description détaillée de la demande
final String description;
/// Type d'aide demandée
final TypeAide typeAide;
/// Statut actuel de la demande
final StatutAide statut;
/// Priorité de la demande
final PrioriteAide priorite;
/// Identifiant du demandeur
final String demandeurId;
/// Nom complet du demandeur
final String nomDemandeur;
/// Identifiant de l'organisation
final String organisationId;
/// Montant demandé (si applicable)
final double? montantDemande;
/// Montant approuvé (si applicable)
final double? montantApprouve;
/// Montant versé (si applicable)
final double? montantVerse;
/// Date de création de la demande
final DateTime dateCreation;
/// Date de modification
final DateTime dateModification;
/// Date de soumission
final DateTime? dateSoumission;
/// Date d'évaluation
final DateTime? dateEvaluation;
/// Date d'approbation
final DateTime? dateApprobation;
/// Date limite de traitement
final DateTime? dateLimiteTraitement;
/// Identifiant de l'évaluateur assigné
final String? evaluateurId;
/// Commentaires de l'évaluateur
final String? commentairesEvaluateur;
/// Motif de rejet (si applicable)
final String? motifRejet;
/// Informations complémentaires requises
final String? informationsRequises;
/// Justification de l'urgence
final String? justificationUrgence;
/// Contact d'urgence
final ContactUrgence? contactUrgence;
/// Localisation du demandeur
final Localisation? localisation;
/// Liste des bénéficiaires
final List<BeneficiaireAide> beneficiaires;
/// Liste des pièces justificatives
final List<PieceJustificative> piecesJustificatives;
/// Historique des changements de statut
final List<HistoriqueStatut> historiqueStatuts;
/// Commentaires et échanges
final List<CommentaireAide> commentaires;
/// Données personnalisées
final Map<String, dynamic> donneesPersonnalisees;
/// Indique si la demande est modifiable
final bool estModifiable;
/// Indique si la demande est urgente
final bool estUrgente;
/// Indique si le délai est dépassé
final bool delaiDepasse;
/// Indique si la demande est terminée
final bool estTerminee;
const DemandeAide({
required this.id,
required this.numeroReference,
required this.titre,
required this.description,
required this.typeAide,
required this.statut,
required this.priorite,
required this.demandeurId,
required this.nomDemandeur,
required this.organisationId,
this.montantDemande,
this.montantApprouve,
this.montantVerse,
required this.dateCreation,
required this.dateModification,
this.dateSoumission,
this.dateEvaluation,
this.dateApprobation,
this.dateLimiteTraitement,
this.evaluateurId,
this.commentairesEvaluateur,
this.motifRejet,
this.informationsRequises,
this.justificationUrgence,
this.contactUrgence,
this.localisation,
this.beneficiaires = const [],
this.piecesJustificatives = const [],
this.historiqueStatuts = const [],
this.commentaires = const [],
this.donneesPersonnalisees = const {},
this.estModifiable = false,
this.estUrgente = false,
this.delaiDepasse = false,
this.estTerminee = false,
});
/// Calcule le pourcentage d'avancement de la demande
double get pourcentageAvancement {
return statut.pourcentageAvancement;
}
/// Calcule le délai restant en heures
int? get delaiRestantHeures {
if (dateLimiteTraitement == null) return null;
final maintenant = DateTime.now();
if (maintenant.isAfter(dateLimiteTraitement!)) return 0;
return dateLimiteTraitement!.difference(maintenant).inHours;
}
/// Calcule la durée de traitement en jours
int get dureeTraitementJours {
if (dateSoumission == null) return 0;
final dateFin = dateEvaluation ?? DateTime.now();
return dateFin.difference(dateSoumission!).inDays;
}
/// Indique si la demande nécessite une action urgente
bool get necessiteActionUrgente {
return estUrgente || delaiDepasse || priorite == PrioriteAide.critique;
}
/// Obtient la couleur associée au statut
String get couleurStatut => statut.couleur;
/// Obtient l'icône associée au type d'aide
String get iconeTypeAide => typeAide.icone;
@override
List<Object?> get props => [
id,
numeroReference,
titre,
description,
typeAide,
statut,
priorite,
demandeurId,
nomDemandeur,
organisationId,
montantDemande,
montantApprouve,
montantVerse,
dateCreation,
dateModification,
dateSoumission,
dateEvaluation,
dateApprobation,
dateLimiteTraitement,
evaluateurId,
commentairesEvaluateur,
motifRejet,
informationsRequises,
justificationUrgence,
contactUrgence,
localisation,
beneficiaires,
piecesJustificatives,
historiqueStatuts,
commentaires,
donneesPersonnalisees,
estModifiable,
estUrgente,
delaiDepasse,
estTerminee,
];
DemandeAide copyWith({
String? id,
String? numeroReference,
String? titre,
String? description,
TypeAide? typeAide,
StatutAide? statut,
PrioriteAide? priorite,
String? demandeurId,
String? nomDemandeur,
String? organisationId,
double? montantDemande,
double? montantApprouve,
double? montantVerse,
DateTime? dateCreation,
DateTime? dateModification,
DateTime? dateSoumission,
DateTime? dateEvaluation,
DateTime? dateApprobation,
DateTime? dateLimiteTraitement,
String? evaluateurId,
String? commentairesEvaluateur,
String? motifRejet,
String? informationsRequises,
String? justificationUrgence,
ContactUrgence? contactUrgence,
Localisation? localisation,
List<BeneficiaireAide>? beneficiaires,
List<PieceJustificative>? piecesJustificatives,
List<HistoriqueStatut>? historiqueStatuts,
List<CommentaireAide>? commentaires,
Map<String, dynamic>? donneesPersonnalisees,
bool? estModifiable,
bool? estUrgente,
bool? delaiDepasse,
bool? estTerminee,
}) {
return DemandeAide(
id: id ?? this.id,
numeroReference: numeroReference ?? this.numeroReference,
titre: titre ?? this.titre,
description: description ?? this.description,
typeAide: typeAide ?? this.typeAide,
statut: statut ?? this.statut,
priorite: priorite ?? this.priorite,
demandeurId: demandeurId ?? this.demandeurId,
nomDemandeur: nomDemandeur ?? this.nomDemandeur,
organisationId: organisationId ?? this.organisationId,
montantDemande: montantDemande ?? this.montantDemande,
montantApprouve: montantApprouve ?? this.montantApprouve,
montantVerse: montantVerse ?? this.montantVerse,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
dateSoumission: dateSoumission ?? this.dateSoumission,
dateEvaluation: dateEvaluation ?? this.dateEvaluation,
dateApprobation: dateApprobation ?? this.dateApprobation,
dateLimiteTraitement: dateLimiteTraitement ?? this.dateLimiteTraitement,
evaluateurId: evaluateurId ?? this.evaluateurId,
commentairesEvaluateur: commentairesEvaluateur ?? this.commentairesEvaluateur,
motifRejet: motifRejet ?? this.motifRejet,
informationsRequises: informationsRequises ?? this.informationsRequises,
justificationUrgence: justificationUrgence ?? this.justificationUrgence,
contactUrgence: contactUrgence ?? this.contactUrgence,
localisation: localisation ?? this.localisation,
beneficiaires: beneficiaires ?? this.beneficiaires,
piecesJustificatives: piecesJustificatives ?? this.piecesJustificatives,
historiqueStatuts: historiqueStatuts ?? this.historiqueStatuts,
commentaires: commentaires ?? this.commentaires,
donneesPersonnalisees: donneesPersonnalisees ?? this.donneesPersonnalisees,
estModifiable: estModifiable ?? this.estModifiable,
estUrgente: estUrgente ?? this.estUrgente,
delaiDepasse: delaiDepasse ?? this.delaiDepasse,
estTerminee: estTerminee ?? this.estTerminee,
);
}
}
/// Énumération des types d'aide disponibles
enum TypeAide {
aideFinanciereUrgente('Aide financière urgente', 'emergency_fund', '#F44336'),
aideFinanciereMedicale('Aide financière médicale', 'medical_services', '#2196F3'),
aideFinanciereEducation('Aide financière éducation', 'school', '#4CAF50'),
aideMaterielleVetements('Aide matérielle vêtements', 'checkroom', '#FF9800'),
aideMaterielleNourriture('Aide matérielle nourriture', 'restaurant', '#795548'),
aideProfessionnelleFormation('Aide professionnelle formation', 'work', '#9C27B0'),
aideSocialeAccompagnement('Aide sociale accompagnement', 'support', '#607D8B'),
autre('Autre', 'help', '#9E9E9E');
const TypeAide(this.libelle, this.icone, this.couleur);
final String libelle;
final String icone;
final String couleur;
}
/// Énumération des statuts de demande d'aide
enum StatutAide {
brouillon('Brouillon', 'draft', '#9E9E9E', 5.0),
soumise('Soumise', 'send', '#2196F3', 10.0),
enAttente('En attente', 'schedule', '#FF9800', 20.0),
enCoursEvaluation('En cours d\'évaluation', 'assessment', '#9C27B0', 40.0),
approuvee('Approuvée', 'check_circle', '#4CAF50', 70.0),
approuveePartiellement('Approuvée partiellement', 'check_circle_outline', '#8BC34A', 70.0),
rejetee('Rejetée', 'cancel', '#F44336', 100.0),
informationsRequises('Informations requises', 'info', '#FF5722', 30.0),
enCoursVersement('En cours de versement', 'payment', '#00BCD4', 85.0),
versee('Versée', 'paid', '#4CAF50', 100.0),
livree('Livrée', 'local_shipping', '#4CAF50', 100.0),
terminee('Terminée', 'done_all', '#4CAF50', 100.0),
cloturee('Clôturée', 'archive', '#607D8B', 100.0);
const StatutAide(this.libelle, this.icone, this.couleur, this.pourcentageAvancement);
final String libelle;
final String icone;
final String couleur;
final double pourcentageAvancement;
}
/// Énumération des priorités de demande d'aide
enum PrioriteAide {
critique('Critique', '#F44336', 1, 24),
urgente('Urgente', '#FF5722', 2, 72),
elevee('Élevée', '#FF9800', 3, 168),
normale('Normale', '#4CAF50', 4, 336),
faible('Faible', '#9E9E9E', 5, 720);
const PrioriteAide(this.libelle, this.couleur, this.niveau, this.delaiTraitementHeures);
final String libelle;
final String couleur;
final int niveau;
final int delaiTraitementHeures;
}
/// Classe représentant un contact d'urgence
class ContactUrgence extends Equatable {
final String nom;
final String telephone;
final String? email;
final String relation;
const ContactUrgence({
required this.nom,
required this.telephone,
this.email,
required this.relation,
});
@override
List<Object?> get props => [nom, telephone, email, relation];
}
/// Classe représentant une localisation
class Localisation extends Equatable {
final String adresse;
final String ville;
final String? codePostal;
final String? pays;
final double? latitude;
final double? longitude;
const Localisation({
required this.adresse,
required this.ville,
this.codePostal,
this.pays,
this.latitude,
this.longitude,
});
@override
List<Object?> get props => [adresse, ville, codePostal, pays, latitude, longitude];
}
/// Classe représentant un bénéficiaire d'aide
class BeneficiaireAide extends Equatable {
final String nom;
final String prenom;
final int age;
final String relation;
final String? telephone;
const BeneficiaireAide({
required this.nom,
required this.prenom,
required this.age,
required this.relation,
this.telephone,
});
@override
List<Object?> get props => [nom, prenom, age, relation, telephone];
}
/// Classe représentant une pièce justificative
class PieceJustificative extends Equatable {
final String id;
final String nom;
final String type;
final String url;
final int taille;
final DateTime dateAjout;
const PieceJustificative({
required this.id,
required this.nom,
required this.type,
required this.url,
required this.taille,
required this.dateAjout,
});
@override
List<Object?> get props => [id, nom, type, url, taille, dateAjout];
}
/// Classe représentant l'historique des statuts
class HistoriqueStatut extends Equatable {
final StatutAide ancienStatut;
final StatutAide nouveauStatut;
final DateTime dateChangement;
final String? commentaire;
final String? utilisateurId;
const HistoriqueStatut({
required this.ancienStatut,
required this.nouveauStatut,
required this.dateChangement,
this.commentaire,
this.utilisateurId,
});
@override
List<Object?> get props => [ancienStatut, nouveauStatut, dateChangement, commentaire, utilisateurId];
}
/// Classe représentant un commentaire sur une demande
class CommentaireAide extends Equatable {
final String id;
final String contenu;
final String auteurId;
final String nomAuteur;
final DateTime dateCreation;
final bool estPrive;
const CommentaireAide({
required this.id,
required this.contenu,
required this.auteurId,
required this.nomAuteur,
required this.dateCreation,
this.estPrive = false,
});
@override
List<Object?> get props => [id, contenu, auteurId, nomAuteur, dateCreation, estPrive];
}

View File

@@ -0,0 +1,303 @@
import 'package:equatable/equatable.dart';
/// Entité représentant une évaluation d'aide dans le système de solidarité
///
/// Cette entité encapsule toutes les informations relatives à l'évaluation
/// d'une demande d'aide ou d'une proposition d'aide.
class EvaluationAide extends Equatable {
/// Identifiant unique de l'évaluation
final String id;
/// Identifiant de la demande d'aide évaluée
final String demandeAideId;
/// Identifiant de la proposition d'aide (si applicable)
final String? propositionAideId;
/// Identifiant de l'évaluateur
final String evaluateurId;
/// Nom de l'évaluateur
final String nomEvaluateur;
/// Type d'évaluateur
final TypeEvaluateur typeEvaluateur;
/// Note globale (1 à 5)
final double noteGlobale;
/// Note pour le délai de réponse
final double? noteDelaiReponse;
/// Note pour la communication
final double? noteCommunication;
/// Note pour le professionnalisme
final double? noteProfessionnalisme;
/// Note pour le respect des engagements
final double? noteRespectEngagements;
/// Notes détaillées par critère
final Map<String, double> notesDetaillees;
/// Commentaire principal
final String commentairePrincipal;
/// Points positifs
final String? pointsPositifs;
/// Points d'amélioration
final String? pointsAmelioration;
/// Recommandations
final String? recommandations;
/// Indique si l'évaluateur recommande cette aide
final bool? recommande;
/// Date de création de l'évaluation
final DateTime dateCreation;
/// Date de modification
final DateTime dateModification;
/// Date de vérification (si applicable)
final DateTime? dateVerification;
/// Identifiant du vérificateur
final String? verificateurId;
/// Statut de l'évaluation
final StatutEvaluation statut;
/// Nombre de signalements reçus
final int nombreSignalements;
/// Score de qualité calculé automatiquement
final double scoreQualite;
/// Indique si l'évaluation a été modifiée
final bool estModifie;
/// Indique si l'évaluation est vérifiée
final bool estVerifiee;
/// Données personnalisées
final Map<String, dynamic> donneesPersonnalisees;
const EvaluationAide({
required this.id,
required this.demandeAideId,
this.propositionAideId,
required this.evaluateurId,
required this.nomEvaluateur,
required this.typeEvaluateur,
required this.noteGlobale,
this.noteDelaiReponse,
this.noteCommunication,
this.noteProfessionnalisme,
this.noteRespectEngagements,
this.notesDetaillees = const {},
required this.commentairePrincipal,
this.pointsPositifs,
this.pointsAmelioration,
this.recommandations,
this.recommande,
required this.dateCreation,
required this.dateModification,
this.dateVerification,
this.verificateurId,
this.statut = StatutEvaluation.active,
this.nombreSignalements = 0,
required this.scoreQualite,
this.estModifie = false,
this.estVerifiee = false,
this.donneesPersonnalisees = const {},
});
/// Calcule la note moyenne des critères détaillés
double get noteMoyenneDetaillees {
if (notesDetaillees.isEmpty) return noteGlobale;
double somme = notesDetaillees.values.fold(0.0, (a, b) => a + b);
return somme / notesDetaillees.length;
}
/// Indique si l'évaluation est positive (note >= 4)
bool get estPositive => noteGlobale >= 4.0;
/// Indique si l'évaluation est négative (note <= 2)
bool get estNegative => noteGlobale <= 2.0;
/// Obtient le niveau de satisfaction textuel
String get niveauSatisfaction {
if (noteGlobale >= 4.5) return 'Excellent';
if (noteGlobale >= 4.0) return 'Très bien';
if (noteGlobale >= 3.0) return 'Bien';
if (noteGlobale >= 2.0) return 'Moyen';
return 'Insuffisant';
}
/// Obtient la couleur associée à la note
String get couleurNote {
if (noteGlobale >= 4.0) return '#4CAF50'; // Vert
if (noteGlobale >= 3.0) return '#FF9800'; // Orange
return '#F44336'; // Rouge
}
/// Indique si l'évaluation peut être modifiée
bool get peutEtreModifiee {
return statut == StatutEvaluation.active &&
!estVerifiee &&
nombreSignalements < 3;
}
@override
List<Object?> get props => [
id,
demandeAideId,
propositionAideId,
evaluateurId,
nomEvaluateur,
typeEvaluateur,
noteGlobale,
noteDelaiReponse,
noteCommunication,
noteProfessionnalisme,
noteRespectEngagements,
notesDetaillees,
commentairePrincipal,
pointsPositifs,
pointsAmelioration,
recommandations,
recommande,
dateCreation,
dateModification,
dateVerification,
verificateurId,
statut,
nombreSignalements,
scoreQualite,
estModifie,
estVerifiee,
donneesPersonnalisees,
];
EvaluationAide copyWith({
String? id,
String? demandeAideId,
String? propositionAideId,
String? evaluateurId,
String? nomEvaluateur,
TypeEvaluateur? typeEvaluateur,
double? noteGlobale,
double? noteDelaiReponse,
double? noteCommunication,
double? noteProfessionnalisme,
double? noteRespectEngagements,
Map<String, double>? notesDetaillees,
String? commentairePrincipal,
String? pointsPositifs,
String? pointsAmelioration,
String? recommandations,
bool? recommande,
DateTime? dateCreation,
DateTime? dateModification,
DateTime? dateVerification,
String? verificateurId,
StatutEvaluation? statut,
int? nombreSignalements,
double? scoreQualite,
bool? estModifie,
bool? estVerifiee,
Map<String, dynamic>? donneesPersonnalisees,
}) {
return EvaluationAide(
id: id ?? this.id,
demandeAideId: demandeAideId ?? this.demandeAideId,
propositionAideId: propositionAideId ?? this.propositionAideId,
evaluateurId: evaluateurId ?? this.evaluateurId,
nomEvaluateur: nomEvaluateur ?? this.nomEvaluateur,
typeEvaluateur: typeEvaluateur ?? this.typeEvaluateur,
noteGlobale: noteGlobale ?? this.noteGlobale,
noteDelaiReponse: noteDelaiReponse ?? this.noteDelaiReponse,
noteCommunication: noteCommunication ?? this.noteCommunication,
noteProfessionnalisme: noteProfessionnalisme ?? this.noteProfessionnalisme,
noteRespectEngagements: noteRespectEngagements ?? this.noteRespectEngagements,
notesDetaillees: notesDetaillees ?? this.notesDetaillees,
commentairePrincipal: commentairePrincipal ?? this.commentairePrincipal,
pointsPositifs: pointsPositifs ?? this.pointsPositifs,
pointsAmelioration: pointsAmelioration ?? this.pointsAmelioration,
recommandations: recommandations ?? this.recommandations,
recommande: recommande ?? this.recommande,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
dateVerification: dateVerification ?? this.dateVerification,
verificateurId: verificateurId ?? this.verificateurId,
statut: statut ?? this.statut,
nombreSignalements: nombreSignalements ?? this.nombreSignalements,
scoreQualite: scoreQualite ?? this.scoreQualite,
estModifie: estModifie ?? this.estModifie,
estVerifiee: estVerifiee ?? this.estVerifiee,
donneesPersonnalisees: donneesPersonnalisees ?? this.donneesPersonnalisees,
);
}
}
/// Énumération des types d'évaluateur
enum TypeEvaluateur {
beneficiaire('Bénéficiaire', 'person', '#2196F3'),
proposant('Proposant', 'volunteer_activism', '#4CAF50'),
evaluateurOfficial('Évaluateur officiel', 'verified_user', '#9C27B0'),
administrateur('Administrateur', 'admin_panel_settings', '#FF5722');
const TypeEvaluateur(this.libelle, this.icone, this.couleur);
final String libelle;
final String icone;
final String couleur;
}
/// Énumération des statuts d'évaluation
enum StatutEvaluation {
active('Active', 'check_circle', '#4CAF50'),
signalee('Signalée', 'flag', '#FF9800'),
masquee('Masquée', 'visibility_off', '#F44336'),
supprimee('Supprimée', 'delete', '#9E9E9E');
const StatutEvaluation(this.libelle, this.icone, this.couleur);
final String libelle;
final String icone;
final String couleur;
}
/// Classe représentant les statistiques d'évaluations
class StatistiquesEvaluation extends Equatable {
final double noteMoyenne;
final int nombreEvaluations;
final Map<int, int> repartitionNotes;
final double pourcentagePositives;
final double pourcentageRecommandations;
final DateTime derniereMiseAJour;
const StatistiquesEvaluation({
required this.noteMoyenne,
required this.nombreEvaluations,
required this.repartitionNotes,
required this.pourcentagePositives,
required this.pourcentageRecommandations,
required this.derniereMiseAJour,
});
@override
List<Object?> get props => [
noteMoyenne,
nombreEvaluations,
repartitionNotes,
pourcentagePositives,
pourcentageRecommandations,
derniereMiseAJour,
];
}

View File

@@ -0,0 +1,401 @@
import 'package:equatable/equatable.dart';
import 'demande_aide.dart';
/// Entité représentant une proposition d'aide dans le système de solidarité
///
/// Cette entité encapsule toutes les informations relatives à une proposition d'aide,
/// incluant les détails du proposant, les capacités et les conditions.
class PropositionAide extends Equatable {
/// Identifiant unique de la proposition
final String id;
/// Numéro de référence unique (format: PA-YYYY-NNNNNN)
final String numeroReference;
/// Titre de la proposition d'aide
final String titre;
/// Description détaillée de la proposition
final String description;
/// Type d'aide proposée
final TypeAide typeAide;
/// Statut actuel de la proposition
final StatutProposition statut;
/// Identifiant du proposant
final String proposantId;
/// Nom complet du proposant
final String nomProposant;
/// Identifiant de l'organisation
final String organisationId;
/// Montant maximum proposé (si applicable)
final double? montantMaximum;
/// Montant minimum proposé (si applicable)
final double? montantMinimum;
/// Nombre maximum de bénéficiaires
final int nombreMaxBeneficiaires;
/// Nombre de bénéficiaires déjà aidés
final int nombreBeneficiairesAides;
/// Nombre de demandes traitées
final int nombreDemandesTraitees;
/// Montant total versé
final double montantTotalVerse;
/// Date de création de la proposition
final DateTime dateCreation;
/// Date de modification
final DateTime dateModification;
/// Date d'expiration
final DateTime? dateExpiration;
/// Délai de réponse en heures
final int delaiReponseHeures;
/// Zones géographiques couvertes
final List<String> zonesGeographiques;
/// Créneaux de disponibilité
final List<CreneauDisponibilite> creneauxDisponibilite;
/// Critères de sélection
final List<CritereSelection> criteresSelection;
/// Contact du proposant
final ContactProposant contactProposant;
/// Conditions particulières
final String? conditionsParticulieres;
/// Instructions spéciales
final String? instructionsSpeciales;
/// Note moyenne des évaluations
final double? noteMoyenne;
/// Nombre d'évaluations reçues
final int nombreEvaluations;
/// Nombre de vues de la proposition
final int nombreVues;
/// Nombre de candidatures reçues
final int nombreCandidatures;
/// Score de pertinence calculé
final double scorePertinence;
/// Données personnalisées
final Map<String, dynamic> donneesPersonnalisees;
/// Indique si la proposition est disponible
final bool estDisponible;
/// Indique si la proposition est vérifiée
final bool estVerifiee;
/// Indique si la proposition est expirée
final bool estExpiree;
const PropositionAide({
required this.id,
required this.numeroReference,
required this.titre,
required this.description,
required this.typeAide,
required this.statut,
required this.proposantId,
required this.nomProposant,
required this.organisationId,
this.montantMaximum,
this.montantMinimum,
required this.nombreMaxBeneficiaires,
this.nombreBeneficiairesAides = 0,
this.nombreDemandesTraitees = 0,
this.montantTotalVerse = 0.0,
required this.dateCreation,
required this.dateModification,
this.dateExpiration,
this.delaiReponseHeures = 48,
this.zonesGeographiques = const [],
this.creneauxDisponibilite = const [],
this.criteresSelection = const [],
required this.contactProposant,
this.conditionsParticulieres,
this.instructionsSpeciales,
this.noteMoyenne,
this.nombreEvaluations = 0,
this.nombreVues = 0,
this.nombreCandidatures = 0,
this.scorePertinence = 50.0,
this.donneesPersonnalisees = const {},
this.estDisponible = true,
this.estVerifiee = false,
this.estExpiree = false,
});
/// Calcule le nombre de places restantes
int get placesRestantes {
return nombreMaxBeneficiaires - nombreBeneficiairesAides;
}
/// Calcule le pourcentage de capacité utilisée
double get pourcentageCapaciteUtilisee {
if (nombreMaxBeneficiaires == 0) return 0.0;
return (nombreBeneficiairesAides / nombreMaxBeneficiaires) * 100;
}
/// Indique si la proposition peut accepter de nouveaux bénéficiaires
bool get peutAccepterBeneficiaires {
return estDisponible && !estExpiree && placesRestantes > 0;
}
/// Indique si la proposition est active et disponible
bool get isActiveEtDisponible {
return statut == StatutProposition.active && estDisponible && !estExpiree;
}
/// Calcule un score de compatibilité avec une demande
double calculerScoreCompatibilite(DemandeAide demande) {
double score = 0.0;
// Correspondance du type d'aide (40 points max)
if (demande.typeAide == typeAide) {
score += 40.0;
} else {
// Bonus partiel pour les types similaires
score += 20.0;
}
// Compatibilité financière (25 points max)
if (demande.montantDemande != null && montantMaximum != null) {
if (demande.montantDemande! <= montantMaximum!) {
score += 25.0;
} else {
// Pénalité proportionnelle
double ratio = montantMaximum! / demande.montantDemande!;
score += 25.0 * ratio;
}
} else if (demande.montantDemande == null) {
score += 25.0; // Pas de contrainte financière
}
// Expérience du proposant (15 points max)
if (nombreBeneficiairesAides > 0) {
score += (nombreBeneficiairesAides * 2.0).clamp(0.0, 15.0);
}
// Réputation (10 points max)
if (noteMoyenne != null && nombreEvaluations >= 3) {
score += (noteMoyenne! - 3.0) * 3.33;
}
// Disponibilité (10 points max)
if (peutAccepterBeneficiaires) {
double ratioCapacite = placesRestantes / nombreMaxBeneficiaires;
score += 10.0 * ratioCapacite;
}
return score.clamp(0.0, 100.0);
}
/// Obtient la couleur associée au statut
String get couleurStatut => statut.couleur;
/// Obtient l'icône associée au type d'aide
String get iconeTypeAide => typeAide.icone;
@override
List<Object?> get props => [
id,
numeroReference,
titre,
description,
typeAide,
statut,
proposantId,
nomProposant,
organisationId,
montantMaximum,
montantMinimum,
nombreMaxBeneficiaires,
nombreBeneficiairesAides,
nombreDemandesTraitees,
montantTotalVerse,
dateCreation,
dateModification,
dateExpiration,
delaiReponseHeures,
zonesGeographiques,
creneauxDisponibilite,
criteresSelection,
contactProposant,
conditionsParticulieres,
instructionsSpeciales,
noteMoyenne,
nombreEvaluations,
nombreVues,
nombreCandidatures,
scorePertinence,
donneesPersonnalisees,
estDisponible,
estVerifiee,
estExpiree,
];
PropositionAide copyWith({
String? id,
String? numeroReference,
String? titre,
String? description,
TypeAide? typeAide,
StatutProposition? statut,
String? proposantId,
String? nomProposant,
String? organisationId,
double? montantMaximum,
double? montantMinimum,
int? nombreMaxBeneficiaires,
int? nombreBeneficiairesAides,
int? nombreDemandesTraitees,
double? montantTotalVerse,
DateTime? dateCreation,
DateTime? dateModification,
DateTime? dateExpiration,
int? delaiReponseHeures,
List<String>? zonesGeographiques,
List<CreneauDisponibilite>? creneauxDisponibilite,
List<CritereSelection>? criteresSelection,
ContactProposant? contactProposant,
String? conditionsParticulieres,
String? instructionsSpeciales,
double? noteMoyenne,
int? nombreEvaluations,
int? nombreVues,
int? nombreCandidatures,
double? scorePertinence,
Map<String, dynamic>? donneesPersonnalisees,
bool? estDisponible,
bool? estVerifiee,
bool? estExpiree,
}) {
return PropositionAide(
id: id ?? this.id,
numeroReference: numeroReference ?? this.numeroReference,
titre: titre ?? this.titre,
description: description ?? this.description,
typeAide: typeAide ?? this.typeAide,
statut: statut ?? this.statut,
proposantId: proposantId ?? this.proposantId,
nomProposant: nomProposant ?? this.nomProposant,
organisationId: organisationId ?? this.organisationId,
montantMaximum: montantMaximum ?? this.montantMaximum,
montantMinimum: montantMinimum ?? this.montantMinimum,
nombreMaxBeneficiaires: nombreMaxBeneficiaires ?? this.nombreMaxBeneficiaires,
nombreBeneficiairesAides: nombreBeneficiairesAides ?? this.nombreBeneficiairesAides,
nombreDemandesTraitees: nombreDemandesTraitees ?? this.nombreDemandesTraitees,
montantTotalVerse: montantTotalVerse ?? this.montantTotalVerse,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
dateExpiration: dateExpiration ?? this.dateExpiration,
delaiReponseHeures: delaiReponseHeures ?? this.delaiReponseHeures,
zonesGeographiques: zonesGeographiques ?? this.zonesGeographiques,
creneauxDisponibilite: creneauxDisponibilite ?? this.creneauxDisponibilite,
criteresSelection: criteresSelection ?? this.criteresSelection,
contactProposant: contactProposant ?? this.contactProposant,
conditionsParticulieres: conditionsParticulieres ?? this.conditionsParticulieres,
instructionsSpeciales: instructionsSpeciales ?? this.instructionsSpeciales,
noteMoyenne: noteMoyenne ?? this.noteMoyenne,
nombreEvaluations: nombreEvaluations ?? this.nombreEvaluations,
nombreVues: nombreVues ?? this.nombreVues,
nombreCandidatures: nombreCandidatures ?? this.nombreCandidatures,
scorePertinence: scorePertinence ?? this.scorePertinence,
donneesPersonnalisees: donneesPersonnalisees ?? this.donneesPersonnalisees,
estDisponible: estDisponible ?? this.estDisponible,
estVerifiee: estVerifiee ?? this.estVerifiee,
estExpiree: estExpiree ?? this.estExpiree,
);
}
}
/// Énumération des statuts de proposition d'aide
enum StatutProposition {
active('Active', 'check_circle', '#4CAF50'),
suspendue('Suspendue', 'pause_circle', '#FF9800'),
terminee('Terminée', 'done_all', '#607D8B'),
expiree('Expirée', 'schedule', '#9E9E9E'),
supprimee('Supprimée', 'delete', '#F44336');
const StatutProposition(this.libelle, this.icone, this.couleur);
final String libelle;
final String icone;
final String couleur;
}
/// Classe représentant un créneau de disponibilité
class CreneauDisponibilite extends Equatable {
final String jourSemaine;
final String heureDebut;
final String heureFin;
final String? commentaire;
const CreneauDisponibilite({
required this.jourSemaine,
required this.heureDebut,
required this.heureFin,
this.commentaire,
});
@override
List<Object?> get props => [jourSemaine, heureDebut, heureFin, commentaire];
}
/// Classe représentant un critère de sélection
class CritereSelection extends Equatable {
final String nom;
final String valeur;
final bool estObligatoire;
final String? description;
const CritereSelection({
required this.nom,
required this.valeur,
this.estObligatoire = false,
this.description,
});
@override
List<Object?> get props => [nom, valeur, estObligatoire, description];
}
/// Classe représentant le contact d'un proposant
class ContactProposant extends Equatable {
final String nom;
final String telephone;
final String? email;
final String? adresse;
final String? methodePrefereee;
const ContactProposant({
required this.nom,
required this.telephone,
this.email,
this.adresse,
this.methodePrefereee,
});
@override
List<Object?> get props => [nom, telephone, email, adresse, methodePrefereee];
}

View File

@@ -0,0 +1,251 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/demande_aide.dart';
import '../entities/proposition_aide.dart';
import '../entities/evaluation_aide.dart';
/// Repository abstrait pour la gestion de la solidarité
///
/// Ce repository définit les contrats pour toutes les opérations
/// liées au système de solidarité : demandes, propositions, évaluations.
abstract class SolidariteRepository {
// === GESTION DES DEMANDES D'AIDE ===
/// Crée une nouvelle demande d'aide
///
/// [demande] La demande d'aide à créer
/// Retourne [Right(DemandeAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, DemandeAide>> creerDemandeAide(DemandeAide demande);
/// Met à jour une demande d'aide existante
///
/// [demande] La demande d'aide à mettre à jour
/// Retourne [Right(DemandeAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, DemandeAide>> mettreAJourDemandeAide(DemandeAide demande);
/// Obtient une demande d'aide par son ID
///
/// [id] Identifiant de la demande
/// Retourne [Right(DemandeAide)] si trouvée
/// Retourne [Left(Failure)] si non trouvée ou erreur
Future<Either<Failure, DemandeAide>> obtenirDemandeAide(String id);
/// Soumet une demande d'aide pour évaluation
///
/// [demandeId] Identifiant de la demande
/// Retourne [Right(DemandeAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, DemandeAide>> soumettreDemande(String demandeId);
/// Évalue une demande d'aide
///
/// [demandeId] Identifiant de la demande
/// [evaluateurId] Identifiant de l'évaluateur
/// [decision] Décision d'évaluation
/// [commentaire] Commentaire de l'évaluateur
/// [montantApprouve] Montant approuvé (optionnel)
/// Retourne [Right(DemandeAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, DemandeAide>> evaluerDemande({
required String demandeId,
required String evaluateurId,
required StatutAide decision,
String? commentaire,
double? montantApprouve,
});
/// Recherche des demandes d'aide avec filtres
///
/// [filtres] Critères de recherche
/// Retourne [Right(List<DemandeAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<DemandeAide>>> rechercherDemandes({
String? organisationId,
TypeAide? typeAide,
StatutAide? statut,
String? demandeurId,
bool? urgente,
int page = 0,
int taille = 20,
});
/// Obtient les demandes urgentes
///
/// [organisationId] Identifiant de l'organisation
/// Retourne [Right(List<DemandeAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<DemandeAide>>> obtenirDemandesUrgentes(String organisationId);
/// Obtient les demandes de l'utilisateur connecté
///
/// [utilisateurId] Identifiant de l'utilisateur
/// Retourne [Right(List<DemandeAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<DemandeAide>>> obtenirMesdemandes(String utilisateurId);
// === GESTION DES PROPOSITIONS D'AIDE ===
/// Crée une nouvelle proposition d'aide
///
/// [proposition] La proposition d'aide à créer
/// Retourne [Right(PropositionAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, PropositionAide>> creerPropositionAide(PropositionAide proposition);
/// Met à jour une proposition d'aide existante
///
/// [proposition] La proposition d'aide à mettre à jour
/// Retourne [Right(PropositionAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, PropositionAide>> mettreAJourPropositionAide(PropositionAide proposition);
/// Obtient une proposition d'aide par son ID
///
/// [id] Identifiant de la proposition
/// Retourne [Right(PropositionAide)] si trouvée
/// Retourne [Left(Failure)] si non trouvée ou erreur
Future<Either<Failure, PropositionAide>> obtenirPropositionAide(String id);
/// Active ou désactive une proposition d'aide
///
/// [propositionId] Identifiant de la proposition
/// [activer] true pour activer, false pour désactiver
/// Retourne [Right(PropositionAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, PropositionAide>> changerStatutProposition({
required String propositionId,
required bool activer,
});
/// Recherche des propositions d'aide avec filtres
///
/// [filtres] Critères de recherche
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> rechercherPropositions({
String? organisationId,
TypeAide? typeAide,
String? proposantId,
bool? actives,
int page = 0,
int taille = 20,
});
/// Obtient les propositions actives pour un type d'aide
///
/// [typeAide] Type d'aide recherché
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> obtenirPropositionsActives(TypeAide typeAide);
/// Obtient les meilleures propositions (top performers)
///
/// [limite] Nombre maximum de propositions à retourner
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> obtenirMeilleuresPropositions(int limite);
/// Obtient les propositions de l'utilisateur connecté
///
/// [utilisateurId] Identifiant de l'utilisateur
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> obtenirMesPropositions(String utilisateurId);
// === MATCHING ET COMPATIBILITÉ ===
/// Trouve les propositions compatibles avec une demande
///
/// [demandeId] Identifiant de la demande
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> trouverPropositionsCompatibles(String demandeId);
/// Trouve les demandes compatibles avec une proposition
///
/// [propositionId] Identifiant de la proposition
/// Retourne [Right(List<DemandeAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<DemandeAide>>> trouverDemandesCompatibles(String propositionId);
/// Recherche des proposants financiers pour une demande approuvée
///
/// [demandeId] Identifiant de la demande
/// Retourne [Right(List<PropositionAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<PropositionAide>>> rechercherProposantsFinanciers(String demandeId);
// === GESTION DES ÉVALUATIONS ===
/// Crée une nouvelle évaluation
///
/// [evaluation] L'évaluation à créer
/// Retourne [Right(EvaluationAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, EvaluationAide>> creerEvaluation(EvaluationAide evaluation);
/// Met à jour une évaluation existante
///
/// [evaluation] L'évaluation à mettre à jour
/// Retourne [Right(EvaluationAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, EvaluationAide>> mettreAJourEvaluation(EvaluationAide evaluation);
/// Obtient une évaluation par son ID
///
/// [id] Identifiant de l'évaluation
/// Retourne [Right(EvaluationAide)] si trouvée
/// Retourne [Left(Failure)] si non trouvée ou erreur
Future<Either<Failure, EvaluationAide>> obtenirEvaluation(String id);
/// Obtient les évaluations d'une demande d'aide
///
/// [demandeId] Identifiant de la demande
/// Retourne [Right(List<EvaluationAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<EvaluationAide>>> obtenirEvaluationsDemande(String demandeId);
/// Obtient les évaluations d'une proposition d'aide
///
/// [propositionId] Identifiant de la proposition
/// Retourne [Right(List<EvaluationAide>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, List<EvaluationAide>>> obtenirEvaluationsProposition(String propositionId);
/// Signale une évaluation comme inappropriée
///
/// [evaluationId] Identifiant de l'évaluation
/// [motif] Motif du signalement
/// Retourne [Right(EvaluationAide)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, EvaluationAide>> signalerEvaluation({
required String evaluationId,
required String motif,
});
// === STATISTIQUES ET ANALYTICS ===
/// Obtient les statistiques de solidarité pour une organisation
///
/// [organisationId] Identifiant de l'organisation
/// Retourne [Right(Map<String, dynamic>)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, Map<String, dynamic>>> obtenirStatistiquesSolidarite(String organisationId);
/// Calcule la note moyenne d'une demande d'aide
///
/// [demandeId] Identifiant de la demande
/// Retourne [Right(StatistiquesEvaluation)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, StatistiquesEvaluation>> calculerMoyenneDemande(String demandeId);
/// Calcule la note moyenne d'une proposition d'aide
///
/// [propositionId] Identifiant de la proposition
/// Retourne [Right(StatistiquesEvaluation)] en cas de succès
/// Retourne [Left(Failure)] en cas d'erreur
Future<Either<Failure, StatistiquesEvaluation>> calculerMoyenneProposition(String propositionId);
}

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,
});
}