481 lines
21 KiB
PHP
481 lines
21 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\MigrationSource;
|
|
use App\Models\MigrationDataset;
|
|
use App\Models\MigrationRecord;
|
|
use App\Models\UserSetting;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Str;
|
|
|
|
class MigrationArchiveController extends Controller
|
|
{
|
|
/** Normalize user-provided path by trimming and removing hidden Unicode spaces/control chars */
|
|
protected function normalizePath(?string $path): string
|
|
{
|
|
$p = trim((string)$path);
|
|
// remove common zero-width/non-breaking/control spaces
|
|
$p = preg_replace('/[\x00-\x1F\x7F\x{00A0}\x{2000}-\x{200B}\x{202F}\x{FEFF}]/u', '', $p);
|
|
return $p;
|
|
}
|
|
public function __construct()
|
|
{
|
|
$this->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 <source_path>/<stabile>.
|
|
*/
|
|
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;
|
|
}
|
|
}
|