netgescon-day0/docs/00-IMPORT/02-MDB-CONNECTION/mdb-import-guide.md

18 KiB

🗃️ CONNESSIONE DATABASE MDB - Configurazione Import

📋 Setup Connessione Microsoft Access (.mdb)

1. Prerequisiti Sistema

# Ubuntu/Debian - Installazione driver MDB
sudo apt-get update
sudo apt-get install mdbtools unixodbc unixodbc-dev
sudo apt-get install libmdbodbc1

# Per PHP - Estensioni necessarie
sudo apt-get install php-odbc php-pdo

2. Configurazione ODBC

# /etc/odbc.ini
[Gescon_Stabili]
Description=Database Gescon Stabili
Driver=MDBTools
Database=/path/to/stabili.mdb
Readonly=yes

3. Test Connessione

# Test connessione ODBC
isql -v Gescon_Stabili

# Test con mdb-tools
mdb-tables /path/to/stabili.mdb
mdb-schema /path/to/stabili.mdb

🔄 Script PHP Import MDB

Classe MDBImporter

<?php
// app/Services/MDBImporter.php

namespace App\Services;

use PDO;
use PDOException;
use Illuminate\Support\Facades\Log;
use App\Models\Stabile;
use App\Models\ComuneItaliano;
use App\Models\User;

class MDBImporter
{
    protected $mdbPath;
    protected $connection;
    protected $batchSize;
    
    public function __construct(string $mdbPath, int $batchSize = 50)
    {
        $this->mdbPath = $mdbPath;
        $this->batchSize = $batchSize;
    }
    
    /**
     * Connessione al database MDB
     */
    public function connect(): bool
    {
        try {
            // Prova prima con ODBC
            $dsn = "odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ={$this->mdbPath};";
            $this->connection = new PDO($dsn);
            
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
            Log::info("Connessione MDB stabilita", ['file' => $this->mdbPath]);
            return true;
            
        } catch (PDOException $e) {
            // Fallback con mdb-tools
            Log::warning("Connessione ODBC fallita, utilizzo mdb-tools", [
                'error' => $e->getMessage()
            ]);
            return $this->connectWithMdbTools();
        }
    }
    
    /**
     * Fallback con mdb-tools per sistemi Linux
     */
    protected function connectWithMdbTools(): bool
    {
        if (!file_exists($this->mdbPath)) {
            Log::error("File MDB non trovato", ['path' => $this->mdbPath]);
            return false;
        }
        
        // Test con mdb-tables
        $output = shell_exec("mdb-tables -1 '{$this->mdbPath}' 2>/dev/null");
        
        if (strpos($output, 'Stabili') !== false) {
            Log::info("Connessione MDB-Tools stabilita", ['tables' => trim($output)]);
            return true;
        }
        
        return false;
    }
    
    /**
     * Importa tabella Stabili
     */
    public function importStabili(): array
    {
        $risultato = [
            'importati' => 0,
            'errori' => 0,
            'duplicati' => 0,
            'dettagli' => []
        ];
        
        try {
            $stabiliData = $this->readStabiliFromMDB();
            
            Log::info("Lettura MDB completata", [
                'records_found' => count($stabiliData)
            ]);
            
            foreach (array_chunk($stabiliData, $this->batchSize) as $batch) {
                $batchResult = $this->processBatch($batch);
                
                $risultato['importati'] += $batchResult['importati'];
                $risultato['errori'] += $batchResult['errori'];
                $risultato['duplicati'] += $batchResult['duplicati'];
                $risultato['dettagli'] = array_merge($risultato['dettagli'], $batchResult['dettagli']);
            }
            
        } catch (\Exception $e) {
            Log::error("Errore import MDB", ['error' => $e->getMessage()]);
            $risultato['errori']++;
            $risultato['dettagli'][] = [
                'tipo' => 'errore_critico',
                'messaggio' => $e->getMessage()
            ];
        }
        
        return $risultato;
    }
    
    /**
     * Legge dati dalla tabella Stabili
     */
    protected function readStabiliFromMDB(): array
    {
        if ($this->connection) {
            return $this->readWithPDO();
        } else {
            return $this->readWithMdbTools();
        }
    }
    
