Files
EM2_ERP/em2rp/lib/services/ics_export_service.dart

276 lines
8.7 KiB
Dart

import 'package:em2rp/config/app_version.dart';
import 'package:em2rp/models/event_model.dart';
import 'package:intl/intl.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class IcsExportService {
/// Génère un fichier ICS à partir d'un événement
///
/// [eventTypeName] : Nom du type d'événement (optionnel, sera résolu si non fourni)
/// [userNames] : Map des IDs utilisateurs vers leurs noms complets (optionnel)
/// [optionNames] : Map des IDs options vers leurs noms (optionnel)
static Future<String> generateIcsContent(
EventModel event, {
String? eventTypeName,
Map<String, String>? userNames,
Map<String, String>? optionNames,
}) async {
final now = DateTime.now().toUtc();
final timestamp = '${DateFormat('yyyyMMddTHHmmss').format(now)}Z';
// Récupérer les informations supplémentaires
final resolvedEventTypeName = eventTypeName ?? await _getEventTypeName(event.eventTypeId);
final workforce = await _getWorkforceDetails(event.workforce, userNames: userNames);
final optionsWithNames = await _getOptionsDetails(event.options, optionNames: optionNames);
// Formater les dates au format ICS (UTC)
final startDate = _formatDateForIcs(event.startDateTime);
final endDate = _formatDateForIcs(event.endDateTime);
// Construire la description détaillée
final description = _buildDescription(event, resolvedEventTypeName, workforce, optionsWithNames);
// Générer un UID unique basé sur l'ID de l'événement
final uid = 'em2rp-${event.id}@em2rp.app';
// Construire le contenu ICS
final icsContent = '''BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//EM2RP//Event Manager//FR
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:$uid
DTSTAMP:$timestamp
DTSTART:$startDate
DTEND:$endDate
SUMMARY:${_escapeIcsText(event.name)}
DESCRIPTION:${_escapeIcsText(description)}
LOCATION:${_escapeIcsText(event.address)}
STATUS:${_getEventStatus(event.status)}
CATEGORIES:${_escapeIcsText(resolvedEventTypeName)}
END:VEVENT
END:VCALENDAR''';
return icsContent;
}
/// Récupère le nom du type d'événement depuis EventModel (déjà chargé)
/// Note: Les eventTypes sont maintenant chargés via Cloud Function dans l'EventModel
static Future<String> _getEventTypeName(String eventTypeId) async {
if (eventTypeId.isEmpty) return 'Non spécifié';
// Les eventTypes sont publics et déjà chargés dans l'app via Cloud Function
// On retourne simplement l'ID, le nom sera résolu par l'app
return eventTypeId;
}
/// Récupère les détails de la main d'œuvre
/// Si userNames est fourni, utilise les noms déjà résolus pour de meilleures performances
static Future<List<String>> _getWorkforceDetails(
List<dynamic> workforce, {
Map<String, String>? userNames,
}) async {
final List<String> workforceNames = [];
for (final ref in workforce) {
try {
// Si c'est déjà une Map avec les données, l'utiliser directement
if (ref is Map<String, dynamic>) {
final firstName = ref['firstName'] ?? '';
final lastName = ref['lastName'] ?? '';
if (firstName.isNotEmpty || lastName.isNotEmpty) {
workforceNames.add('$firstName $lastName'.trim());
}
continue;
}
// Si c'est un String (UID) et qu'on a les noms résolus, les utiliser
if (ref is String) {
if (userNames != null && userNames.containsKey(ref)) {
workforceNames.add(userNames[ref]!);
} else {
workforceNames.add('Utilisateur $ref');
}
continue;
}
// Si c'est une DocumentReference
if (ref is DocumentReference) {
final userId = ref.id;
if (userNames != null && userNames.containsKey(userId)) {
workforceNames.add(userNames[userId]!);
} else {
workforceNames.add('Utilisateur $userId');
}
}
} catch (e) {
print('Erreur lors du traitement des détails utilisateur: $e');
}
}
return workforceNames;
}
/// Récupère les détails des options
/// Si optionNames est fourni, utilise les noms déjà résolus
static Future<List<Map<String, dynamic>>> _getOptionsDetails(
List<Map<String, dynamic>> options, {
Map<String, String>? optionNames,
}) async {
final List<Map<String, dynamic>> optionsWithNames = [];
for (final option in options) {
try {
String optionName = option['name'] ?? 'Option inconnue';
// Si on a l'ID de l'option et les noms résolus, utiliser le nom résolu
final optionId = option['id'] ?? option['optionId'];
if (optionId != null && optionNames != null && optionNames.containsKey(optionId)) {
optionName = optionNames[optionId]!;
} else if (optionName == 'Option inconnue' && optionId != null) {
optionName = 'Option $optionId';
}
optionsWithNames.add({
'name': optionName,
'quantity': option['quantity'],
'price': option['price'],
});
} catch (e) {
print('Erreur lors du traitement des options: $e');
}
}
return optionsWithNames;
}
/// Construit la description détaillée de l'événement
static String _buildDescription(
EventModel event,
String eventTypeName,
List<String> workforce,
List<Map<String, dynamic>> optionsWithNames,
) {
final buffer = StringBuffer();
// Type d'événement
buffer.writeln('TYPE: $eventTypeName');
buffer.writeln('');
// Description
if (event.description.isNotEmpty) {
buffer.writeln('DESCRIPTION:');
buffer.writeln(event.description);
buffer.writeln('');
}
// Jauge
if (event.jauge != null) {
buffer.writeln('JAUGE: ${event.jauge} personnes');
}
// Contact email
if (event.contactEmail != null && event.contactEmail!.isNotEmpty) {
buffer.writeln('EMAIL DE CONTACT: ${event.contactEmail}');
}
// Contact téléphone
if (event.contactPhone != null && event.contactPhone!.isNotEmpty) {
buffer.writeln('TÉLÉPHONE DE CONTACT: ${event.contactPhone}');
}
// Adresse
if (event.address.isNotEmpty) {
buffer.writeln('');
buffer.writeln('ADRESSE: ${event.address}');
}
// Temps d'installation et démontage
if (event.installationTime > 0 || event.disassemblyTime > 0) {
buffer.writeln('');
if (event.installationTime > 0) {
buffer.writeln('INSTALLATION: ${event.installationTime}h');
}
if (event.disassemblyTime > 0) {
buffer.writeln('DÉMONTAGE: ${event.disassemblyTime}h');
}
}
// Main d'œuvre
if (workforce.isNotEmpty) {
buffer.writeln('');
buffer.writeln('MAIN D\'ŒUVRE:');
for (final name in workforce) {
buffer.writeln(' - $name');
}
}
// Options
if (optionsWithNames.isNotEmpty) {
buffer.writeln('');
buffer.writeln('OPTIONS:');
for (final option in optionsWithNames) {
final optionName = option['name'] ?? 'Option inconnue';
final quantity = option['quantity'];
if (quantity != null && quantity > 1) {
buffer.writeln(' - $optionName (x$quantity)');
} else {
buffer.writeln(' - $optionName');
}
}
}
// Prix
if (event.basePrice > 0) {
buffer.writeln('');
buffer.writeln('PRIX DE BASE: ${event.basePrice.toStringAsFixed(2)}');
}
// Lien vers l'application
buffer.writeln('');
buffer.writeln('---');
buffer.writeln('Généré par EM2 Hub ${AppVersion.fullVersion} http://app.em2events.fr');
return buffer.toString();
}
/// Formate une date au format ICS (yyyyMMddTHHmmssZ)
static String _formatDateForIcs(DateTime dateTime) {
final utcDate = dateTime.toUtc();
return '${DateFormat('yyyyMMddTHHmmss').format(utcDate)}Z';
}
/// Échappe les caractères spéciaux pour le format ICS
static String _escapeIcsText(String text) {
return text
.replaceAll('\\', '\\\\')
.replaceAll(',', '\\,')
.replaceAll(';', '\\;')
.replaceAll('\n', '\\n')
.replaceAll('\r', '');
}
/// Convertit le statut de l'événement en statut ICS
static String _getEventStatus(EventStatus status) {
switch (status) {
case EventStatus.confirmed:
return 'CONFIRMED';
case EventStatus.canceled:
return 'CANCELLED';
case EventStatus.waitingForApproval:
return 'TENTATIVE';
}
}
/// Génère le nom du fichier ICS
static String generateFileName(EventModel event) {
final safeName = event.name
.replaceAll(RegExp(r'[^\w\s-]'), '')
.replaceAll(RegExp(r'\s+'), '_');
final dateStr = DateFormat('yyyyMMdd').format(event.startDateTime);
return 'event_${safeName}_$dateStr.ics';
}
}