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,11 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import '../models/dashboard_stats_model.dart';
import '../cache/dashboard_cache_manager.dart';
import '../../../../core/storage/dashboard_cache_manager.dart';
/// Service de mode hors ligne avec synchronisation pour le Dashboard
class DashboardOfflineService {
@@ -14,7 +15,7 @@ class DashboardOfflineService {
static const String _offlineModeKey = 'dashboard_offline_mode';
final DashboardCacheManager _cacheManager;
final Dio _dio;
final ApiClient _apiClient;
final Connectivity _connectivity = Connectivity();
SharedPreferences? _prefs;
@@ -35,7 +36,7 @@ class DashboardOfflineService {
Stream<OfflineStatus> get statusStream => _statusController.stream;
Stream<SyncProgress> get syncStream => _syncController.stream;
DashboardOfflineService(this._cacheManager, this._dio);
DashboardOfflineService(this._cacheManager, this._apiClient);
/// Initialise le service hors ligne
Future<void> initialize() async {
@@ -216,14 +217,13 @@ class DashboardOfflineService {
final userId = action.data['userId'] as String?;
if (orgId == null || userId == null) return;
final response = await _dio.get('/api/dashboard/stats', queryParameters: {
final response = await _apiClient.get('/api/dashboard/stats', queryParameters: {
'organisationId': orgId,
});
if (response.statusCode == 200 && response.data != null) {
await _cacheManager.cacheDashboardData(
DashboardDataModel.fromJson(response.data as Map<String, dynamic>),
orgId,
userId,
await _cacheManager.setKey(
'dashboard_${orgId}_$userId',
response.data as Map<String, dynamic>,
);
}
}
@@ -234,7 +234,7 @@ class DashboardOfflineService {
final preferences = action.data['preferences'] as Map<String, dynamic>?;
if (userId == null || preferences == null) return;
await _dio.put('/api/membres/$userId/preferences', data: preferences);
await _apiClient.put('/api/membres/$userId/preferences', data: preferences);
}
/// Synchronise le marquage d'activité comme lue
@@ -242,7 +242,7 @@ class DashboardOfflineService {
final activityId = action.data['activityId'] as String?;
if (activityId == null) return;
await _dio.put('/api/notifications/$activityId/read');
await _apiClient.put('/api/notifications/$activityId/read');
}
/// Synchronise l'inscription à un événement
@@ -251,7 +251,7 @@ class DashboardOfflineService {
final membreId = action.data['membreId'] as String?;
if (eventId == null || membreId == null) return;
await _dio.post('/api/evenements/$eventId/inscription', data: {
await _apiClient.post('/api/evenements/$eventId/inscription', data: {
'membreId': membreId,
});
}
@@ -262,7 +262,7 @@ class DashboardOfflineService {
final params = action.data['params'] as Map<String, dynamic>?;
if (reportType == null) return;
await _dio.post('/api/export/$reportType', data: params ?? {});
await _apiClient.post('/api/export/$reportType', data: params ?? {});
}
/// Sauvegarde les actions en attente
@@ -315,7 +315,7 @@ class DashboardOfflineService {
}
/// Force une synchronisation manuelle
Future<void> forcSync() async {
Future<void> forceSync() async {
if (!_isOnline) {
throw Exception('Impossible de synchroniser hors ligne');
}
@@ -328,7 +328,8 @@ class DashboardOfflineService {
String organizationId,
String userId,
) async {
return await _cacheManager.getCachedDashboardData(organizationId, userId);
final m = _cacheManager.getKey<Map<String, dynamic>>('dashboard_${organizationId}_$userId');
return m != null ? DashboardDataModel.fromJson(m) : null;
}
/// Vérifie si des données sont disponibles hors ligne

View File

@@ -19,7 +19,8 @@ class DashboardPerformanceMonitor {
bool _isMonitoring = false;
DateTime _startTime = DateTime.now();
int _alertsGeneratedCount = 0;
// Seuils d'alerte configurables
final double _memoryThreshold = DashboardConfig.getAlertThreshold('memoryUsage');
final double _cpuThreshold = DashboardConfig.getAlertThreshold('cpuUsage');
@@ -147,18 +148,16 @@ class DashboardPerformanceMonitor {
}
}
/// Obtient la latence réseau
/// Obtient la latence réseau (hôte/port depuis DashboardConfig.apiBaseUrl).
Future<int> _getNetworkLatency() async {
try {
final uri = Uri.parse(DashboardConfig.apiBaseUrl);
final host = uri.host.isNotEmpty ? uri.host : 'localhost';
final port = uri.hasPort ? uri.port : 8085;
final stopwatch = Stopwatch()..start();
// Ping vers le serveur de l'API
final socket = await Socket.connect('localhost', 8080)
.timeout(const Duration(seconds: 5));
final socket = await Socket.connect(host, port).timeout(const Duration(seconds: 5));
stopwatch.stop();
await socket.close();
return stopwatch.elapsedMilliseconds;
} catch (e) {
return _simulateNetworkLatency();
@@ -228,6 +227,7 @@ class DashboardPerformanceMonitor {
void _checkAlerts(PerformanceMetrics metrics) {
// Alerte mémoire
if (metrics.memoryUsage > _memoryThreshold) {
_alertsGeneratedCount++;
_alertController.add(PerformanceAlert(
type: AlertType.memory,
severity: AlertSeverity.warning,
@@ -240,6 +240,7 @@ class DashboardPerformanceMonitor {
// Alerte CPU
if (metrics.cpuUsage > _cpuThreshold) {
_alertsGeneratedCount++;
_alertController.add(PerformanceAlert(
type: AlertType.cpu,
severity: AlertSeverity.warning,
@@ -252,6 +253,7 @@ class DashboardPerformanceMonitor {
// Alerte latence réseau
if (metrics.networkLatency > _networkLatencyThreshold) {
_alertsGeneratedCount++;
_alertController.add(PerformanceAlert(
type: AlertType.network,
severity: AlertSeverity.error,
@@ -264,6 +266,7 @@ class DashboardPerformanceMonitor {
// Alerte frame rate
if (metrics.frameRate < _frameRateThreshold) {
_alertsGeneratedCount++;
_alertController.add(PerformanceAlert(
type: AlertType.performance,
severity: AlertSeverity.warning,
@@ -298,8 +301,7 @@ class DashboardPerformanceMonitor {
if (_snapshots.isEmpty) {
return PerformanceStats.empty();
}
return PerformanceStats.fromSnapshots(_snapshots);
return PerformanceStats.fromSnapshots(_snapshots, alertsGenerated: _alertsGeneratedCount);
}
/// Méthodes de simulation pour le développement
@@ -508,7 +510,7 @@ class PerformanceStats {
);
}
factory PerformanceStats.fromSnapshots(List<PerformanceSnapshot> snapshots) {
factory PerformanceStats.fromSnapshots(List<PerformanceSnapshot> snapshots, {int alertsGenerated = 0}) {
if (snapshots.isEmpty) return PerformanceStats.empty();
final metrics = snapshots.map((s) => s.metrics).toList();
@@ -520,7 +522,7 @@ class PerformanceStats {
peakMemoryUsage: metrics.map((m) => m.memoryUsage).reduce((a, b) => a > b ? a : b),
averageCpuUsage: metrics.map((m) => m.cpuUsage).reduce((a, b) => a + b) / metrics.length,
peakCpuUsage: metrics.map((m) => m.cpuUsage).reduce((a, b) => a > b ? a : b),
alertsGenerated: 0, // À implémenter si nécessaire
alertsGenerated: alertsGenerated,
);
}
}