268 lines
7.9 KiB
JavaScript
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 };
|