/// Service pour la gestion des organisations /// Helpers pour tri, filtrage local et recherche library organization_service; import 'package:injectable/injectable.dart'; import '../models/organization_model.dart'; import '../../domain/repositories/organization_repository.dart'; /// Service de gestion des organisations (helpers uniquement) @injectable class OrganizationService { final IOrganizationRepository _repository; OrganizationService(this._repository); /// Récupère la liste des organisations avec pagination et recherche Future> 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 les organisations du membre connecté (pour admin d'organisation) Future> getMesOrganisations() async { try { return await _repository.getMesOrganisations(); } catch (e) { throw Exception('Erreur lors de la récupération de mes organisations: $e'); } } /// Récupère une organisation par son ID Future 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 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 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 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 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'); } } /// Suspend une organisation Future suspendOrganization(String id) async { if (id.isEmpty) { throw ArgumentError('L\'ID de l\'organisation ne peut pas être vide'); } try { return await _repository.suspendOrganization(id); } catch (e) { throw Exception('Erreur lors de la suspension de l\'organisation: $e'); } } /// Recherche avancée d'organisations Future> 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> 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 filterByStatus( List organizations, StatutOrganization statut, ) { return organizations.where((org) => org.statut == statut).toList(); } /// Filtre les organisations par type List filterByType( List organizations, TypeOrganization type, ) { return organizations.where((org) => org.typeOrganisation == type).toList(); } /// Trie les organisations par nom List sortByName( List organizations, { bool ascending = true, }) { final sorted = List.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 sortByCreationDate( List organizations, { bool ascending = true, }) { final sorted = List.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 sortByMemberCount( List organizations, { bool ascending = true, }) { final sorted = List.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 searchLocal( List 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 calculateLocalStats(List organizations) { if (organizations.isEmpty) { return { 'total': 0, 'actives': 0, 'inactives': 0, 'totalMembres': 0, 'moyenneMembres': 0.0, 'parType': {}, 'parStatut': {}, }; } final actives = organizations.where((org) => org.statut == StatutOrganization.active).length; final inactives = organizations.length - actives; final totalMembres = organizations.fold(0, (sum, org) => sum + org.nombreMembres); final moyenneMembres = totalMembres / organizations.length; // Statistiques par type final parType = {}; for (final org in organizations) { final type = org.typeOrganisation.displayName; parType[type] = (parType[type] ?? 0) + 1; } // Statistiques par statut final parStatut = {}; 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; } } }