- Ajout d'une Cloud Function `aiEquipmentProposal` utilisant le modèle Gemini avec function calling pour suggérer du matériel et des containers. - Implémentation de plusieurs outils (tools) côté serveur pour permettre à l'IA d'interagir avec Firestore : `search_equipment`, `check_availability_batch`, `get_past_events`, `search_event_reference` et `search_containers`. - Ajout de la dépendance `@google/generative-ai` dans le backend. - Création d'un service Flutter `AiEquipmentAssistantService` pour communiquer avec la nouvelle Cloud Function. - Ajout d'une interface de dialogue `AiEquipmentAssistantDialog` permettant aux utilisateurs de discuter avec l'IA pour affiner les propositions de matériel. - Intégration de l'assistant IA dans la section de gestion du matériel des événements (`EventAssignedEquipmentSection`). - Mise à jour de `DataService` avec de nouvelles méthodes de recherche et de vérification de disponibilité optimisées pour l'assistant. - Activation du mode développement et configuration des identifiants de test dans `env.dart`. - Optimisation des paramètres de la Cloud Function (timeout de 300s et 1GiB de RAM) pour supporter les traitements IA.
890 lines
30 KiB
Dart
890 lines
30 KiB
Dart
import 'package:em2rp/services/api_service.dart';
|
|
import 'package:em2rp/utils/debug_log.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 {
|
|
// Correction : fusionner eventId et les champs de data à la racine
|
|
final requestData = {'eventId': eventId, ...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 équipement
|
|
Future<void> createEquipment(
|
|
String equipmentId, Map<String, dynamic> data) async {
|
|
try {
|
|
// S'assurer que l'ID est dans les données
|
|
final equipmentData = Map<String, dynamic>.from(data);
|
|
equipmentData['id'] = equipmentId;
|
|
|
|
await _apiService.call('createEquipment', equipmentData);
|
|
} 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 {
|
|
await _apiService.call('updateEquipment', {
|
|
'equipmentId': equipmentId,
|
|
'data': data,
|
|
});
|
|
} 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 = {
|
|
'id': code, // Ajouter l'ID en utilisant le code comme identifiant
|
|
'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': 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 les événements d'un mois spécifique (lazy loading optimisé)
|
|
Future<Map<String, dynamic>> getEventsByMonth({
|
|
required String userId,
|
|
required int year,
|
|
required int month,
|
|
}) async {
|
|
try {
|
|
print('[DataService] Calling getEventsByMonth for $year-$month');
|
|
final result = await _apiService.call('getEventsByMonth', {
|
|
'userId': userId,
|
|
'year': year,
|
|
'month': month,
|
|
});
|
|
|
|
// Extraire events et users
|
|
final events = result['events'] as List<dynamic>? ?? [];
|
|
final users = result['users'] as Map<String, dynamic>? ?? {};
|
|
|
|
print(
|
|
'[DataService] Events loaded for $year-$month: ${events.length} events');
|
|
|
|
return {
|
|
'events': events.map((e) => e as Map<String, dynamic>).toList(),
|
|
'users': users,
|
|
};
|
|
} catch (e) {
|
|
print('[DataService] Error getting events by month: $e');
|
|
throw Exception(
|
|
'Erreur lors de la récupération des événements du mois: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère un événement avec tous les détails (équipements complets + containers avec enfants)
|
|
Future<Map<String, dynamic>> getEventWithDetails(String eventId) async {
|
|
try {
|
|
print('[DataService] Getting event with details: $eventId');
|
|
final result = await _apiService.call('getEventWithDetails', {
|
|
'eventId': eventId,
|
|
});
|
|
|
|
final event = result['event'] as Map<String, dynamic>?;
|
|
final equipments = result['equipments'] as Map<String, dynamic>? ?? {};
|
|
final containers = result['containers'] as Map<String, dynamic>? ?? {};
|
|
|
|
if (event == null) {
|
|
throw Exception('Event not found');
|
|
}
|
|
|
|
print(
|
|
'[DataService] Event loaded with ${equipments.length} equipments and ${containers.length} containers');
|
|
|
|
return {
|
|
'event': event,
|
|
'equipments': equipments,
|
|
'containers': containers,
|
|
};
|
|
} catch (e) {
|
|
print('[DataService] Error getting event with details: $e');
|
|
throw Exception(
|
|
'Erreur lors de la récupération de l\'événement avec détails: $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 containers 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 containers: $e');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EQUIPMENTS & CONTAINERS - Pagination
|
|
// ============================================================================
|
|
|
|
/// Récupère les équipements avec pagination et filtrage
|
|
Future<Map<String, dynamic>> getEquipmentsPaginated({
|
|
int limit = 20,
|
|
String? startAfter,
|
|
String? category,
|
|
String? status,
|
|
String? searchQuery,
|
|
String sortBy = 'id',
|
|
String sortOrder = 'asc',
|
|
}) async {
|
|
try {
|
|
final params = <String, dynamic>{
|
|
'limit': limit,
|
|
'sortBy': sortBy,
|
|
'sortOrder': sortOrder,
|
|
};
|
|
|
|
if (startAfter != null) params['startAfter'] = startAfter;
|
|
if (category != null) params['category'] = category;
|
|
if (status != null) params['status'] = status;
|
|
if (searchQuery != null && searchQuery.isNotEmpty) {
|
|
params['searchQuery'] = searchQuery;
|
|
}
|
|
|
|
final result =
|
|
await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
|
'getEquipmentsPaginated',
|
|
params,
|
|
);
|
|
|
|
return {
|
|
'equipments': (result['equipments'] as List<dynamic>?)
|
|
?.map((e) => e as Map<String, dynamic>)
|
|
.toList() ??
|
|
[],
|
|
'hasMore': result['hasMore'] as bool? ?? false,
|
|
'lastVisible': result['lastVisible'] as String?,
|
|
'total': result['total'] as int? ?? 0,
|
|
};
|
|
} catch (e) {
|
|
DebugLog.error('[DataService] Error in getEquipmentsPaginated', e);
|
|
throw Exception(
|
|
'Erreur lors de la récupération paginée des équipements: $e');
|
|
}
|
|
}
|
|
|
|
/// Récupère les containers avec pagination et filtrage
|
|
Future<Map<String, dynamic>> getContainersPaginated({
|
|
int limit = 20,
|
|
String? startAfter,
|
|
String? type,
|
|
String? status,
|
|
String? searchQuery,
|
|
String? category,
|
|
String sortBy = 'id',
|
|
String sortOrder = 'asc',
|
|
}) async {
|
|
try {
|
|
final params = <String, dynamic>{
|
|
'limit': limit,
|
|
'sortBy': sortBy,
|
|
'sortOrder': sortOrder,
|
|
};
|
|
|
|
if (startAfter != null) params['startAfter'] = startAfter;
|
|
if (type != null) params['type'] = type;
|
|
if (status != null) params['status'] = status;
|
|
if (category != null) params['category'] = category;
|
|
if (searchQuery != null && searchQuery.isNotEmpty) {
|
|
params['searchQuery'] = searchQuery;
|
|
}
|
|
|
|
final result =
|
|
await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
|
'getContainersPaginated',
|
|
params,
|
|
);
|
|
|
|
return {
|
|
'containers': (result['containers'] as List<dynamic>?)
|
|
?.map((e) => e as Map<String, dynamic>)
|
|
.toList() ??
|
|
[],
|
|
'hasMore': result['hasMore'] as bool? ?? false,
|
|
'lastVisible': result['lastVisible'] as String?,
|
|
'total': result['total'] as int? ?? 0,
|
|
};
|
|
} catch (e) {
|
|
DebugLog.error('[DataService] Error in getContainersPaginated', e);
|
|
throw Exception(
|
|
'Erreur lors de la récupération paginée des containers: $e');
|
|
}
|
|
}
|
|
|
|
/// Recherche rapide (autocomplétion)
|
|
Future<List<Map<String, dynamic>>> quickSearch(
|
|
String query, {
|
|
int limit = 10,
|
|
bool includeEquipments = true,
|
|
bool includeContainers = true,
|
|
}) async {
|
|
try {
|
|
return await (_apiService as FirebaseFunctionsApiService).quickSearch(
|
|
query,
|
|
limit: limit,
|
|
includeEquipments: includeEquipments,
|
|
includeContainers: includeContainers,
|
|
);
|
|
} catch (e) {
|
|
DebugLog.error('[DataService] Error in quickSearch', e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Recherche des équipements pour l'assistant IA avec fallback paginé.
|
|
Future<List<Map<String, dynamic>>> searchEquipmentsForAssistant({
|
|
required String query,
|
|
int limit = 12,
|
|
}) async {
|
|
final normalizedQuery = query.trim();
|
|
if (normalizedQuery.isEmpty) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
final quickResults = await quickSearch(
|
|
normalizedQuery,
|
|
limit: limit,
|
|
includeEquipments: true,
|
|
includeContainers: false,
|
|
);
|
|
|
|
final equipmentResults = quickResults
|
|
.where((item) =>
|
|
(item['type']?.toString().toLowerCase() ?? '') == 'equipment')
|
|
.map(_normalizeAssistantEquipment)
|
|
.toList();
|
|
|
|
if (equipmentResults.isNotEmpty) {
|
|
return equipmentResults;
|
|
}
|
|
|
|
final paginated = await getEquipmentsPaginated(
|
|
limit: limit,
|
|
searchQuery: normalizedQuery,
|
|
sortBy: 'id',
|
|
sortOrder: 'asc',
|
|
);
|
|
|
|
final equipments =
|
|
paginated['equipments'] as List<Map<String, dynamic>>? ?? [];
|
|
return equipments.map(_normalizeAssistantEquipment).toList();
|
|
} catch (e) {
|
|
DebugLog.error('[DataService] Error in searchEquipmentsForAssistant', e);
|
|
throw Exception('Erreur lors de la recherche de matériel: $e');
|
|
}
|
|
}
|
|
|
|
/// Vérifie la disponibilité d'un équipement dans un format normalisé pour l'IA.
|
|
Future<Map<String, dynamic>> checkEquipmentAvailabilityForAssistant({
|
|
required String equipmentId,
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
String? excludeEventId,
|
|
}) async {
|
|
try {
|
|
final result = await checkEquipmentAvailability(
|
|
equipmentId: equipmentId,
|
|
startDate: startDate,
|
|
endDate: endDate,
|
|
excludeEventId: excludeEventId,
|
|
);
|
|
|
|
final available = result['available'] as bool? ?? true;
|
|
final conflicts = (result['conflicts'] as List<dynamic>? ?? const [])
|
|
.whereType<Map<String, dynamic>>()
|
|
.map((conflict) {
|
|
final eventData =
|
|
conflict['eventData'] as Map<String, dynamic>? ?? const {};
|
|
final eventName =
|
|
(eventData['Name'] ?? conflict['eventName'] ?? '').toString();
|
|
return {
|
|
'eventId': conflict['eventId']?.toString() ?? '',
|
|
'eventName': eventName,
|
|
'overlapDays': conflict['overlapDays'] as int? ?? 0,
|
|
};
|
|
}).toList();
|
|
|
|
return {
|
|
'equipmentId': equipmentId,
|
|
'available': available,
|
|
'conflictCount': conflicts.length,
|
|
'conflicts': conflicts,
|
|
};
|
|
} catch (e) {
|
|
DebugLog.error(
|
|
'[DataService] Error in checkEquipmentAvailabilityForAssistant', e);
|
|
throw Exception('Erreur lors de la vérification de disponibilité: $e');
|
|
}
|
|
}
|
|
|
|
/// Retourne des événements passés, idéalement filtrés par type d'événement.
|
|
Future<List<Map<String, dynamic>>> getPastEventsForAssistant({
|
|
String? eventTypeId,
|
|
int limit = 10,
|
|
}) async {
|
|
try {
|
|
final now = DateTime.now();
|
|
final events = eventTypeId != null && eventTypeId.isNotEmpty
|
|
? await getEventsByEventType(eventTypeId)
|
|
: (await getEvents())['events'] as List<Map<String, dynamic>>? ?? [];
|
|
|
|
final pastEvents = events.where((event) {
|
|
final endDate = _parseEventDate(event['EndDateTime']);
|
|
return endDate != null && endDate.isBefore(now);
|
|
}).toList();
|
|
|
|
pastEvents.sort((a, b) {
|
|
final aDate = _parseEventDate(a['StartDateTime']) ??
|
|
DateTime.fromMillisecondsSinceEpoch(0);
|
|
final bDate = _parseEventDate(b['StartDateTime']) ??
|
|
DateTime.fromMillisecondsSinceEpoch(0);
|
|
return bDate.compareTo(aDate);
|
|
});
|
|
|
|
return pastEvents.take(limit).map((event) {
|
|
final assignedEquipment =
|
|
event['assignedEquipment'] as List<dynamic>? ?? const [];
|
|
return {
|
|
'id': event['id']?.toString() ?? '',
|
|
'name': (event['Name'] ?? '').toString(),
|
|
'startDate': event['StartDateTime']?.toString() ?? '',
|
|
'endDate': event['EndDateTime']?.toString() ?? '',
|
|
'assignedEquipment': assignedEquipment,
|
|
'assignedEquipmentCount': assignedEquipment.length,
|
|
};
|
|
}).toList();
|
|
} catch (e) {
|
|
DebugLog.error('[DataService] Error in getPastEventsForAssistant', e);
|
|
throw Exception(
|
|
'Erreur lors de la récupération des événements passés: $e');
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> _normalizeAssistantEquipment(Map<String, dynamic> item) {
|
|
return {
|
|
'id': (item['id'] ?? '').toString(),
|
|
'name': (item['name'] ?? item['id'] ?? '').toString(),
|
|
'category': (item['category'] ?? '').toString(),
|
|
'status': (item['status'] ?? '').toString(),
|
|
'brand': item['brand']?.toString(),
|
|
'model': item['model']?.toString(),
|
|
'availableQuantity': item['availableQuantity'],
|
|
'totalQuantity': item['totalQuantity'],
|
|
};
|
|
}
|
|
|
|
DateTime? _parseEventDate(dynamic rawValue) {
|
|
if (rawValue is String) {
|
|
return DateTime.tryParse(rawValue);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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
|
|
// ============================================================================
|
|
|
|
/// Récupère toutes les maintenances
|
|
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');
|
|
}
|
|
}
|
|
|
|
/// 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');
|
|
}
|
|
}
|
|
|
|
/// Récupère les containers contenant un équipement
|
|
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: $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');
|
|
}
|
|
}
|
|
}
|