xpeditis2.0/scripts/convert-rates-csv.js
2025-10-29 21:18:38 +01:00

268 lines
7.9 KiB
JavaScript

#!/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 };