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

@@ -0,0 +1,128 @@
/// Repository pour la gestion de la configuration système
/// Implémentation avec l'API backend SystemResource
library system_config_repository_impl;
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
import 'package:unionflow_mobile_apps/core/utils/logger.dart';
import '../../domain/repositories/system_config_repository.dart';
import '../models/system_config_model.dart';
import '../models/cache_stats_model.dart';
import '../models/system_metrics_model.dart';
/// Implémentation du repository de configuration système
@LazySingleton(as: ISystemConfigRepository)
class SystemConfigRepositoryImpl implements ISystemConfigRepository {
final ApiClient _apiClient;
static const String _base = '/api/system';
SystemConfigRepositoryImpl(this._apiClient);
@override
Future<SystemConfigModel> getConfig() async {
final response = await _apiClient.get('$_base/config');
if (response.statusCode == 200) {
return SystemConfigModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<SystemConfigModel> updateConfig(Map<String, dynamic> config) async {
final response = await _apiClient.put('$_base/config', data: config);
if (response.statusCode == 200) {
return SystemConfigModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<CacheStatsModel> getCacheStats() async {
final response = await _apiClient.get('$_base/cache/stats');
if (response.statusCode == 200) {
return CacheStatsModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<SystemMetricsModel> getMetrics() async {
final response = await _apiClient.get('$_base/metrics');
if (response.statusCode == 200) {
return SystemMetricsModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<void> clearCache() async {
final response = await _apiClient.post('$_base/cache/clear');
if (response.statusCode != 200) {
throw Exception('Erreur ${response.statusCode}');
}
}
@override
Future<Map<String, dynamic>> testDatabase() async {
final response = await _apiClient.post('$_base/test/database');
if (response.statusCode == 200) {
return response.data as Map<String, dynamic>;
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<Map<String, dynamic>> testEmail() async {
final response = await _apiClient.post('$_base/test/email');
if (response.statusCode == 200) {
return response.data as Map<String, dynamic>;
}
throw Exception('Erreur ${response.statusCode}');
}
@override
Future<SystemConfigModel> resetConfig() async {
try {
// Tente d'abord l'endpoint dédié pour le reset
final response = await _apiClient.post('$_base/config/reset');
if (response.statusCode == 200 || response.statusCode == 201) {
return SystemConfigModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur ${response.statusCode}');
} on DioException catch (e, st) {
// Si l'endpoint n'existe pas (404), fallback : récupérer config par défaut via GET
if (e.response?.statusCode == 404) {
AppLogger.warning(
'SystemConfigRepository: Endpoint /reset non disponible, fallback sur config par défaut',
);
// Alternative: Appeler un endpoint qui retourne la config par défaut
// ou construire une config minimale côté client
try {
final defaultResponse = await _apiClient.get('$_base/config/default');
if (defaultResponse.statusCode == 200) {
return SystemConfigModel.fromJson(defaultResponse.data as Map<String, dynamic>);
}
} catch (_) {
// Si même le default échoue, retourner une config minimale
AppLogger.error(
'SystemConfigRepository: resetConfig fallback échoué, config minimale retournée',
error: e,
stackTrace: st,
);
// Config minimale codée en dur pour éviter un crash total
return SystemConfigModel.fromJson({
'id': 'default',
'appName': 'UnionFlow',
'version': '1.0.0',
'maintenance': false,
'enableCache': true,
'cacheExpirationMinutes': 30,
});
}
}
AppLogger.error('SystemConfigRepository: resetConfig échoué', error: e, stackTrace: st);
rethrow;
}
}
}