refactor: Remplacement de l'accès direct à Firestore par des Cloud Functions
Migration complète du backend pour utiliser des Cloud Functions comme couche API sécurisée, en remplacement des appels directs à Firestore depuis le client.
**Backend (Cloud Functions):**
- **Centralisation CORS :** Ajout d'un middleware `withCors` et d'une configuration `httpOptions` pour gérer uniformément les en-têtes CORS et les requêtes `OPTIONS` sur toutes les fonctions.
- **Nouvelles Fonctions de Lecture (GET) :**
- `getEquipments`, `getContainers`, `getEvents`, `getUsers`, `getOptions`, `getEventTypes`, `getRoles`, `getMaintenances`, `getAlerts`.
- Ces fonctions gèrent les permissions côté serveur, masquant les données sensibles (ex: prix des équipements) pour les utilisateurs non-autorisés.
- `getEvents` retourne également une map des utilisateurs (`usersMap`) pour optimiser le chargement des données de la main d'œuvre.
- **Nouvelle Fonction de Recherche :**
- `getContainersByEquipment` : Endpoint dédié pour trouver efficacement tous les containers qui contiennent un équipement spécifique.
- **Nouvelles Fonctions d'Écriture (CRUD) :**
- Fonctions CRUD complètes pour `eventTypes` (`create`, `update`, `delete`), incluant la validation (unicité du nom, vérification des événements futurs avant suppression).
- **Mise à jour de Fonctions Existantes :**
- Toutes les fonctions CRUD existantes (`create/update/deleteEquipment`, `create/update/deleteContainer`, etc.) sont wrappées avec le nouveau gestionnaire CORS.
**Frontend (Flutter):**
- **Introduction du `DataService` :** Nouveau service centralisant tous les appels aux Cloud Functions, servant d'intermédiaire entre l'UI/Providers et l'API.
- **Refactorisation des Providers :**
- `EquipmentProvider`, `ContainerProvider`, `EventProvider`, `UsersProvider`, `MaintenanceProvider` et `AlertProvider` ont été refactorisés pour utiliser le `DataService` au lieu d'accéder directement à Firestore.
- Les `Stream` Firestore sont remplacés par des chargements de données via des méthodes `Future` (`loadEquipments`, `loadEvents`, etc.).
- **Gestion des Relations Équipement-Container :**
- Le modèle `EquipmentModel` ne stocke plus `parentBoxIds`.
- La relation est maintenant gérée par le `ContainerModel` qui contient `equipmentIds`.
- Le `ContainerEquipmentService` est introduit pour utiliser la nouvelle fonction `getContainersByEquipment`.
- L'affichage des boîtes parentes (`EquipmentParentContainers`) et le formulaire d'équipement (`EquipmentFormPage`) ont été mis à jour pour refléter ce nouveau modèle de données, synchronisant les ajouts/suppressions d'équipements dans les containers.
- **Amélioration de l'UI :**
- Nouveau widget `ParentBoxesSelector` pour une sélection améliorée et visuelle des boîtes parentes dans le formulaire d'équipement.
- Refonte visuelle de `EquipmentParentContainers` pour une meilleure présentation.
This commit is contained in:
@@ -6,8 +6,10 @@ import 'package:em2rp/models/equipment_model.dart';
|
||||
import 'package:em2rp/models/container_model.dart';
|
||||
import 'package:em2rp/providers/equipment_provider.dart';
|
||||
import 'package:em2rp/providers/container_provider.dart';
|
||||
import 'package:em2rp/services/event_preparation_service.dart';
|
||||
import 'package:em2rp/services/event_preparation_service_extended.dart';
|
||||
import 'package:em2rp/providers/event_provider.dart';
|
||||
import 'package:em2rp/providers/local_user_provider.dart';
|
||||
import 'package:em2rp/services/data_service.dart';
|
||||
import 'package:em2rp/services/api_service.dart';
|
||||
import 'package:em2rp/views/widgets/equipment/equipment_checklist_item.dart' show EquipmentChecklistItem, ChecklistStep;
|
||||
import 'package:em2rp/views/widgets/equipment/missing_equipment_dialog.dart';
|
||||
import 'package:em2rp/utils/colors.dart';
|
||||
@@ -34,9 +36,8 @@ class EventPreparationPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _EventPreparationPageState extends State<EventPreparationPage> with SingleTickerProviderStateMixin {
|
||||
final EventPreparationService _preparationService = EventPreparationService();
|
||||
final EventPreparationServiceExtended _extendedService = EventPreparationServiceExtended();
|
||||
late AnimationController _animationController;
|
||||
late final DataService _dataService;
|
||||
|
||||
Map<String, EquipmentModel> _equipmentCache = {};
|
||||
Map<String, ContainerModel> _containerCache = {};
|
||||
@@ -89,6 +90,7 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentEvent = widget.initialEvent;
|
||||
_dataService = DataService(FirebaseFunctionsApiService());
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
@@ -131,24 +133,6 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Recharger l'événement depuis Firestore
|
||||
Future<void> _reloadEvent() async {
|
||||
try {
|
||||
final doc = await FirebaseFirestore.instance
|
||||
.collection('events')
|
||||
.doc(_currentEvent.id)
|
||||
.get();
|
||||
|
||||
if (doc.exists) {
|
||||
setState(() {
|
||||
_currentEvent = EventModel.fromMap(doc.data() as Map<String, dynamic>, doc.id);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('[EventPreparationPage] Error reloading event: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadEquipmentAndContainers() async {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
@@ -293,11 +277,15 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
break;
|
||||
}
|
||||
|
||||
// Sauvegarder dans Firestore
|
||||
await FirebaseFirestore.instance
|
||||
.collection('events')
|
||||
.doc(_currentEvent.id)
|
||||
.update(updateData);
|
||||
// Sauvegarder dans Firestore via l'API
|
||||
await _dataService.updateEventEquipment(
|
||||
eventId: _currentEvent.id,
|
||||
assignedEquipment: updatedEquipment.map((e) => e.toMap()).toList(),
|
||||
preparationStatus: updateData['preparationStatus'],
|
||||
loadingStatus: updateData['loadingStatus'],
|
||||
unloadingStatus: updateData['unloadingStatus'],
|
||||
returnStatus: updateData['returnStatus'],
|
||||
);
|
||||
|
||||
// Mettre à jour les statuts des équipements si nécessaire
|
||||
if (_currentStep == PreparationStep.preparation ||
|
||||
@@ -305,6 +293,14 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
await _updateEquipmentStatuses(updatedEquipment);
|
||||
}
|
||||
|
||||
// Recharger l'événement depuis le provider
|
||||
final eventProvider = context.read<EventProvider>();
|
||||
// Recharger la liste des événements pour rafraîchir les données
|
||||
final userId = context.read<LocalUserProvider>().uid;
|
||||
if (userId != null) {
|
||||
await eventProvider.loadUserEvents(userId, canViewAllEvents: true);
|
||||
}
|
||||
|
||||
setState(() => _showSuccessAnimation = true);
|
||||
_animationController.forward();
|
||||
|
||||
@@ -338,52 +334,37 @@ class _EventPreparationPageState extends State<EventPreparationPage> with Single
|
||||
Future<void> _updateEquipmentStatuses(List<EventEquipment> equipment) async {
|
||||
for (var eq in equipment) {
|
||||
try {
|
||||
final doc = await FirebaseFirestore.instance
|
||||
.collection('equipments')
|
||||
.doc(eq.equipmentId)
|
||||
.get();
|
||||
final equipmentData = _equipmentCache[eq.equipmentId];
|
||||
if (equipmentData == null) continue;
|
||||
|
||||
if (doc.exists) {
|
||||
final equipmentData = EquipmentModel.fromMap(
|
||||
doc.data() as Map<String, dynamic>,
|
||||
doc.id,
|
||||
// Déterminer le nouveau statut
|
||||
EquipmentStatus newStatus;
|
||||
if (eq.isReturned) {
|
||||
newStatus = EquipmentStatus.available;
|
||||
} else if (eq.isPrepared || eq.isLoaded) {
|
||||
newStatus = EquipmentStatus.inUse;
|
||||
} else {
|
||||
continue; // Pas de changement
|
||||
}
|
||||
|
||||
// Ne mettre à jour que les équipements non quantifiables
|
||||
if (!equipmentData.hasQuantity) {
|
||||
await _dataService.updateEquipmentStatusOnly(
|
||||
equipmentId: eq.equipmentId,
|
||||
status: equipmentStatusToString(newStatus),
|
||||
);
|
||||
}
|
||||
|
||||
// Déterminer le nouveau statut
|
||||
EquipmentStatus newStatus;
|
||||
if (eq.isReturned) {
|
||||
newStatus = EquipmentStatus.available;
|
||||
} else if (eq.isPrepared || eq.isLoaded) {
|
||||
newStatus = EquipmentStatus.inUse;
|
||||
} else {
|
||||
continue; // Pas de changement
|
||||
}
|
||||
|
||||
// Ne mettre à jour que les équipements non quantifiables
|
||||
if (!equipmentData.hasQuantity) {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('equipments')
|
||||
.doc(eq.equipmentId)
|
||||
.update({
|
||||
'status': equipmentStatusToString(newStatus),
|
||||
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
||||
});
|
||||
}
|
||||
|
||||
// Gérer les stocks pour les consommables
|
||||
if (equipmentData.hasQuantity && eq.isReturned && eq.returnedQuantity != null) {
|
||||
final currentAvailable = equipmentData.availableQuantity ?? 0;
|
||||
await FirebaseFirestore.instance
|
||||
.collection('equipments')
|
||||
.doc(eq.equipmentId)
|
||||
.update({
|
||||
'availableQuantity': currentAvailable + eq.returnedQuantity!,
|
||||
'updatedAt': Timestamp.fromDate(DateTime.now()),
|
||||
});
|
||||
}
|
||||
// Gérer les stocks pour les consommables
|
||||
if (equipmentData.hasQuantity && eq.isReturned && eq.returnedQuantity != null) {
|
||||
final currentAvailable = equipmentData.availableQuantity ?? 0;
|
||||
await _dataService.updateEquipmentStatusOnly(
|
||||
equipmentId: eq.equipmentId,
|
||||
availableQuantity: currentAvailable + eq.returnedQuantity!,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error updating equipment status for ${eq.equipmentId}: $e');
|
||||
// Erreur silencieuse pour ne pas bloquer le processus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user