    /**
     * Lettura con PDO/ODBC
     */
    protected function readWithPDO(): array
    {
        $sql = "SELECT * FROM Stabili ORDER BY cod_stabile";
        $stmt = $this->connection->prepare($sql);
        $stmt->execute();
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    /**
     * Lettura con mdb-tools
     */
    protected function readWithMdbTools(): array
    {
        $command = "mdb-export '{$this->mdbPath}' Stabili";
        $csvOutput = shell_exec($command);
        
        if (!$csvOutput) {
            throw new \Exception("Impossibile leggere tabella Stabili da MDB");
        }
        
        // Parsing CSV output
        $lines = explode("\n", trim($csvOutput));
        $headers = str_getcsv(array_shift($lines));
        
        $data = [];
        foreach ($lines as $line) {
            if (trim($line)) {
                $values = str_getcsv($line);
                $data[] = array_combine($headers, $values);
            }
        }
        
        return $data;
    }
    
    /**
     * Processa batch di record
     */
    protected function processBatch(array $batch): array
    {
        $risultato = [
            'importati' => 0,
            'errori' => 0,
            'duplicati' => 0,
            'dettagli' => []
        ];
        
        foreach ($batch as $record) {
            try {
                $recordResult = $this->processStabileRecord($record);
                
                if ($recordResult['success']) {
                    if ($recordResult['type'] === 'created') {
                        $risultato['importati']++;
                    } else {
                        $risultato['duplicati']++;
                    }
                } else {
                    $risultato['errori']++;
                }
                
                $risultato['dettagli'][] = $recordResult;
                
            } catch (\Exception $e) {
                $risultato['errori']++;
                $risultato['dettagli'][] = [
                    'success' => false,
                    'cod_stabile' => $record['cod_stabile'] ?? 'N/A',
                    'error' => $e->getMessage()
                ];
            }
        }
        
        return $risultato;
    }
    
    /**
     * Processa singolo record Stabile
     */
    protected function processStabileRecord(array $record): array
    {
        $codStabile = $record['cod_stabile'];
        
        // Controlla duplicati
        $esistente = Stabile::where('cod_stabile_old', $codStabile)->first();
        
        if ($esistente) {
            return [
                'success' => true,
                'type' => 'duplicate',
                'cod_stabile' => $codStabile,
                'message' => 'Record già esistente'
            ];
        }
        
        // Mapping dati
        $stabileData = $this->mapStabileData($record);
        
        // Validazione
        $validation = $this->validateStabileData($stabileData);
        if (!$validation['valid']) {
            return [
                'success' => false,
                'cod_stabile' => $codStabile,
                'error' => 'Validazione fallita: ' . implode(', ', $validation['errors'])
            ];
        }
        
        // Creazione record
        $stabile = Stabile::create($stabileData);
        
        return [
            'success' => true,
            'type' => 'created',
            'cod_stabile' => $codStabile,
            'netgescon_id' => $stabile->id,
            'message' => 'Importato con successo'
        ];
    }
    
    /**
     * Mapping dati da MDB a NetGescon
     */
    protected function mapStabileData(array $mdbRecord): array
    {
        // Trova comune
        $comune = ComuneItaliano::where('nome', $mdbRecord['citta'])
                                ->where('provincia', $mdbRecord['provincia'])
                                ->first();
        
        // Trova amministratore
        $amministratore = null;
        if (!empty($mdbRecord['cf_amministratore'])) {
            $amministratore = User::where('codice_fiscale', $mdbRecord['cf_amministratore'])->first();
        }
        
        // Prepara dati bancari JSON
        $datiBancari = [
            'iban_principale' => $mdbRecord['iban_bancario'] ?? null,
            'iban_postale' => $mdbRecord['iban_postale'] ?? null,
            'banca_denominazione' => $mdbRecord['banca'] ?? null,
            'abi' => $mdbRecord['abi'] ?? null,
            'cab' => $mdbRecord['cab'] ?? null,
            'cc_postale' => $mdbRecord['conto_postale'] ?? null
        ];
        
        // Prepara dati catastali JSON
        $datiCatastali = [
            'foglio' => $mdbRecord['AC_Foglio'] ?? null,
            'particella' => $mdbRecord['AC_partic'] ?? null
        ];
        
        // Prepara configurazione rate JSON
        $configRate = [
            'ordinarie' => [
                'q1' => ($mdbRecord['ORD_trimestre1'] ?? false) === true,
                'q2' => ($mdbRecord['ORD_trimestre2'] ?? false) === true,
                'q3' => ($mdbRecord['ORD_trimestre3'] ?? false) === true,
                'q4' => ($mdbRecord['ORD_trimestre4'] ?? false) === true
            ],
            'riserva' => [
                'q1' => ($mdbRecord['RIS_trimestre1'] ?? false) === true,
                'q2' => ($mdbRecord['RIS_trimestre2'] ?? false) === true,
                'q3' => ($mdbRecord['RIS_trimestre3'] ?? false) === true,
                'q4' => ($mdbRecord['RIS_trimestre4'] ?? false) === true
            ]
        ];
        
        return [
            'cod_stabile_old' => $mdbRecord['cod_stabile'],
            'directory_name' => $mdbRecord['nome_directory'] ?? null,
            'denominazione' => $mdbRecord['denominazione'],
            'indirizzo' => $mdbRecord['indirizzo'],
            'cap' => $mdbRecord['cap'],
            'citta' => $mdbRecord['citta'],
            'provincia' => $mdbRecord['provincia'],
            'codice_fiscale' => $mdbRecord['codice_fiscale'] ?? null,
            'comune_id' => $comune?->id,
            'amministratore_id' => $amministratore?->id,
            'totale_unita_immobiliari' => (int)($mdbRecord['num_condomini'] ?? 0),
            'numero_scale' => (int)($mdbRecord['num_scale'] ?? 0),
            'data_nomina_amministratore' => $this->parseDate($mdbRecord['data_nomina'] ?? null),
            'dati_bancari' => json_encode($datiBancari),
            'dati_catastali' => json_encode($datiCatastali),
            'configurazione_rate' => json_encode($configRate),
            'note_migrazione' => $mdbRecord['note'] ?? null,
            'stato_attivo' => ($mdbRecord['attivo'] ?? true) === true,
            'data_creazione_old' => $this->parseDate($mdbRecord['data_creazione'] ?? null),
            'data_modifica_old' => $this->parseDate($mdbRecord['data_modifica'] ?? null),
            'migrazione_completata_at' => now()
        ];
    }
    
    /**
     * Validazione dati stabile
     */
    protected function validateStabileData(array $data): array
    {
        $errors = [];
        
        if (empty($data['denominazione'])) {
            $errors[] = 'Denominazione obbligatoria';
        }
        
        if (empty($data['indirizzo'])) {
            $errors[] = 'Indirizzo obbligatorio';
        }
        
        if (empty($data['cap']) || !preg_match('/^\d{5}$/', $data['cap'])) {
            $errors[] = 'CAP non valido';
        }
        
        if (empty($data['citta'])) {
            $errors[] = 'Città obbligatoria';
        }
        
        if (empty($data['provincia']) || strlen($data['provincia']) !== 2) {
            $errors[] = 'Provincia non valida';
        }
        
        return [
            'valid' => empty($errors),
            'errors' => $errors
        ];
    }
    
    /**
     * Parse date da formato MDB
     */
    protected function parseDate($dateString): ?string
    {
        if (empty($dateString)) {
            return null;
        }
        
        try {
            $date = new \DateTime($dateString);
            return $date->format('Y-m-d');
        } catch (\Exception $e) {
            return null;
        }
    }
    
    /**
     * Chiude connessione
     */
    public function disconnect(): void
    {
        $this->connection = null;
    }
}

🚀 Comando Artisan Import

<?php
// app/Console/Commands/ImportMdbStabili.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\MDBImporter;
use Illuminate\Support\Facades\DB;

class ImportMdbStabili extends Command
{
    protected $signature = 'import:mdb-stabili 
                            {file : Path al file stabili.mdb}
                            {--batch-size=50 : Dimensione batch per import}
                            {--validate-only : Solo validazione senza import}
                            {--force : Forza import anche con errori}';
    
