feat: WebSocket temps réel + Finance Workflow + corrections

- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics)
  * Backend: KafkaEventProducer, KafkaEventConsumer
  * Mobile: WebSocketService (reconnection, heartbeat, typed events)
  * DashboardBloc: Auto-refresh depuis WebSocket events

- Finance Workflow: approbations + budgets (backend + mobile)
  * Backend: entities, services, resources, migrations Flyway V6
  * Mobile: features finance_workflow complète avec BLoC

- Corrections DI: interfaces IRepository partout
  * IProfileRepository, IOrganizationRepository, IMembreRepository
  * GetIt configuré avec @injectable

- Spec-Kit: constitution + templates mis à jour
  * .specify/memory/constitution.md enrichie
  * Templates agent, plan, spec, tasks, checklist

- Nettoyage: fichiers temporaires supprimés

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -1,8 +1,11 @@
/// Repository pour la gestion des événements
/// Implémentation du repository pour la gestion des événements
/// Interface avec l'API backend EvenementResource
library evenement_repository;
library evenement_repository_impl;
import 'package:dio/dio.dart';
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
import 'package:injectable/injectable.dart';
import '../../domain/repositories/evenement_repository.dart';
import '../models/evenement_model.dart';
/// Résultat de recherche paginé
@@ -49,55 +52,30 @@ class EvenementSearchResult {
}
}
/// Interface du repository des événements
abstract class EvenementRepository {
/// Récupère la liste des événements avec pagination
Future<EvenementSearchResult> getEvenements({
int page = 0,
int size = 20,
String? recherche,
});
/// Récupère un événement par son ID
Future<EvenementModel?> getEvenementById(String id);
/// Crée un nouvel événement
Future<EvenementModel> createEvenement(EvenementModel evenement);
/// Met à jour un événement
Future<EvenementModel> updateEvenement(String id, EvenementModel evenement);
/// Supprime un événement
Future<void> deleteEvenement(String id);
/// Récupère les événements à venir
Future<EvenementSearchResult> getEvenementsAVenir({int page = 0, int size = 20});
/// Récupère les événements en cours
Future<EvenementSearchResult> getEvenementsEnCours({int page = 0, int size = 20});
/// Récupère les événements passés
Future<EvenementSearchResult> getEvenementsPasses({int page = 0, int size = 20});
/// S'inscrire à un événement
Future<void> inscrireEvenement(String evenementId);
/// Se désinscrire d'un événement
Future<void> desinscrireEvenement(String evenementId);
/// Récupère les participants d'un événement
Future<List<Map<String, dynamic>>> getParticipants(String evenementId);
/// Récupère les statistiques des événements
Future<Map<String, dynamic>> getEvenementsStats();
}
/// Implémentation du repository des événements
class EvenementRepositoryImpl implements EvenementRepository {
final Dio _dio;
@LazySingleton(as: IEvenementRepository)
class EvenementRepositoryImpl implements IEvenementRepository {
final ApiClient _apiClient;
static const String _baseUrl = '/api/evenements';
EvenementRepositoryImpl(this._dio);
EvenementRepositoryImpl(this._apiClient);
/// Parse une réponse API : liste directe ou objet paginé (content / data).
EvenementSearchResult _parseSearchResponse(dynamic data, int page, int size) {
if (data is List) {
final evenements = (data as List<dynamic>)
.map((e) => EvenementModel.fromJson(e as Map<String, dynamic>))
.toList();
return EvenementSearchResult(
evenements: evenements,
total: evenements.length,
page: page,
size: size,
totalPages: 1,
);
}
return EvenementSearchResult.fromJson(data as Map<String, dynamic>);
}
@override
Future<EvenementSearchResult> getEvenements({
@@ -115,7 +93,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
queryParams['recherche'] = recherche;
}
final response = await _dio.get(
final response = await _apiClient.get(
_baseUrl,
queryParameters: queryParams,
);
@@ -155,7 +133,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementModel?> getEvenementById(String id) async {
try {
final response = await _dio.get('$_baseUrl/$id');
final response = await _apiClient.get('$_baseUrl/$id');
if (response.statusCode == 200) {
return EvenementModel.fromJson(response.data as Map<String, dynamic>);
@@ -177,7 +155,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementModel> createEvenement(EvenementModel evenement) async {
try {
final response = await _dio.post(
final response = await _apiClient.post(
_baseUrl,
data: evenement.toJson(),
);
@@ -197,7 +175,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementModel> updateEvenement(String id, EvenementModel evenement) async {
try {
final response = await _dio.put(
final response = await _apiClient.put(
'$_baseUrl/$id',
data: evenement.toJson(),
);
@@ -217,7 +195,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<void> deleteEvenement(String id) async {
try {
final response = await _dio.delete('$_baseUrl/$id');
final response = await _apiClient.delete('$_baseUrl/$id');
if (response.statusCode != 204 && response.statusCode != 200) {
throw Exception('Erreur lors de la suppression de l\'événement: ${response.statusCode}');
@@ -232,13 +210,13 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementSearchResult> getEvenementsAVenir({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
final response = await _apiClient.get(
'$_baseUrl/a-venir',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
return _parseSearchResponse(response.data, page, size);
} else {
throw Exception('Erreur lors de la récupération des événements à venir: ${response.statusCode}');
}
@@ -252,13 +230,13 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementSearchResult> getEvenementsEnCours({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
final response = await _apiClient.get(
'$_baseUrl/en-cours',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
return _parseSearchResponse(response.data, page, size);
} else {
throw Exception('Erreur lors de la récupération des événements en cours: ${response.statusCode}');
}
@@ -272,13 +250,13 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<EvenementSearchResult> getEvenementsPasses({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
final response = await _apiClient.get(
'$_baseUrl/passes',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
return _parseSearchResponse(response.data, page, size);
} else {
throw Exception('Erreur lors de la récupération des événements passés: ${response.statusCode}');
}
@@ -292,7 +270,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<void> inscrireEvenement(String evenementId) async {
try {
final response = await _dio.post('$_baseUrl/$evenementId/inscrire');
final response = await _apiClient.post('$_baseUrl/$evenementId/inscrire');
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception('Erreur lors de l\'inscription à l\'événement: ${response.statusCode}');
@@ -307,7 +285,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<void> desinscrireEvenement(String evenementId) async {
try {
final response = await _dio.delete('$_baseUrl/$evenementId/desinscrire');
final response = await _apiClient.delete('$_baseUrl/$evenementId/desinscrire');
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception('Erreur lors de la désinscription de l\'événement: ${response.statusCode}');
@@ -322,7 +300,7 @@ class EvenementRepositoryImpl implements EvenementRepository {
@override
Future<List<Map<String, dynamic>>> getParticipants(String evenementId) async {
try {
final response = await _dio.get('$_baseUrl/$evenementId/participants');
final response = await _apiClient.get('$_baseUrl/$evenementId/participants');
if (response.statusCode == 200) {
return (response.data as List<dynamic>)
@@ -338,10 +316,26 @@ class EvenementRepositoryImpl implements EvenementRepository {
}
}
@override
Future<bool> getInscriptionStatus(String evenementId) async {
try {
final response = await _apiClient.get('$_baseUrl/$evenementId/me/inscrit');
if (response.statusCode == 200 && response.data is Map) {
final data = response.data as Map<String, dynamic>;
return data['inscrit'] == true;
}
return false;
} on DioException catch (_) {
return false;
} catch (_) {
return false;
}
}
@override
Future<Map<String, dynamic>> getEvenementsStats() async {
try {
final response = await _dio.get('$_baseUrl/statistiques');
final response = await _apiClient.get('$_baseUrl/statistiques');
if (response.statusCode == 200) {
return response.data as Map<String, dynamic>;