502 lines
16 KiB
JavaScript
502 lines
16 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 🗃️ NETGESCON MDB IMPORTER
|
||
* Script Node.js per import dati da file stabili.mdb
|
||
* Supporta connessione diretta via ODBC e fallback mdb-tools
|
||
*/
|
||
|
||
const { exec } = require('child_process');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const axios = require('axios');
|
||
|
||
class MDBImporter {
|
||
constructor(config) {
|
||
this.config = {
|
||
mdbFile: config.mdbFile,
|
||
apiBaseUrl: config.apiBaseUrl || 'http://localhost:8000/api/v1',
|
||
apiToken: config.apiToken,
|
||
batchSize: config.batchSize || 50,
|
||
validateOnly: config.validateOnly || false,
|
||
verbose: config.verbose || false,
|
||
...config
|
||
};
|
||
|
||
this.results = {
|
||
processed: 0,
|
||
imported: 0,
|
||
errors: 0,
|
||
duplicates: 0,
|
||
details: []
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 🚀 Avvia processo import completo
|
||
*/
|
||
async run() {
|
||
try {
|
||
console.log('🗃️ NETGESCON MDB IMPORTER');
|
||
console.log(`📁 File MDB: ${this.config.mdbFile}`);
|
||
console.log(`🔗 API Base: ${this.config.apiBaseUrl}`);
|
||
console.log(`📦 Batch Size: ${this.config.batchSize}`);
|
||
|
||
if (this.config.validateOnly) {
|
||
console.log('🔍 MODALITÀ VALIDAZIONE - Nessun dato verrà importato');
|
||
}
|
||
|
||
// 1. Verifica prerequisiti
|
||
await this.checkPrerequisites();
|
||
|
||
// 2. Test connessione MDB
|
||
await this.testMdbConnection();
|
||
|
||
// 3. Leggi dati da MDB
|
||
const stabiliData = await this.readStabiliFromMDB();
|
||
|
||
console.log(`📊 Record trovati: ${stabiliData.length}`);
|
||
|
||
if (stabiliData.length === 0) {
|
||
console.log('⚠️ Nessun record trovato nella tabella Stabili');
|
||
return this.results;
|
||
}
|
||
|
||
// 4. Processa in batch
|
||
await this.processInBatches(stabiliData);
|
||
|
||
// 5. Mostra risultati finali
|
||
this.showFinalResults();
|
||
|
||
return this.results;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Errore critico:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 🔍 Verifica prerequisiti sistema
|
||
*/
|
||
async checkPrerequisites() {
|
||
console.log('\n🔍 Verifica prerequisiti...');
|
||
|
||
// Verifica file MDB
|
||
if (!fs.existsSync(this.config.mdbFile)) {
|
||
throw new Error(`File MDB non trovato: ${this.config.mdbFile}`);
|
||
}
|
||
|
||
// Verifica mdb-tools
|
||
try {
|
||
await this.execCommand('mdb-tools --version');
|
||
console.log('✅ mdb-tools disponibile');
|
||
} catch (error) {
|
||
console.log('⚠️ mdb-tools non disponibile, verifico alternative...');
|
||
|
||
// Qui potresti aggiungere controlli per altri metodi di connessione
|
||
// come node-adodb per Windows o altri driver ODBC
|
||
}
|
||
|
||
// Test API se non in modalità validate-only
|
||
if (!this.config.validateOnly) {
|
||
await this.testApiConnection();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 🔗 Test connessione API NetGescon
|
||
*/
|
||
async testApiConnection() {
|
||
try {
|
||
console.log('🔗 Test connessione API...');
|
||
|
||
const response = await axios.get(`${this.config.apiBaseUrl}/auth/me`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.config.apiToken}`,
|
||
'Accept': 'application/json'
|
||
},
|
||
timeout: 10000
|
||
});
|
||
|
||
if (response.data.success) {
|
||
console.log('✅ API connessa - Utente:', response.data.data.user.name);
|
||
} else {
|
||
throw new Error('Risposta API non valida');
|
||
}
|
||
|
||
} catch (error) {
|
||
throw new Error(`Connessione API fallita: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 🗃️ Test connessione database MDB
|
||
*/
|
||
async testMdbConnection() {
|
||
console.log('\n🗃️ Test connessione MDB...');
|
||
|
||
try {
|
||
// Test lettura tabelle
|
||
const tables = await this.execCommand(`mdb-tables -1 "${this.config.mdbFile}"`);
|
||
|
||
if (!tables.includes('Stabili')) {
|
||
throw new Error('Tabella Stabili non trovata nel database MDB');
|
||
}
|
||
|
||
console.log('✅ Tabella Stabili trovata');
|
||
|
||
// Test conteggio record
|
||
const output = await this.execCommand(`mdb-export "${this.config.mdbFile}" Stabili | wc -l`);
|
||
const recordCount = parseInt(output) - 1; // -1 per header
|
||
|
||
console.log(`📊 Record nella tabella: ${recordCount}`);
|
||
|
||
} catch (error) {
|
||
throw new Error(`Test MDB fallito: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 📖 Legge dati dalla tabella Stabili
|
||
*/
|
||
async readStabiliFromMDB() {
|
||
console.log('\n📖 Lettura dati da MDB...');
|
||
|
||
try {
|
||
const csvOutput = await this.execCommand(`mdb-export "${this.config.mdbFile}" Stabili`);
|
||
|
||
const lines = csvOutput.trim().split('\n');
|
||
const headers = this.parseCsvLine(lines[0]);
|
||
|
||
const data = [];
|
||
for (let i = 1; i < lines.length; i++) {
|
||
if (lines[i].trim()) {
|
||
const values = this.parseCsvLine(lines[i]);
|
||
const record = {};
|
||
|
||
headers.forEach((header, index) => {
|
||
record[header] = values[index] || null;
|
||
});
|
||
|
||
data.push(record);
|
||
}
|
||
}
|
||
|
||
console.log(`✅ Lettura completata: ${data.length} record`);
|
||
|
||
if (this.config.verbose && data.length > 0) {
|
||
console.log('\n📋 Preview primo record:');
|
||
console.log(JSON.stringify(data[0], null, 2));
|
||
}
|
||
|
||
return data;
|
||
|
||
} catch (error) {
|
||
throw new Error(`Lettura MDB fallita: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 📦 Processa dati in batch
|
||
*/
|
||
async processInBatches(data) {
|
||
console.log('\n📦 Avvio processamento batch...');
|
||
|
||
const batches = this.createBatches(data, this.config.batchSize);
|
||
|
||
for (let i = 0; i < batches.length; i++) {
|
||
const batch = batches[i];
|
||
console.log(`\n🔄 Batch ${i + 1}/${batches.length} (${batch.length} record)`);
|
||
|
||
try {
|
||
await this.processBatch(batch, i + 1);
|
||
} catch (error) {
|
||
console.error(`❌ Errore batch ${i + 1}:`, error.message);
|
||
this.results.errors += batch.length;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 🔄 Processa singolo batch
|
||
*/
|
||
async processBatch(batch, batchNumber) {
|
||
const mappedData = batch.map(record => this.mapStabileData(record));
|
||
|
||
if (this.config.validateOnly) {
|
||
// Solo validazione
|
||
mappedData.forEach((data, index) => {
|
||
const validation = this.validateStabileData(data);
|
||
this.results.processed++;
|
||
|
||
if (validation.valid) {
|
||
this.results.imported++;
|
||
} else {
|
||
this.results.errors++;
|
||
if (this.config.verbose) {
|
||
console.log(`❌ Validazione fallita record ${index + 1}:`, validation.errors);
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log(`✅ Batch ${batchNumber} validato: ${mappedData.length} record`);
|
||
return;
|
||
}
|
||
|
||
// Import reale via API
|
||
try {
|
||
const payload = {
|
||
bulk_import: true,
|
||
validate_only: false,
|
||
stabili: mappedData
|
||
};
|
||
|
||
const response = await axios.post(
|
||
`${this.config.apiBaseUrl}/import/stabili`,
|
||
payload,
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${this.config.apiToken}`,
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
},
|
||
timeout: 30000
|
||
}
|
||
);
|
||
|
||
if (response.data.success) {
|
||
const result = response.data.data;
|
||
this.results.processed += batch.length;
|
||
this.results.imported += result.importati || 0;
|
||
this.results.duplicates += result.duplicati || 0;
|
||
this.results.errors += result.errori || 0;
|
||
|
||
console.log(`✅ Batch ${batchNumber}: ${result.importati} importati, ${result.duplicati} duplicati, ${result.errori} errori`);
|
||
|
||
if (this.config.verbose && result.dettagli) {
|
||
result.dettagli.forEach(detail => {
|
||
this.results.details.push(detail);
|
||
});
|
||
}
|
||
} else {
|
||
throw new Error(response.data.message || 'Risposta API non valida');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Errore API batch ${batchNumber}:`, error.response?.data?.message || error.message);
|
||
this.results.errors += batch.length;
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 🗺️ Mapping dati da MDB a formato NetGescon
|
||
*/
|
||
mapStabileData(mdbRecord) {
|
||
return {
|
||
cod_stabile_old: mdbRecord.cod_stabile,
|
||
directory_name: mdbRecord.nome_directory,
|
||
denominazione: mdbRecord.denominazione,
|
||
indirizzo: mdbRecord.indirizzo,
|
||
cap: mdbRecord.cap,
|
||
citta: mdbRecord.citta,
|
||
provincia: mdbRecord.provincia,
|
||
codice_fiscale: mdbRecord.codice_fiscale,
|
||
totale_unita_immobiliari: parseInt(mdbRecord.num_condomini) || 0,
|
||
numero_scale: parseInt(mdbRecord.num_scale) || 0,
|
||
cf_amministratore: mdbRecord.cf_amministratore,
|
||
data_nomina_amministratore: mdbRecord.data_nomina,
|
||
|
||
// Dati bancari
|
||
iban_bancario: mdbRecord.iban_bancario,
|
||
iban_postale: mdbRecord.iban_postale,
|
||
banca: mdbRecord.banca,
|
||
abi: mdbRecord.abi,
|
||
cab: mdbRecord.cab,
|
||
conto_postale: mdbRecord.conto_postale,
|
||
|
||
// Dati catastali
|
||
catasto_foglio: mdbRecord.AC_Foglio,
|
||
catasto_particella: mdbRecord.AC_partic,
|
||
|
||
// Configurazione rate (True/False -> boolean)
|
||
config_rate_ord_q1: this.parseBoolean(mdbRecord.ORD_trimestre1),
|
||
config_rate_ord_q2: this.parseBoolean(mdbRecord.ORD_trimestre2),
|
||
config_rate_ord_q3: this.parseBoolean(mdbRecord.ORD_trimestre3),
|
||
config_rate_ord_q4: this.parseBoolean(mdbRecord.ORD_trimestre4),
|
||
config_rate_ris_q1: this.parseBoolean(mdbRecord.RIS_trimestre1),
|
||
config_rate_ris_q2: this.parseBoolean(mdbRecord.RIS_trimestre2),
|
||
config_rate_ris_q3: this.parseBoolean(mdbRecord.RIS_trimestre3),
|
||
config_rate_ris_q4: this.parseBoolean(mdbRecord.RIS_trimestre4),
|
||
|
||
// Metadati
|
||
note_migrazione: mdbRecord.note,
|
||
stato_attivo: this.parseBoolean(mdbRecord.attivo, true), // default true
|
||
data_creazione_old: mdbRecord.data_creazione,
|
||
data_modifica_old: mdbRecord.data_modifica,
|
||
migrazione_data: new Date().toISOString().split('T')[0]
|
||
};
|
||
}
|
||
|
||
/**
|
||
* ✅ Validazione dati stabile
|
||
*/
|
||
validateStabileData(data) {
|
||
const errors = [];
|
||
|
||
if (!data.denominazione) errors.push('Denominazione obbligatoria');
|
||
if (!data.indirizzo) errors.push('Indirizzo obbligatorio');
|
||
if (!data.cap || !/^\d{5}$/.test(data.cap)) errors.push('CAP non valido');
|
||
if (!data.citta) errors.push('Città obbligatoria');
|
||
if (!data.provincia || data.provincia.length !== 2) errors.push('Provincia non valida');
|
||
|
||
return {
|
||
valid: errors.length === 0,
|
||
errors: errors
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 📊 Mostra risultati finali
|
||
*/
|
||
showFinalResults() {
|
||
console.log('\n📊 RISULTATI FINALI:');
|
||
console.log(`📋 Record processati: ${this.results.processed}`);
|
||
console.log(`✅ Importati: ${this.results.imported}`);
|
||
console.log(`🔄 Duplicati: ${this.results.duplicates}`);
|
||
console.log(`❌ Errori: ${this.results.errors}`);
|
||
|
||
if (this.config.verbose && this.results.details.length > 0) {
|
||
console.log('\n📝 DETTAGLI:');
|
||
this.results.details.forEach(detail => {
|
||
const status = detail.success ? '✅' : '❌';
|
||
console.log(`${status} ${detail.cod_stabile}: ${detail.message || detail.error}`);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 🛠️ UTILITY FUNCTIONS
|
||
|
||
createBatches(array, size) {
|
||
const batches = [];
|
||
for (let i = 0; i < array.length; i += size) {
|
||
batches.push(array.slice(i, i + size));
|
||
}
|
||
return batches;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
parseBoolean(value, defaultValue = false) {
|
||
if (value === null || value === undefined || value === '') {
|
||
return defaultValue;
|
||
}
|
||
|
||
const str = String(value).toLowerCase();
|
||
return str === 'true' || str === '1' || str === 'yes' || str === 'si';
|
||
}
|
||
|
||
execCommand(command) {
|
||
return new Promise((resolve, reject) => {
|
||
exec(command, (error, stdout, stderr) => {
|
||
if (error) {
|
||
reject(new Error(`Command failed: ${error.message}`));
|
||
return;
|
||
}
|
||
|
||
if (stderr) {
|
||
console.warn('Warning:', stderr);
|
||
}
|
||
|
||
resolve(stdout);
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// 🚀 ESECUZIONE SCRIPT
|
||
if (require.main === module) {
|
||
const args = process.argv.slice(2);
|
||
|
||
if (args.length < 1) {
|
||
console.log('❌ Uso: node mdb-importer.js <file.mdb> [options]');
|
||
console.log('');
|
||
console.log('Opzioni:');
|
||
console.log(' --api-url <url> URL base API (default: http://localhost:8000/api/v1)');
|
||
console.log(' --api-token <token> Token autenticazione API');
|
||
console.log(' --batch-size <size> Dimensione batch (default: 50)');
|
||
console.log(' --validate-only Solo validazione senza import');
|
||
console.log(' --verbose Output dettagliato');
|
||
console.log('');
|
||
console.log('Esempio:');
|
||
console.log(' node mdb-importer.js stabili.mdb --api-token abc123 --batch-size 25');
|
||
process.exit(1);
|
||
}
|
||
|
||
const config = {
|
||
mdbFile: args[0]
|
||
};
|
||
|
||
// Parsing argomenti
|
||
for (let i = 1; i < args.length; i += 2) {
|
||
const flag = args[i];
|
||
const value = args[i + 1];
|
||
|
||
switch (flag) {
|
||
case '--api-url':
|
||
config.apiBaseUrl = value;
|
||
break;
|
||
case '--api-token':
|
||
config.apiToken = value;
|
||
break;
|
||
case '--batch-size':
|
||
config.batchSize = parseInt(value);
|
||
break;
|
||
case '--validate-only':
|
||
config.validateOnly = true;
|
||
i--; // Non ha valore
|
||
break;
|
||
case '--verbose':
|
||
config.verbose = true;
|
||
i--; // Non ha valore
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Esegui import
|
||
const importer = new MDBImporter(config);
|
||
|
||
importer.run()
|
||
.then(results => {
|
||
console.log('\n🎉 Import completato!');
|
||
process.exit(0);
|
||
})
|
||
.catch(error => {
|
||
console.error('\n💥 Import fallito:', error.message);
|
||
process.exit(1);
|
||
});
|
||
}
|
||
|
||
module.exports = MDBImporter;
|