846 lines
31 KiB
PHP
Executable File
846 lines
31 KiB
PHP
Executable File
<?php
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\StabileServizio;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class ImportGesconAcquaLegacyCommand extends Command
|
|
{
|
|
protected $signature = 'gescon:import-acqua-legacy
|
|
{--stabile= : Codice stabile legacy/cartella (es. 0023)}
|
|
{--stabile-id= : ID stabile locale (opzionale)}
|
|
{--mdb= : Percorso esplicito al generale_stabile.mdb}
|
|
{--base-path=/mnt/gescon-archives/gescon : Base path archivi GESCON}
|
|
{--acqua-years= : Anni acqua da importare (csv, es. 2024 oppure 2024,2025)}
|
|
{--purge-non-target-water : Elimina letture legacy acqua fuori anni target}
|
|
{--open-years=2024,2025 : Anni da mantenere visibili/aperti}
|
|
{--no-close-gestioni : Non chiudere le gestioni precedenti agli open-years}
|
|
{--dry-run : Simula senza scrivere}
|
|
{--limit= : Limita righe acqua_dett per debug}';
|
|
|
|
protected $description = 'Importa acqua legacy (anni, acqua_gen, acqua_fatture, acqua_dett) in servizi/letture per stabile e chiude gli anni storici.';
|
|
|
|
private bool $dryRun = false;
|
|
|
|
private ?string $tariffeSkipReason = null;
|
|
|
|
public function handle(): int
|
|
{
|
|
$this->dryRun = (bool) $this->option('dry-run');
|
|
|
|
$stabile = trim((string) ($this->option('stabile') ?? ''));
|
|
$stabileIdOption = (int) ($this->option('stabile-id') ?: 0);
|
|
|
|
if ($stabile === '' && $stabileIdOption <= 0) {
|
|
$this->error('Passa almeno --stabile=0023 oppure --stabile-id=<id>.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$stabileRow = $this->resolveStabile($stabile, $stabileIdOption);
|
|
if (! $stabileRow) {
|
|
$this->error('Stabile locale non trovato. Verifica codice/id e anagrafica stabili.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$stabileId = (int) $stabileRow->id;
|
|
$stabileCode = $stabile !== '' ? $stabile : trim((string) ($stabileRow->codice_stabile ?? ''));
|
|
$mdb = $this->resolveMdbPath($stabileCode);
|
|
|
|
if (! is_file($mdb)) {
|
|
$this->error('File MDB non trovato: ' . $mdb);
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$bin = trim((string) @shell_exec('command -v mdb-export'));
|
|
if ($bin === '') {
|
|
$this->error('mdb-export non disponibile (installa mdbtools).');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info('Stabile locale: #' . $stabileId . ' [' . ($stabileRow->codice_stabile ?? 'n/d') . '] ' . ($stabileRow->denominazione ?? ''));
|
|
$this->line('MDB: ' . $mdb . ($this->dryRun ? ' (DRY RUN)' : ''));
|
|
|
|
$anniRows = $this->exportTable($mdb, ['anni', 'Anni', 'ANNI']);
|
|
$genRowsAll = $this->exportTable($mdb, ['acqua_gen', 'Acqua_Gen', 'ACQUA_GEN']);
|
|
$fattRowsAll = $this->exportTable($mdb, ['acqua_fatture', 'Acqua_Fatture', 'ACQUA_FATTURE']);
|
|
$dettRowsAll = $this->exportTable($mdb, ['acqua_dett', 'Acqua_Dett', 'ACQUA_DETT']);
|
|
|
|
$targetAcquaYears = $this->parseYearsCsv((string) ($this->option('acqua-years') ?? ''));
|
|
$allowedRifUte = [];
|
|
$genRows = $genRowsAll;
|
|
if (! empty($targetAcquaYears)) {
|
|
$genRows = array_values(array_filter($genRowsAll, function (array $row) use ($targetAcquaYears): bool {
|
|
$year = $this->extractYear(trim((string) ($row['rif_anno'] ?? '')));
|
|
return $year !== null && in_array((int) $year, $targetAcquaYears, true);
|
|
}));
|
|
|
|
foreach ($genRows as $row) {
|
|
$rif = trim((string) ($row['rif_ute'] ?? ''));
|
|
if ($rif !== '') {
|
|
$allowedRifUte[$rif] = true;
|
|
}
|
|
}
|
|
} else {
|
|
foreach ($genRows as $row) {
|
|
$rif = trim((string) ($row['rif_ute'] ?? ''));
|
|
if ($rif !== '') {
|
|
$allowedRifUte[$rif] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$fattRows = array_values(array_filter($fattRowsAll, function (array $row) use ($allowedRifUte): bool {
|
|
$rif = trim((string) ($row['rif_ute'] ?? ''));
|
|
return $rif !== '' && isset($allowedRifUte[$rif]);
|
|
}));
|
|
|
|
$dettRows = array_values(array_filter($dettRowsAll, function (array $row) use ($allowedRifUte): bool {
|
|
$rif = trim((string) ($row['rif_ute'] ?? ''));
|
|
return $rif !== '' && isset($allowedRifUte[$rif]);
|
|
}));
|
|
|
|
if (empty($dettRows)) {
|
|
$this->warn('Nessun record in acqua_dett: nulla da importare.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$limit = (int) ($this->option('limit') ?: 0);
|
|
if ($limit > 0) {
|
|
$dettRows = array_slice($dettRows, 0, $limit);
|
|
}
|
|
|
|
$servizio = $this->ensureAcquaServizio($stabileId, $stabileCode);
|
|
if (! $servizio) {
|
|
$this->error('Impossibile creare/risolvere il servizio acqua.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->line('Servizio acqua: #' . (int) $servizio->id . ' ' . ($servizio->nome ?? 'acqua'));
|
|
|
|
$purgedWaterRows = 0;
|
|
if ((bool) $this->option('purge-non-target-water') && ! empty($allowedRifUte)) {
|
|
$purgedWaterRows = $this->purgeLegacyLettureOutsideRifUte($stabileId, (int) $servizio->id, array_keys($allowedRifUte));
|
|
}
|
|
|
|
$genByRif = $this->groupByKey($genRows, 'rif_ute');
|
|
$fattByRif = $this->groupByKey($fattRows, 'rif_ute');
|
|
|
|
$unitMaps = $this->buildUnitaMaps($stabileId);
|
|
$lettureStats = $this->importLetture(
|
|
$stabileId,
|
|
(int) $servizio->id,
|
|
$dettRows,
|
|
$genByRif,
|
|
$fattByRif,
|
|
$unitMaps['byLegacyCond'] ?? [],
|
|
$unitMaps['byScalaInterno'] ?? []
|
|
);
|
|
|
|
$tariffeStats = $this->importTariffeDaAcquaGen($stabileId, (int) $servizio->id, $genRows, $fattByRif);
|
|
|
|
$closedCount = 0;
|
|
if (! (bool) $this->option('no-close-gestioni')) {
|
|
$closedCount = $this->closeHistoricGestioni($stabileId, (string) ($this->option('open-years') ?? '2024,2025'));
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->info('Import ACQUA completato');
|
|
$this->line('- anni (legacy): ' . count($anniRows));
|
|
$this->line('- acqua_gen: ' . count($genRows));
|
|
$this->line('- acqua_fatture: ' . count($fattRows));
|
|
$this->line('- acqua_dett processate: ' . count($dettRows));
|
|
if (! empty($targetAcquaYears)) {
|
|
$this->line('- anni acqua target: ' . implode(', ', $targetAcquaYears));
|
|
}
|
|
$this->line('- letture create: ' . $lettureStats['created']);
|
|
$this->line('- letture aggiornate: ' . $lettureStats['updated']);
|
|
$this->line('- letture purge fuori target: ' . $purgedWaterRows);
|
|
$this->line('- letture con unita risolta: ' . $lettureStats['matched_unita']);
|
|
$this->line('- letture senza match unita: ' . $lettureStats['unmatched_unita']);
|
|
$this->line('- nominativi C/I sincronizzati: ' . $lettureStats['nominativi_synced']);
|
|
$this->line('- tariffe create: ' . $tariffeStats['created']);
|
|
$this->line('- tariffe aggiornate: ' . $tariffeStats['updated']);
|
|
if ($this->tariffeSkipReason) {
|
|
$this->line('- tariffe skip: ' . $this->tariffeSkipReason);
|
|
}
|
|
$this->line('- gestioni storiche chiuse (< open years): ' . $closedCount);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function resolveStabile(string $stabileCode, int $stabileId): ?object
|
|
{
|
|
if (! Schema::hasTable('stabili')) {
|
|
return null;
|
|
}
|
|
|
|
if ($stabileId > 0) {
|
|
return DB::table('stabili')->where('id', $stabileId)->first();
|
|
}
|
|
|
|
$candidates = [];
|
|
if ($stabileCode !== '') {
|
|
$candidates[] = $stabileCode;
|
|
$trim = ltrim($stabileCode, '0');
|
|
if ($trim !== '') {
|
|
$candidates[] = $trim;
|
|
}
|
|
if (is_numeric($stabileCode)) {
|
|
$int = (int) $stabileCode;
|
|
$candidates[] = (string) $int;
|
|
$candidates[] = str_pad((string) $int, 4, '0', STR_PAD_LEFT);
|
|
}
|
|
}
|
|
|
|
$candidates = array_values(array_unique(array_filter($candidates, fn($v) => trim((string) $v) !== '')));
|
|
|
|
$query = DB::table('stabili');
|
|
$query->where(function ($q) use ($candidates): void {
|
|
foreach ($candidates as $candidate) {
|
|
$q->orWhere('codice_stabile', $candidate)
|
|
->orWhere('cod_stabile', $candidate)
|
|
->orWhere('old_id', $candidate);
|
|
}
|
|
});
|
|
|
|
return $query->orderByDesc('id')->first();
|
|
}
|
|
|
|
private function resolveMdbPath(string $stabileCode): string
|
|
{
|
|
$explicit = trim((string) ($this->option('mdb') ?? ''));
|
|
if ($explicit !== '') {
|
|
return $explicit;
|
|
}
|
|
|
|
$base = rtrim((string) ($this->option('base-path') ?? '/mnt/gescon-archives/gescon'), '/');
|
|
return $base . '/' . $stabileCode . '/generale_stabile.mdb';
|
|
}
|
|
|
|
private function exportTable(string $mdb, array $tableCandidates): array
|
|
{
|
|
foreach ($tableCandidates as $table) {
|
|
$cmd = "mdb-export -D '%Y-%m-%d %H:%M:%S' -d '|' -q '" . '"' . "' -R '\\n' "
|
|
. escapeshellarg($mdb)
|
|
. ' '
|
|
. escapeshellarg($table);
|
|
|
|
$csv = $this->runShell($cmd);
|
|
|
|
if (! is_string($csv) || trim($csv) === '') {
|
|
continue;
|
|
}
|
|
|
|
$rows = $this->parseDelimited($csv, '|');
|
|
if (! empty($rows)) {
|
|
return $rows;
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private function runShell(string $cmd): ?string
|
|
{
|
|
$out = shell_exec($cmd . ' 2>/dev/null');
|
|
if (! is_string($out) || trim($out) === '') {
|
|
return null;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
private function parseDelimited(string $text, string $sep): array
|
|
{
|
|
$lines = preg_split('/\r?\n/', trim($text));
|
|
if (! is_array($lines) || count($lines) === 0) {
|
|
return [];
|
|
}
|
|
|
|
$headers = str_getcsv((string) array_shift($lines), $sep);
|
|
$headers = array_map(fn($h) => strtolower(trim((string) $h)), $headers);
|
|
|
|
$rows = [];
|
|
foreach ($lines as $line) {
|
|
if (trim((string) $line) === '') {
|
|
continue;
|
|
}
|
|
|
|
$cols = str_getcsv((string) $line, $sep);
|
|
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 (! is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$rows[] = $row;
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function ensureAcquaServizio(int $stabileId, string $stabileCode): ?StabileServizio
|
|
{
|
|
$servizio = StabileServizio::query()
|
|
->where('stabile_id', $stabileId)
|
|
->where('tipo', 'acqua')
|
|
->orderByDesc('attivo')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if ($servizio) {
|
|
return $servizio;
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
return new StabileServizio([
|
|
'id' => 0,
|
|
'stabile_id' => $stabileId,
|
|
'tipo' => 'acqua',
|
|
'nome' => 'Utenza acqua legacy ' . $stabileCode,
|
|
'attivo' => true,
|
|
]);
|
|
}
|
|
|
|
return StabileServizio::query()->create([
|
|
'stabile_id' => $stabileId,
|
|
'tipo' => 'acqua',
|
|
'nome' => 'Utenza acqua legacy ' . $stabileCode,
|
|
'attivo' => true,
|
|
'meta' => [
|
|
'canale_acquisizione' => 'import_file',
|
|
'sorgente' => 'generale_stabile.mdb',
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function groupByKey(array $rows, string $key): array
|
|
{
|
|
$out = [];
|
|
foreach ($rows as $row) {
|
|
$k = trim((string) ($row[strtolower($key)] ?? ''));
|
|
if ($k === '') {
|
|
continue;
|
|
}
|
|
$out[$k][] = $row;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
private function buildUnitaMaps(int $stabileId): array
|
|
{
|
|
if (! Schema::hasTable('unita_immobiliari')) {
|
|
return ['byLegacyCond' => [], 'byScalaInterno' => []];
|
|
}
|
|
|
|
$rows = DB::table('unita_immobiliari')
|
|
->where('stabile_id', $stabileId)
|
|
->select(['id', 'legacy_cond_id', 'scala', 'interno'])
|
|
->get();
|
|
|
|
$byLegacyCond = [];
|
|
$byScalaInterno = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$id = (int) ($row->id ?? 0);
|
|
if ($id <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$legacy = trim((string) ($row->legacy_cond_id ?? ''));
|
|
if ($legacy !== '' && ! isset($byLegacyCond[$legacy])) {
|
|
$byLegacyCond[$legacy] = $id;
|
|
}
|
|
|
|
$scala = $this->normalizeToken((string) ($row->scala ?? ''));
|
|
$interno = $this->normalizeToken((string) ($row->interno ?? ''));
|
|
if ($scala !== '' || $interno !== '') {
|
|
$byScalaInterno[$scala . '|' . $interno] = $id;
|
|
}
|
|
}
|
|
|
|
return ['byLegacyCond' => $byLegacyCond, 'byScalaInterno' => $byScalaInterno];
|
|
}
|
|
|
|
private function importLetture(
|
|
int $stabileId,
|
|
int $servizioId,
|
|
array $dettRows,
|
|
array $genByRif,
|
|
array $fattByRif,
|
|
array $unitaByLegacyCond,
|
|
array $unitaByScalaInterno
|
|
): array {
|
|
if (! Schema::hasTable('stabile_servizio_letture')) {
|
|
return ['created' => 0, 'updated' => 0, 'matched_unita' => 0, 'unmatched_unita' => 0, 'nominativi_synced' => 0];
|
|
}
|
|
|
|
$tableCols = Schema::getColumnListing('stabile_servizio_letture');
|
|
$colsSet = array_flip($tableCols);
|
|
|
|
$created = 0;
|
|
$updated = 0;
|
|
$matchedUnita = 0;
|
|
$unmatchedUnita = 0;
|
|
$nominativiSynced = 0;
|
|
|
|
foreach ($dettRows as $row) {
|
|
$rifUte = trim((string) ($row['rif_ute'] ?? ''));
|
|
if ($rifUte === '') {
|
|
continue;
|
|
}
|
|
|
|
$legacyCondId = trim((string) ($row['id_cond'] ?? $row['t_cod_cond'] ?? ''));
|
|
$ruolo = strtoupper(trim((string) ($row['cond_inquil'] ?? '')));
|
|
if (! in_array($ruolo, ['C', 'I'], true)) {
|
|
$ruolo = 'C';
|
|
}
|
|
|
|
$scala = $this->normalizeToken((string) ($row['t_scala'] ?? ''));
|
|
$interno = $this->normalizeToken((string) ($row['t_interno'] ?? ''));
|
|
|
|
$unitaId = null;
|
|
if ($legacyCondId !== '' && isset($unitaByLegacyCond[$legacyCondId])) {
|
|
$unitaId = (int) $unitaByLegacyCond[$legacyCondId];
|
|
} elseif (isset($unitaByScalaInterno[$scala . '|' . $interno])) {
|
|
$unitaId = (int) $unitaByScalaInterno[$scala . '|' . $interno];
|
|
}
|
|
|
|
if ($unitaId) {
|
|
$matchedUnita++;
|
|
} else {
|
|
$unmatchedUnita++;
|
|
}
|
|
|
|
$genRef = $genByRif[$rifUte][0] ?? [];
|
|
$rifAnno = trim((string) ($genRef['rif_anno'] ?? ''));
|
|
$year = $this->extractYear($rifAnno);
|
|
$periodoDal = $year ? sprintf('%04d-01-01', $year) : null;
|
|
$periodoAl = $year ? sprintf('%04d-12-31', $year) : null;
|
|
|
|
$letturaInizio = $this->firstMoney($row, ['lett_vecchia_1', 'lett_vecchia_2', 'lett_vecchia_3']);
|
|
$letturaFine = $this->firstMoney($row, ['lett_nuova_1', 'lett_nuova_2', 'lett_nuova_3']);
|
|
|
|
$consumo = $this->money($row['consumo'] ?? null);
|
|
if (abs($consumo) < 0.00001) {
|
|
$consumo = 0.0;
|
|
foreach (['cons_1', 'cons_2', 'cons_3', 'cons_4', 'cons_5'] as $consKey) {
|
|
$consumo += $this->money($row[$consKey] ?? null);
|
|
}
|
|
}
|
|
|
|
$importoTotale = 0.0;
|
|
foreach ((array) ($fattByRif[$rifUte] ?? []) as $fattRow) {
|
|
$importoTotale += $this->money($fattRow['importo_ft'] ?? null);
|
|
}
|
|
if (abs($importoTotale) < 0.00001) {
|
|
$importoTotale = $this->money($genRef['di_cui_prop_consumi_euro'] ?? $genRef['di_cui_prop_consumi'] ?? null);
|
|
}
|
|
|
|
$tipoUtenza = trim((string) ($row['tipo_utenza'] ?? ''));
|
|
$nominativo = trim((string) ($row['t_nome'] ?? ''));
|
|
|
|
$ref = 'LEGACY:' . $rifUte . ':COND:' . ($legacyCondId !== '' ? $legacyCondId : 'NA') . ':' . $ruolo;
|
|
|
|
$match = DB::table('stabile_servizio_letture')
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', $servizioId)
|
|
->where('riferimento_acquisizione', $ref)
|
|
->first(['id']);
|
|
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'stabile_servizio_id' => $servizioId,
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'periodo_dal' => $periodoDal,
|
|
'periodo_al' => $periodoAl,
|
|
'tipologia_lettura' => trim((string) ($row['tipo_lettura'] ?? '')) ?: null,
|
|
'canale_acquisizione' => 'import_file',
|
|
'riferimento_acquisizione' => $ref,
|
|
'lettura_inizio' => $letturaInizio,
|
|
'lettura_fine' => $letturaFine,
|
|
'consumo_valore' => $consumo !== 0.0 ? $consumo : null,
|
|
'consumo_unita' => 'mc',
|
|
'importo_totale' => $importoTotale !== 0.0 ? $importoTotale : null,
|
|
'raw' => json_encode([
|
|
'source' => 'legacy_generale_stabile_mdb',
|
|
'rif_ute' => $rifUte,
|
|
'rif_anno' => $rifAnno,
|
|
'legacy_cond_id' => $legacyCondId,
|
|
'ruolo_legacy' => $ruolo,
|
|
'tipo_utenza' => $tipoUtenza !== '' ? $tipoUtenza : null,
|
|
'nome_legacy' => $nominativo !== '' ? $nominativo : null,
|
|
't_scala' => trim((string) ($row['t_scala'] ?? '')),
|
|
't_interno' => trim((string) ($row['t_interno'] ?? '')),
|
|
'n_persone' => trim((string) ($row['n_persone'] ?? '')),
|
|
'relativa_a' => trim((string) ($row['relativa_a'] ?? '')),
|
|
'acqua_gen' => $genRef,
|
|
], JSON_UNESCAPED_UNICODE),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$payload = array_intersect_key($payload, $colsSet);
|
|
|
|
if ($this->dryRun) {
|
|
if ($match) {
|
|
$updated++;
|
|
} else {
|
|
$created++;
|
|
}
|
|
} else {
|
|
if ($match) {
|
|
DB::table('stabile_servizio_letture')->where('id', (int) $match->id)->update($payload);
|
|
$updated++;
|
|
} else {
|
|
$payload['created_at'] = now();
|
|
DB::table('stabile_servizio_letture')->insert($payload);
|
|
$created++;
|
|
}
|
|
}
|
|
|
|
if ($unitaId && $nominativo !== '') {
|
|
$nominativiSynced += $this->syncNominativoRuolo(
|
|
$stabileId,
|
|
$unitaId,
|
|
$legacyCondId,
|
|
$ruolo,
|
|
$nominativo,
|
|
$rifUte,
|
|
$tipoUtenza
|
|
);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'created' => $created,
|
|
'updated' => $updated,
|
|
'matched_unita' => $matchedUnita,
|
|
'unmatched_unita' => $unmatchedUnita,
|
|
'nominativi_synced' => $nominativiSynced,
|
|
];
|
|
}
|
|
|
|
private function syncNominativoRuolo(
|
|
int $stabileId,
|
|
int $unitaId,
|
|
string $legacyCondId,
|
|
string $ruolo,
|
|
string $nominativo,
|
|
string $rifUte,
|
|
string $tipoUtenza
|
|
): int {
|
|
if (! Schema::hasTable('unita_immobiliare_nominativi')) {
|
|
return 0;
|
|
}
|
|
|
|
$cols = array_flip(Schema::getColumnListing('unita_immobiliare_nominativi'));
|
|
|
|
$where = [
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'stabile_id' => $stabileId,
|
|
'legacy_cond_id' => $legacyCondId !== '' ? $legacyCondId : null,
|
|
'ruolo' => $ruolo,
|
|
'nominativo' => $nominativo,
|
|
];
|
|
|
|
$exists = DB::table('unita_immobiliare_nominativi')->where($where)->first(['id']);
|
|
|
|
$payload = [
|
|
'unita_immobiliare_id' => $unitaId,
|
|
'stabile_id' => $stabileId,
|
|
'legacy_cond_id' => $legacyCondId !== '' ? $legacyCondId : null,
|
|
'ruolo' => $ruolo,
|
|
'nominativo' => $nominativo,
|
|
'fonte' => 'legacy_acqua_dett',
|
|
'legacy_payload' => json_encode([
|
|
'rif_ute' => $rifUte,
|
|
'tipo_utenza' => $tipoUtenza !== '' ? $tipoUtenza : null,
|
|
], JSON_UNESCAPED_UNICODE),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$payload = array_intersect_key($payload, $cols);
|
|
|
|
if ($this->dryRun) {
|
|
return $exists ? 0 : 1;
|
|
}
|
|
|
|
if ($exists) {
|
|
DB::table('unita_immobiliare_nominativi')->where('id', (int) $exists->id)->update($payload);
|
|
return 0;
|
|
}
|
|
|
|
$payload['created_at'] = now();
|
|
DB::table('unita_immobiliare_nominativi')->insert($payload);
|
|
return 1;
|
|
}
|
|
|
|
private function importTariffeDaAcquaGen(int $stabileId, int $servizioId, array $genRows, array $fattByRif): array
|
|
{
|
|
if (! Schema::hasTable('stabile_servizio_tariffe') || empty($genRows)) {
|
|
return ['created' => 0, 'updated' => 0];
|
|
}
|
|
|
|
// In alcuni ambienti la tabella richiede FK obbligatoria verso fatture_elettroniche.
|
|
// I dati legacy acqua_gen non hanno questo riferimento diretto: evitiamo hard-fail.
|
|
if (Schema::hasColumn('stabile_servizio_tariffe', 'fattura_elettronica_id')) {
|
|
$this->tariffeSkipReason = 'richiesta fattura_elettronica_id obbligatoria (non disponibile su sorgente legacy acqua_gen)';
|
|
return ['created' => 0, 'updated' => 0];
|
|
}
|
|
|
|
$cols = array_flip(Schema::getColumnListing('stabile_servizio_tariffe'));
|
|
|
|
$created = 0;
|
|
$updated = 0;
|
|
|
|
foreach ($genRows as $row) {
|
|
$rifUte = trim((string) ($row['rif_ute'] ?? ''));
|
|
if ($rifUte === '') {
|
|
continue;
|
|
}
|
|
|
|
$rifAnno = trim((string) ($row['rif_anno'] ?? ''));
|
|
$year = $this->extractYear($rifAnno);
|
|
$decorrenza = $year ? sprintf('%04d-01-01', $year) : null;
|
|
|
|
$totFatture = 0.0;
|
|
foreach ((array) ($fattByRif[$rifUte] ?? []) as $fattRow) {
|
|
$totFatture += $this->money($fattRow['importo_ft'] ?? null);
|
|
}
|
|
|
|
$componenti = [
|
|
'ripart_precedente' => $this->money($row['ripart_precedente'] ?? null),
|
|
'di_cui_prop_consumi_euro' => $this->money($row['di_cui_prop_consumi_euro'] ?? $row['di_cui_prop_consumi'] ?? null),
|
|
'di_cui_millesimale_euro' => $this->money($row['di_cui_millesimale_euro'] ?? $row['di_cui_millesimale'] ?? null),
|
|
'di_cui_parti_uguali_euro' => $this->money($row['di_cui_parti_uguali_euro'] ?? $row['di_cui_parti_uguali'] ?? null),
|
|
'totale_fatture_rif_ute' => $totFatture,
|
|
'max_fascia' => trim((string) ($row['max_fascia'] ?? '')),
|
|
'criterio_tariffazione' => trim((string) ($row['criterio_tariffazione'] ?? '')),
|
|
'n_mesi' => trim((string) ($row['n_mesi'] ?? '')),
|
|
'tariffa_periodo' => trim((string) ($row['tariffa_periodo'] ?? '')),
|
|
];
|
|
|
|
$keyWhere = [
|
|
'stabile_id' => $stabileId,
|
|
'stabile_servizio_id' => $servizioId,
|
|
'profilo_tariffario' => 'LEGACY-RIF-' . $rifUte,
|
|
];
|
|
|
|
$existing = DB::table('stabile_servizio_tariffe')->where($keyWhere)->first(['id']);
|
|
|
|
$payload = [
|
|
'stabile_id' => $stabileId,
|
|
'stabile_servizio_id' => $servizioId,
|
|
'data_fattura' => $decorrenza,
|
|
'decorrenza_tariffe' => $decorrenza,
|
|
'profilo_tariffario' => 'LEGACY-RIF-' . $rifUte,
|
|
'delibera_ato' => trim((string) ($row['nom_ute'] ?? '')) ?: null,
|
|
'tariffe_componenti' => json_encode($componenti, JSON_UNESCAPED_UNICODE),
|
|
'payload' => json_encode([
|
|
'source' => 'legacy_generale_stabile_mdb',
|
|
'rif_ute' => $rifUte,
|
|
'acqua_gen' => $row,
|
|
'acqua_fatture' => $fattByRif[$rifUte] ?? [],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
'updated_at' => now(),
|
|
];
|
|
|
|
$payload = array_intersect_key($payload, $cols);
|
|
|
|
if ($this->dryRun) {
|
|
if ($existing) {
|
|
$updated++;
|
|
} else {
|
|
$created++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ($existing) {
|
|
DB::table('stabile_servizio_tariffe')->where('id', (int) $existing->id)->update($payload);
|
|
$updated++;
|
|
} else {
|
|
$payload['created_at'] = now();
|
|
DB::table('stabile_servizio_tariffe')->insert($payload);
|
|
$created++;
|
|
}
|
|
}
|
|
|
|
return ['created' => $created, 'updated' => $updated];
|
|
}
|
|
|
|
private function closeHistoricGestioni(int $stabileId, string $openYearsRaw): int
|
|
{
|
|
if (! Schema::hasTable('gestioni_contabili')) {
|
|
return 0;
|
|
}
|
|
|
|
$openYears = array_values(array_filter(array_map(function ($y) {
|
|
$v = trim((string) $y);
|
|
return preg_match('/^\d{4}$/', $v) ? (int) $v : null;
|
|
}, explode(',', $openYearsRaw))));
|
|
|
|
if (empty($openYears)) {
|
|
return 0;
|
|
}
|
|
|
|
$minOpen = min($openYears);
|
|
|
|
$query = DB::table('gestioni_contabili')
|
|
->where('anno_gestione', '<', $minOpen)
|
|
->when(Schema::hasColumn('gestioni_contabili', 'stabile_id'), fn($q) => $q->where('stabile_id', $stabileId));
|
|
|
|
$rows = $query->get(['id']);
|
|
if ($rows->isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
return $rows->count();
|
|
}
|
|
|
|
$update = ['updated_at' => now()];
|
|
if (Schema::hasColumn('gestioni_contabili', 'stato')) {
|
|
$update['stato'] = 'chiusa';
|
|
}
|
|
if (Schema::hasColumn('gestioni_contabili', 'gestione_attiva')) {
|
|
$update['gestione_attiva'] = false;
|
|
}
|
|
|
|
DB::table('gestioni_contabili')
|
|
->whereIn('id', $rows->pluck('id')->all())
|
|
->update($update);
|
|
|
|
return $rows->count();
|
|
}
|
|
|
|
/** @return array<int> */
|
|
private function parseYearsCsv(string $value): array
|
|
{
|
|
$years = [];
|
|
foreach (explode(',', $value) as $raw) {
|
|
$v = trim($raw);
|
|
if (! preg_match('/^\d{4}$/', $v)) {
|
|
continue;
|
|
}
|
|
$years[] = (int) $v;
|
|
}
|
|
$years = array_values(array_unique($years));
|
|
sort($years);
|
|
return $years;
|
|
}
|
|
|
|
/** @param array<int, string> $allowedRifUte */
|
|
private function purgeLegacyLettureOutsideRifUte(int $stabileId, int $servizioId, array $allowedRifUte): int
|
|
{
|
|
if (! Schema::hasTable('stabile_servizio_letture')) {
|
|
return 0;
|
|
}
|
|
|
|
$allowed = array_fill_keys($allowedRifUte, true);
|
|
|
|
$rows = DB::table('stabile_servizio_letture')
|
|
->where('stabile_id', $stabileId)
|
|
->where('stabile_servizio_id', $servizioId)
|
|
->where('canale_acquisizione', 'import_file')
|
|
->where('riferimento_acquisizione', 'like', 'LEGACY:%')
|
|
->get(['id', 'riferimento_acquisizione']);
|
|
|
|
$toDeleteIds = [];
|
|
foreach ($rows as $row) {
|
|
$ref = (string) ($row->riferimento_acquisizione ?? '');
|
|
if (! preg_match('/^LEGACY:([^:]+):COND:/', $ref, $m)) {
|
|
continue;
|
|
}
|
|
$rif = trim((string) ($m[1] ?? ''));
|
|
if ($rif === '' || isset($allowed[$rif])) {
|
|
continue;
|
|
}
|
|
$toDeleteIds[] = (int) $row->id;
|
|
}
|
|
|
|
if (empty($toDeleteIds)) {
|
|
return 0;
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
return count($toDeleteIds);
|
|
}
|
|
|
|
DB::table('stabile_servizio_letture')->whereIn('id', $toDeleteIds)->delete();
|
|
return count($toDeleteIds);
|
|
}
|
|
|
|
private function firstMoney(array $row, array $keys): ?float
|
|
{
|
|
foreach ($keys as $key) {
|
|
$v = $this->money($row[$key] ?? null);
|
|
if (abs($v) > 0.00001) {
|
|
return $v;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function extractYear(string $value): ?int
|
|
{
|
|
if (preg_match('/(19|20)\d{2}/', $value, $m)) {
|
|
return (int) $m[0];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function normalizeToken(string $value): string
|
|
{
|
|
$v = strtoupper(trim($value));
|
|
$v = preg_replace('/\s+/', '', $v) ?? $v;
|
|
return $v;
|
|
}
|
|
|
|
private function money($value): float
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return 0.0;
|
|
}
|
|
|
|
$s = trim((string) $value);
|
|
if ($s === '') {
|
|
return 0.0;
|
|
}
|
|
|
|
$s = str_replace([" ", "\u{00A0}"], '', $s);
|
|
|
|
if (preg_match('/^-?\d{1,3}(?:\.\d{3})+(?:,\d+)?$/', $s)) {
|
|
$s = str_replace('.', '', $s);
|
|
$s = str_replace(',', '.', $s);
|
|
return (float) $s;
|
|
}
|
|
|
|
if (preg_match('/^-?\d{1,3}(?:,\d{3})+(?:\.\d+)?$/', $s)) {
|
|
$s = str_replace(',', '', $s);
|
|
return (float) $s;
|
|
}
|
|
|
|
if (str_contains($s, ',') && ! str_contains($s, '.')) {
|
|
$s = str_replace(',', '.', $s);
|
|
} elseif (str_contains($s, ',') && str_contains($s, '.')) {
|
|
$s = str_replace('.', '', $s);
|
|
$s = str_replace(',', '.', $s);
|
|
}
|
|
|
|
$s = preg_replace('/[^0-9\-\.]/', '', $s) ?? '';
|
|
if ($s === '' || $s === '-' || $s === '.') {
|
|
return 0.0;
|
|
}
|
|
|
|
return (float) $s;
|
|
}
|
|
}
|