    protected $description = 'Importa stabili da file MDB del vecchio programma Gescon';
    
    public function handle()
    {
        $filePath = $this->argument('file');
        $batchSize = (int)$this->option('batch-size');
        $validateOnly = $this->option('validate-only');
        $force = $this->option('force');
        
        $this->info("🗃️  Avvio import stabili da MDB");
        $this->info("File: {$filePath}");
        $this->info("Batch size: {$batchSize}");
        
        if (!file_exists($filePath)) {
            $this->error("File MDB non trovato: {$filePath}");
            return 1;
        }
        
        try {
            $importer = new MDBImporter($filePath, $batchSize);
            
            $this->info("🔌 Connessione al database MDB...");
            if (!$importer->connect()) {
                $this->error("Impossibile connettersi al database MDB");
                return 1;
            }
            
            $this->info("✅ Connessione stabilita");
            
            if ($validateOnly) {
                $this->info("🔍 Modalità validazione - nessun dato verrà importato");
                // TODO: Implementare validazione
                $this->info("✅ Validazione completata");
                return 0;
            }
            
            if (!$force && !$this->confirm('Procedere con l\'import?')) {
                $this->info("Import annullato");
                return 0;
            }
            
            $this->info("📦 Avvio import in transazione...");
            
            DB::beginTransaction();
            
            try {
                $risultato = $importer->importStabili();
                
                $this->newLine();
                $this->info("📊 RISULTATI IMPORT:");
                $this->info("✅ Importati: {$risultato['importati']}");
                $this->info("🔄 Duplicati: {$risultato['duplicati']}");
                $this->info("❌ Errori: {$risultato['errori']}");
                
                if ($risultato['errori'] > 0) {
                    $this->warn("⚠️  Import completato con errori");
                    
                    if (!$force) {
                        $this->error("Import interrotto per errori. Usa --force per procedere");
                        DB::rollback();
                        return 1;
                    }
                }
                
                DB::commit();
                $this->info("✅ Import completato con successo");
                
                // Mostra dettagli se richiesto
                if ($this->option('verbose')) {
                    $this->showDetails($risultato['dettagli']);
                }
                
            } catch (\Exception $e) {
                DB::rollback();
                $this->error("Errore durante import: " . $e->getMessage());
                return 1;
            }
            
        } catch (\Exception $e) {
            $this->error("Errore critico: " . $e->getMessage());
            return 1;
        } finally {
            $importer->disconnect();
        }
        
        return 0;
    }
    
