netgescon-day0/app/Console/Commands/ImportGesconRelazionale.php

584 lines
22 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class ImportGesconRelazionale extends Command
{
protected $signature = 'gescon:import-relazionale
{--test : Modalità test, non salva i dati}
{--limit=1000 : Limite record per test MySQL}
{--stabile-id=1 : ID dello stabile}
{--stabile-path=0021 : Cartella stabile GESCON}
{--anno-path=0001 : Cartella anno GESCON}';
protected $description = 'Import GESCON completo con relazioni tra tabelle';
private $mysqlLimits = [
'max_allowed_packet' => 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;
}
}