feat: Intégration d'un assistant IA logisticien basé sur Gemini
- 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.
This commit is contained in:
178
em2rp/lib/services/ai_equipment_assistant_service.dart
Normal file
178
em2rp/lib/services/ai_equipment_assistant_service.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:em2rp/models/event_model.dart';
|
||||
import 'package:em2rp/services/api_service.dart';
|
||||
import 'package:em2rp/utils/debug_log.dart';
|
||||
|
||||
/// Représente un tour de conversation dans le chat.
|
||||
class AiAssistantChatTurn {
|
||||
final bool isUser;
|
||||
final String text;
|
||||
|
||||
const AiAssistantChatTurn({required this.isUser, required this.text});
|
||||
}
|
||||
|
||||
/// Un item proposé par l'IA dans la liste de matériel.
|
||||
class AiEquipmentProposalItem {
|
||||
final String equipmentId;
|
||||
final int quantity;
|
||||
final String rationale;
|
||||
|
||||
const AiEquipmentProposalItem({
|
||||
required this.equipmentId,
|
||||
required this.quantity,
|
||||
required this.rationale,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proposition complète retournée par l'IA.
|
||||
class AiEquipmentProposal {
|
||||
final String summary;
|
||||
final List<AiEquipmentProposalItem> items;
|
||||
|
||||
/// Équipements individuels prêts à être injectés dans l'état local de l'événement.
|
||||
final List<EventEquipment> asEventEquipment;
|
||||
|
||||
/// IDs des containers (flight cases) proposés par l'IA.
|
||||
final List<String> containerIds;
|
||||
|
||||
const AiEquipmentProposal({
|
||||
required this.summary,
|
||||
required this.items,
|
||||
required this.asEventEquipment,
|
||||
required this.containerIds,
|
||||
});
|
||||
}
|
||||
|
||||
/// Réponse complète de l'assistant IA (message + proposition optionnelle).
|
||||
class AiEquipmentAssistantResponse {
|
||||
final String assistantMessage;
|
||||
final AiEquipmentProposal? proposal;
|
||||
|
||||
const AiEquipmentAssistantResponse({
|
||||
required this.assistantMessage,
|
||||
this.proposal,
|
||||
});
|
||||
}
|
||||
|
||||
/// Service assistant IA logisticien.
|
||||
/// Délègue tous les appels Gemini à la Cloud Function [aiEquipmentProposal].
|
||||
/// L'authentification Firebase (token Bearer) suffit — aucune clé API côté client.
|
||||
class AiEquipmentAssistantService {
|
||||
final ApiService _apiService;
|
||||
|
||||
AiEquipmentAssistantService({ApiService? apiService})
|
||||
: _apiService = apiService ?? FirebaseFunctionsApiService();
|
||||
|
||||
/// Envoie un message et retourne la réponse de l'assistant IA.
|
||||
Future<AiEquipmentAssistantResponse> generateProposal({
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
required List<AiAssistantChatTurn> history,
|
||||
required String userMessage,
|
||||
String? eventTypeId,
|
||||
String? excludeEventId,
|
||||
List<EventEquipment> currentAssignedEquipment = const [],
|
||||
List<EventEquipment> workingProposalEquipment = const [],
|
||||
}) async {
|
||||
final payload = <String, dynamic>{
|
||||
'startDate': startDate.toIso8601String(),
|
||||
'endDate': endDate.toIso8601String(),
|
||||
'userMessage': userMessage.trim(),
|
||||
'history': history
|
||||
.where((turn) => turn.text.trim().isNotEmpty)
|
||||
.map((turn) => {'isUser': turn.isUser, 'text': turn.text.trim()})
|
||||
.toList(),
|
||||
'currentEquipment': currentAssignedEquipment
|
||||
.map((eq) => {'equipmentId': eq.equipmentId, 'quantity': eq.quantity})
|
||||
.toList(),
|
||||
'workingProposal': workingProposalEquipment
|
||||
.map((eq) => {'equipmentId': eq.equipmentId, 'quantity': eq.quantity})
|
||||
.toList(),
|
||||
};
|
||||
|
||||
if (eventTypeId != null) payload['eventTypeId'] = eventTypeId;
|
||||
if (excludeEventId != null) payload['excludeEventId'] = excludeEventId;
|
||||
|
||||
try {
|
||||
DebugLog.info('[AiEquipmentAssistantService] Calling aiEquipmentProposal Cloud Function');
|
||||
|
||||
final result = await _apiService.call('aiEquipmentProposal', payload);
|
||||
final assistantMessage = result['assistantMessage']?.toString().trim() ?? '';
|
||||
final proposal = _parseProposal(result['proposal']);
|
||||
|
||||
DebugLog.info(
|
||||
'[AiEquipmentAssistantService] Response received, items: ${proposal?.items.length ?? 0}',
|
||||
);
|
||||
|
||||
return AiEquipmentAssistantResponse(
|
||||
assistantMessage: assistantMessage.isNotEmpty
|
||||
? assistantMessage
|
||||
: 'Je n\'ai pas pu générer de réponse.',
|
||||
proposal: proposal,
|
||||
);
|
||||
} on ApiException catch (e) {
|
||||
DebugLog.error('[AiEquipmentAssistantService] API error', e);
|
||||
if (e.isUnauthorized) {
|
||||
throw Exception('Vous n\'êtes pas authentifié. Reconnectez-vous et réessayez.');
|
||||
}
|
||||
throw Exception('Erreur du service IA (${e.statusCode}): ${e.message}');
|
||||
} catch (e) {
|
||||
DebugLog.error('[AiEquipmentAssistantService] Error', e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
AiEquipmentProposal? _parseProposal(dynamic rawProposal) {
|
||||
if (rawProposal == null || rawProposal is! Map<String, dynamic>) return null;
|
||||
|
||||
final proposalItems = <AiEquipmentProposalItem>[];
|
||||
final eventEquipmentList = <EventEquipment>[];
|
||||
final containerIds = <String>[];
|
||||
|
||||
final rawItems = rawProposal['items'];
|
||||
if (rawItems is List) {
|
||||
for (final rawItem in rawItems) {
|
||||
if (rawItem is! Map) continue;
|
||||
final item = Map<String, dynamic>.from(rawItem);
|
||||
|
||||
final equipmentId = item['equipmentId']?.toString().trim() ?? '';
|
||||
final quantity = int.tryParse(item['quantity']?.toString() ?? '1') ?? 1;
|
||||
|
||||
if (equipmentId.isEmpty || quantity <= 0) continue;
|
||||
|
||||
final rationale = item['rationale']?.toString().trim() ?? 'Proposition IA';
|
||||
|
||||
proposalItems.add(AiEquipmentProposalItem(
|
||||
equipmentId: equipmentId,
|
||||
quantity: quantity,
|
||||
rationale: rationale,
|
||||
));
|
||||
eventEquipmentList.add(EventEquipment(equipmentId: equipmentId, quantity: quantity));
|
||||
}
|
||||
}
|
||||
|
||||
final rawContainers = rawProposal['containers'];
|
||||
if (rawContainers is List) {
|
||||
for (final rawContainer in rawContainers) {
|
||||
if (rawContainer is! Map) continue;
|
||||
final container = Map<String, dynamic>.from(rawContainer);
|
||||
final containerId = container['containerId']?.toString().trim() ?? '';
|
||||
if (containerId.isNotEmpty) {
|
||||
containerIds.add(containerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proposalItems.isEmpty && containerIds.isEmpty) return null;
|
||||
|
||||
return AiEquipmentProposal(
|
||||
summary: rawProposal['summary']?.toString().trim().isNotEmpty == true
|
||||
? rawProposal['summary'].toString().trim()
|
||||
: 'Proposition matériel générée automatiquement.',
|
||||
items: proposalItems,
|
||||
asEventEquipment: eventEquipmentList,
|
||||
containerIds: containerIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,8 @@ class DataService {
|
||||
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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération des types d\'événements: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,15 +56,18 @@ class DataService {
|
||||
try {
|
||||
final data = <String, dynamic>{'eventId': eventId};
|
||||
|
||||
if (assignedEquipment != null) data['assignedEquipment'] = assignedEquipment;
|
||||
if (preparationStatus != null) data['preparationStatus'] = preparationStatus;
|
||||
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');
|
||||
throw Exception(
|
||||
'Erreur lors de la mise à jour des équipements de l\'événement: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +81,13 @@ class DataService {
|
||||
final data = <String, dynamic>{'equipmentId': equipmentId};
|
||||
|
||||
if (status != null) data['status'] = status;
|
||||
if (availableQuantity != null) data['availableQuantity'] = availableQuantity;
|
||||
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');
|
||||
throw Exception(
|
||||
'Erreur lors de la mise à jour du statut de l\'équipement: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +112,8 @@ class DataService {
|
||||
}
|
||||
|
||||
/// Crée un équipement
|
||||
Future<void> createEquipment(String equipmentId, Map<String, dynamic> data) async {
|
||||
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);
|
||||
@@ -119,7 +126,8 @@ class DataService {
|
||||
}
|
||||
|
||||
/// Met à jour un équipement
|
||||
Future<void> updateEquipment(String equipmentId, Map<String, dynamic> data) async {
|
||||
Future<void> updateEquipment(
|
||||
String equipmentId, Map<String, dynamic> data) async {
|
||||
try {
|
||||
await _apiService.call('updateEquipment', {
|
||||
'equipmentId': equipmentId,
|
||||
@@ -140,9 +148,11 @@ class DataService {
|
||||
}
|
||||
|
||||
/// Récupère les événements utilisant un type d'événement donné
|
||||
Future<List<Map<String, dynamic>>> getEventsByEventType(String eventTypeId) async {
|
||||
Future<List<Map<String, dynamic>>> getEventsByEventType(
|
||||
String eventTypeId) async {
|
||||
try {
|
||||
final result = await _apiService.call('getEventsByEventType', {'eventTypeId': eventTypeId});
|
||||
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();
|
||||
@@ -271,7 +281,8 @@ class DataService {
|
||||
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');
|
||||
print(
|
||||
'[DataService] Events loaded for $year-$month: ${events.length} events');
|
||||
|
||||
return {
|
||||
'events': events.map((e) => e as Map<String, dynamic>).toList(),
|
||||
@@ -279,7 +290,8 @@ class DataService {
|
||||
};
|
||||
} 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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération des événements du mois: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +311,8 @@ class DataService {
|
||||
throw Exception('Event not found');
|
||||
}
|
||||
|
||||
print('[DataService] Event loaded with ${equipments.length} equipments and ${containers.length} containers');
|
||||
print(
|
||||
'[DataService] Event loaded with ${equipments.length} equipments and ${containers.length} containers');
|
||||
|
||||
return {
|
||||
'event': event,
|
||||
@@ -308,7 +321,8 @@ class DataService {
|
||||
};
|
||||
} 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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération de l\'événement avec détails: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,11 +346,13 @@ class DataService {
|
||||
}
|
||||
|
||||
/// Récupère plusieurs équipements par leurs IDs
|
||||
Future<List<Map<String, dynamic>>> getEquipmentsByIds(List<String> equipmentIds) async {
|
||||
Future<List<Map<String, dynamic>>> getEquipmentsByIds(
|
||||
List<String> equipmentIds) async {
|
||||
try {
|
||||
if (equipmentIds.isEmpty) return [];
|
||||
|
||||
print('[DataService] Getting equipments by IDs: ${equipmentIds.length} items');
|
||||
print(
|
||||
'[DataService] Getting equipments by IDs: ${equipmentIds.length} items');
|
||||
final result = await _apiService.call('getEquipmentsByIds', {
|
||||
'equipmentIds': equipmentIds,
|
||||
});
|
||||
@@ -366,11 +382,13 @@ class DataService {
|
||||
}
|
||||
|
||||
/// Récupère plusieurs containers par leurs IDs
|
||||
Future<List<Map<String, dynamic>>> getContainersByIds(List<String> containerIds) async {
|
||||
Future<List<Map<String, dynamic>>> getContainersByIds(
|
||||
List<String> containerIds) async {
|
||||
try {
|
||||
if (containerIds.isEmpty) return [];
|
||||
|
||||
print('[DataService] Getting containers by IDs: ${containerIds.length} items');
|
||||
print(
|
||||
'[DataService] Getting containers by IDs: ${containerIds.length} items');
|
||||
final result = await _apiService.call('getContainersByIds', {
|
||||
'containerIds': containerIds,
|
||||
});
|
||||
@@ -415,22 +433,25 @@ class DataService {
|
||||
params['searchQuery'] = searchQuery;
|
||||
}
|
||||
|
||||
final result = await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
||||
final result =
|
||||
await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
||||
'getEquipmentsPaginated',
|
||||
params,
|
||||
);
|
||||
|
||||
return {
|
||||
'equipments': (result['equipments'] as List<dynamic>?)
|
||||
?.map((e) => e as Map<String, dynamic>)
|
||||
.toList() ?? [],
|
||||
?.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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération paginée des équipements: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,22 +481,25 @@ class DataService {
|
||||
params['searchQuery'] = searchQuery;
|
||||
}
|
||||
|
||||
final result = await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
||||
final result =
|
||||
await (_apiService as FirebaseFunctionsApiService).callPaginated(
|
||||
'getContainersPaginated',
|
||||
params,
|
||||
);
|
||||
|
||||
return {
|
||||
'containers': (result['containers'] as List<dynamic>?)
|
||||
?.map((e) => e as Map<String, dynamic>)
|
||||
.toList() ?? [],
|
||||
?.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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération paginée des containers: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +523,156 @@ class DataService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
// ============================================================================
|
||||
@@ -512,7 +686,8 @@ class DataService {
|
||||
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');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération de l\'utilisateur actuel: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +768,8 @@ class DataService {
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
throw Exception('Erreur lors de la récupération des équipements en conflit: $e');
|
||||
throw Exception(
|
||||
'Erreur lors de la récupération des équipements en conflit: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +778,8 @@ class DataService {
|
||||
// ============================================================================
|
||||
|
||||
/// Récupère toutes les maintenances
|
||||
Future<List<Map<String, dynamic>>> getMaintenances({String? equipmentId}) async {
|
||||
Future<List<Map<String, dynamic>>> getMaintenances(
|
||||
{String? equipmentId}) async {
|
||||
try {
|
||||
final data = <String, dynamic>{};
|
||||
if (equipmentId != null) data['equipmentId'] = equipmentId;
|
||||
@@ -619,14 +796,16 @@ class DataService {
|
||||
/// Supprime une maintenance
|
||||
Future<void> deleteMaintenance(String maintenanceId) async {
|
||||
try {
|
||||
await _apiService.call('deleteMaintenance', {'maintenanceId': maintenanceId});
|
||||
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 {
|
||||
Future<List<Map<String, dynamic>>> getContainersByEquipment(
|
||||
String equipmentId) async {
|
||||
try {
|
||||
final result = await _apiService.call('getContainersByEquipment', {
|
||||
'equipmentId': equipmentId,
|
||||
|
||||
Reference in New Issue
Block a user