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,24 +1,36 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/utils/logger.dart';
import '../models/dashboard_stats_model.dart';
import '../../../../core/network/dio_client.dart';
import '../models/membre_dashboard_synthese_model.dart';
import '../models/compte_adherent_model.dart';
import '../../../../core/error/exceptions.dart';
abstract class DashboardRemoteDataSource {
Future<DashboardDataModel> getDashboardData(String organizationId, String userId);
/// Dashboard personnel du membre connecté (sans organisationId). GET /api/dashboard/membre/me
Future<MembreDashboardSyntheseModel> getMemberDashboardData();
/// Synthèse des cotisations du membre connecté. GET /api/cotisations/mes-cotisations/synthese
/// Utilisé en fallback quand les montants de getMemberDashboardData() sont à 0.
Future<Map<String, dynamic>?> getMesCotisationsSynthese();
/// Compte adhérent unifié (soldes, crédits, capacité d'emprunt). GET /api/membres/mon-compte
Future<CompteAdherentModel> getCompteAdherent();
Future<DashboardStatsModel> getDashboardStats(String organizationId, String userId);
Future<List<RecentActivityModel>> getRecentActivities(String organizationId, String userId, {int limit = 10});
Future<List<UpcomingEventModel>> getUpcomingEvents(String organizationId, String userId, {int limit = 5});
}
@Injectable(as: DashboardRemoteDataSource)
class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
final DioClient dioClient;
final ApiClient apiClient;
DashboardRemoteDataSourceImpl({required this.dioClient});
DashboardRemoteDataSourceImpl(this.apiClient);
@override
Future<DashboardDataModel> getDashboardData(String organizationId, String userId) async {
try {
final response = await dioClient.get(
final response = await apiClient.get(
'/api/v1/dashboard/data',
queryParameters: {
'organizationId': organizationId,
@@ -32,16 +44,77 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load dashboard data: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getDashboardData', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e) {
throw ServerException('Unexpected error: $e');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getDashboardData', error: e, stackTrace: st);
rethrow;
}
}
@override
Future<MembreDashboardSyntheseModel> getMemberDashboardData() async {
try {
final response = await apiClient.get('/api/dashboard/membre/me');
if (response.statusCode == 200) {
return MembreDashboardSyntheseModel.fromJson(
response.data is Map<String, dynamic> ? response.data as Map<String, dynamic> : Map<String, dynamic>.from(response.data as Map),
);
} else {
throw ServerException('Failed to load member dashboard: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getMemberDashboardData', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getMemberDashboardData', error: e, stackTrace: st);
rethrow;
}
}
@override
Future<Map<String, dynamic>?> getMesCotisationsSynthese() async {
try {
final response = await apiClient.get('/api/cotisations/mes-cotisations/synthese');
if (response.statusCode == 200 && response.data != null) {
return response.data is Map<String, dynamic>
? response.data as Map<String, dynamic>
: Map<String, dynamic>.from(response.data as Map);
}
return null;
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getMesCotisationsSynthese échoué', error: e, stackTrace: st);
rethrow;
}
}
@override
Future<CompteAdherentModel> getCompteAdherent() async {
try {
final response = await apiClient.get('/api/membres/mon-compte');
if (response.statusCode == 200) {
return CompteAdherentModel.fromJson(
response.data is Map<String, dynamic> ? response.data as Map<String, dynamic> : Map<String, dynamic>.from(response.data as Map),
);
} else {
throw ServerException('Failed to load adherent account: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getCompteAdherent', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getCompteAdherent', error: e, stackTrace: st);
rethrow;
}
}
@override
Future<DashboardStatsModel> getDashboardStats(String organizationId, String userId) async {
try {
final response = await dioClient.get(
final response = await apiClient.get(
'/api/v1/dashboard/stats',
queryParameters: {
'organizationId': organizationId,
@@ -55,9 +128,11 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load dashboard stats: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getDashboardStats', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e) {
throw ServerException('Unexpected error: $e');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getDashboardStats', error: e, stackTrace: st);
rethrow;
}
}
@@ -68,7 +143,7 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
int limit = 10,
}) async {
try {
final response = await dioClient.get(
final response = await apiClient.get(
'/api/v1/dashboard/activities',
queryParameters: {
'organizationId': organizationId,
@@ -84,9 +159,11 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load recent activities: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getRecentActivities', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e) {
throw ServerException('Unexpected error: $e');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getRecentActivities', error: e, stackTrace: st);
rethrow;
}
}
@@ -97,7 +174,7 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
int limit = 5,
}) async {
try {
final response = await dioClient.get(
final response = await apiClient.get(
'/api/v1/dashboard/events/upcoming',
queryParameters: {
'organizationId': organizationId,
@@ -113,9 +190,11 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load upcoming events: ${response.statusCode}');
}
} on DioException catch (e) {
AppLogger.error('DashboardRemoteDataSource: getUpcomingEvents', error: e);
throw ServerException('Network error: ${e.message}');
} catch (e) {
throw ServerException('Unexpected error: $e');
} catch (e, st) {
AppLogger.error('DashboardRemoteDataSource: getUpcomingEvents', error: e, stackTrace: st);
rethrow;
}
}
}