#!/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 [options]'); console.log(''); console.log('Opzioni:'); console.log(' --api-url URL base API (default: http://localhost:8000/api/v1)'); console.log(' --api-token Token autenticazione API'); console.log(' --batch-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;