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

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

View File

@@ -0,0 +1,316 @@
/// Service pour la gestion des organisations
/// Couche de logique métier entre le repository et l'interface utilisateur
library organisation_service;
import '../models/organisation_model.dart';
import '../repositories/organisation_repository.dart';
/// Service de gestion des organisations
class OrganisationService {
final OrganisationRepository _repository;
OrganisationService(this._repository);
/// Récupère la liste des organisations avec pagination et recherche
Future<List<OrganisationModel>> getOrganisations({
int page = 0,
int size = 20,
String? recherche,
}) async {
try {
return await _repository.getOrganisations(
page: page,
size: size,
recherche: recherche,
);
} catch (e) {
throw Exception('Erreur lors de la récupération des organisations: $e');
}
}
/// Récupère une organisation par son ID
Future<OrganisationModel?> getOrganisationById(String id) async {
if (id.isEmpty) {
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
}
try {
return await _repository.getOrganisationById(id);
} catch (e) {
throw Exception('Erreur lors de la récupération de l\'organisation: $e');
}
}
/// Crée une nouvelle organisation avec validation
Future<OrganisationModel> createOrganisation(OrganisationModel organisation) async {
// Validation des données obligatoires
_validateOrganisation(organisation);
try {
return await _repository.createOrganisation(organisation);
} catch (e) {
throw Exception('Erreur lors de la création de l\'organisation: $e');
}
}
/// Met à jour une organisation avec validation
Future<OrganisationModel> updateOrganisation(String id, OrganisationModel organisation) async {
if (id.isEmpty) {
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
}
// Validation des données obligatoires
_validateOrganisation(organisation);
try {
return await _repository.updateOrganisation(id, organisation);
} catch (e) {
throw Exception('Erreur lors de la mise à jour de l\'organisation: $e');
}
}
/// Supprime une organisation
Future<void> deleteOrganisation(String id) async {
if (id.isEmpty) {
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
}
try {
await _repository.deleteOrganisation(id);
} catch (e) {
throw Exception('Erreur lors de la suppression de l\'organisation: $e');
}
}
/// Active une organisation
Future<OrganisationModel> activateOrganisation(String id) async {
if (id.isEmpty) {
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
}
try {
return await _repository.activateOrganisation(id);
} catch (e) {
throw Exception('Erreur lors de l\'activation de l\'organisation: $e');
}
}
/// Recherche avancée d'organisations
Future<List<OrganisationModel>> searchOrganisations({
String? nom,
TypeOrganisation? type,
StatutOrganisation? statut,
String? ville,
String? region,
String? pays,
int page = 0,
int size = 20,
}) async {
try {
return await _repository.searchOrganisations(
nom: nom,
type: type,
statut: statut,
ville: ville,
region: region,
pays: pays,
page: page,
size: size,
);
} catch (e) {
throw Exception('Erreur lors de la recherche d\'organisations: $e');
}
}
/// Récupère les statistiques des organisations
Future<Map<String, dynamic>> getOrganisationsStats() async {
try {
return await _repository.getOrganisationsStats();
} catch (e) {
throw Exception('Erreur lors de la récupération des statistiques: $e');
}
}
/// Filtre les organisations par statut
List<OrganisationModel> filterByStatus(
List<OrganisationModel> organisations,
StatutOrganisation statut,
) {
return organisations.where((org) => org.statut == statut).toList();
}
/// Filtre les organisations par type
List<OrganisationModel> filterByType(
List<OrganisationModel> organisations,
TypeOrganisation type,
) {
return organisations.where((org) => org.typeOrganisation == type).toList();
}
/// Trie les organisations par nom
List<OrganisationModel> sortByName(
List<OrganisationModel> organisations, {
bool ascending = true,
}) {
final sorted = List<OrganisationModel>.from(organisations);
sorted.sort((a, b) {
final comparison = a.nom.toLowerCase().compareTo(b.nom.toLowerCase());
return ascending ? comparison : -comparison;
});
return sorted;
}
/// Trie les organisations par date de création
List<OrganisationModel> sortByCreationDate(
List<OrganisationModel> organisations, {
bool ascending = true,
}) {
final sorted = List<OrganisationModel>.from(organisations);
sorted.sort((a, b) {
final dateA = a.dateCreation ?? DateTime.fromMillisecondsSinceEpoch(0);
final dateB = b.dateCreation ?? DateTime.fromMillisecondsSinceEpoch(0);
final comparison = dateA.compareTo(dateB);
return ascending ? comparison : -comparison;
});
return sorted;
}
/// Trie les organisations par nombre de membres
List<OrganisationModel> sortByMemberCount(
List<OrganisationModel> organisations, {
bool ascending = true,
}) {
final sorted = List<OrganisationModel>.from(organisations);
sorted.sort((a, b) {
final comparison = a.nombreMembres.compareTo(b.nombreMembres);
return ascending ? comparison : -comparison;
});
return sorted;
}
/// Recherche locale dans une liste d'organisations
List<OrganisationModel> searchLocal(
List<OrganisationModel> organisations,
String query,
) {
if (query.isEmpty) return organisations;
final lowerQuery = query.toLowerCase();
return organisations.where((org) {
return org.nom.toLowerCase().contains(lowerQuery) ||
(org.nomCourt?.toLowerCase().contains(lowerQuery) ?? false) ||
(org.description?.toLowerCase().contains(lowerQuery) ?? false) ||
(org.ville?.toLowerCase().contains(lowerQuery) ?? false) ||
(org.region?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
}
/// Calcule les statistiques locales d'une liste d'organisations
Map<String, dynamic> calculateLocalStats(List<OrganisationModel> organisations) {
if (organisations.isEmpty) {
return {
'total': 0,
'actives': 0,
'inactives': 0,
'totalMembres': 0,
'moyenneMembres': 0.0,
'parType': <String, int>{},
'parStatut': <String, int>{},
};
}
final actives = organisations.where((org) => org.statut == StatutOrganisation.active).length;
final inactives = organisations.length - actives;
final totalMembres = organisations.fold<int>(0, (sum, org) => sum + org.nombreMembres);
final moyenneMembres = totalMembres / organisations.length;
// Statistiques par type
final parType = <String, int>{};
for (final org in organisations) {
final type = org.typeOrganisation.displayName;
parType[type] = (parType[type] ?? 0) + 1;
}
// Statistiques par statut
final parStatut = <String, int>{};
for (final org in organisations) {
final statut = org.statut.displayName;
parStatut[statut] = (parStatut[statut] ?? 0) + 1;
}
return {
'total': organisations.length,
'actives': actives,
'inactives': inactives,
'totalMembres': totalMembres,
'moyenneMembres': moyenneMembres,
'parType': parType,
'parStatut': parStatut,
};
}
/// Validation des données d'organisation
void _validateOrganisation(OrganisationModel organisation) {
if (organisation.nom.trim().isEmpty) {
throw ArgumentError('Le nom de l\'organisation est obligatoire');
}
if (organisation.nom.trim().length < 2) {
throw ArgumentError('Le nom de l\'organisation doit contenir au moins 2 caractères');
}
if (organisation.nom.trim().length > 200) {
throw ArgumentError('Le nom de l\'organisation ne peut pas dépasser 200 caractères');
}
if (organisation.nomCourt != null && organisation.nomCourt!.length > 50) {
throw ArgumentError('Le nom court ne peut pas dépasser 50 caractères');
}
if (organisation.email != null && organisation.email!.isNotEmpty) {
if (!_isValidEmail(organisation.email!)) {
throw ArgumentError('L\'adresse email n\'est pas valide');
}
}
if (organisation.telephone != null && organisation.telephone!.isNotEmpty) {
if (!_isValidPhone(organisation.telephone!)) {
throw ArgumentError('Le numéro de téléphone n\'est pas valide');
}
}
if (organisation.siteWeb != null && organisation.siteWeb!.isNotEmpty) {
if (!_isValidUrl(organisation.siteWeb!)) {
throw ArgumentError('L\'URL du site web n\'est pas valide');
}
}
if (organisation.budgetAnnuel != null && organisation.budgetAnnuel! < 0) {
throw ArgumentError('Le budget annuel doit être positif');
}
if (organisation.montantCotisationAnnuelle != null && organisation.montantCotisationAnnuelle! < 0) {
throw ArgumentError('Le montant de cotisation doit être positif');
}
}
/// Validation d'email
bool _isValidEmail(String email) {
return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(email);
}
/// Validation de téléphone
bool _isValidPhone(String phone) {
return RegExp(r'^\+?[0-9\s\-\(\)]{8,15}$').hasMatch(phone);
}
/// Validation d'URL
bool _isValidUrl(String url) {
try {
final uri = Uri.parse(url);
return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https');
} catch (e) {
return false;
}
}
}