netgescon-day0/docs/00-IMPORT/03-SCRIPTS/mdb-importer.js

502 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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