Cette mise à jour introduit la gestion complète des utilisateurs (création, mise à jour, suppression) via des Cloud Functions et optimise de manière significative le chargement des données dans toute l'application.
**Features :**
- **Gestion des utilisateurs (Backend & Frontend) :**
- Ajout des Cloud Functions `getUser`, `updateUser` et `deleteUser` pour gérer les utilisateurs de manière sécurisée, en respectant les permissions des rôles.
- L'authentification passe désormais par `onCall` pour plus de sécurité.
- **Optimisation du chargement des données :**
- Introduction de nouvelles Cloud Functions `getEquipmentsByIds` et `getContainersByIds` pour récupérer uniquement les documents nécessaires, réduisant ainsi la charge sur le client et Firestore.
- Les fournisseurs (`EquipmentProvider`, `ContainerProvider`) ont été refactorisés pour utiliser un chargement à la demande (`ensureLoaded`) et mettre en cache les données récupérées.
- Les écrans de détails et de préparation d'événements n'utilisent plus de `Stream` globaux, mais chargent les équipements et boites spécifiques via ces nouvelles fonctions, améliorant considérablement les performances.
**Refactorisation et Améliorations :**
- **Backend (Cloud Functions) :**
- Le service de vérification de disponibilité (`checkEquipmentAvailability`) est désormais une Cloud Function, déplaçant la logique métier côté serveur.
- La récupération des données (utilisateurs, événements, alertes) a été centralisée derrière des Cloud Functions, remplaçant les appels directs à Firestore depuis le client.
- Amélioration de la sérialisation des données (timestamps, références) dans les réponses des fonctions.
- **Frontend (Flutter) :**
- `LocalUserProvider` charge désormais les informations de l'utilisateur connecté via la fonction `getCurrentUser`, incluant son rôle et ses permissions en un seul appel.
- `AlertProvider` utilise des fonctions pour charger et manipuler les alertes, abandonnant le `Stream` Firestore.
- `EventAvailabilityService` utilise maintenant la Cloud Function `checkEquipmentAvailability` au lieu d'une logique client complexe.
- Correction de la gestion des références de rôles (`roles/ADMIN`) et des `DocumentReference` pour les utilisateurs dans l'ensemble de l'application.
- Le service d'export ICS (`IcsExportService`) a été simplifié, partant du principe que les données nécessaires (utilisateurs, options) sont déjà chargées dans l'application.
272 lines
8.0 KiB
Dart
272 lines
8.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:em2rp/models/container_model.dart';
|
|
import 'package:em2rp/models/equipment_model.dart';
|
|
import 'package:em2rp/services/container_service.dart';
|
|
import 'package:em2rp/services/data_service.dart';
|
|
import 'package:em2rp/services/api_service.dart';
|
|
|
|
class ContainerProvider with ChangeNotifier {
|
|
final ContainerService _containerService = ContainerService();
|
|
final DataService _dataService = DataService(FirebaseFunctionsApiService());
|
|
|
|
List<ContainerModel> _containers = [];
|
|
ContainerType? _selectedType;
|
|
EquipmentStatus? _selectedStatus;
|
|
String _searchQuery = '';
|
|
bool _isLoading = false;
|
|
bool _isInitialized = false;
|
|
|
|
List<ContainerModel> get containers => _containers;
|
|
ContainerType? get selectedType => _selectedType;
|
|
EquipmentStatus? get selectedStatus => _selectedStatus;
|
|
String get searchQuery => _searchQuery;
|
|
bool get isLoading => _isLoading;
|
|
bool get isInitialized => _isInitialized;
|
|
|
|
/// S'assure que les conteneurs sont chargés (charge si nécessaire)
|
|
Future<void> ensureLoaded() async {
|
|
if (_isInitialized || _isLoading) {
|
|
print('[ContainerProvider] Containers already loaded or loading, skipping...');
|
|
return;
|
|
}
|
|
print('[ContainerProvider] Containers not loaded, loading now...');
|
|
await loadContainers();
|
|
}
|
|
|
|
/// Charger tous les containers via l'API
|
|
Future<void> loadContainers() async {
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// Pour l'instant, on écoute le stream et on garde la première valeur
|
|
_containerService.getContainers(
|
|
type: _selectedType,
|
|
status: _selectedStatus,
|
|
searchQuery: _searchQuery,
|
|
).listen((containers) {
|
|
_containers = containers;
|
|
_isLoading = false;
|
|
_isInitialized = true;
|
|
notifyListeners();
|
|
});
|
|
} catch (e) {
|
|
print('Error loading containers: $e');
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Stream des containers avec filtres appliqués
|
|
Stream<List<ContainerModel>> get containersStream {
|
|
return _containerService.getContainers(
|
|
type: _selectedType,
|
|
status: _selectedStatus,
|
|
searchQuery: _searchQuery,
|
|
);
|
|
}
|
|
|
|
/// Définir le type sélectionné
|
|
void setSelectedType(ContainerType? type) {
|
|
_selectedType = type;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Définir le statut sélectionné
|
|
void setSelectedStatus(EquipmentStatus? status) {
|
|
_selectedStatus = status;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Définir la requête de recherche
|
|
void setSearchQuery(String query) {
|
|
_searchQuery = query;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Créer un nouveau container
|
|
Future<void> createContainer(ContainerModel container) async {
|
|
await _containerService.createContainer(container);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Mettre à jour un container
|
|
Future<void> updateContainer(String id, Map<String, dynamic> data) async {
|
|
await _containerService.updateContainer(id, data);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Supprimer un container
|
|
Future<void> deleteContainer(String id) async {
|
|
await _containerService.deleteContainer(id);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Récupérer un container par ID
|
|
Future<ContainerModel?> getContainerById(String id) async {
|
|
return await _containerService.getContainerById(id);
|
|
}
|
|
|
|
/// Charge plusieurs conteneurs par leurs IDs (optimisé pour les détails d'événement)
|
|
Future<List<ContainerModel>> getContainersByIds(List<String> containerIds) async {
|
|
if (containerIds.isEmpty) return [];
|
|
|
|
print('[ContainerProvider] Loading ${containerIds.length} containers by IDs...');
|
|
|
|
try {
|
|
// Vérifier d'abord le cache local
|
|
final cachedContainers = <ContainerModel>[];
|
|
final missingIds = <String>[];
|
|
|
|
for (final id in containerIds) {
|
|
final cached = _containers.firstWhere(
|
|
(c) => c.id == id,
|
|
orElse: () => ContainerModel(
|
|
id: '',
|
|
name: '',
|
|
type: ContainerType.flightCase,
|
|
status: EquipmentStatus.available,
|
|
equipmentIds: [],
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
if (cached.id.isNotEmpty) {
|
|
cachedContainers.add(cached);
|
|
} else {
|
|
missingIds.add(id);
|
|
}
|
|
}
|
|
|
|
print('[ContainerProvider] Found ${cachedContainers.length} in cache, ${missingIds.length} missing');
|
|
|
|
// Si tous sont en cache, retourner directement
|
|
if (missingIds.isEmpty) {
|
|
return cachedContainers;
|
|
}
|
|
|
|
// Charger les manquants depuis l'API
|
|
final containersData = await _dataService.getContainersByIds(missingIds);
|
|
|
|
final loadedContainers = containersData.map((data) {
|
|
return ContainerModel.fromMap(data, data['id'] as String);
|
|
}).toList();
|
|
|
|
// Ajouter au cache
|
|
for (final container in loadedContainers) {
|
|
if (!_containers.any((c) => c.id == container.id)) {
|
|
_containers.add(container);
|
|
}
|
|
}
|
|
|
|
print('[ContainerProvider] Loaded ${loadedContainers.length} containers from API');
|
|
|
|
// Retourner tous les conteneurs (cache + chargés)
|
|
return [...cachedContainers, ...loadedContainers];
|
|
} catch (e) {
|
|
print('[ContainerProvider] Error loading containers by IDs: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Ajouter un équipement à un container
|
|
Future<Map<String, dynamic>> addEquipmentToContainer({
|
|
required String containerId,
|
|
required String equipmentId,
|
|
String? userId,
|
|
}) async {
|
|
final result = await _containerService.addEquipmentToContainer(
|
|
containerId: containerId,
|
|
equipmentId: equipmentId,
|
|
userId: userId,
|
|
);
|
|
notifyListeners();
|
|
return result;
|
|
}
|
|
|
|
/// Retirer un équipement d'un container
|
|
Future<void> removeEquipmentFromContainer({
|
|
required String containerId,
|
|
required String equipmentId,
|
|
String? userId,
|
|
}) async {
|
|
await _containerService.removeEquipmentFromContainer(
|
|
containerId: containerId,
|
|
equipmentId: equipmentId,
|
|
userId: userId,
|
|
);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Vérifier la disponibilité d'un container
|
|
Future<Map<String, dynamic>> checkContainerAvailability({
|
|
required String containerId,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
String? excludeEventId,
|
|
}) async {
|
|
return await _containerService.checkContainerAvailability(
|
|
containerId: containerId,
|
|
startDate: startDate,
|
|
endDate: endDate,
|
|
excludeEventId: excludeEventId,
|
|
);
|
|
}
|
|
|
|
/// Récupérer les équipements d'un container
|
|
Future<List<EquipmentModel>> getContainerEquipment(String containerId) async {
|
|
return await _containerService.getContainerEquipment(containerId);
|
|
}
|
|
|
|
/// Trouver tous les containers contenant un équipement
|
|
Future<List<ContainerModel>> findContainersWithEquipment(String equipmentId) async {
|
|
return await _containerService.findContainersWithEquipment(equipmentId);
|
|
}
|
|
|
|
/// Vérifier si un ID existe
|
|
Future<bool> checkContainerIdExists(String id) async {
|
|
return await _containerService.checkContainerIdExists(id);
|
|
}
|
|
|
|
/// Générer un ID unique pour un container
|
|
/// Format: BOX_{TYPE}_{NAME}_{NUMBER}
|
|
static String generateContainerId({
|
|
required ContainerType type,
|
|
required String name,
|
|
int? number,
|
|
}) {
|
|
// Obtenir le type en majuscules
|
|
final typeStr = containerTypeToString(type);
|
|
|
|
// Nettoyer le nom (enlever espaces, caractères spéciaux)
|
|
final cleanName = name
|
|
.replaceAll(' ', '_')
|
|
.replaceAll(RegExp(r'[^a-zA-Z0-9_-]'), '')
|
|
.toUpperCase();
|
|
|
|
if (number != null) {
|
|
return 'BOX_${typeStr}_${cleanName}_#$number';
|
|
}
|
|
|
|
return 'BOX_${typeStr}_$cleanName';
|
|
}
|
|
|
|
/// Assurer l'unicité d'un ID de container
|
|
static Future<String> ensureUniqueContainerId(
|
|
String baseId,
|
|
ContainerService service,
|
|
) async {
|
|
String uniqueId = baseId;
|
|
int counter = 1;
|
|
|
|
while (await service.checkContainerIdExists(uniqueId)) {
|
|
uniqueId = '${baseId}_$counter';
|
|
counter++;
|
|
}
|
|
|
|
return uniqueId;
|
|
}
|
|
}
|
|
|