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} 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; } }