491 lines
18 KiB
PHP
491 lines
18 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 (['Stabili.mdb', 'stabili.mdb', 'cartellestabili_e_anni.mdb'] as $n) {
|
|
$stabiliCandidates[] = $root . '/' . $n;
|
|
$stabiliCandidates[] = $root . '/dbc/' . $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', 'nome_directory', 'codice_directory', 'internet_cod_stab'], $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', 'nome_directory', 'codice_directory', 'internet_cod_stab'] 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;
|
|
}
|
|
}
|