64 * 1024 * 1024, // 64MB 'max_connections' => 100, 'max_execution_time' => 300, // 5 minuti 'batch_size' => 500, 'memory_limit' => '256M' ]; public function handle() { $testMode = $this->option('test'); $limit = $this->option('limit'); $stabileId = $this->option('stabile-id'); $stabilePath = $this->option('stabile-path'); $annoPath = $this->option('anno-path'); $this->info("🔧 IMPORT GESCON RELAZIONALE"); $this->info("Test Mode: " . ($testMode ? 'SI' : 'NO')); $this->info("Limite record: {$limit}"); $this->info("Stabile: {$stabilePath} Anno: {$annoPath}"); // Verifica capacità MySQL if (!$this->checkMysqlLimits()) { $this->error("❌ MySQL non ha capacità sufficienti"); return 1; } // Crea database strutture relazionali $this->createRelationalDatabase(); // FASE 1: Import dati base (generale_stabile del stabile specifico) $this->importDatiBase($testMode, $stabileId, $stabilePath); // FASE 2: Import operazioni con relazioni (singolo_anno del stabile/anno specifico) $this->importOperazioniRelazionali($testMode, $limit, $stabileId, $stabilePath, $annoPath); // FASE 3: Verifica integrità relazioni $this->verificaIntegrazioneRelazioni(); return 0; } private function checkMysqlLimits() { $this->info("🔍 Verifica capacità MySQL..."); try { $variables = DB::select("SHOW VARIABLES WHERE Variable_name IN ('max_allowed_packet', 'max_connections', 'innodb_buffer_pool_size')"); foreach ($variables as $var) { $value = is_numeric($var->Value) ? intval($var->Value) : $var->Value; $this->info("📊 {$var->Variable_name}: {$value}"); } // Test connessione e memoria $memoryTest = DB::select("SELECT COUNT(*) as count FROM information_schema.tables"); $this->info("✅ MySQL operativo - Tabelle sistema: " . $memoryTest[0]->count); return true; } catch (\Exception $e) { $this->error("❌ Errore verifica MySQL: " . $e->getMessage()); return false; } } private function createRelationalDatabase() { $this->info("📊 Creazione database relazionale GESCON..."); // Database principale import DB::statement('CREATE DATABASE IF NOT EXISTS gescon_relational CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'); // Configura connessione al database import config(['database.connections.gescon_import' => [ 'driver' => 'mysql', 'host' => '127.0.0.1', 'database' => 'gescon_relational', 'username' => 'netgescon_user', 'password' => 'NetGescon2024!', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', ]]); // Tabelle di configurazione (da generale_stabile) $this->createConfigTables(); // Tabelle operazioni (da Singolo_anno) $this->createOperationTables(); $this->info("✅ Database relazionale creato"); } private function createConfigTables() { DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS stabili_config ( id INT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, codice_stabile VARCHAR(20), denominazione VARCHAR(255), indirizzo VARCHAR(255), amministratore VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile (stabile_id) ) ENGINE=InnoDB '); DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS voci_spesa_config ( id INT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, cod_spe VARCHAR(10) NOT NULL, descrizione_spesa VARCHAR(255), categoria VARCHAR(100), tipo_voce VARCHAR(50), ordinamento INT, attiva BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile_cod (stabile_id, cod_spe), UNIQUE KEY uk_stabile_cod_spe (stabile_id, cod_spe) ) ENGINE=InnoDB '); DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS tabelle_millesimi_config ( id INT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, codice_tabella VARCHAR(10) NOT NULL, denominazione VARCHAR(255), tipo_tabella VARCHAR(50), ordinamento INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile_tab (stabile_id, codice_tabella), UNIQUE KEY uk_stabile_tabella (stabile_id, codice_tabella) ) ENGINE=InnoDB '); DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS fornitori_config ( id INT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, cod_for INT NOT NULL, ragione_sociale VARCHAR(255), codice_fiscale VARCHAR(20), partita_iva VARCHAR(20), indirizzo TEXT, telefono VARCHAR(50), email VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile_cod (stabile_id, cod_for), UNIQUE KEY uk_stabile_fornitore (stabile_id, cod_for) ) ENGINE=InnoDB '); } private function createOperationTables() { DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS operazioni_import ( id BIGINT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, id_operazione_gescon BIGINT, -- Riferimenti relazionali cod_spe VARCHAR(10), cod_for INT, codice_tabella VARCHAR(10), -- Dati operazione numero_spesa INT, data_spesa DATE, importo_euro DECIMAL(15,2), beneficiario VARCHAR(255), note TEXT, -- Classificazione natura VARCHAR(10), competenza CHAR(1), anno_gestione VARCHAR(20), -- Relazioni risolte (popolate dopo import config) voce_spesa_id INT NULL, fornitore_id INT NULL, tabella_millesimi_id INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile (stabile_id), INDEX idx_cod_spe (cod_spe), INDEX idx_cod_for (cod_for), INDEX idx_data (data_spesa), INDEX idx_anno (anno_gestione), FOREIGN KEY (voce_spesa_id) REFERENCES voci_spesa_config(id), FOREIGN KEY (fornitore_id) REFERENCES fornitori_config(id), FOREIGN KEY (tabella_millesimi_id) REFERENCES tabelle_millesimi_config(id) ) ENGINE=InnoDB '); DB::connection('gescon_import')->statement(' CREATE TABLE IF NOT EXISTS incassi_import ( id BIGINT AUTO_INCREMENT PRIMARY KEY, stabile_id INT NOT NULL, numero_rata INT, data_incasso DATE, importo_euro DECIMAL(15,2), cod_spe VARCHAR(10), unita_immobiliare VARCHAR(20), note TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_stabile (stabile_id), INDEX idx_rata (numero_rata), INDEX idx_data (data_incasso) ) ENGINE=InnoDB '); } private function importDatiBase($testMode, $stabileId, $stabilePath) { $this->info("📂 FASE 1: Import dati base..."); // 1. Import voci spesa dal file anno specifico $annoFile = "/home/michele/netgescon/docs/02-architettura-laravel/Gescon/{$stabilePath}/0001/singolo_anno.mdb"; if (file_exists($annoFile)) { $this->importVociSpesa($annoFile, $testMode, $stabileId); } // 2. Import fornitori dal database centrale DBC $fornitoriFile = "/home/michele/netgescon/docs/02-architettura-laravel/Gescon/dbc/Fornitori.mdb"; if (file_exists($fornitoriFile)) { $this->importFornitoriConfig($fornitoriFile, $testMode, $stabileId); } } private function importVociSpesa($file, $testMode, $stabileId) { $this->info("📋 Import voci spesa da: " . basename($file)); $tempCsv = '/tmp/voci_spesa_' . time() . '.csv'; $command = "mdb-export '{$file}' voc_spe > {$tempCsv} 2>/dev/null"; exec($command); if (file_exists($tempCsv) && filesize($tempCsv) > 50) { $count = $this->processVociSpesaCsv($tempCsv, $testMode, $stabileId); $this->info("✅ Voci spesa importate: {$count}"); unlink($tempCsv); } else { $this->warn("⚠️ Nessuna voce spesa trovata in " . basename($file)); } } private function importFornitoriConfig($file, $testMode, $stabileId) { $this->info("👥 Import fornitori da: " . basename($file)); $tempCsv = '/tmp/fornitori_config_' . time() . '.csv'; $command = "mdb-export '{$file}' Fornitori > {$tempCsv} 2>/dev/null"; exec($command); if (file_exists($tempCsv) && filesize($tempCsv) > 50) { $count = $this->processFornitoriConfigCsv($tempCsv, $testMode, $stabileId); $this->info("✅ Fornitori config importati: {$count}"); unlink($tempCsv); } else { $this->warn("⚠️ Nessun fornitore trovato in " . basename($file)); } } private function importOperazioniRelazionali($testMode, $limit, $stabileId, $stabilePath, $annoPath) { $this->info("📂 FASE 2: Import operazioni relazionali (singolo_anno.mdb stabile {$stabilePath} anno {$annoPath})..."); $annoFile = "/home/michele/netgescon/docs/02-architettura-laravel/Gescon/{$stabilePath}/{$annoPath}/singolo_anno.mdb"; if (!file_exists($annoFile)) { $this->error("❌ File singolo_anno.mdb non trovato: {$annoFile}"); return; } // Export operazioni con controllo dimensioni $this->info("📊 Export operazioni con controllo MySQL..."); $tempCsv = '/tmp/operazioni_rel_' . time() . '.csv'; $command = "timeout 60s mdb-export '{$annoFile}' Operazioni > {$tempCsv}"; exec($command, $output, $returnCode); if ($returnCode !== 0 || !file_exists($tempCsv)) { $this->error("❌ Errore export operazioni o timeout"); return; } // Controlla dimensioni file $fileSize = filesize($tempCsv); $this->info("📏 Dimensione file operazioni: " . round($fileSize / 1024 / 1024, 2) . " MB"); if ($fileSize > $this->mysqlLimits['max_allowed_packet']) { $this->warn("⚠️ File troppo grande, uso import a lotti"); $this->importOperazioniALotti($tempCsv, $testMode, $limit, $stabileId); } else { $this->importOperazioniDirette($tempCsv, $testMode, $limit, $stabileId); } unlink($tempCsv); } private function importOperazioniALotti($csvFile, $testMode, $limit, $stabileId) { $this->info("🔄 Import operazioni a lotti..."); $handle = fopen($csvFile, 'r'); $headers = fgetcsv($handle); $fieldMap = $this->createFieldMap($headers); $batch = []; $processed = 0; $imported = 0; while (($row = fgetcsv($handle)) !== false && $processed < $limit) { $processed++; if ($this->isValidOperationRow($row, $fieldMap)) { $operationData = $this->prepareOperationRow($row, $fieldMap, $stabileId); if ($operationData) { $batch[] = $operationData; } } // Import batch quando pieno if (count($batch) >= $this->mysqlLimits['batch_size']) { if (!$testMode) { try { DB::connection('gescon_import')->table('operazioni_import')->insert($batch); $imported += count($batch); } catch (\Exception $e) { $this->error("❌ Errore batch import: " . $e->getMessage()); } } $batch = []; if ($imported % 1000 == 0) { $this->info("📈 Operazioni importate: {$imported}"); } } } // Import ultimo batch if (!empty($batch) && !$testMode) { DB::connection('gescon_import')->table('operazioni_import')->insert($batch); $imported += count($batch); } fclose($handle); $this->info("✅ Operazioni elaborate: {$processed}"); $this->info("✅ Operazioni importate: {$imported}"); } private function importOperazioniDirette($csvFile, $testMode, $limit, $stabileId) { $this->info("⚡ Import operazioni diretto..."); $this->importOperazioniALotti($csvFile, $testMode, $limit, $stabileId); } private function verificaIntegrazioneRelazioni() { $this->info("🔍 FASE 3: Verifica integrità relazioni..."); try { // Collega voci spesa $vociCollegate = DB::connection('gescon_import')->statement(" UPDATE operazioni_import o JOIN voci_spesa_config v ON o.stabile_id = v.stabile_id AND o.cod_spe = v.cod_spe SET o.voce_spesa_id = v.id WHERE o.voce_spesa_id IS NULL "); // Collega fornitori $fornitoriCollegati = DB::connection('gescon_import')->statement(" UPDATE operazioni_import o JOIN fornitori_config f ON o.stabile_id = f.stabile_id AND o.cod_for = f.cod_for SET o.fornitore_id = f.id WHERE o.fornitore_id IS NULL AND o.cod_for IS NOT NULL "); // Report relazioni $stats = DB::connection('gescon_import')->select(" SELECT COUNT(*) as totale_operazioni, COUNT(voce_spesa_id) as operazioni_con_voce, COUNT(fornitore_id) as operazioni_con_fornitore, COUNT(CASE WHEN voce_spesa_id IS NULL AND cod_spe IS NOT NULL THEN 1 END) as voci_non_trovate, COUNT(CASE WHEN fornitore_id IS NULL AND cod_for IS NOT NULL THEN 1 END) as fornitori_non_trovati FROM operazioni_import "); $stat = $stats[0]; $this->info("📊 REPORT RELAZIONI:"); $this->info(" Operazioni totali: {$stat->totale_operazioni}"); $this->info(" Con voce spesa: {$stat->operazioni_con_voce}"); $this->info(" Con fornitore: {$stat->operazioni_con_fornitore}"); $this->warn(" Voci non trovate: {$stat->voci_non_trovate}"); $this->warn(" Fornitori non trovati: {$stat->fornitori_non_trovati}"); } catch (\Exception $e) { $this->error("❌ Errore verifica relazioni: " . $e->getMessage()); } } // Utility methods (simili ai comandi precedenti) private function createFieldMap($headers) { $map = []; foreach ($headers as $index => $header) { $map[strtolower(trim($header))] = $index; } return $map; } private function isValidOperationRow($row, $fieldMap) { $idOperaz = $this->getFieldValue($row, $fieldMap, 'id_operaz'); $importoEuro = $this->getFieldValue($row, $fieldMap, 'importo_euro'); return !empty($idOperaz) && (!empty($importoEuro) && $importoEuro != 0); } private function prepareOperationRow($row, $fieldMap, $stabileId) { return [ 'stabile_id' => $stabileId, 'id_operazione_gescon' => $this->getFieldValue($row, $fieldMap, 'id_operaz'), 'cod_spe' => $this->getFieldValue($row, $fieldMap, 'cod_spe'), 'cod_for' => $this->getFieldValue($row, $fieldMap, 'cod_for'), 'codice_tabella' => $this->getFieldValue($row, $fieldMap, 'tabella'), 'numero_spesa' => $this->getFieldValue($row, $fieldMap, 'n_spe'), 'data_spesa' => $this->parseDate($this->getFieldValue($row, $fieldMap, 'dt_spe')), 'importo_euro' => $this->parseDecimal($this->getFieldValue($row, $fieldMap, 'importo_euro')), 'beneficiario' => substr($this->getFieldValue($row, $fieldMap, 'benef'), 0, 255), 'note' => $this->getFieldValue($row, $fieldMap, 'note'), 'natura' => $this->getFieldValue($row, $fieldMap, 'natura'), 'competenza' => $this->getFieldValue($row, $fieldMap, 'compet'), 'anno_gestione' => $this->getFieldValue($row, $fieldMap, 'anno') ]; } private function processVociSpesaCsv($csvFile, $testMode, $stabileId) { $handle = fopen($csvFile, 'r'); if (!$handle) return 0; $headers = fgetcsv($handle); if (!$headers) { fclose($handle); return 0; } $fieldMap = $this->createFieldMap($headers); $imported = 0; while (($row = fgetcsv($handle)) !== false) { $voceData = [ 'stabile_id' => $stabileId, 'cod_spe' => $this->getFieldValue($row, $fieldMap, 'cod'), 'descrizione_spesa' => substr($this->getFieldValue($row, $fieldMap, 'descriz'), 0, 255), 'categoria' => 'Standard', 'tipo_voce' => $this->getFieldValue($row, $fieldMap, 't_spese') == 'O' ? 'Ordinaria' : 'Straordinaria', 'ordinamento' => $this->getFieldValue($row, $fieldMap, 'id_vocspe'), 'attiva' => true ]; if (!empty($voceData['cod_spe']) && !$testMode) { try { DB::connection('gescon_import')->table('voci_spesa_config')->insert($voceData); $imported++; } catch (\Exception $e) { // Ignora duplicati } } elseif (!empty($voceData['cod_spe'])) { $imported++; } } fclose($handle); return $imported; } private function processFornitoriConfigCsv($csvFile, $testMode, $stabileId) { $handle = fopen($csvFile, 'r'); if (!$handle) return 0; $headers = fgetcsv($handle); if (!$headers) { fclose($handle); return 0; } $fieldMap = $this->createFieldMap($headers); $imported = 0; while (($row = fgetcsv($handle)) !== false) { $fornitoreData = [ 'stabile_id' => $stabileId, 'cod_for' => $this->getFieldValue($row, $fieldMap, 'cod_forn'), 'ragione_sociale' => substr($this->getFieldValue($row, $fieldMap, 'cognome') . ' ' . $this->getFieldValue($row, $fieldMap, 'nome'), 0, 255), 'codice_fiscale' => $this->getFieldValue($row, $fieldMap, 'cod_fisc'), 'partita_iva' => $this->getFieldValue($row, $fieldMap, 'p_iva'), 'indirizzo' => $this->getFieldValue($row, $fieldMap, 'indirizzo') . ' ' . $this->getFieldValue($row, $fieldMap, 'cap') . ' ' . $this->getFieldValue($row, $fieldMap, 'citta'), 'telefono' => $this->getFieldValue($row, $fieldMap, 'telef_1'), 'email' => $this->getFieldValue($row, $fieldMap, 'indir_email') ]; if (!empty($fornitoreData['cod_for']) && !$testMode) { try { DB::connection('gescon_import')->table('fornitori_config')->insert($fornitoreData); $imported++; } catch (\Exception $e) { // Ignora duplicati } } elseif (!empty($fornitoreData['cod_for'])) { $imported++; } } fclose($handle); return $imported; } private function getFieldValue($row, $fieldMap, $fieldName) { $fieldName = strtolower($fieldName); if (!isset($fieldMap[$fieldName])) return null; $index = $fieldMap[$fieldName]; return isset($row[$index]) ? trim($row[$index]) : null; } private function parseDate($dateString) { if (empty($dateString)) return null; try { $date = \DateTime::createFromFormat('Y-m-d H:i:s', $dateString); if (!$date) { $date = \DateTime::createFromFormat('Y-m-d', $dateString); } return $date ? $date->format('Y-m-d') : null; } catch (\Exception $e) { return null; } } private function parseDecimal($value) { if (empty($value)) return 0; $value = preg_replace('/[^\d.,\-]/', '', $value); $value = str_replace(',', '.', $value); return is_numeric($value) ? (float)$value : 0; } }