Files
EM2_ERP/em2rp/lib/services/alert_service.dart
T
ElPoyo af5ecaeee1 feat: optimisation du démarrage de l'application et de la gestion de l'authentification
- **Refonte du démarrage** : Mise en place d'un `AppInitializer` pour gérer l'initialisation asynchrone de Firebase et du cache en arrière-plan, réduisant le travail synchrone au lancement.
- **Sécurisation de l'authentification** :
    - Création d'un `AppStartGate` pour gérer proprement la restauration de la session Firebase Auth et les erreurs potentielles sur le Web.
    - Amélioration du `LocalUserProvider` avec un "bootstrap léger" permettant de rendre l'UID disponible immédiatement avant le chargement complet du profil.
    - Ajout de protections contre les erreurs d'accès à `FirebaseAuth.instance` (notamment pour les problèmes d'interop JS sur le Web).
- **Optimisation de l'UI** :
    - Remplacement du `AutoLoginWrapper` par une gestion plus robuste de la navigation post-authentification.
    - Amélioration de l'`AuthGuard` pour permettre l'affichage de certains écrans (comme le calendrier) pendant le chargement des données utilisateur (`allowWhileLoading`).
    - Ajout d'un écran de splash screen uniformisé (`StartupSplashScreen`).
- **Services & Cache** :
    - Introduction de `CacheService` utilisant `shared_preferences` pour le stockage local léger.
    - Refactoring des services (`AlertService`, `EmailService`, `FirebaseStorageManager`) pour accéder aux instances Firebase de manière plus flexible via des getters.
    - Mise à jour des dépendances dans `pubspec.yaml` pour inclure `shared_preferences`.
- **Calendrier** : Ajout d'une logique de chargement initial différé des événements (`_scheduleInitialEventsLoad`) pour éviter les appels redondants au démarrage.
- **Maintenance** : Mise à jour de la version de l'application à `1.1.23` et nettoyage des fichiers de cache de déploiement.
2026-05-05 12:25:45 +02:00

