import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; import '../models/membre_model.dart'; import '../models/cotisation_model.dart'; import '../models/evenement_model.dart'; import '../models/wave_checkout_session_model.dart'; import '../models/payment_model.dart'; import '../network/dio_client.dart'; /// Service API principal pour communiquer avec le serveur UnionFlow @singleton class ApiService { final DioClient _dioClient; ApiService(this._dioClient); Dio get _dio => _dioClient.dio; // ======================================== // MEMBRES // ======================================== /// Récupère la liste de tous les membres actifs Future> getMembres() async { try { final response = await _dio.get('/api/membres'); if (response.data is List) { return (response.data as List) .map((json) => MembreModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la liste des membres'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des membres'); } } /// Récupère un membre par son ID Future getMembreById(String id) async { try { final response = await _dio.get('/api/membres/$id'); return MembreModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération du membre'); } } /// Crée un nouveau membre Future createMembre(MembreModel membre) async { try { final response = await _dio.post( '/api/membres', data: membre.toJson(), ); return MembreModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la création du membre'); } } /// Met à jour un membre existant Future updateMembre(String id, MembreModel membre) async { try { final response = await _dio.put( '/api/membres/$id', data: membre.toJson(), ); return MembreModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la mise à jour du membre'); } } /// Désactive un membre Future deleteMembre(String id) async { try { await _dio.delete('/api/membres/$id'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la suppression du membre'); } } /// Recherche des membres par nom ou prénom Future> searchMembres(String query) async { try { final response = await _dio.get( '/api/membres/recherche', queryParameters: {'q': query}, ); if (response.data is List) { return (response.data as List) .map((json) => MembreModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la recherche'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la recherche de membres'); } } /// Recherche avancée des membres avec filtres multiples Future> advancedSearchMembres(Map filters) async { try { // Nettoyer les filtres vides final cleanFilters = {}; filters.forEach((key, value) { if (value != null && value.toString().isNotEmpty) { cleanFilters[key] = value; } }); final response = await _dio.get( '/api/membres/recherche-avancee', queryParameters: cleanFilters, ); if (response.data is List) { return (response.data as List) .map((json) => MembreModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la recherche avancée'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la recherche avancée de membres'); } } /// Récupère les statistiques des membres Future> getMembresStats() async { try { final response = await _dio.get('/api/membres/stats'); return response.data as Map; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des statistiques'); } } // ======================================== // PAIEMENTS WAVE // ======================================== /// Crée une session de paiement Wave Future createWaveSession({ required double montant, required String devise, required String successUrl, required String errorUrl, String? organisationId, String? membreId, String? typePaiement, String? description, }) async { try { final response = await _dio.post( '/api/paiements/wave/sessions', data: { 'montant': montant, 'devise': devise, 'successUrl': successUrl, 'errorUrl': errorUrl, if (organisationId != null) 'organisationId': organisationId, if (membreId != null) 'membreId': membreId, if (typePaiement != null) 'typePaiement': typePaiement, if (description != null) 'description': description, }, ); return WaveCheckoutSessionModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la création de la session Wave'); } } /// Récupère une session de paiement Wave par son ID Future getWaveSession(String sessionId) async { try { final response = await _dio.get('/api/paiements/wave/sessions/$sessionId'); return WaveCheckoutSessionModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération de la session Wave'); } } /// Vérifie le statut d'une session de paiement Wave Future checkWaveSessionStatus(String sessionId) async { try { final response = await _dio.get('/api/paiements/wave/sessions/$sessionId/status'); return response.data['statut'] as String; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la vérification du statut Wave'); } } // ======================================== // COTISATIONS // ======================================== /// Récupère la liste de toutes les cotisations avec pagination Future> getCotisations({int page = 0, int size = 20}) async { try { final response = await _dio.get('/api/cotisations', queryParameters: { 'page': page, 'size': size, }); if (response.data is List) { return (response.data as List) .map((json) => CotisationModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la liste des cotisations'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des cotisations'); } } /// Récupère une cotisation par son ID Future getCotisationById(String id) async { try { final response = await _dio.get('/api/cotisations/$id'); return CotisationModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération de la cotisation'); } } /// Récupère une cotisation par son numéro de référence Future getCotisationByReference(String numeroReference) async { try { final response = await _dio.get('/api/cotisations/reference/$numeroReference'); return CotisationModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération de la cotisation'); } } /// Crée une nouvelle cotisation Future createCotisation(CotisationModel cotisation) async { try { final response = await _dio.post( '/api/cotisations', data: cotisation.toJson(), ); return CotisationModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la création de la cotisation'); } } /// Met à jour une cotisation existante Future updateCotisation(String id, CotisationModel cotisation) async { try { final response = await _dio.put( '/api/cotisations/$id', data: cotisation.toJson(), ); return CotisationModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la mise à jour de la cotisation'); } } /// Supprime une cotisation Future deleteCotisation(String id) async { try { await _dio.delete('/api/cotisations/$id'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la suppression de la cotisation'); } } /// Récupère les cotisations d'un membre Future> getCotisationsByMembre(String membreId, {int page = 0, int size = 20}) async { try { final response = await _dio.get('/api/cotisations/membre/$membreId', queryParameters: { 'page': page, 'size': size, }); if (response.data is List) { return (response.data as List) .map((json) => CotisationModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les cotisations du membre'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des cotisations du membre'); } } /// Récupère les cotisations par statut Future> getCotisationsByStatut(String statut, {int page = 0, int size = 20}) async { try { final response = await _dio.get('/api/cotisations/statut/$statut', queryParameters: { 'page': page, 'size': size, }); if (response.data is List) { return (response.data as List) .map((json) => CotisationModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les cotisations par statut'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des cotisations par statut'); } } /// Récupère les cotisations en retard Future> getCotisationsEnRetard({int page = 0, int size = 20}) async { try { final response = await _dio.get('/api/cotisations/en-retard', queryParameters: { 'page': page, 'size': size, }); if (response.data is List) { return (response.data as List) .map((json) => CotisationModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les cotisations en retard'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des cotisations en retard'); } } /// Recherche avancée de cotisations Future> rechercherCotisations({ String? membreId, String? statut, String? typeCotisation, int? annee, int? mois, int page = 0, int size = 20, }) async { try { final queryParams = { 'page': page, 'size': size, }; if (membreId != null) queryParams['membreId'] = membreId; if (statut != null) queryParams['statut'] = statut; if (typeCotisation != null) queryParams['typeCotisation'] = typeCotisation; if (annee != null) queryParams['annee'] = annee; if (mois != null) queryParams['mois'] = mois; final response = await _dio.get('/api/cotisations/recherche', queryParameters: queryParams); if (response.data is List) { return (response.data as List) .map((json) => CotisationModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la recherche de cotisations'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la recherche de cotisations'); } } /// Récupère les statistiques des cotisations Future> getCotisationsStats() async { try { final response = await _dio.get('/api/cotisations/stats'); return response.data as Map; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des statistiques des cotisations'); } } // ======================================== // GESTION DES ERREURS // ======================================== /// Gère les exceptions Dio et les convertit en messages d'erreur appropriés Exception _handleDioException(DioException e, String defaultMessage) { switch (e.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: return Exception('Délai d\'attente dépassé. Vérifiez votre connexion internet.'); case DioExceptionType.badResponse: final statusCode = e.response?.statusCode; final responseData = e.response?.data; if (statusCode == 400) { if (responseData is Map && responseData.containsKey('message')) { return Exception(responseData['message']); } return Exception('Données invalides'); } else if (statusCode == 401) { return Exception('Non autorisé. Veuillez vous reconnecter.'); } else if (statusCode == 403) { return Exception('Accès interdit'); } else if (statusCode == 404) { return Exception('Ressource non trouvée'); } else if (statusCode == 500) { return Exception('Erreur serveur. Veuillez réessayer plus tard.'); } return Exception('$defaultMessage (Code: $statusCode)'); case DioExceptionType.cancel: return Exception('Requête annulée'); case DioExceptionType.connectionError: return Exception('Erreur de connexion. Vérifiez votre connexion internet.'); case DioExceptionType.badCertificate: return Exception('Certificat SSL invalide'); case DioExceptionType.unknown: default: return Exception(defaultMessage); } } // ======================================== // ÉVÉNEMENTS // ======================================== /// Récupère la liste des événements à venir (optimisé mobile) Future> getEvenementsAVenir({ int page = 0, int size = 10, }) async { try { final response = await _dio.get( '/api/evenements/a-venir-public', queryParameters: { 'page': page, 'size': size, }, ); if (response.data is List) { return (response.data as List) .map((json) => EvenementModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les événements à venir'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des événements à venir'); } } /// Récupère la liste des événements publics (sans authentification) Future> getEvenementsPublics({ int page = 0, int size = 20, }) async { try { final response = await _dio.get( '/api/evenements/publics', queryParameters: { 'page': page, 'size': size, }, ); if (response.data is List) { return (response.data as List) .map((json) => EvenementModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les événements publics'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des événements publics'); } } /// Récupère tous les événements avec pagination Future> getEvenements({ int page = 0, int size = 20, String sortField = 'dateDebut', String sortDirection = 'asc', }) async { try { final response = await _dio.get( '/api/evenements', queryParameters: { 'page': page, 'size': size, 'sort': sortField, 'direction': sortDirection, }, ); if (response.data is List) { return (response.data as List) .map((json) => EvenementModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la liste des événements'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des événements'); } } /// Récupère un événement par son ID Future getEvenementById(String id) async { try { final response = await _dio.get('/api/evenements/$id'); return EvenementModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération de l\'événement'); } } /// Recherche d'événements par terme Future> rechercherEvenements( String terme, { int page = 0, int size = 20, }) async { try { final response = await _dio.get( '/api/evenements/recherche', queryParameters: { 'q': terme, 'page': page, 'size': size, }, ); if (response.data is List) { return (response.data as List) .map((json) => EvenementModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour la recherche d\'événements'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la recherche d\'événements'); } } /// Récupère les événements par type Future> getEvenementsByType( TypeEvenement type, { int page = 0, int size = 20, }) async { try { final response = await _dio.get( '/api/evenements/type/${type.name.toUpperCase()}', queryParameters: { 'page': page, 'size': size, }, ); if (response.data is List) { return (response.data as List) .map((json) => EvenementModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour les événements par type'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des événements par type'); } } /// Crée un nouvel événement Future createEvenement(EvenementModel evenement) async { try { final response = await _dio.post( '/api/evenements', data: evenement.toJson(), ); return EvenementModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la création de l\'événement'); } } /// Met à jour un événement existant Future updateEvenement(String id, EvenementModel evenement) async { try { final response = await _dio.put( '/api/evenements/$id', data: evenement.toJson(), ); return EvenementModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la mise à jour de l\'événement'); } } /// Supprime un événement Future deleteEvenement(String id) async { try { await _dio.delete('/api/evenements/$id'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la suppression de l\'événement'); } } /// Change le statut d'un événement Future changerStatutEvenement( String id, StatutEvenement nouveauStatut, ) async { try { final response = await _dio.patch( '/api/evenements/$id/statut', queryParameters: { 'statut': nouveauStatut.name.toUpperCase(), }, ); return EvenementModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors du changement de statut'); } } /// Récupère les statistiques des événements Future> getStatistiquesEvenements() async { try { final response = await _dio.get('/api/evenements/statistiques'); return response.data as Map; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des statistiques'); } } // ======================================== // PAIEMENTS // ======================================== /// Initie un paiement Future initiatePayment(Map paymentData) async { try { final response = await _dio.post('/api/paiements/initier', data: paymentData); return PaymentModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de l\'initiation du paiement'); } } /// Récupère le statut d'un paiement Future getPaymentStatus(String paymentId) async { try { final response = await _dio.get('/api/paiements/$paymentId/statut'); return PaymentModel.fromJson(response.data as Map); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la vérification du statut'); } } /// Annule un paiement Future cancelPayment(String paymentId) async { try { final response = await _dio.post('/api/paiements/$paymentId/annuler'); return response.statusCode == 200; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de l\'annulation du paiement'); } } /// Récupère l'historique des paiements Future> getPaymentHistory(Map filters) async { try { final response = await _dio.get('/api/paiements/historique', queryParameters: filters); if (response.data is List) { return (response.data as List) .map((json) => PaymentModel.fromJson(json as Map)) .toList(); } throw Exception('Format de réponse invalide pour l\'historique des paiements'); } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération de l\'historique'); } } /// Vérifie le statut d'un service de paiement Future> checkServiceStatus(String serviceType) async { try { final response = await _dio.get('/api/paiements/services/$serviceType/statut'); return response.data as Map; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la vérification du service'); } } /// Récupère les statistiques de paiement Future> getPaymentStatistics(Map filters) async { try { final response = await _dio.get('/api/paiements/statistiques', queryParameters: filters); return response.data as Map; } on DioException catch (e) { throw _handleDioException(e, 'Erreur lors de la récupération des statistiques'); } } }