    protected function showDetails(array $dettagli): void
    {
        $this->newLine();
        $this->info("📋 DETTAGLI IMPORT:");
        
        foreach ($dettagli as $dettaglio) {
            $status = $dettaglio['success'] ? '✅' : '❌';
            $this->line("{$status} {$dettaglio['cod_stabile']}: {$dettaglio['message'] ?? $dettaglio['error'] ?? 'N/A'}");
        }
    }
}

🔧 Script di Test e Validazione

#!/bin/bash
# test-mdb-connection.sh

echo "🧪 Test connessione database MDB"

MDB_FILE="$1"

if [ -z "$MDB_FILE" ]; then
    echo "❌ Specificare il path del file MDB"
    echo "Uso: $0 /path/to/stabili.mdb"
    exit 1
fi

if [ ! -f "$MDB_FILE" ]; then
    echo "❌ File non trovato: $MDB_FILE"
    exit 1
fi

echo "📁 File: $MDB_FILE"

# Test 1: mdb-tools
echo "🔍 Test 1: Verifica tabelle con mdb-tools"
if command -v mdb-tables >/dev/null 2>&1; then
    echo "Tabelle trovate:"
    mdb-tables -1 "$MDB_FILE"
    
    echo -e "\n📊 Schema tabella Stabili:"
    mdb-schema "$MDB_FILE" -T Stabili
    
    echo -e "\n📈 Conteggio record:"
    mdb-export "$MDB_FILE" Stabili | wc -l
    
else
    echo "⚠️  mdb-tools non installato"
fi

# Test 2: ODBC
echo -e "\n🔍 Test 2: Verifica connessione ODBC"
if command -v isql >/dev/null 2>&1; then
    echo "SELECT COUNT(*) FROM Stabili;" | isql -v "Driver={Microsoft Access Driver (*.mdb)};DBQ=$MDB_FILE;" 2>/dev/null
    if [ $? -eq 0 ]; then
        echo "✅ Connessione ODBC riuscita"
    else
        echo "⚠️  Connessione ODBC fallita"
    fi
else
    echo "⚠️  isql non disponibile"
fi

# Test 3: Preview dati
echo -e "\n📋 Preview primi 5 record:"
mdb-export "$MDB_FILE" Stabili | head -6

echo -e "\n✅ Test completato"