Cette mise à jour majeure refactorise entièrement la gestion des utilisateurs pour la faire passer par des Cloud Functions sécurisées et migre une part importante de la logique métier (gestion des événements, maintenances, containers) du client vers le backend.
**Gestion des Utilisateurs (Backend & Frontend):**
- **Nouvelle fonction `createUserWithInvite` :**
- Crée l'utilisateur dans Firebase Auth avec un mot de passe temporaire.
- Crée le document utilisateur correspondant dans Firestore.
- Envoie automatiquement un e-mail de réinitialisation de mot de passe (via l'API REST de Firebase et `axios`) pour que l'utilisateur définisse son propre mot de passe, améliorant la sécurité et l'expérience d'intégration.
- **Refactorisation de `updateUser` et `deleteUser` :**
- Les anciennes fonctions `onCall` sont remplacées par des fonctions `onRequest` (HTTP) standards, alignées avec le reste de l'API.
- La logique de suppression gère désormais la suppression dans Auth et Firestore.
- **Réinitialisation de Mot de Passe (UI) :**
- Ajout d'un bouton "Réinitialiser le mot de passe" sur la carte utilisateur, permettant aux administrateurs d'envoyer un e-mail de réinitialisation à n'importe quel utilisateur.
- **Amélioration de l'UI :**
- Boîte de dialogue de confirmation améliorée pour la suppression d'un utilisateur.
- Notifications (Snackbars) pour les opérations de création, suppression et réinitialisation de mot de passe.
**Migration de la Logique Métier vers les Cloud Functions:**
- **Gestion de la Préparation d'Événements :**
- Migration complète de la logique de validation des étapes (préparation, chargement, déchargement, retour) du client vers de nouvelles Cloud Functions (`validateEquipmentPreparation`, `validateAllLoading`, etc.).
- Le backend gère désormais la mise à jour des statuts de l'événement (`inProgress`, `completed`) et des équipements (`inUse`, `available`).
- Le code frontend (`EventPreparationService`) a été simplifié pour appeler ces nouvelles fonctions au lieu d'effectuer des écritures directes sur Firestore.
- **Création de Maintenance :**
- La fonction `createMaintenance` gère maintenant la mise à jour des équipements associés (`maintenanceIds`) et la création d'alertes (`maintenanceDue`) si une maintenance est prévue prochainement. La logique client a été supprimée.
- **Suppression de Container :**
- La fonction `deleteContainer` a été améliorée pour nettoyer automatiquement les références (`parentBoxIds`) dans tous les équipements contenus avant de supprimer le container.
**Refactorisation et Corrections (Backend & Frontend) :**
- **Fiabilisation des Appels API (Frontend) :**
- Le `ApiService` a été renforcé pour convertir de manière plus robuste les données (notamment les `Map` de type `_JsonMap`) en JSON standard avant de les envoyer aux Cloud Functions, évitant ainsi des erreurs de sérialisation.
- **Correction des Références (Backend) :**
- La fonction `updateUser` convertit correctement les `roleId` (string) en `DocumentReference` Firestore.
- Sécurisation de la vérification de l'assignation d'un utilisateur à un événement (`workforce`) pour éviter les erreurs sur des références nulles.
- **Dépendance (Backend) :**
- Ajout de la librairie `axios` pour effectuer des appels à l'API REST de Firebase.
530 lines
19 KiB
Dart
530 lines
19 KiB
Dart
import 'package:em2rp/services/api_service.dart';
|
|
|
|
/// Service générique pour les opérations de lecture de données via Cloud Functions
|
|
class DataService {
|
|
final ApiService _apiService;
|
|
|
|
DataService(this._apiService);
|
|
|
|
/// Récupère toutes les options
|
|
Future<List<Map<String, dynamic>>> getOptions() async {
|
|
try {
|
|
final result = await _apiService.call('getOptions', {});
|
|
final options = result['options'] as List<dynamic>?;
|
|
if (options == null) return [];
|
|
return options.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des options: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère tous les types d'événements
|
|
Future<List<Map<String, dynamic>>> getEventTypes() async {
|
|
try {
|
|
final result = await _apiService.call('getEventTypes', {});
|
|
final eventTypes = result['eventTypes'] as List<dynamic>?;
|
|
if (eventTypes == null) return [];
|
|
return eventTypes.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des types d\'événements: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère tous les rôles
|
|
Future<List<Map<String, dynamic>>> getRoles() async {
|
|
try {
|
|
final result = await _apiService.call('getRoles', {});
|
|
final roles = result['roles'] as List<dynamic>?;
|
|
if (roles == null) return [];
|
|
return roles.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des rôles: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour les équipements d'un événement
|
|
Future<void> updateEventEquipment({
|
|
required String eventId,
|
|
List<Map<String, dynamic>>? assignedEquipment,
|
|
String? preparationStatus,
|
|
String? loadingStatus,
|
|
String? unloadingStatus,
|
|
String? returnStatus,
|
|
}) async {
|
|
try {
|
|
final data = <String, dynamic>{'eventId': eventId};
|
|
|
|
if (assignedEquipment != null) data['assignedEquipment'] = assignedEquipment;
|
|
if (preparationStatus != null) data['preparationStatus'] = preparationStatus;
|
|
if (loadingStatus != null) data['loadingStatus'] = loadingStatus;
|
|
if (unloadingStatus != null) data['unloadingStatus'] = unloadingStatus;
|
|
if (returnStatus != null) data['returnStatus'] = returnStatus;
|
|
|
|
await _apiService.call('updateEventEquipment', data);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour des équipements de l\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour uniquement le statut d'un équipement
|
|
Future<void> updateEquipmentStatusOnly({
|
|
required String equipmentId,
|
|
String? status,
|
|
int? availableQuantity,
|
|
}) async {
|
|
try {
|
|
final data = <String, dynamic>{'equipmentId': equipmentId};
|
|
|
|
if (status != null) data['status'] = status;
|
|
if (availableQuantity != null) data['availableQuantity'] = availableQuantity;
|
|
|
|
await _apiService.call('updateEquipmentStatusOnly', data);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour du statut de l\'équipement: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour un événement
|
|
Future<void> updateEvent(String eventId, Map<String, dynamic> data) async {
|
|
try {
|
|
final requestData = {'eventId': eventId, 'data': data};
|
|
await _apiService.call('updateEvent', requestData);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour de l\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime un événement
|
|
Future<void> deleteEvent(String eventId) async {
|
|
try {
|
|
await _apiService.call('deleteEvent', {'eventId': eventId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de l\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée un nouvel équipement
|
|
Future<void> createEquipment(String equipmentId, Map<String, dynamic> data) async {
|
|
try {
|
|
final requestData = {'equipmentId': equipmentId, ...data};
|
|
await _apiService.call('createEquipment', requestData);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de l\'équipement: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour un équipement
|
|
Future<void> updateEquipment(String equipmentId, Map<String, dynamic> data) async {
|
|
try {
|
|
final requestData = {'equipmentId': equipmentId, ...data};
|
|
await _apiService.call('updateEquipment', requestData);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour de l\'équipement: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime un équipement
|
|
Future<void> deleteEquipment(String equipmentId) async {
|
|
try {
|
|
await _apiService.call('deleteEquipment', {'equipmentId': equipmentId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de l\'équipement: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les événements utilisant un type d'événement donné
|
|
Future<List<Map<String, dynamic>>> getEventsByEventType(String eventTypeId) async {
|
|
try {
|
|
final result = await _apiService.call('getEventsByEventType', {'eventTypeId': eventTypeId});
|
|
final events = result['events'] as List<dynamic>?;
|
|
if (events == null) return [];
|
|
return events.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des événements: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée un type d'événement
|
|
Future<String> createEventType({
|
|
required String name,
|
|
required double defaultPrice,
|
|
}) async {
|
|
try {
|
|
final result = await _apiService.call('createEventType', {
|
|
'name': name,
|
|
'defaultPrice': defaultPrice,
|
|
});
|
|
return result['id'] as String;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création du type d\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour un type d'événement
|
|
Future<void> updateEventType({
|
|
required String eventTypeId,
|
|
String? name,
|
|
double? defaultPrice,
|
|
}) async {
|
|
try {
|
|
final data = <String, dynamic>{'eventTypeId': eventTypeId};
|
|
if (name != null) data['name'] = name;
|
|
if (defaultPrice != null) data['defaultPrice'] = defaultPrice;
|
|
|
|
await _apiService.call('updateEventType', data);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour du type d\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime un type d'événement
|
|
Future<void> deleteEventType(String eventTypeId) async {
|
|
try {
|
|
await _apiService.call('deleteEventType', {'eventTypeId': eventTypeId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression du type d\'événement: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée une option
|
|
Future<String> createOption(String code, Map<String, dynamic> data) async {
|
|
try {
|
|
final requestData = {'code': code, ...data};
|
|
final result = await _apiService.call('createOption', requestData);
|
|
return result['id'] as String? ?? code;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de l\'option: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour une option
|
|
Future<void> updateOption(String optionId, Map<String, dynamic> data) async {
|
|
try {
|
|
final requestData = {'optionId': optionId, ...data};
|
|
await _apiService.call('updateOption', requestData);
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour de l\'option: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime une option
|
|
Future<void> deleteOption(String optionId) async {
|
|
try {
|
|
await _apiService.call('deleteOption', {'optionId': optionId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de l\'option: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// LECTURE DES DONNÉES (avec permissions côté serveur)
|
|
// ============================================================================
|
|
|
|
/// Récupère tous les événements (filtrés selon permissions)
|
|
/// Retourne { events: List<Map>, users: Map<String, Map> }
|
|
Future<Map<String, dynamic>> getEvents({String? userId}) async {
|
|
try {
|
|
final data = <String, dynamic>{};
|
|
if (userId != null) data['userId'] = userId;
|
|
|
|
final result = await _apiService.call('getEvents', data);
|
|
|
|
// Extraire events et users
|
|
final events = result['events'] as List<dynamic>? ?? [];
|
|
final users = result['users'] as Map<String, dynamic>? ?? {};
|
|
|
|
return {
|
|
'events': events.map((e) => e as Map<String, dynamic>).toList(),
|
|
'users': users,
|
|
};
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des événements: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère tous les équipements (avec masquage des prix selon permissions)
|
|
Future<List<Map<String, dynamic>>> getEquipments() async {
|
|
try {
|
|
print('[DataService] Calling getEquipments API...');
|
|
final result = await _apiService.call('getEquipments', {});
|
|
print('[DataService] API call successful, parsing result...');
|
|
final equipments = result['equipments'] as List<dynamic>?;
|
|
if (equipments == null) {
|
|
print('[DataService] No equipments in result');
|
|
return [];
|
|
}
|
|
print('[DataService] Found ${equipments.length} equipments');
|
|
return equipments.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
print('[DataService] Error getting equipments: $e');
|
|
throw Exception('Erreur lors de la récupération des équipements: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère plusieurs équipements par leurs IDs
|
|
Future<List<Map<String, dynamic>>> getEquipmentsByIds(List<String> equipmentIds) async {
|
|
try {
|
|
if (equipmentIds.isEmpty) return [];
|
|
|
|
print('[DataService] Getting equipments by IDs: ${equipmentIds.length} items');
|
|
final result = await _apiService.call('getEquipmentsByIds', {
|
|
'equipmentIds': equipmentIds,
|
|
});
|
|
final equipments = result['equipments'] as List<dynamic>?;
|
|
if (equipments == null) {
|
|
print('[DataService] No equipments in result');
|
|
return [];
|
|
}
|
|
print('[DataService] Found ${equipments.length} equipments by IDs');
|
|
return equipments.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
print('[DataService] Error getting equipments by IDs: $e');
|
|
throw Exception('Erreur lors de la récupération des équipements: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère tous les conteneurs
|
|
Future<List<Map<String, dynamic>>> getContainers() async {
|
|
try {
|
|
final result = await _apiService.call('getContainers', {});
|
|
final containers = result['containers'] as List<dynamic>?;
|
|
if (containers == null) return [];
|
|
return containers.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des conteneurs: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère plusieurs conteneurs par leurs IDs
|
|
Future<List<Map<String, dynamic>>> getContainersByIds(List<String> containerIds) async {
|
|
try {
|
|
if (containerIds.isEmpty) return [];
|
|
|
|
print('[DataService] Getting containers by IDs: ${containerIds.length} items');
|
|
final result = await _apiService.call('getContainersByIds', {
|
|
'containerIds': containerIds,
|
|
});
|
|
final containers = result['containers'] as List<dynamic>?;
|
|
if (containers == null) {
|
|
print('[DataService] No containers in result');
|
|
return [];
|
|
}
|
|
print('[DataService] Found ${containers.length} containers by IDs');
|
|
return containers.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
print('[DataService] Error getting containers by IDs: $e');
|
|
throw Exception('Erreur lors de la récupération des conteneurs: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les maintenances (optionnellement filtrées par équipement)
|
|
Future<List<Map<String, dynamic>>> getMaintenances({String? equipmentId}) async {
|
|
try {
|
|
final data = <String, dynamic>{};
|
|
if (equipmentId != null) data['equipmentId'] = equipmentId;
|
|
|
|
final result = await _apiService.call('getMaintenances', data);
|
|
final maintenances = result['maintenances'] as List<dynamic>?;
|
|
if (maintenances == null) return [];
|
|
return maintenances.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des maintenances: $e');
|
|
}
|
|
}
|
|
|
|
|
|
/// Récupère les containers contenant un équipement spécifique
|
|
Future<List<Map<String, dynamic>>> getContainersByEquipment(String equipmentId) async {
|
|
try {
|
|
final result = await _apiService.call('getContainersByEquipment', {
|
|
'equipmentId': equipmentId,
|
|
});
|
|
final containers = result['containers'] as List<dynamic>?;
|
|
if (containers == null) return [];
|
|
return containers.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des containers pour l\'équipement: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// USER - Current User
|
|
// ============================================================================
|
|
|
|
/// Récupère l'utilisateur actuellement authentifié avec son rôle
|
|
Future<Map<String, dynamic>> getCurrentUser() async {
|
|
try {
|
|
print('[DataService] Calling getCurrentUser API...');
|
|
final result = await _apiService.call('getCurrentUser', {});
|
|
print('[DataService] Current user loaded successfully');
|
|
return result['user'] as Map<String, dynamic>;
|
|
} catch (e) {
|
|
print('[DataService] Error getting current user: $e');
|
|
throw Exception('Erreur lors de la récupération de l\'utilisateur actuel: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// ALERTS
|
|
// ============================================================================
|
|
|
|
/// Récupère toutes les alertes
|
|
Future<List<Map<String, dynamic>>> getAlerts() async {
|
|
try {
|
|
final result = await _apiService.call('getAlerts', {});
|
|
final alerts = result['alerts'] as List<dynamic>?;
|
|
if (alerts == null) return [];
|
|
return alerts.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des alertes: $e');
|
|
}
|
|
}
|
|
|
|
/// Marque une alerte comme lue
|
|
Future<void> markAlertAsRead(String alertId) async {
|
|
try {
|
|
await _apiService.call('markAlertAsRead', {'alertId': alertId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors du marquage de l\'alerte comme lue: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime une alerte
|
|
Future<void> deleteAlert(String alertId) async {
|
|
try {
|
|
await _apiService.call('deleteAlert', {'alertId': alertId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de l\'alerte: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EQUIPMENT AVAILABILITY
|
|
// ============================================================================
|
|
|
|
/// Vérifie la disponibilité d'un équipement
|
|
Future<Map<String, dynamic>> checkEquipmentAvailability({
|
|
required String equipmentId,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
String? excludeEventId,
|
|
}) async {
|
|
try {
|
|
final result = await _apiService.call('checkEquipmentAvailability', {
|
|
'equipmentId': equipmentId,
|
|
'startDate': startDate.toIso8601String(),
|
|
'endDate': endDate.toIso8601String(),
|
|
if (excludeEventId != null) 'excludeEventId': excludeEventId,
|
|
});
|
|
return result;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la vérification de disponibilité: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère tous les IDs d'équipements et conteneurs en conflit pour une période
|
|
/// Optimisé : une seule requête au lieu d'une par équipement
|
|
Future<Map<String, dynamic>> getConflictingEquipmentIds({
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
String? excludeEventId,
|
|
int installationTime = 0,
|
|
int disassemblyTime = 0,
|
|
}) async {
|
|
try {
|
|
final result = await _apiService.call('getConflictingEquipmentIds', {
|
|
'startDate': startDate.toIso8601String(),
|
|
'endDate': endDate.toIso8601String(),
|
|
if (excludeEventId != null) 'excludeEventId': excludeEventId,
|
|
'installationTime': installationTime,
|
|
'disassemblyTime': disassemblyTime,
|
|
});
|
|
return result;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des équipements en conflit: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// MAINTENANCES
|
|
// ============================================================================
|
|
|
|
/// Supprime une maintenance
|
|
Future<void> deleteMaintenance(String maintenanceId) async {
|
|
try {
|
|
await _apiService.call('deleteMaintenance', {'maintenanceId': maintenanceId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de la maintenance: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// USERS
|
|
// ============================================================================
|
|
|
|
/// Récupère tous les utilisateurs (selon permissions)
|
|
Future<List<Map<String, dynamic>>> getUsers() async {
|
|
try {
|
|
final result = await _apiService.call('getUsers', {});
|
|
final users = result['users'] as List<dynamic>?;
|
|
if (users == null) return [];
|
|
return users.map((e) => e as Map<String, dynamic>).toList();
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération des utilisateurs: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère un utilisateur spécifique
|
|
Future<Map<String, dynamic>> getUser(String userId) async {
|
|
try {
|
|
final result = await _apiService.call('getUser', {'userId': userId});
|
|
return result['user'] as Map<String, dynamic>;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la récupération de l\'utilisateur: $e');
|
|
}
|
|
}
|
|
|
|
/// Supprime un utilisateur (Auth + Firestore)
|
|
Future<void> deleteUser(String userId) async {
|
|
try {
|
|
await _apiService.call('deleteUser', {'userId': userId});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la suppression de l\'utilisateur: $e');
|
|
}
|
|
}
|
|
|
|
/// Met à jour un utilisateur
|
|
Future<void> updateUser(String userId, Map<String, dynamic> data) async {
|
|
try {
|
|
await _apiService.call('updateUser', {
|
|
'userId': userId,
|
|
'data': data,
|
|
});
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la mise à jour de l\'utilisateur: $e');
|
|
}
|
|
}
|
|
|
|
/// Crée un utilisateur avec invitation par email
|
|
Future<Map<String, dynamic>> createUserWithInvite({
|
|
required String email,
|
|
required String firstName,
|
|
required String lastName,
|
|
String? phoneNumber,
|
|
required String roleId,
|
|
}) async {
|
|
try {
|
|
final result = await _apiService.call('createUserWithInvite', {
|
|
'email': email,
|
|
'firstName': firstName,
|
|
'lastName': lastName,
|
|
'phoneNumber': phoneNumber ?? '',
|
|
'roleId': roleId,
|
|
});
|
|
return result;
|
|
} catch (e) {
|
|
throw Exception('Erreur lors de la création de l\'utilisateur: $e');
|
|
}
|
|
}
|
|
}
|