netgescon-day0/app/Services/GesconImport/LegacyArchiveService.php

917 lines
47 KiB
PHP

<?php
namespace App\Services\GesconImport;
use App\Models\MigrationDataset;
use App\Models\MigrationRecord;
use App\Models\UserSetting;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class LegacyArchiveService
{
// Public API used by controllers
public function scanLegacyYears(string $legacy): array
{
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy;
if (!is_dir($dir)) return [];
$years = [];
foreach (scandir($dir) as $f) {
if ($f === '.' || $f === '..') continue;
if (preg_match('/^\d{4}$/', $f) && is_file($dir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
$years[] = $f;
}
}
sort($years, SORT_STRING);
return $years;
}
public function gestioniYear(string $legacy, $annoOrFolder): array
{
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$resolve = $this->resolveAnnoFolder($legacy, $annoOrFolder);
$folder = $resolve['folder'] ?? (is_numeric($annoOrFolder) ? str_pad((string)intval($annoOrFolder), 4, '0', STR_PAD_LEFT) : (string)$annoOrFolder);
$anno = $resolve['anno'] ?? (is_numeric($annoOrFolder) && (int)$annoOrFolder >= 1900 ? (int)$annoOrFolder : null);
$period = $resolve['period'] ?? null;
$dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy . DIRECTORY_SEPARATOR . $folder;
$singoloAnno = $dir . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
$generale = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy . DIRECTORY_SEPARATOR . 'generale_stabile.mdb';
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
$ordinaria = null;
$riscaldamento = null;
$straord = [];
if ($hasMdbTools && is_file($singoloAnno) && is_readable($singoloAnno)) {
try {
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$tablesRaw = @shell_exec(sprintf('%s -1 %s 2>/dev/null', 'mdb-tables', escapeshellarg($singoloAnno))) ?: '';
$tableCandidates = [];
foreach (preg_split('/\r?\n/', trim($tablesRaw)) as $t) {
if ($t === '') continue;
if (preg_match('/(gest|rate|piano)/i', $t)) $tableCandidates[] = trim($t);
}
$table = $tableCandidates[0] ?? null;
if ($table) {
$cmd = sprintf(
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
escapeshellarg($binExport),
escapeshellarg('%Y-%m-%d'),
escapeshellarg('|'),
escapeshellarg('"'),
escapeshellarg($singoloAnno),
escapeshellarg($table),
escapeshellarg($tmp)
);
@shell_exec($cmd);
if (@filesize($tmp) > 0) {
$fh = fopen($tmp, 'r');
$headers = fgetcsv($fh, 0, '|');
$headers = $headers ? array_map(fn($h) => strtolower(trim((string)$h)), $headers) : [];
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if ($cols === [null]) continue;
if (!$headers) break;
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$row = @array_combine($headers, $cols) ?: [];
$tipo = $row['tipo'] ?? ($row['tipo_gestione'] ?? null);
$isRisc = false;
if ($tipo) {
$isRisc = preg_match('/ris|risc|cald/i', $tipo);
}
$rate = [];
foreach ($row as $k => $v) {
if (preg_match('/^(rata|rata_)(\d{1,2})_(ord|ris)/i', $k, $m)) {
$num = (int)$m[2];
$flag = (string)$v !== '0' && $v !== '' && $v !== null;
if ($flag) $rate[$num] = 1;
} elseif (preg_match('/^(ord|ris)_rata_(\d{1,2})$/i', $k, $m)) {
$num = (int)$m[2];
$flag = (string)$v !== '0' && $v !== '' && $v !== null;
if ($flag) $rate[$num] = 1;
$isRisc = strtolower($m[1]) === 'ris';
}
}
$gest = ['anno' => $anno, 'stato' => ($row['stato'] ?? $row['stato_gestione'] ?? 'aperta'), 'rate' => $rate];
if ($isRisc) {
if (!$riscaldamento) $riscaldamento = $gest;
} else {
if (!$ordinaria) $ordinaria = $gest;
}
if ($ordinaria && $riscaldamento) break;
}
fclose($fh);
}
}
@unlink($tmp);
} catch (\Throwable $e) {
}
}
if ($hasMdbTools && is_file($generale) && is_readable($generale)) {
try {
$tmpS = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$tablesRaw = @shell_exec(sprintf('%s -1 %s 2>/dev/null', 'mdb-tables', escapeshellarg($generale))) ?: '';
$cand = null;
foreach (preg_split('/\r?\n/', trim($tablesRaw)) as $t) {
if (preg_match('/(straord|lavori|interventi)/i', $t)) {
$cand = trim($t);
break;
}
}
if ($cand) {
$cmd = sprintf(
'%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null',
escapeshellarg($binExport),
escapeshellarg('%Y-%m-%d'),
escapeshellarg('|'),
escapeshellarg('"'),
escapeshellarg($generale),
escapeshellarg($cand),
escapeshellarg($tmpS)
);
@shell_exec($cmd);
if (@filesize($tmpS) > 0) {
$fh = fopen($tmpS, 'r');
$headers = fgetcsv($fh, 0, '|');
$headers = $headers ? array_map(fn($h) => strtolower(trim((string)$h)), $headers) : [];
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if (!$headers) break;
if ($cols === [null]) continue;
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$row = @array_combine($headers, $cols) ?: [];
$dataInizio = $row['data'] ?? ($row['data_inizio'] ?? null);
$yr = null;
if ($dataInizio && preg_match('/^(\d{4})/', $dataInizio, $m)) $yr = (int)$m[1];
if ($yr && $yr != $anno) continue;
$descr = $row['descrizione'] ?? ($row['lavoro'] ?? ($row['oggetto'] ?? 'Straordinaria'));
$rateTot = (int)($row['rate_totali'] ?? ($row['num_rate'] ?? 0));
$rateEmesse = (int)($row['rate_emesse'] ?? ($row['rate_emessa'] ?? 0));
$straord[] = ['id' => $row['id'] ?? ($row['codice'] ?? null), 'descrizione' => $descr, 'data_inizio' => $dataInizio, 'data_fine' => $row['data_fine'] ?? null, 'rate_totali' => $rateTot, 'rate_emesse' => $rateEmesse];
if (count($straord) >= 50) break;
}
fclose($fh);
}
@unlink($tmpS);
}
} catch (\Throwable $e) {
}
}
$meta = ['path_singolo_anno' => (is_file($singoloAnno) ? $singoloAnno : null), 'path_generale' => (is_file($generale) ? $generale : null), 'folder' => $folder, 'period' => $period];
return [
'ok' => true,
'anno' => $anno,
'ordinaria' => $ordinaria,
'riscaldamento' => $riscaldamento,
'straordinarie' => array_map(function ($s) {
$s['periodicita'] = $s['periodicita'] ?? null;
if (isset($s['data_inizio']) && preg_match('/^(\d{4})-(\d{2})/', $s['data_inizio'], $m)) {
$s['anno_inizio'] = (int)$m[1];
$s['mese_inizio'] = (int)$m[2];
}
$s['codice'] = $s['id'] ?? null;
return $s;
}, $straord),
'meta' => $meta,
];
}
public function unitaYear(string $legacy, $annoOrFolder, int $limit = 20): array
{
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$resolve = $this->resolveAnnoFolder($legacy, $annoOrFolder);
$folder = $resolve['folder'] ?? (is_numeric($annoOrFolder) ? str_pad((string)intval($annoOrFolder), 4, '0', STR_PAD_LEFT) : (string)$annoOrFolder);
$anno = $resolve['anno'] ?? (is_numeric($annoOrFolder) && (int)$annoOrFolder >= 1900 ? (int)$annoOrFolder : null);
$period = $resolve['period'] ?? null;
$dir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy . DIRECTORY_SEPARATOR . $folder;
$singoloAnno = $dir . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '' && trim((string)@shell_exec('command -v mdb-tables')) !== '';
if (!$hasMdbTools || !is_file($singoloAnno) || !is_readable($singoloAnno)) {
try {
$primaryConn = UserSetting::get('gescon.import_conn', 'gescon_import');
$connCandidates = array_values(array_unique([$primaryConn, 'gescon_import_00000000']));
foreach ($connCandidates as $conn) {
$candidates = ['condomin', 'condomini', 'cond', 'condo', 'unita', 'unita_immobili', 'unita_immobiliari', 'ac_condomin'];
$table = null;
foreach ($candidates as $t) {
if (Schema::connection($conn)->hasTable($t)) {
$table = $t;
break;
}
}
if ($table) {
$cols = Schema::connection($conn)->getColumnListing($table);
$prefer = ['cod_stabile', 'codice_stabile', 'codstab', 'cod_condominio', 'cod_cond', 'scala', 'interno', 'piano', 'cognome', 'nome', 'codice_fiscale', 'telefono', 'cellulare', 'email', 'pec', 'indirizzo_corrispondenza', 'millesimi_generali', 'millesimi_risc', 'millesimi_proprieta', 'millesimi_riscaldamento', 'millesimi_ascensore', 'proprietario', 'inquilino', 'note'];
$stableCol = null;
foreach ($cols as $c) {
$lc = strtolower($c);
if (in_array($lc, ['cod_stabile', 'codice_stabile', 'codstab', 'cod_condominio', 'codice_condominio'], true)) {
$stableCol = $c;
break;
}
}
$headers = array_values(array_unique(array_merge($prefer, $cols)));
$select = array_values(array_filter($headers, function ($c) use ($cols) {
return in_array($c, $cols, true);
}));
if (empty($select)) $select = $cols;
$select = array_slice($select, 0, 24);
$q = DB::connection($conn)->table($table)->select($select);
if ($stableCol) {
$q->where($stableCol, $legacy);
}
$rows = $q->limit(max(1, $limit))->get()->map(fn($r) => (array)$r)->toArray();
if (!empty($rows)) {
return ['ok' => true, 'legacy_code' => $legacy, 'anno' => $anno ?: null, 'headers' => $select, 'rows' => $rows, 'staging_connection' => $conn, 'staging_table' => $table];
}
}
}
} catch (\Throwable $e) {
}
return ['ok' => false, 'error' => 'Dati Unità non disponibili: singolo_anno.mdb non leggibile e staging assente'];
}
try {
$tablesRaw = @shell_exec(sprintf('%s -1 %s 2>/dev/null', 'mdb-tables', escapeshellarg($singoloAnno))) ?: '';
$cand = [];
foreach (preg_split('/\r?\n/', trim($tablesRaw)) as $t) {
$tt = trim($t);
if ($tt === '') continue;
if (preg_match('/^condomin$/i', $tt)) {
$cand = [$tt];
break;
}
if (preg_match('/condo|min|unita|unit/i', $tt)) $cand[] = $tt;
}
$table = $cand[0] ?? null;
if (!$table) return ['ok' => false, 'error' => 'Tabella condomin non trovata'];
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$bin = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$cmd = sprintf('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($bin), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($singoloAnno), escapeshellarg($table), escapeshellarg($tmp));
@shell_exec($cmd);
if (!@filesize($tmp)) return ['ok' => false, 'error' => 'Nessun dato disponibile'];
$fh = fopen($tmp, 'r');
$headers = fgetcsv($fh, 0, '|') ?: [];
$headers = array_map(fn($h) => (string)$h, $headers);
$rows = [];
$count = 0;
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if ($cols === [null]) continue;
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$row = @array_combine($headers, $cols) ?: [];
$rows[] = $row;
$count++;
if ($count >= max(1, $limit)) break;
}
fclose($fh);
@unlink($tmp);
return ['ok' => true, 'legacy_code' => $legacy, 'anno' => $anno, 'headers' => $headers, 'rows' => $rows, 'meta' => ['folder' => $folder, 'period' => $period]];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => $e->getMessage()];
}
}
public function casse(string $legacy): array
{
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$generale = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy . DIRECTORY_SEPARATOR . 'generale_stabile.mdb';
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
$out = [];
if ($hasMdbTools && is_file($generale) && is_readable($generale)) {
try {
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$tablesRaw = @shell_exec(sprintf('%s -1 %s 2>/dev/null', 'mdb-tables', escapeshellarg($generale))) ?: '';
$cand = null;
foreach (preg_split('/\r?\n/', trim($tablesRaw)) as $t) {
if (preg_match('/(casse|banche|conti|conto)/i', $t)) {
$cand = trim($t);
break;
}
}
if ($cand) {
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$cmd = sprintf('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($binExport), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($generale), escapeshellarg($cand), escapeshellarg($tmp));
@shell_exec($cmd);
if (@filesize($tmp) > 0) {
$fh = fopen($tmp, 'r');
$headers = fgetcsv($fh, 0, '|');
$headers = $headers ? array_map(fn($h) => strtolower(trim((string)$h)), $headers) : [];
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if (!$headers) break;
if ($cols === [null]) continue;
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$row = @array_combine($headers, $cols) ?: [];
$cod = trim((string)($row['codice'] ?? ($row['cod_cassa'] ?? ($row['cod'] ?? ''))));
if ($cod === '') continue;
$desc = $row['descrizione'] ?? ($row['des'] ?? ($row['cassa'] ?? ''));
$iban = $row['iban'] ?? null;
$abi = $row['abi'] ?? null;
$cab = $row['cab'] ?? null;
$out[$cod] = ['codice' => $cod, 'descrizione' => $desc, 'abi' => $abi, 'cab' => $cab, 'iban' => $iban];
}
fclose($fh);
}
@unlink($tmp);
}
} catch (\Throwable $e) {
}
}
if (empty($out)) {
try {
$primaryConn = UserSetting::get('gescon.import_conn', 'gescon_import');
$connCandidates = array_values(array_unique([$primaryConn, 'gescon_import_00000000']));
foreach ($connCandidates as $conn) {
$table = null;
foreach (['Anag_casse', 'anag_casse', 'casse', 'banche'] as $t) {
if (Schema::connection($conn)->hasTable($t)) {
$table = $t;
break;
}
}
if ($table) {
$cols = Schema::connection($conn)->getColumnListing($table);
$colCode = null;
$colDesc = null;
$colIban = null;
$colAbi = null;
$colCab = null;
foreach ($cols as $c) {
$lc = strtolower($c);
if (in_array($lc, ['codice', 'cod_cassa', 'cod'], true)) {
$colCode = $c;
break;
}
}
foreach ($cols as $c) {
$lc = strtolower($c);
if (in_array($lc, ['descrizione', 'des', 'cassa', 'banca'], true)) {
$colDesc = $c;
break;
}
}
foreach ($cols as $c) {
if (strtolower($c) === 'iban') {
$colIban = $c;
break;
}
}
foreach ($cols as $c) {
if (strtolower($c) === 'abi') {
$colAbi = $c;
break;
}
}
foreach ($cols as $c) {
if (strtolower($c) === 'cab') {
$colCab = $c;
break;
}
}
$q = DB::connection($conn)->table($table);
if (Schema::connection($conn)->hasColumn($table, 'cod_stabile')) {
$q->where('cod_stabile', $legacy);
}
$rows = $q->limit(500)->get();
foreach ($rows as $r) {
$cod = trim((string)($r->{$colCode} ?? ''));
if ($cod === '') continue;
$out[$cod] = ['codice' => $cod, 'descrizione' => (string)($r->{$colDesc} ?? ''), 'abi' => $colAbi ? (string)($r->{$colAbi} ?? '') : null, 'cab' => $colCab ? (string)($r->{$colCab} ?? '') : null, 'iban' => $colIban ? (string)($r->{$colIban} ?? '') : null];
}
if (!empty($out)) break;
}
}
} catch (\Throwable $e) {
}
}
if (!isset($out['CON'])) {
$out['CON'] = ['codice' => 'CON', 'descrizione' => 'CASSA CONTANTI', 'abi' => null, 'cab' => null, 'iban' => null];
}
$rows = array_values($out);
usort($rows, fn($a, $b) => strnatcasecmp($a['codice'], $b['codice']));
return $rows;
}
public function sampleLegacy(?string $legacy, int $index = 0): array
{
try {
$hasMdb = trim((string)@shell_exec('command -v mdb-export')) !== '';
$mdbPath = UserSetting::get('gescon.mdb_stabili');
if (!$mdbPath || !@is_file($mdbPath) || !@is_readable($mdbPath)) {
$storageFallback = '/var/www/netgescon/storage/gescon/Stabili.mdb';
if (@is_file($storageFallback) && @is_readable($storageFallback)) {
$mdbPath = $storageFallback;
}
}
if ($hasMdb && $mdbPath && is_file($mdbPath)) {
$row = $this->sampleStabileFromMdb($mdbPath, $legacy, $index);
if (!empty($row)) {
$legacyKey = $row['legacy_key'] ?? null;
unset($row['legacy_key']);
return ['ok' => true, 'index' => $index, 'data' => $row, 'legacy_key' => $legacyKey];
}
}
$ds = MigrationDataset::where('key', 'stabili')->orderByDesc('updated_at')->first();
if ($ds) {
$rec = MigrationRecord::where('dataset_id', $ds->id)->when($legacy, function ($q) use ($legacy) {
$q->where('legacy_key', $legacy);
})->orderBy('id')->skip($index)->take(1)->first();
if ($rec) {
return ['ok' => true, 'index' => $index, 'data' => (array)($rec->data ?? []), 'legacy_key' => $rec->legacy_key];
}
}
return ['ok' => false, 'error' => 'Anteprima non disponibile: nessun dataset e MDB non accessibile', 'index' => $index];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => 'Errore: ' . $e->getMessage()];
}
}
public function sampleUnita(?string $legacy, int $index = 0): array
{
try {
$rows = [];
if ($legacy && Schema::connection('gescon_import')->hasTable('condomin')) {
$rows = DB::connection('gescon_import')->table('condomin')->when($legacy, function ($q) use ($legacy) {
$q->where('cod_stabile', $legacy);
})->select(['cod_stabile', 'cod_cond', 'scala', 'interno', 'cognome', 'nome', 'codice_fiscale', 'millesimi_generali', 'millesimi_risc'])->orderBy('cod_cond')->skip($index)->take(1)->get()->map(fn($r) => (array)$r)->toArray();
}
if (empty($rows)) {
$rows[] = ['cod_stabile' => $legacy, 'cod_cond' => 'U' . str_pad((string)$index, 4, '0', STR_PAD_LEFT), 'scala' => 'A', 'interno' => '1', 'cognome' => 'Rossi', 'nome' => 'Mario', 'codice_fiscale' => 'RSSMRA80A01H501Z', 'millesimi_generali' => 45.67, 'millesimi_risc' => 12.34];
}
return ['ok' => true, 'index' => $index, 'data' => $rows[0]];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => $e->getMessage()];
}
}
public function sampleTabelle(?string $legacy, int $index = 0): array
{
return ['ok' => true, 'index' => $index, 'data' => ['cod_stabile' => $legacy, 'tabella' => 'Generali', 'voce' => 'Scala A (app1)', 'millesimi' => 45.6700, 'nord_x10' => null]];
}
public function sampleVoci(?string $legacy, int $index = 0): array
{
return ['ok' => true, 'index' => $index, 'data' => ['cod_stabile' => $legacy, 'cod_voce' => 'A' . str_pad((string)$index, 3, '0', STR_PAD_LEFT), 'descrizione' => 'Spese generali condominio', 'gruppo' => 'MANUT', 'tipo' => 'ordinaria']];
}
// Discovery helpers used by index/config views
public function scanStabiliFromMasterIndex(string $indexMdbPath, string $legacyBasePath): array
{
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
if (!is_file($indexMdbPath) || !is_readable($indexMdbPath)) return [];
$tableGuess = null;
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($indexMdbPath))) ?: '';
if ($tables) {
foreach (preg_split('/\r?\n/', trim($tables)) as $t) {
if ($t === '') continue;
if (preg_match('/(cartelle|stabil|anni)/i', $t)) {
$tableGuess = trim($t);
break;
}
}
}
if (!$tableGuess) {
$lines = array_filter(preg_split('/\r?\n/', trim($tables)));
$tableGuess = $lines ? trim(array_values($lines)[0]) : null;
}
if (!$tableGuess) return [];
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$cmd = sprintf('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($binExport), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($indexMdbPath), escapeshellarg($tableGuess), escapeshellarg($tmp));
@shell_exec($cmd);
if (!is_file($tmp) || filesize($tmp) === 0) {
@unlink($tmp);
return [];
}
$fh = fopen($tmp, 'r');
if (!$fh) {
@unlink($tmp);
return [];
}
$headers = fgetcsv($fh, 0, '|');
if (!$headers) {
fclose($fh);
@unlink($tmp);
return [];
}
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
$byCodeYears = [];
$byCodeName = [];
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if ($cols === [null]) continue;
$cols = array_map(fn($v) => is_string($v) ? trim($v) : $v, $cols);
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$row = @array_combine($headers, $cols) ?: [];
if (!$row) continue;
$code = $row['nome_directory'] ?? $row['cod_stabile'] ?? $row['internet_cod_stab'] ?? $row['codice'] ?? null;
$denom = $row['denominazione'] ?? $row['fe_denominazione'] ?? null;
$anno = $row['anno'] ?? $row['year'] ?? null;
if (!$code) continue;
$code = (string)$code;
if (!isset($byCodeYears[$code])) $byCodeYears[$code] = [];
if ($anno && preg_match('/^\d{4}$/', (string)$anno)) {
$byCodeYears[$code][(string)$anno] = true;
}
if ($denom) $byCodeName[$code] = (string)$denom;
}
fclose($fh);
@unlink($tmp);
$items = [];
foreach ($byCodeYears as $code => $yearsSet) {
$den = $byCodeName[$code] ?? null;
$years = array_keys($yearsSet);
sort($years, SORT_STRING);
$items[] = ['legacy_code' => $code, 'denominazione' => $den, 'years_count' => count($years), 'years' => $years];
}
if (empty($items)) {
$items = $this->scanLegacyCodesFromDir($legacyBasePath);
$items = $this->enrichLegacyWithArchives($items, $legacyBasePath);
}
usort($items, fn($a, $b) => strnatcasecmp((string)($a['legacy_code'] ?? ''), (string)($b['legacy_code'] ?? '')));
return $items;
}
public function scanStabiliFromMdb(string $mdbPath): array
{
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$tableGuess = 'Stabili';
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
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('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($binExport), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($mdbPath), escapeshellarg($tableGuess), escapeshellarg($tmp));
@shell_exec($cmd);
if (!is_file($tmp) || filesize($tmp) === 0) {
@unlink($tmp);
return [];
}
$fh = fopen($tmp, 'r');
if (!$fh) {
@unlink($tmp);
return [];
}
$headers = fgetcsv($fh, 0, '|');
if (!$headers) {
fclose($fh);
@unlink($tmp);
return [];
}
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
$items = [];
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;
$legacyCode = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null));
$denom = $row['denominazione'] ?? ($row['fe_denominazione'] ?? null);
if ($legacyCode) {
$items[] = ['legacy_code' => trim((string)$legacyCode), 'denominazione' => $denom ? trim((string)$denom) : null];
}
}
fclose($fh);
@unlink($tmp);
$byCode = [];
foreach ($items as $it) {
if (!empty($it['legacy_code'])) $byCode[$it['legacy_code']] = $it;
}
ksort($byCode, SORT_NATURAL);
return array_values($byCode);
}
public function sampleStabileFromMdb(string $mdbPath, ?string $legacyCode, int $index = 0): array
{
$binTables = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$tableGuess = 'Stabili';
$tables = @shell_exec(sprintf('%s -1 %s 2>/dev/null', escapeshellarg($binTables), escapeshellarg($mdbPath))) ?: '';
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('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($binExport), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($mdbPath), escapeshellarg($tableGuess), escapeshellarg($tmp));
@shell_exec($cmd);
if (!is_file($tmp) || filesize($tmp) === 0) {
@unlink($tmp);
return [];
}
$fh = fopen($tmp, 'r');
if (!$fh) {
@unlink($tmp);
return [];
}
$headers = fgetcsv($fh, 0, '|');
if (!$headers) {
fclose($fh);
@unlink($tmp);
return [];
}
$headers = array_map(fn($h) => strtolower(trim((string)$h)), $headers);
$rowData = [];
$i = 0;
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if ($cols === [null]) 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) ?: [];
$legacyKey = $row['nome_directory'] ?? ($row['internet_cod_stab'] ?? ($row['cod_stabile'] ?? null));
if ($legacyCode) {
if ((string)$legacyKey === (string)$legacyCode) {
$rowData = $row;
break;
}
} else {
if ($i++ >= $index) {
$rowData = $row;
break;
}
}
}
fclose($fh);
@unlink($tmp);
if (empty($rowData)) return [];
$rowData['legacy_key'] = (string)($rowData['nome_directory'] ?? ($rowData['internet_cod_stab'] ?? ($rowData['cod_stabile'] ?? '')));
return $rowData;
}
public function scanLegacyCodesFromDir(string $basePath): array
{
$items = [];
if (!is_dir($basePath)) return $items;
try {
$dh = opendir($basePath);
if ($dh) {
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..') continue;
$full = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
if (!is_dir($full)) continue;
$code = null;
if (preg_match('/^(\d{2,5})/', $file, $m)) {
$code = $m[1];
} elseif (preg_match('/(\d{2,5})/', $file, $m)) {
$code = $m[1];
}
$hasGenerale = is_file($full . DIRECTORY_SEPARATOR . 'generale_stabile.mdb');
$hasYears = false;
if (is_dir($full)) {
try {
$sub = opendir($full);
if ($sub) {
while (($yf = readdir($sub)) !== false) {
if ($yf === '.' || $yf === '..') continue;
if (!preg_match('/^\d{4}$/', $yf)) continue;
$ya = $full . DIRECTORY_SEPARATOR . $yf . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
if (is_file($ya)) {
$hasYears = true;
break;
}
}
closedir($sub);
}
} catch (\Throwable $e) {
}
}
if ($code || $hasGenerale || $hasYears) {
$items[] = ['legacy_code' => $code ?: $file, 'denominazione' => null];
}
}
closedir($dh);
}
} catch (\Throwable $e) {
}
$byCode = [];
foreach ($items as $it) {
$c = (string)($it['legacy_code'] ?? '');
if ($c !== '') {
$byCode[$c] = $it;
}
}
uksort($byCode, 'strnatcasecmp');
return array_values($byCode);
}
public function enrichLegacyWithArchives(array $items, string $basePath): array
{
$enriched = [];
foreach ($items as $it) {
$code = trim((string)($it['legacy_code'] ?? ''));
if ($code === '') {
$enriched[] = $it;
continue;
}
$dir = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $code;
$hasGenerale = is_file($dir . DIRECTORY_SEPARATOR . 'generale_stabile.mdb');
$yearsCount = 0;
if (is_dir($dir)) {
try {
$dh = opendir($dir);
if ($dh) {
while (($f = readdir($dh)) !== false) {
if ($f === '.' || $f === '..') continue;
if (!preg_match('/^\d{4}$/', $f)) continue;
$sub = $dir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
if (is_file($sub)) $yearsCount++;
}
closedir($dh);
}
} catch (\Throwable $e) {
}
}
$it['has_generale'] = $hasGenerale;
$it['years_count'] = $yearsCount;
$enriched[] = $it;
}
return $enriched;
}
private function resolveAnnoFolder(string $legacy, $annoOrFolder): array
{
$base = UserSetting::get('gescon.source_path', '/mnt/gescon-archives/gescon');
$legacyDir = rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $legacy;
$folder = null;
$anno = null;
// If provided a string like '0001' or '2024'
if (is_string($annoOrFolder)) {
$cand = trim($annoOrFolder);
if ($cand !== '') {
if (is_dir($legacyDir . DIRECTORY_SEPARATOR . $cand) && is_file($legacyDir . DIRECTORY_SEPARATOR . $cand . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
$folder = $cand;
} elseif (ctype_digit($cand) && strlen($cand) <= 4) {
$pad = str_pad($cand, 4, '0', STR_PAD_LEFT);
if (is_dir($legacyDir . DIRECTORY_SEPARATOR . $pad) && is_file($legacyDir . DIRECTORY_SEPARATOR . $pad . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
$folder = $pad;
}
}
if (ctype_digit($cand) && strlen($cand) === 4 && (int)$cand >= 1900) {
$anno = (int)$cand;
}
}
}
// If provided a numeric like 1 or 2024
if ($folder === null && is_numeric($annoOrFolder)) {
$n = (int)$annoOrFolder;
if ($n >= 1900) {
$yr = (string)$n;
if (is_dir($legacyDir . DIRECTORY_SEPARATOR . $yr) && is_file($legacyDir . DIRECTORY_SEPARATOR . $yr . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
$folder = $yr;
$anno = $n;
}
}
if ($folder === null) {
$pad = str_pad((string)$n, 4, '0', STR_PAD_LEFT);
if (is_dir($legacyDir . DIRECTORY_SEPARATOR . $pad) && is_file($legacyDir . DIRECTORY_SEPARATOR . $pad . DIRECTORY_SEPARATOR . 'singolo_anno.mdb')) {
$folder = $pad;
}
}
}
// If still unknown, pick first available folder containing singolo_anno.mdb
if ($folder === null && is_dir($legacyDir)) {
$cands = [];
foreach (scandir($legacyDir) as $f) {
if ($f === '.' || $f === '..') continue;
$p = $legacyDir . DIRECTORY_SEPARATOR . $f . DIRECTORY_SEPARATOR . 'singolo_anno.mdb';
if (is_file($p)) $cands[] = $f;
}
if (!empty($cands)) {
natsort($cands);
$cands = array_values($cands);
$folder = end($cands);
}
}
// Try map folder->anno from generale_stabile.mdb: table ANNI
$generale = $legacyDir . DIRECTORY_SEPARATOR . 'generale_stabile.mdb';
$hasMdbTools = trim((string)@shell_exec('command -v mdb-export')) !== '';
if ($hasMdbTools && is_file($generale) && is_readable($generale)) {
try {
$tablesRaw = @shell_exec(sprintf('%s -1 %s 2>/dev/null', 'mdb-tables', escapeshellarg($generale))) ?: '';
$anniTable = null;
foreach (preg_split('/\r?\n/', trim($tablesRaw)) as $t) {
if (preg_match('/^anni$/i', trim($t))) {
$anniTable = trim($t);
break;
}
}
if ($anniTable) {
$tmp = tempnam(sys_get_temp_dir(), 'mdbcsv_');
$binExport = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$cmd = sprintf('%s -D %s -d %s -q %s -R "\\n" %s %s > %s 2>/dev/null', escapeshellarg($binExport), escapeshellarg('%Y-%m-%d'), escapeshellarg('|'), escapeshellarg('"'), escapeshellarg($generale), escapeshellarg($anniTable), escapeshellarg($tmp));
@shell_exec($cmd);
if (@filesize($tmp) > 0) {
$fh = fopen($tmp, 'r');
$headers = fgetcsv($fh, 0, '|');
$headers = $headers ? array_map(fn($h) => strtolower(trim((string)$h)), $headers) : [];
$rows = [];
while (($cols = fgetcsv($fh, 0, '|')) !== false) {
if ($cols === [null]) continue;
if (count($cols) < count($headers)) $cols = array_pad($cols, count($headers), null);
if (count($cols) > count($headers)) $cols = array_slice($cols, 0, count($headers));
$rows[] = @array_combine($headers, $cols) ?: [];
}
fclose($fh);
// Try to find mapping by folder code
$folderCols = ['nome_dir', 'cartella', 'id_cartella', 'dir', 'directory', 'idcart', 'id_dir', 'num', 'numero'];
$annoCols = ['anno_o', 'anno', 'year'];
$startCols = ['ordinarie_dal', 'data_inizio', 'inizio', 'dal', 'data_dal'];
$endCols = ['ordinarie_al', 'data_fine', 'fine', 'al', 'data_al'];
$riscStartCols = ['riscald_dal'];
$riscEndCols = ['riscald_al'];
$folderCol = null;
$annoCol = null;
$startCol = null;
$endCol = null;
foreach ($headers as $h) {
if (in_array($h, $folderCols, true)) {
$folderCol = $h;
break;
}
}
foreach ($headers as $h) {
if (in_array($h, $annoCols, true)) {
$annoCol = $h;
break;
}
}
foreach ($headers as $h) {
if (in_array($h, $startCols, true)) {
$startCol = $h;
break;
}
}
foreach ($headers as $h) {
if (in_array($h, $endCols, true)) {
$endCol = $h;
break;
}
}
$riscStartCol = null;
$riscEndCol = null;
foreach ($headers as $h) {
if (in_array($h, $riscStartCols, true)) {
$riscStartCol = $h;
break;
}
}
foreach ($headers as $h) {
if (in_array($h, $riscEndCols, true)) {
$riscEndCol = $h;
break;
}
}
if ($folderCol || $annoCol) {
foreach ($rows as $r) {
$rFolder = isset($r[$folderCol]) ? trim((string)$r[$folderCol]) : null;
$rAnno = isset($r[$annoCol]) ? (int)preg_replace('/[^0-9]/', '', (string)$r[$annoCol]) : null;
// Normalize folder zero-padded
if ($rFolder !== null && ctype_digit($rFolder)) {
$rFolder = str_pad($rFolder, 4, '0', STR_PAD_LEFT);
}
$period = [
'ordinaria_dal' => $startCol ? ($r[$startCol] ?? null) : null,
'ordinaria_al' => $endCol ? ($r[$endCol] ?? null) : null,
'riscald_dal' => $riscStartCol ? ($r[$riscStartCol] ?? null) : null,
'riscald_al' => $riscEndCol ? ($r[$riscEndCol] ?? null) : null,
];
// If a folder is already picked but ANNO is specified and maps to a different row, prefer the ANNO mapping
if ($anno && $rAnno && $rAnno === $anno && $rFolder) {
if ($folder !== $rFolder) {
$folder = $rFolder;
}
return ['folder' => $folder, 'anno' => $anno, 'period' => $period];
}
if ($folder && $rFolder && $rFolder === $folder) {
$anno = $rAnno ?: $anno;
return ['folder' => $folder, 'anno' => $anno, 'period' => $period];
}
}
}
}
@unlink($tmp);
}
} catch (\Throwable $e) {
}
}
return ['folder' => $folder, 'anno' => $anno];
}
}