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

422 lines
17 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\GestioneContabile;
use App\Models\Stabile;
use Illuminate\Console\Command;
use Symfony\Component\Process\Process;
class ImportGestioniFromLegacy extends Command
{
protected $signature = 'gescon:import-gestioni \
{--stabile= : Codice stabile a 4 cifre (es. 0019)} \
{--root=/mnt/gescon-archives/gescon : Root archivi legacy} \
{--stabili-mdb= : Path al master MDB (Stabili/cartellestabili)} \
{--tenant=default : Tenant ID da usare} \
{--force-update-months : Aggiorna i mesi anche se la gestione esiste} \
{--dry-run : Non scrivere, solo report}';
protected $description = 'Crea le gestioni contabili (per anno) leggendo MDB legacy: mesi da Stabili (ORD_RATA_/RIS_RATA_), anni da generale_stabile.mdb';
public function handle(): int
{
$stabileCode = (string)$this->option('stabile');
if (!$stabileCode) {
$this->error('Specificare --stabile=0000');
return 1;
}
$stabileCode = str_pad($stabileCode, 4, '0', STR_PAD_LEFT);
$root = rtrim((string)$this->option('root') ?: '/mnt/gescon-archives/gescon', '/');
$tenant = (string)$this->option('tenant') ?: 'default';
$dry = (bool)$this->option('dry-run');
// Heuristics per master MDB (Stabili)
$stabiliCandidates = [];
if ($this->option('stabili-mdb')) {
$stabiliCandidates[] = (string)$this->option('stabili-mdb');
}
foreach (['cartellestabili_e_anni.mdb', 'Stabili.mdb', 'stabili.mdb'] as $n) {
$stabiliCandidates[] = $root . '/' . $n;
}
$stabiliMdb = null;
foreach ($stabiliCandidates as $cand) {
if (is_file($cand)) {
$stabiliMdb = $cand;
break;
}
}
if (!$stabiliMdb) {
$this->warn('Master MDB Stabili non trovato; continuerò usando convenzione directory.');
}
// 1) Leggi mesi da Stabili (ORD_RATA_*, RIS_RATA_*) e risolvi directory
$ordMesi = [];
$risMesi = [];
$stableDir = $root . '/' . $stabileCode;
if ($stabiliMdb) {
[$row, $headers] = $this->findRowInMdb($stabiliMdb, ['cod', 'codice', 'cod_stabile', 'stabile'], $stabileCode);
if ($row) {
$hmap = array_change_key_case($headers, CASE_LOWER);
// Estrai mesi
foreach ($hmap as $col => $idx) {
if (str_starts_with($col, 'ord_rata_')) {
$num = (int)preg_replace('/[^0-9]/', '', substr($col, strlen('ord_rata_')));
if ($num >= 1 && $num <= 12) {
if (self::truthy($row[$idx] ?? null)) $ordMesi[] = $num;
}
} elseif (str_starts_with($col, 'ris_rata_')) {
$num = (int)preg_replace('/[^0-9]/', '', substr($col, strlen('ris_rata_')));
if ($num >= 1 && $num <= 12) {
if (self::truthy($row[$idx] ?? null)) $risMesi[] = $num;
}
}
}
// Directory se presente (campi possibili)
foreach (['directory', 'dir', 'cartella', 'path', 'folder', 'dir_stabile'] as $k) {
if (isset($hmap[$k])) {
$val = trim((string)($row[$hmap[$k]] ?? ''));
if ($val) {
$stableDir = $root . '/' . trim($val, '/');
break;
}
}
}
}
}
if (!is_dir($stableDir)) {
$this->warn("Directory stabile non esiste: {$stableDir}; userò {$root}/{$stabileCode}");
$stableDir = $root . '/' . $stabileCode;
}
// 2) Determina anni gestione da generale_stabile.mdb o fallback
$generale = $stableDir . '/generale_stabile.mdb';
$anni = [];
if (is_file($generale)) {
$anni = $this->extractAnniFromGenerale($generale);
}
if (!$anni) {
// Fallback: prova da singolo_anno.mdb (incassi anno_rif)
foreach (glob($stableDir . '/*/singolo_anno.mdb') as $mdb) {
$anni = array_values(array_unique(array_merge($anni, $this->extractAnniFromSingoloAnno($mdb))));
}
}
sort($anni);
if (!$anni) {
$this->warn('Nessun anno trovato; userò anno corrente.');
$anni = [(int)date('Y')];
}
// 2b) Fallback mesi per anno da singolo_anno.mdb (tabella rate)
$mesiPerAnno = [];
if (!$ordMesi || !$risMesi) {
foreach (glob($stableDir . '/*/singolo_anno.mdb') as $mdb) {
$estratti = $this->extractMesiPerAnnoFromSingoloAnno($mdb);
foreach ($estratti as $y => $tipi) {
if (!isset($mesiPerAnno[$y])) $mesiPerAnno[$y] = ['O' => [], 'R' => []];
$mesiPerAnno[$y]['O'] = array_values(array_unique(array_merge($mesiPerAnno[$y]['O'], $tipi['O'] ?? [])));
$mesiPerAnno[$y]['R'] = array_values(array_unique(array_merge($mesiPerAnno[$y]['R'], $tipi['R'] ?? [])));
}
}
}
$this->info("Stabile {$stabileCode} → dir: {$stableDir}");
$this->line('Mesi Ordinaria (master Stabili): ' . json_encode($ordMesi) . ' | Mesi Riscaldamento: ' . json_encode($risMesi));
if ($mesiPerAnno) {
$this->line('Mesi per anno da rate (fallback): ' . json_encode($mesiPerAnno));
}
$this->line('Anni trovati: ' . implode(', ', $anni));
if ($dry) {
$this->warn('Dry-run attivo: non scrivo su database.');
return 0;
}
// 3) Crea/aggiorna gestioni per ciascun anno
$created = 0;
$skipped = 0;
$updated = 0;
// Recupera Stabile record
$stabileModel = Stabile::where('codice_stabile', $stabileCode)->first();
$stabileId = $stabileModel?->id;
$forceUpdate = (bool)$this->option('force-update-months');
foreach ($anni as $anno) {
foreach (['ordinaria' => $ordMesi, 'riscaldamento' => $risMesi] as $tipo => $mesi) {
// Se mesi vuoti dal master, prova fallback per-anno
if (!$mesi) {
$mesi = $tipo === 'ordinaria'
? ($mesiPerAnno[$anno]['O'] ?? [])
: ($mesiPerAnno[$anno]['R'] ?? []);
}
$exists = GestioneContabile::where('tenant_id', $tenant)
->when($stabileId, fn($q) => $q->where('stabile_id', $stabileId))
->where('anno_gestione', $anno)
->where('tipo_gestione', $tipo)
->first();
if ($exists) {
// Aggiorna mesi se forzato o vuoti
$campo = $tipo === 'ordinaria' ? 'mesi_rate_ordinaria' : 'mesi_rate_riscaldamento';
$valEsist = (array)($exists->{$campo} ?? []);
if ($forceUpdate || (empty($valEsist) && !empty($mesi))) {
$exists->{$campo} = array_values(array_unique(array_map('intval', $mesi)));
if (!$dry) {
$exists->save();
}
$updated++;
} else {
$skipped++;
}
continue;
}
$gestione = new GestioneContabile();
$gestione->tenant_id = $tenant;
$gestione->stabile_id = $stabileId;
$gestione->anno_gestione = $anno;
$gestione->tipo_gestione = $tipo;
$gestione->denominazione = ucfirst($tipo) . ' ' . $anno;
$gestione->data_inizio = sprintf('%04d-01-01', $anno);
$gestione->data_fine = sprintf('%04d-12-31', $anno);
$gestione->stato = 'aperta';
$gestione->protocollo_prefix = strtoupper(substr($tipo, 0, 1)) . $anno;
$gestione->ultimo_protocollo = 0;
if ($tipo === 'ordinaria') {
$gestione->mesi_rate_ordinaria = array_values(array_unique(array_map('intval', $mesi)));
}
if ($tipo === 'riscaldamento') {
$gestione->mesi_rate_riscaldamento = array_values(array_unique(array_map('intval', $mesi)));
}
$gestione->gestione_attiva = true;
$gestione->codice_archivio_legacy = $stableDir;
if (!$dry) {
$gestione->save();
}
$created++;
}
}
$this->info("Gestioni create: {$created}, aggiornate: {$updated}, saltate: {$skipped}");
return 0;
}
private static function truthy($v): bool
{
$s = strtolower(trim((string)$v));
return $s !== '' && !in_array($s, ['0', 'no', 'false', 'off', 'n'], true);
}
/**
* Trova una riga nel MDB master dove un campo (tra keys) corrisponde al valore cercato
* @return array{0: array|null, 1: array<string,int>} row e mappa header->index
*/
private function findRowInMdb(string $mdbPath, array $keys, string $value): array
{
$table = $this->resolveTable($mdbPath, ['stabili', 'Stabili', 'STABILI']);
if (!$table) return [null, []];
$csv = $this->mdbExport($mdbPath, $table);
if ($csv === null) return [null, []];
$fh = fopen('php://temp', 'r+');
fwrite($fh, $csv);
rewind($fh);
$header = fgetcsv($fh);
if (!$header) return [null, []];
$hmap = [];
foreach ($header as $i => $h) {
$hmap[strtolower(trim((string)$h))] = $i;
}
while (($row = fgetcsv($fh)) !== false) {
foreach ($keys as $k) {
$i = $hmap[strtolower($k)] ?? null;
if ($i === null) continue;
if (str_pad(trim((string)($row[$i] ?? '')), 4, '0', STR_PAD_LEFT) === $value) {
return [$row, $hmap];
}
}
}
return [null, $hmap];
}
private function extractAnniFromGenerale(string $mdbPath): array
{
$years = [];
$table = $this->resolveTable($mdbPath, ['gestioni', 'gestione', 'esercizi', 'Esercizi', 'Anni']);
if (!$table) return $years;
$csv = $this->mdbExport($mdbPath, $table);
if ($csv === null) return $years;
$fh = fopen('php://temp', 'r+');
fwrite($fh, $csv);
rewind($fh);
$header = fgetcsv($fh);
if (!$header) return $years;
$hmap = [];
foreach ($header as $i => $h) {
$hmap[strtolower(trim((string)$h))] = $i;
}
while (($row = fgetcsv($fh)) !== false) {
$y = null;
foreach (['anno', 'esercizio', 'str_anno'] as $k) {
if (isset($hmap[$k])) {
$val = trim((string)($row[$hmap[$k]] ?? ''));
if ($val !== '' && is_numeric($val)) {
$y = (int)$val;
break;
}
}
}
if ($y === null) {
foreach (['data_inizio', 'inizio', 'dal'] as $k) {
if (isset($hmap[$k])) {
$val = trim((string)($row[$hmap[$k]] ?? ''));
if ($val && preg_match('/^(\d{4})-/', $val, $m)) {
$y = (int)$m[1];
break;
}
}
}
}
if ($y !== null) {
$years[] = $y;
}
}
return array_values(array_unique($years));
}
private function extractAnniFromSingoloAnno(string $mdbPath): array
{
$table = $this->resolveTable($mdbPath, ['incassi', 'Incassi']);
if (!$table) return [];
$csv = $this->mdbExport($mdbPath, $table);
if ($csv === null) return [];
$fh = fopen('php://temp', 'r+');
fwrite($fh, $csv);
rewind($fh);
$header = fgetcsv($fh);
if (!$header) return [];
$hmap = [];
foreach ($header as $i => $h) {
$hmap[strtolower(trim((string)$h))] = $i;
}
$years = [];
while (($row = fgetcsv($fh)) !== false) {
$ar = $hmap['anno_rif'] ?? null;
if ($ar !== null) {
$val = trim((string)($row[$ar] ?? ''));
if ($val && is_numeric($val)) {
$years[] = (int)$val;
continue;
}
}
$dt = $hmap['dt_empag'] ?? null;
if ($dt !== null) {
$val = trim((string)($row[$dt] ?? ''));
if ($val && preg_match('/^(\d{4})-/', $val, $m)) {
$years[] = (int)$m[1];
}
}
}
return array_values(array_unique($years));
}
private function resolveTable(string $mdbPath, array $candidates): ?string
{
$bin = trim((string)@shell_exec('command -v mdb-tables')) ?: '/usr/bin/mdb-tables';
$list = @shell_exec(escapeshellcmd($bin) . ' -1 ' . escapeshellarg($mdbPath));
if (!$list) return null;
$names = array_filter(array_map('trim', explode("\n", $list)));
foreach ($candidates as $c) {
foreach ($names as $n) {
if (strcasecmp($n, $c) === 0) return $n;
}
}
// fallback: first contains substring
foreach ($candidates as $c) {
foreach ($names as $n) {
if (stripos($n, $c) !== false) return $n;
}
}
return null;
}
private function mdbExport(string $mdbPath, string $table): ?string
{
$bin = trim((string)@shell_exec('command -v mdb-export')) ?: '/usr/bin/mdb-export';
$p = new Process([$bin, '-D', '%Y-%m-%d', $mdbPath, $table]);
$p->setTimeout(120);
$p->run();
if (!$p->isSuccessful()) return null;
return $p->getOutput();
}
/** Estrae mesi per anno dalla tabella rate del singolo_anno.mdb: ritorna [anno=>['O'=>[mesi],'R'=>[mesi]]] */
private function extractMesiPerAnnoFromSingoloAnno(string $mdbPath): array
{
$out = [];
$table = $this->resolveTable($mdbPath, ['rate', 'Rate']);
if (!$table) return $out;
$csv = $this->mdbExport($mdbPath, $table);
if ($csv === null) return $out;
$fh = fopen('php://temp', 'r+');
fwrite($fh, $csv);
rewind($fh);
$header = fgetcsv($fh);
if (!$header) return $out;
$hmap = [];
foreach ($header as $i => $h) {
$hmap[strtolower(trim((string)$h))] = $i;
}
while (($row = fgetcsv($fh)) !== false) {
$tipo = null;
if (isset($hmap['o_r_s'])) {
$tipo = strtoupper(trim((string)($row[$hmap['o_r_s']] ?? '')));
}
if (!in_array($tipo, ['O', 'R'], true)) continue;
$mese = null;
if (isset($hmap['n_mese'])) {
$val = trim((string)($row[$hmap['n_mese']] ?? ''));
if ($val !== '' && is_numeric($val)) {
$mese = max(1, min(12, (int)$val));
}
}
if ($mese === null) continue;
$anno = null;
foreach (['str_anno'] as $k) {
if (isset($hmap[$k])) {
$s = trim((string)($row[$hmap[$k]] ?? ''));
if ($s && preg_match('/(\d{4})/', $s, $m)) {
$anno = (int)$m[1];
break;
}
}
}
if ($anno === null) {
foreach (['dt_empag', 'dt1_da', 'data_emissione'] as $k) {
if (isset($hmap[$k])) {
$s = trim((string)($row[$hmap[$k]] ?? ''));
if ($s && preg_match('/^(\d{4})-/', $s, $m)) {
$anno = (int)$m[1];
break;
}
}
}
}
if ($anno === null) continue;
if (!isset($out[$anno])) {
$out[$anno] = ['O' => [], 'R' => []];
}
if ($tipo === 'O') {
$out[$anno]['O'][] = $mese;
} else {
$out[$anno]['R'][] = $mese;
}
}
// Unique/sorted
foreach ($out as $y => $tipi) {
$out[$y]['O'] = array_values(array_unique(array_filter($out[$y]['O'], fn($m) => $m >= 1 && $m <= 12)));
sort($out[$y]['O']);
$out[$y]['R'] = array_values(array_unique(array_filter($out[$y]['R'], fn($m) => $m >= 1 && $m <= 12)));
sort($out[$y]['R']);
}
return $out;
}
}