#!/usr/bin/env node /** * Script de conversion CSV pour les tarifs maritimes * Convertit le format "Frais FOB FRET" vers le format d'import Xpeditis */ const fs = require('fs'); const path = require('path'); // Configuration const INPUT_FILE = process.argv[2] || path.join(__dirname, '../Frais FOB FRET.csv'); const OUTPUT_FILE = process.argv[3] || path.join(__dirname, '../rates-import-ready.csv'); const COMPANY_NAME = process.argv[4] || 'Consolidation Maritime'; // Headers attendus par l'API const OUTPUT_HEADERS = [ 'companyName', 'origin', 'destination', 'containerType', 'minVolumeCBM', 'maxVolumeCBM', 'minWeightKG', 'maxWeightKG', 'palletCount', 'pricePerCBM', 'pricePerKG', 'basePriceUSD', 'basePriceEUR', 'currency', 'hasSurcharges', 'surchargeBAF', 'surchargeCAF', 'surchargeDetails', 'transitDays', 'validFrom', 'validUntil' ]; /** * Parse CSV line handling quoted fields */ function parseCSVLine(line) { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { result.push(current.trim()); current = ''; } else { current += char; } } result.push(current.trim()); return result; } /** * Calcule les frais supplémentaires à partir des colonnes FOB */ function calculateSurcharges(row) { const surcharges = []; // Documentation (LS et Minimum) if (row['Documentation (LS et Minimum)']) { surcharges.push(`DOC:${row['Documentation (LS et Minimum)']}`); } // ISPS (LS et Minimum) if (row['ISPS (LS et Minimum)']) { surcharges.push(`ISPS:${row['ISPS (LS et Minimum)']}`); } // Manutention if (row['Manutention']) { surcharges.push(`HANDLING:${row['Manutention']} ${row['Unité de manutention (UP;Tonne)'] || 'UP'}`); } // SOLAS if (row['Solas (LS et Minimum)']) { surcharges.push(`SOLAS:${row['Solas (LS et Minimum)']}`); } // Douane if (row['Douane (LS et Minimum)']) { surcharges.push(`CUSTOMS:${row['Douane (LS et Minimum)']}`); } // AMS/ACI if (row['AMS/ACI (LS et Minimum)']) { surcharges.push(`AMS_ACI:${row['AMS/ACI (LS et Minimum)']}`); } // ISF5 if (row['ISF5 (LS et Minimum)']) { surcharges.push(`ISF5:${row['ISF5 (LS et Minimum)']}`); } // Frais admin dangereux if (row['Frais admin de dangereux (LS et Minimum)']) { surcharges.push(`DG_FEE:${row['Frais admin de dangereux (LS et Minimum)']}`); } return surcharges.join(' | '); } /** * Conversion d'une ligne du CSV source vers le format de destination */ function convertRow(sourceRow) { const currency = sourceRow['Devise FRET'] || 'USD'; const freightRate = parseFloat(sourceRow['Taux de FRET (UP)']) || 0; const minFreight = parseFloat(sourceRow['Minimum FRET (LS)']) || 0; const transitDays = parseInt(sourceRow['Transit time']) || 0; // Calcul des surcharges const surchargeDetails = calculateSurcharges(sourceRow); const hasSurcharges = surchargeDetails.length > 0; // Dates de validité (par défaut: 90 jours à partir d'aujourd'hui) const validFrom = new Date().toISOString().split('T')[0]; const validUntil = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // Estimation volume min/max (1-20 CBM pour LCL standard) const minVolumeCBM = 1; const maxVolumeCBM = 20; // Estimation poids min/max (100-20000 KG) const minWeightKG = 100; const maxWeightKG = 20000; // Prix par CBM (basé sur le taux de fret) const pricePerCBM = freightRate > 0 ? freightRate : minFreight; // Prix par KG (estimation: prix CBM / 200 kg/m³) const pricePerKG = pricePerCBM > 0 ? (pricePerCBM / 200).toFixed(2) : 0; return { companyName: COMPANY_NAME, origin: sourceRow['Origine UN code'] || '', destination: sourceRow['Destination UN code'] || '', containerType: 'LCL', minVolumeCBM: minVolumeCBM, maxVolumeCBM: maxVolumeCBM, minWeightKG: minWeightKG, maxWeightKG: maxWeightKG, palletCount: 0, pricePerCBM: pricePerCBM, pricePerKG: pricePerKG, basePriceUSD: currency === 'USD' ? pricePerCBM : 0, basePriceEUR: currency === 'EUR' ? pricePerCBM : 0, currency: currency, hasSurcharges: hasSurcharges, surchargeBAF: '', // BAF non spécifié dans le CSV source surchargeCAF: '', // CAF non spécifié dans le CSV source surchargeDetails: surchargeDetails, transitDays: transitDays, validFrom: validFrom, validUntil: validUntil }; } /** * Main conversion function */ async function convertCSV() { console.log('🔄 Conversion CSV - Tarifs Maritimes\n'); console.log(`📥 Fichier source: ${INPUT_FILE}`); console.log(`📤 Fichier destination: ${OUTPUT_FILE}`); console.log(`🏢 Compagnie: ${COMPANY_NAME}\n`); try { // Lecture du fichier source if (!fs.existsSync(INPUT_FILE)) { throw new Error(`Fichier source introuvable: ${INPUT_FILE}`); } const fileContent = fs.readFileSync(INPUT_FILE, 'utf-8'); const lines = fileContent.split('\n').filter(line => line.trim()); console.log(`📊 ${lines.length} lignes trouvées\n`); // Parse headers const headers = parseCSVLine(lines[0]); console.log(`📋 Colonnes source détectées: ${headers.length}`); // Parse data rows const dataRows = []; for (let i = 1; i < lines.length; i++) { const values = parseCSVLine(lines[i]); // Créer un objet avec les headers comme clés const row = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); // Vérifier que la ligne a des données valides if (row['Origine UN code'] && row['Destination UN code']) { dataRows.push(row); } } console.log(`✅ ${dataRows.length} lignes valides à convertir\n`); // Conversion des lignes const convertedRows = dataRows.map(convertRow); // Génération du CSV de sortie const outputLines = [OUTPUT_HEADERS.join(',')]; convertedRows.forEach(row => { const values = OUTPUT_HEADERS.map(header => { const value = row[header]; // Échapper les virgules et quotes if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) { return `"${value.replace(/"/g, '""')}"`; } return value; }); outputLines.push(values.join(',')); }); // Écriture du fichier fs.writeFileSync(OUTPUT_FILE, outputLines.join('\n'), 'utf-8'); console.log('✅ Conversion terminée avec succès!\n'); console.log('📊 Statistiques:'); console.log(` - Lignes converties: ${convertedRows.length}`); console.log(` - Origines uniques: ${new Set(convertedRows.map(r => r.origin)).size}`); console.log(` - Destinations uniques: ${new Set(convertedRows.map(r => r.destination)).size}`); console.log(` - Devises: ${new Set(convertedRows.map(r => r.currency)).size} (${[...new Set(convertedRows.map(r => r.currency))].join(', ')})`); // Exemples de tarifs console.log('\n📦 Exemples de tarifs convertis:'); convertedRows.slice(0, 3).forEach((row, idx) => { console.log(` ${idx + 1}. ${row.origin} → ${row.destination}`); console.log(` Prix: ${row.pricePerCBM} ${row.currency}/CBM (${row.pricePerKG} ${row.currency}/KG)`); console.log(` Transit: ${row.transitDays} jours`); }); console.log(`\n🎯 Fichier prêt à importer: ${OUTPUT_FILE}`); console.log('\n📝 Commande d\'import:'); console.log(` curl -X POST http://localhost:4000/api/v1/admin/rates/csv/upload \\`); console.log(` -H "Authorization: Bearer $TOKEN" \\`); console.log(` -F "file=@${OUTPUT_FILE}"`); } catch (error) { console.error('❌ Erreur lors de la conversion:', error.message); process.exit(1); } } // Exécution if (require.main === module) { convertCSV(); } module.exports = { convertCSV };