Files
EM2_ERP/em2rp/lib/providers/container_provider.dart
ElPoyo 2bcd1ca4c3 feat: Ajout de la gestion des utilisateurs et optimisation du chargement des données
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.
2026-01-13 01:40:28 +01:00

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;
}
}