256 lines
7.8 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../models/alert_model.dart';
import '../utils/debug_log.dart';
import 'api_service.dart' show FirebaseFunctionsApiService;
/// Service de gestion des alertes
/// Architecture simplifiée : le client appelle uniquement les Cloud Functions
/// Toute la logique métier est gérée côté backend
class AlertService {
FirebaseFirestore get _firestore => FirebaseFirestore.instance;
FirebaseAuth get _auth => FirebaseAuth.instance;
/// Stream des alertes pour l'utilisateur connecté
Stream<List<AlertModel>> getAlertsStream() {
final user = _auth.currentUser;
if (user == null) {
DebugLog.info('[AlertService] Pas d\'utilisateur connecté');
return Stream.value([]);
}
DebugLog.info('[AlertService] Stream alertes pour utilisateur: ${user.uid}');
return _firestore
.collection('alerts')
.where('assignedTo', arrayContains: user.uid)
.where('status', isEqualTo: 'ACTIVE')
.orderBy('createdAt', descending: true)
.snapshots()
.map((snapshot) {
final alerts = snapshot.docs
.map((doc) => AlertModel.fromFirestore(doc))
.toList();
DebugLog.info('[AlertService] ${alerts.length} alertes actives');
return alerts;
});
}
/// Récupère les alertes non lues
Future<List<AlertModel>> getUnreadAlerts() async {
final user = _auth.currentUser;
if (user == null) return [];
try {
final snapshot = await _firestore
.collection('alerts')
.where('assignedTo', arrayContains: user.uid)
.where('isRead', isEqualTo: false)
.where('status', isEqualTo: 'ACTIVE')
.orderBy('createdAt', descending: true)
.get();
return snapshot.docs
.map((doc) => AlertModel.fromFirestore(doc))
.toList();
} catch (e) {
DebugLog.error('[AlertService] Erreur récupération alertes', e);
return [];
}
}
/// Marque une alerte comme lue
Future<void> markAsRead(String alertId) async {
try {
await _firestore.collection('alerts').doc(alertId).update({
'isRead': true,
'readAt': FieldValue.serverTimestamp(),
});
DebugLog.info('[AlertService] Alerte $alertId marquée comme lue');
} catch (e) {
DebugLog.error('[AlertService] Erreur marquage alerte', e);
rethrow;
}
}
/// Marque toutes les alertes comme lues
Future<void> markAllAsRead() async {
final user = _auth.currentUser;
if (user == null) return;
try {
final snapshot = await _firestore
.collection('alerts')
.where('assignedTo', arrayContains: user.uid)
.where('isRead', isEqualTo: false)
.get();
final batch = _firestore.batch();
for (var doc in snapshot.docs) {
batch.update(doc.reference, {
'isRead': true,
'readAt': FieldValue.serverTimestamp(),
});
}
await batch.commit();
DebugLog.info('[AlertService] ${snapshot.docs.length} alertes marquées comme lues');
} catch (e) {
DebugLog.error('[AlertService] Erreur marquage alertes', e);
rethrow;
}
}
/// Archive une alerte
Future<void> archiveAlert(String alertId) async {
try {
await _firestore.collection('alerts').doc(alertId).update({
'status': 'ARCHIVED',
'archivedAt': FieldValue.serverTimestamp(),
});
DebugLog.info('[AlertService] Alerte $alertId archivée');
} catch (e) {
DebugLog.error('[AlertService] Erreur archivage alerte', e);
rethrow;
}
}
/// Crée une alerte manuelle (appelée par l'utilisateur)
/// Cette méthode appelle la Cloud Function createAlert
Future<String> createManualAlert({
required AlertType type,
required AlertSeverity severity,
required String message,
String? title,
String? equipmentId,
String? eventId,
String? actionUrl,
Map<String, dynamic>? metadata,
}) async {
try {
DebugLog.info('[AlertService] === CRÉATION ALERTE MANUELLE ===');
DebugLog.info('[AlertService] Type: $type');
DebugLog.info('[AlertService] Severity: $severity');
final apiService = FirebaseFunctionsApiService();
final result = await apiService.call(
'createAlert',
{
'type': alertTypeToString(type),
'severity': severity.name.toUpperCase(),
'title': title,
'message': message,
'equipmentId': equipmentId,
'eventId': eventId,
'actionUrl': actionUrl,
'metadata': metadata ?? {},
},
);
final alertId = result['alertId'] as String;
DebugLog.info('[AlertService] ✓ Alerte créée: $alertId');
return alertId;
} catch (e, stackTrace) {
DebugLog.error('[AlertService] ❌ Erreur création alerte', e);
DebugLog.error('[AlertService] Stack', stackTrace);
rethrow;
}
}
/// Stream des alertes pour un utilisateur spécifique
Stream<List<AlertModel>> alertsStreamForUser(String userId) {
return _firestore
.collection('alerts')
.where('assignedTo', arrayContains: userId)
.where('status', isEqualTo: 'ACTIVE')
.orderBy('createdAt', descending: true)
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => AlertModel.fromFirestore(doc))
.toList());
}
/// Récupère les alertes pour un utilisateur
Future<List<AlertModel>> getAlertsForUser(String userId) async {
try {
final snapshot = await _firestore
.collection('alerts')
.where('assignedTo', arrayContains: userId)
.where('status', isEqualTo: 'ACTIVE')
.orderBy('createdAt', descending: true)
.get();
return snapshot.docs
.map((doc) => AlertModel.fromFirestore(doc))
.toList();
} catch (e) {
DebugLog.error('[AlertService] Erreur récupération alertes', e);
return [];
}
}
/// Stream du nombre d'alertes non lues pour un utilisateur
Stream<int> unreadCountStreamForUser(String userId) {
return _firestore
.collection('alerts')
.where('assignedTo', arrayContains: userId)
.where('isRead', isEqualTo: false)
.where('status', isEqualTo: 'ACTIVE')
.snapshots()
.map((snapshot) => snapshot.docs.length);
}
/// Supprime une alerte
Future<void> deleteAlert(String alertId) async {
try {
await _firestore.collection('alerts').doc(alertId).delete();
DebugLog.info('[AlertService] Alerte $alertId supprimée');
} catch (e) {
DebugLog.error('[AlertService] Erreur suppression alerte', e);
rethrow;
}
}
/// Crée une alerte de création d'événement
Future<void> createEventCreatedAlert({
required String eventId,
required String eventName,
required DateTime eventDate,
}) async {
await createManualAlert(
type: AlertType.eventCreated,
severity: AlertSeverity.info,
message: 'Nouvel événement créé: "$eventName" le ${_formatDate(eventDate)}',
eventId: eventId,
metadata: {
'eventName': eventName,
'eventDate': eventDate.toIso8601String(),
},
);
}
/// Crée une alerte de modification d'événement
Future<void> createEventModifiedAlert({
required String eventId,
required String eventName,
required String modification,
}) async {
await createManualAlert(
type: AlertType.eventModified,
severity: AlertSeverity.info,
message: 'Événement "$eventName" modifié: $modification',
eventId: eventId,
metadata: {
'eventName': eventName,
'modification': modification,
},
);
}
String _formatDate(DateTime date) {
return '${date.day}/${date.month}/${date.year}';
}
}