middleware(['auth', 'role:admin|amministratore|super-admin']); } public function registerStabili(Request $request) { $path = $this->normalizePath($request->input('mdb_path') ?: UserSetting::get('gescon.mdb_stabili')); if (!$path) { return redirect()->back()->with('error', 'Percorso Stabili.mdb non configurato.'); } // Non bloccare se non esiste, ma avvisa: l'ingest farà i controlli completi $exists = @file_exists($path); $source = MigrationSource::updateOrCreate( ['name' => 'GESCON Stabili'], ['type' => 'mdb', 'path' => $path, 'enabled' => true] ); return redirect()->back()->with( $exists ? 'success' : 'warning', 'Sorgente "GESCON Stabili" registrata.' . ($exists ? '' : ' Attenzione: il file non è stato trovato al momento della registrazione.') ); } public function ingestStabili(Request $request) { $source = MigrationSource::where('name', 'GESCON Stabili')->first(); if (!$source) { return redirect()->back()->with('error', 'Registra prima la sorgente GESCON Stabili.'); } $submitted = $this->normalizePath($request->input('mdb_path')); $mdbPath = $submitted ?: $source->path; $mdbPath = $this->normalizePath($mdbPath); $real = @realpath($mdbPath) ?: $mdbPath; // Fallback: se la path inviata non esiste ma la sorgente registrata sì, usa quella if ($submitted && !@file_exists($submitted) && @file_exists($source->path)) { $mdbPath = $source->path; $real = @realpath($mdbPath) ?: $mdbPath; } if (!$mdbPath || !@file_exists($mdbPath)) { return redirect()->back()->with('error', 'File MDB non trovato. Inviato: ' . ($submitted ?: '—') . ' | Sorgente: ' . ($source->path ?: '—') . ' — attenzione a maiuscole/minuscole (es. Stabili.mdb).'); } $hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== ''; if (!$hasMdbTools) { return redirect()->back()->with('error', 'mdb-tools non disponibile sul server.'); } // Verifica file e permessi if (!$mdbPath || !is_file($mdbPath)) { return redirect()->back()->with('error', 'File MDB non trovato: ' . ($mdbPath ?: '(vuoto)')); } if (!is_readable($mdbPath)) { return redirect()->back()->with('error', 'File MDB non leggibile (permessi). Percorso: ' . $real); } // Esporta tabella Stabili con separatore pipe $tableGuess = 'Stabili'; $tables = @shell_exec(sprintf('mdb-tables -1 %s 2>&1', escapeshellarg($mdbPath))) ?: ''; if ($tables && stripos($tables, 'Couldn') !== false) { return redirect()->back()->with('error', 'Errore mdb-tables: ' . trim($tables)); } if ($tables) { foreach (preg_split('/\r?\n/', trim($tables)) as $t) { if ($t === '') continue; if (preg_match('/stabil/i', $t)) { $tableGuess = trim($t); break; } } } $tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_'); $cmd = sprintf( 'mdb-export -D %s -d %s -q %s -R "\\n" %s %s > %s 2>%s', escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($mdbPath), escapeshellarg($tableGuess), escapeshellarg($tmp), escapeshellarg($tmp . '.err') ); @shell_exec($cmd); $errOut = @is_file($tmp . '.err') ? trim((string)@file_get_contents($tmp . '.err')) : ''; if (!is_file($tmp) || filesize($tmp) === 0) { @unlink($tmp); return redirect()->back()->with('error', 'Esportazione MDB vuota. Tabella utilizzata: ' . $tableGuess . '. ' . ($errOut ? ('Dettagli: ' . mb_strimwidth($errOut, 0, 300, '…')) : '')); } // Parse CSV pipe $fh = fopen($tmp, 'r'); $headers = fgetcsv($fh, 0, '|'); $headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers ?: []); DB::beginTransaction(); try { // Dataset upsert $dataset = MigrationDataset::updateOrCreate( ['source_id' => $source->id, 'key' => 'stabili'], ['title' => 'Stabili.mdb', 'state' => 'ready', 'schema' => $headers] ); // Clear previous records MigrationRecord::where('dataset_id', $dataset->id)->delete(); $count = 0; while (($cols = fgetcsv($fh, 0, '|')) !== false) { if ($cols === [null] || $cols === false) continue; $cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols); $countH = count($headers); if (count($cols) < $countH) $cols = array_pad($cols, $countH, null); if (count($cols) > $countH) $cols = array_slice($cols, 0, $countH); $row = @array_combine($headers, $cols); if (!$row) continue; $legacy = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null)); $legacy = $legacy ? trim((string)$legacy) : null; $hash = substr(hash('sha256', json_encode($row)), 0, 64); MigrationRecord::create([ 'dataset_id' => $dataset->id, 'legacy_key' => $legacy, 'hash' => $hash, 'data' => $row, ]); $count++; } fclose($fh); @unlink($tmp); $dataset->records_count = $count; $dataset->save(); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); if (is_resource($fh)) fclose($fh); @unlink($tmp); return redirect()->back()->with('error', 'Errore ingest: ' . $e->getMessage()); } return redirect()->back()->with('success', 'Acquisizione Stabili completata: ' . $count . ' record.'); } /** * Verifica server-side l'accessibilità del file MDB. */ public function verifyMdb(Request $request) { $path = $this->normalizePath($request->input('mdb_path')); // Fallback suggerito: copia server-friendly in storage if ((!$path || !@file_exists($path)) && @file_exists('/var/www/netgescon/storage/gescon/Stabili.mdb')) { $path = '/var/www/netgescon/storage/gescon/Stabili.mdb'; } $real = @realpath($path) ?: null; $exists = $path ? @file_exists($path) : false; $readable = $exists ? @is_readable($path) : false; $size = ($exists && @is_file($path)) ? @filesize($path) : null; $owner = $exists ? @fileowner($path) : null; $group = $exists ? @filegroup($path) : null; $perms = $exists ? substr(sprintf('%o', @fileperms($path)), -4) : null; return response()->json([ 'input' => $path, 'realpath' => $real, 'exists' => (bool)$exists, 'readable' => (bool)$readable, 'size' => $size, 'owner' => $owner, 'group' => $group, 'perms' => $perms, 'cwd' => getcwd(), 'user' => get_current_user(), ]); } public function status(Request $request) { $src = MigrationSource::where('name', 'GESCON Stabili')->first(); $ds = $src ? MigrationDataset::where('source_id', $src->id)->where('key', 'stabili')->first() : null; return response()->json([ 'source' => $src, 'dataset' => $ds ? ['id' => $ds->id, 'records' => $ds->records_count] : null, ]); } /** * UI semplice per caricare/ingestione MDB in staging. */ public function ui(Request $request) { $settings = [ 'gescon.source_path' => UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon'), 'gescon.mdb_stabili' => UserSetting::get('gescon.mdb_stabili', '/mnt/gescon-archives/gescon/dbc/Stabili.mdb'), 'gescon.import_conn' => UserSetting::get('gescon.import_conn', 'gescon_import'), ]; $prefillMdb = $this->normalizePath($request->query('mdb_path')) ?: null; if ($prefillMdb) { $settings['gescon.mdb_stabili'] = $prefillMdb; } // UI: se la path configurata non esiste, proponi automaticamente la copia in storage come suggerimento if ((!$settings['gescon.mdb_stabili'] || !@file_exists($settings['gescon.mdb_stabili'])) && @file_exists('/var/www/netgescon/storage/gescon/Stabili.mdb')) { $settings['gescon.mdb_stabili'] = '/var/www/netgescon/storage/gescon/Stabili.mdb'; } $hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== ''; return view('admin.gescon-import.archive-ui', compact('settings', 'hasMdbTools')); } /** * Elenca le tabelle presenti nel file MDB. */ public function scanTables(Request $request) { $path = $this->normalizePath($request->input('mdb_path') ?: UserSetting::get('gescon.mdb_stabili')); if ((!$path || !@file_exists($path)) && @file_exists('/var/www/netgescon/storage/gescon/Stabili.mdb')) { $path = '/var/www/netgescon/storage/gescon/Stabili.mdb'; } if (!$path || !@file_exists($path)) { return response()->json(['error' => 'File MDB non trovato', 'path' => $path], 400); } $has = trim((string)@shell_exec('command -v mdb-tables')) !== ''; if (!$has) return response()->json(['error' => 'mdb-tools non disponibile'], 500); $out = @shell_exec(sprintf('mdb-tables -1 %s 2>&1', escapeshellarg($path))) ?: ''; if (!$out) return response()->json(['tables' => []]); $list = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', trim($out)) ?? []))); return response()->json(['tables' => $list, 'path' => $path]); } /** * Importa tabelle selezionate dal MDB nel DB di staging (connessione gescon_import o come configurato). */ public function ingestTablesToDb(Request $request) { $path = $this->normalizePath($request->input('mdb_path') ?: UserSetting::get('gescon.mdb_stabili')); if ((!$path || !@file_exists($path)) && @file_exists('/var/www/netgescon/storage/gescon/Stabili.mdb')) { $path = '/var/www/netgescon/storage/gescon/Stabili.mdb'; } $tables = (array)$request->input('tables', []); $conn = UserSetting::get('gescon.import_conn', 'gescon_import'); // Opzionale: mappa tabella sorgente → tabella destinazione $targets = (array)($request->input('targets', [])); $truncate = (bool)$request->boolean('truncate', true); if (!$path || !@file_exists($path)) return response()->json(['error' => 'File MDB non trovato', 'path' => $path], 400); $has = trim((string)@shell_exec('command -v mdb-export')) !== ''; if (!$has) return response()->json(['error' => 'mdb-tools non disponibile'], 500); if (empty($tables)) return response()->json(['error' => 'Nessuna tabella selezionata'], 400); $result = []; foreach ($tables as $table) { $table = trim((string)$table); if ($table === '') continue; $targetName = null; if (isset($targets[$table]) && is_string($targets[$table]) && trim($targets[$table]) !== '') { $targetName = trim($targets[$table]); } $count = $this->importSingleTable($conn, $path, $table, $targetName, $truncate); $result[] = ['table' => $table, 'target' => $targetName ?: ('mdb_' . \Illuminate\Support\Str::snake($table)), 'rows' => $count]; } return response()->json(['ok' => true, 'connection' => $conn, 'result' => $result]); } /** * Import rapido per singolo_anno.mdb (stabile/anno) su tabelle di staging standard. * Supporta multi-anno leggendo le sottocartelle sotto /. */ public function quickLoad(Request $request) { $stabile = $this->normalizePath($request->input('stabile')); $year = $this->normalizePath($request->input('year')); $allYears = $request->boolean('all_years'); $tables = array_values(array_filter((array)$request->input('tables', []))); $truncate = $request->boolean('truncate', true); if (!$stabile) { return response()->json(['ok' => false, 'error' => 'Codice stabile richiesto.'], 422); } if (empty($tables)) { return response()->json(['ok' => false, 'error' => 'Seleziona almeno una tabella.'], 422); } $allowedTables = [ 'condomin', 'dett_tab', 'voc_spe', 'cre_deb_preced', 'operazioni', 'incassi', 'rate', 'giri_conti', 'straordinarie', 'dett_pers', 'detrazioni_fiscali', ]; $tables = array_values(array_filter($tables, fn ($t) => in_array($t, $allowedTables, true))); if (empty($tables)) { return response()->json(['ok' => false, 'error' => 'Nessuna tabella valida selezionata.'], 422); } $base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon'); $stableDir = rtrim((string)$base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $stabile; $years = []; if ($allYears) { if (is_dir($stableDir)) { foreach (glob($stableDir . DIRECTORY_SEPARATOR . '*') as $dir) { if (!is_dir($dir)) continue; $mdb = $dir . DIRECTORY_SEPARATOR . 'singolo_anno.mdb'; if (is_file($mdb)) { $years[] = basename($dir); } } sort($years, SORT_STRING); } if (empty($years)) { return response()->json(['ok' => false, 'error' => 'Nessuna cartella anno con singolo_anno.mdb trovata.'], 404); } } else { if (!$year) { return response()->json(['ok' => false, 'error' => 'Anno/cartella richiesta o seleziona "tutti gli anni".'], 422); } $years = [$year]; } $results = []; foreach ($years as $idx => $yr) { $mdb = $stableDir . DIRECTORY_SEPARATOR . $yr . DIRECTORY_SEPARATOR . 'singolo_anno.mdb'; if (!is_file($mdb)) { $results[] = ['year' => $yr, 'error' => 'singolo_anno.mdb non trovato', 'path' => $mdb]; continue; } foreach ($tables as $t) { $args = [ '--mdb' => $mdb, '--table' => $t, '--stabile' => $stabile, '--legacy-year' => $yr, ]; if ($truncate && $idx === 0) { $args['--truncate'] = true; } Artisan::call('gescon:load-mdb', $args); $results[] = [ 'year' => $yr, 'table' => $t, 'path' => $mdb, 'output' => trim((string)Artisan::output()), ]; } } return response()->json(['ok' => true, 'stabile' => $stabile, 'years' => $years, 'results' => $results]); } protected function importSingleTable(string $conn, string $mdbPath, string $tableName, ?string $targetName = null, bool $truncate = true): int { // Esporta CSV temporaneo $tmp = tempnam(sys_get_temp_dir(), 'mdbi_'); $cmd = sprintf( 'mdb-export -D %s -d %s -q %s -R "\\n" %s %s > %s 2>%s', escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($mdbPath), escapeshellarg($tableName), escapeshellarg($tmp), escapeshellarg($tmp . '.err') ); @shell_exec($cmd); if (!@is_file($tmp) || @filesize($tmp) === 0) { @unlink($tmp); return 0; } $fh = fopen($tmp, 'r'); if (!$fh) { @unlink($tmp); return 0; } $headers = fgetcsv($fh, 0, '|'); if (!$headers) { fclose($fh); @unlink($tmp); return 0; } $headers = array_map(function ($h) { return strtolower(Str::snake(trim((string)$h))); }, $headers); // Determina tabella destinazione (personalizzata o prefisso mdb_) $stagingTable = $targetName ? Str::snake($targetName) : ('mdb_' . Str::snake($tableName)); // Crea/aggiorna tabella con colonne testo per ogni header if (!Schema::connection($conn)->hasTable($stagingTable)) { Schema::connection($conn)->create($stagingTable, function (Blueprint $t) use ($headers) { $t->bigIncrements('id'); $t->string('row_hash', 64)->index(); $t->string('legacy_key')->nullable()->index(); foreach ($headers as $col) { $safe = substr($col, 0, 191); if ($safe === 'id') $safe = 'col_id'; $t->text($safe)->nullable(); } $t->timestamps(); }); } else { // Aggiungi eventuali nuove colonne $existing = Schema::connection($conn)->getColumnListing($stagingTable); $toAdd = array_values(array_diff($headers, $existing)); if (!empty($toAdd)) { Schema::connection($conn)->table($stagingTable, function (Blueprint $t) use ($toAdd) { foreach ($toAdd as $col) { $safe = substr($col, 0, 191); if ($safe === 'id') $safe = 'col_id'; $t->text($safe)->nullable(); } }); } } // Pulisci tabella prima di importare (opzionale, default true) if ($truncate) { DB::connection($conn)->table($stagingTable)->truncate(); } $count = 0; while (($cols = fgetcsv($fh, 0, '|')) !== false) { if ($cols === [null]) continue; $cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols); $data = $cols; // Allinea a headers $countH = count($headers); if (count($cols) < $countH) $cols = array_pad($cols, $countH, null); if (count($cols) > $countH) $cols = array_slice($cols, 0, $countH); $row = @array_combine($headers, $cols); if (!$row) continue; $legacy = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? ($row['id'] ?? null))); $hash = substr(hash('sha256', json_encode($row)), 0, 64); $rowToInsert = array_merge($row, [ 'row_hash' => $hash, 'legacy_key' => $legacy ? (string)$legacy : null, 'created_at' => now(), 'updated_at' => now(), ]); // Upsert semplice basato su row_hash se non si tronca if ($truncate) { DB::connection($conn)->table($stagingTable)->insert($rowToInsert); } else { $exists = DB::connection($conn)->table($stagingTable)->where('row_hash', $hash)->exists(); if (!$exists) { DB::connection($conn)->table($stagingTable)->insert($rowToInsert); } else { DB::connection($conn)->table($stagingTable)->where('row_hash', $hash)->update(array_merge($row, [ 'legacy_key' => $rowToInsert['legacy_key'], 'updated_at' => now(), ])); } } $count++; } fclose($fh); @unlink($tmp); return $count; } }