Refactoring - Version OK
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
/// Service pour la gestion des organisations
|
||||
/// Couche de logique métier entre le repository et l'interface utilisateur
|
||||
library organization_service;
|
||||
|
||||
import '../models/organization_model.dart';
|
||||
import '../repositories/organization_repository.dart';
|
||||
|
||||
/// Service de gestion des organisations
|
||||
class OrganizationService {
|
||||
final OrganizationRepository _repository;
|
||||
|
||||
OrganizationService(this._repository);
|
||||
|
||||
/// Récupère la liste des organisations avec pagination et recherche
|
||||
Future<List<OrganizationModel>> getOrganizations({
|
||||
int page = 0,
|
||||
int size = 20,
|
||||
String? recherche,
|
||||
}) async {
|
||||
try {
|
||||
return await _repository.getOrganizations(
|
||||
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<OrganizationModel?> getOrganizationById(String id) async {
|
||||
if (id.isEmpty) {
|
||||
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
return await _repository.getOrganizationById(id);
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la récupération de l\'organisation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée une nouvelle organisation avec validation
|
||||
Future<OrganizationModel> createOrganization(OrganizationModel organization) async {
|
||||
// Validation des données obligatoires
|
||||
_validateOrganization(organization);
|
||||
|
||||
try {
|
||||
return await _repository.createOrganization(organization);
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la création de l\'organisation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Met à jour une organisation avec validation
|
||||
Future<OrganizationModel> updateOrganization(String id, OrganizationModel organization) async {
|
||||
if (id.isEmpty) {
|
||||
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
|
||||
}
|
||||
|
||||
// Validation des données obligatoires
|
||||
_validateOrganization(organization);
|
||||
|
||||
try {
|
||||
return await _repository.updateOrganization(id, organization);
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la mise à jour de l\'organisation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprime une organisation
|
||||
Future<void> deleteOrganization(String id) async {
|
||||
if (id.isEmpty) {
|
||||
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
await _repository.deleteOrganization(id);
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la suppression de l\'organisation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Active une organisation
|
||||
Future<OrganizationModel> activateOrganization(String id) async {
|
||||
if (id.isEmpty) {
|
||||
throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide');
|
||||
}
|
||||
|
||||
try {
|
||||
return await _repository.activateOrganization(id);
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de l\'activation de l\'organisation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche avancée d'organisations
|
||||
Future<List<OrganizationModel>> searchOrganizations({
|
||||
String? nom,
|
||||
TypeOrganization? type,
|
||||
StatutOrganization? statut,
|
||||
String? ville,
|
||||
String? region,
|
||||
String? pays,
|
||||
int page = 0,
|
||||
int size = 20,
|
||||
}) async {
|
||||
try {
|
||||
return await _repository.searchOrganizations(
|
||||
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>> getOrganizationsStats() async {
|
||||
try {
|
||||
return await _repository.getOrganizationsStats();
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la récupération des statistiques: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Filtre les organisations par statut
|
||||
List<OrganizationModel> filterByStatus(
|
||||
List<OrganizationModel> organizations,
|
||||
StatutOrganization statut,
|
||||
) {
|
||||
return organizations.where((org) => org.statut == statut).toList();
|
||||
}
|
||||
|
||||
/// Filtre les organisations par type
|
||||
List<OrganizationModel> filterByType(
|
||||
List<OrganizationModel> organizations,
|
||||
TypeOrganization type,
|
||||
) {
|
||||
return organizations.where((org) => org.typeOrganisation == type).toList();
|
||||
}
|
||||
|
||||
/// Trie les organisations par nom
|
||||
List<OrganizationModel> sortByName(
|
||||
List<OrganizationModel> organizations, {
|
||||
bool ascending = true,
|
||||
}) {
|
||||
final sorted = List<OrganizationModel>.from(organizations);
|
||||
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<OrganizationModel> sortByCreationDate(
|
||||
List<OrganizationModel> organizations, {
|
||||
bool ascending = true,
|
||||
}) {
|
||||
final sorted = List<OrganizationModel>.from(organizations);
|
||||
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<OrganizationModel> sortByMemberCount(
|
||||
List<OrganizationModel> organizations, {
|
||||
bool ascending = true,
|
||||
}) {
|
||||
final sorted = List<OrganizationModel>.from(organizations);
|
||||
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<OrganizationModel> searchLocal(
|
||||
List<OrganizationModel> organizations,
|
||||
String query,
|
||||
) {
|
||||
if (query.isEmpty) return organizations;
|
||||
|
||||
final lowerQuery = query.toLowerCase();
|
||||
return organizations.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<OrganizationModel> organizations) {
|
||||
if (organizations.isEmpty) {
|
||||
return {
|
||||
'total': 0,
|
||||
'actives': 0,
|
||||
'inactives': 0,
|
||||
'totalMembres': 0,
|
||||
'moyenneMembres': 0.0,
|
||||
'parType': <String, int>{},
|
||||
'parStatut': <String, int>{},
|
||||
};
|
||||
}
|
||||
|
||||
final actives = organizations.where((org) => org.statut == StatutOrganization.active).length;
|
||||
final inactives = organizations.length - actives;
|
||||
final totalMembres = organizations.fold<int>(0, (sum, org) => sum + org.nombreMembres);
|
||||
final moyenneMembres = totalMembres / organizations.length;
|
||||
|
||||
// Statistiques par type
|
||||
final parType = <String, int>{};
|
||||
for (final org in organizations) {
|
||||
final type = org.typeOrganisation.displayName;
|
||||
parType[type] = (parType[type] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// Statistiques par statut
|
||||
final parStatut = <String, int>{};
|
||||
for (final org in organizations) {
|
||||
final statut = org.statut.displayName;
|
||||
parStatut[statut] = (parStatut[statut] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
'total': organizations.length,
|
||||
'actives': actives,
|
||||
'inactives': inactives,
|
||||
'totalMembres': totalMembres,
|
||||
'moyenneMembres': moyenneMembres,
|
||||
'parType': parType,
|
||||
'parStatut': parStatut,
|
||||
};
|
||||
}
|
||||
|
||||
/// Validation des données d'organisation
|
||||
void _validateOrganization(OrganizationModel organization) {
|
||||
if (organization.nom.trim().isEmpty) {
|
||||
throw ArgumentError('Le nom de l\'organisation est obligatoire');
|
||||
}
|
||||
|
||||
if (organization.nom.trim().length < 2) {
|
||||
throw ArgumentError('Le nom de l\'organisation doit contenir au moins 2 caractères');
|
||||
}
|
||||
|
||||
if (organization.nom.trim().length > 200) {
|
||||
throw ArgumentError('Le nom de l\'organisation ne peut pas dépasser 200 caractères');
|
||||
}
|
||||
|
||||
if (organization.nomCourt != null && organization.nomCourt!.length > 50) {
|
||||
throw ArgumentError('Le nom court ne peut pas dépasser 50 caractères');
|
||||
}
|
||||
|
||||
if (organization.email != null && organization.email!.isNotEmpty) {
|
||||
if (!_isValidEmail(organization.email!)) {
|
||||
throw ArgumentError('L\'adresse email n\'est pas valide');
|
||||
}
|
||||
}
|
||||
|
||||
if (organization.telephone != null && organization.telephone!.isNotEmpty) {
|
||||
if (!_isValidPhone(organization.telephone!)) {
|
||||
throw ArgumentError('Le numéro de téléphone n\'est pas valide');
|
||||
}
|
||||
}
|
||||
|
||||
if (organization.siteWeb != null && organization.siteWeb!.isNotEmpty) {
|
||||
if (!_isValidUrl(organization.siteWeb!)) {
|
||||
throw ArgumentError('L\'URL du site web n\'est pas valide');
|
||||
}
|
||||
}
|
||||
|
||||
if (organization.budgetAnnuel != null && organization.budgetAnnuel! < 0) {
|
||||
throw ArgumentError('Le budget annuel doit être positif');
|
||||
}
|
||||
|
||||
if (organization.montantCotisationAnnuelle != null && organization.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user