268 lines
7.9 KiB
Dart
268 lines
7.9 KiB
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
|
|
static Future<String> generateIcsContent(EventModel event) async {
|
|
final now = DateTime.now().toUtc();
|
|
final timestamp = DateFormat('yyyyMMddTHHmmss').format(now) + 'Z';
|
|
|
|
// Récupérer les informations supplémentaires
|
|
final eventTypeName = await _getEventTypeName(event.eventTypeId);
|
|
final workforce = await _getWorkforceDetails(event.workforce);
|
|
final optionsWithNames = await _getOptionsDetails(event.options);
|
|
|
|
// 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, eventTypeName, 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(eventTypeName)}
|
|
END:VEVENT
|
|
END:VCALENDAR''';
|
|
|
|
return icsContent;
|
|
}
|
|
|
|
/// Récupère le nom du type d'événement
|
|
static Future<String> _getEventTypeName(String eventTypeId) async {
|
|
if (eventTypeId.isEmpty) return 'Non spécifié';
|
|
|
|
try {
|
|
final doc = await FirebaseFirestore.instance
|
|
.collection('eventTypes')
|
|
.doc(eventTypeId)
|
|
.get();
|
|
|
|
if (doc.exists) {
|
|
return doc.data()?['name'] as String? ?? eventTypeId;
|
|
}
|
|
} catch (e) {
|
|
print('Erreur lors de la récupération du type d\'événement: $e');
|
|
}
|
|
|
|
return eventTypeId;
|
|
}
|
|
|
|
/// Récupère les détails de la main d'œuvre
|
|
static Future<List<String>> _getWorkforceDetails(List<DocumentReference> workforce) async {
|
|
final List<String> workforceNames = [];
|
|
|
|
for (final ref in workforce) {
|
|
try {
|
|
final doc = await ref.get();
|
|
if (doc.exists) {
|
|
final data = doc.data() as Map<String, dynamic>?;
|
|
if (data != null) {
|
|
final firstName = data['firstName'] ?? '';
|
|
final lastName = data['lastName'] ?? '';
|
|
if (firstName.isNotEmpty || lastName.isNotEmpty) {
|
|
workforceNames.add('$firstName $lastName'.trim());
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Erreur lors de la récupération des détails utilisateur: $e');
|
|
}
|
|
}
|
|
|
|
return workforceNames;
|
|
}
|
|
|
|
/// Récupère les détails des options
|
|
static Future<List<Map<String, dynamic>>> _getOptionsDetails(List<Map<String, dynamic>> options) async {
|
|
final List<Map<String, dynamic>> optionsWithNames = [];
|
|
|
|
for (final option in options) {
|
|
try {
|
|
final optionId = option['id'] ?? option['optionId'];
|
|
if (optionId == null || optionId.toString().isEmpty) {
|
|
// Si pas d'ID, garder le nom tel quel
|
|
optionsWithNames.add({
|
|
'name': option['name'] ?? 'Option inconnue',
|
|
'quantity': option['quantity'],
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Récupérer le nom depuis Firestore
|
|
final doc = await FirebaseFirestore.instance
|
|
.collection('options')
|
|
.doc(optionId.toString())
|
|
.get();
|
|
|
|
if (doc.exists) {
|
|
final data = doc.data();
|
|
optionsWithNames.add({
|
|
'name': data?['name'] ?? option['name'] ?? 'Option inconnue',
|
|
'quantity': option['quantity'],
|
|
});
|
|
} else {
|
|
// Document n'existe pas, garder le nom de l'option
|
|
optionsWithNames.add({
|
|
'name': option['name'] ?? 'Option inconnue',
|
|
'quantity': option['quantity'],
|
|
});
|
|
}
|
|
} catch (e) {
|
|
print('Erreur lors de la récupération des détails option: $e');
|
|
optionsWithNames.add({
|
|
'name': option['name'] ?? 'Option inconnue',
|
|
'quantity': option['quantity'],
|
|
});
|
|
}
|
|
}
|
|
|
|
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éré par EM2RP Event Manager');
|
|
|
|
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';
|
|
}
|
|
}
|
|
|