error('Tabelle richieste mancanti: unita_immobiliari / rubrica_ruoli.'); return self::FAILURE; } $stabileId = (int) ($this->option('stabile_id') ?: 0); $applyFix = (bool) $this->option('fix'); $this->line('QA Unita/Nominativi avviato...'); $scope = function ($q) use ($stabileId): void { if ($stabileId > 0) { $q->where('stabile_id', $stabileId); } }; $totUnita = DB::table('unita_immobiliari') ->whereNull('deleted_at') ->when($stabileId > 0, fn($q) => $q->where('stabile_id', $stabileId)) ->count(); $unitaSenzaNominativi = Schema::hasTable('unita_immobiliare_nominativi') ? DB::table('unita_immobiliari as ui') ->whereNull('ui.deleted_at') ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->whereNotExists(function ($sub) : void { $sub->selectRaw('1') ->from('unita_immobiliare_nominativi as uin') ->whereColumn('uin.unita_immobiliare_id', 'ui.id'); }) ->count() : 0; $mismatchRuoliStabile = DB::table('rubrica_ruoli as rr') ->join('unita_immobiliari as ui', 'ui.id', '=', 'rr.unita_immobiliare_id') ->whereColumn('rr.stabile_id', '!=', 'ui.stabile_id') ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->count(); if ($applyFix && $mismatchRuoliStabile > 0) { DB::table('rubrica_ruoli as rr') ->join('unita_immobiliari as ui', 'ui.id', '=', 'rr.unita_immobiliare_id') ->whereColumn('rr.stabile_id', '!=', 'ui.stabile_id') ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->update(['rr.stabile_id' => DB::raw('ui.stabile_id')]); } $ruoliAttiviSenzaCell = DB::table('rubrica_ruoli as rr') ->join('rubrica_universale as ru', 'ru.id', '=', 'rr.rubrica_id') ->join('unita_immobiliari as ui', 'ui.id', '=', 'rr.unita_immobiliare_id') ->where('rr.is_attivo', true) ->whereIn(DB::raw('LOWER(rr.ruolo_standard)'), ['condomino', 'proprietario', 'comproprietario', 'inquilino', 'locatario', 'conduttore']) ->where(function ($q): void { $q->whereNull('ru.telefono_cellulare')->orWhereRaw("TRIM(ru.telefono_cellulare) = ''"); }) ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->count(); $sameNormalizedNameSql = "UPPER(TRIM(REGEXP_REPLACE(COALESCE(a.nominativo, ''), '[[:space:]]+', ' '))) = UPPER(TRIM(REGEXP_REPLACE(COALESCE(b.nominativo, ''), '[[:space:]]+', ' ')))"; $sameComproprietarioSeriesSql = "a.fonte = 'legacy_comproprietari' AND b.fonte = 'legacy_comproprietari' AND COALESCE(JSON_UNQUOTE(JSON_EXTRACT(a.legacy_payload, '$.id_compr')), '') != '' AND COALESCE(JSON_UNQUOTE(JSON_EXTRACT(a.legacy_payload, '$.id_compr')), '') = COALESCE(JSON_UNQUOTE(JSON_EXTRACT(b.legacy_payload, '$.id_compr')), '')"; $periodiSiSovrappongonoSql = "COALESCE(a.data_inizio, '1000-01-01') <= COALESCE(b.data_fine, '9999-12-31') AND COALESCE(b.data_inizio, '1000-01-01') <= COALESCE(a.data_fine, '9999-12-31')"; $duplicatiPeriodiStessoNominativo = Schema::hasTable('unita_immobiliare_nominativi') ? DB::table('unita_immobiliare_nominativi as a') ->join('unita_immobiliare_nominativi as b', function ($join) : void { $join->on('a.unita_immobiliare_id', '=', 'b.unita_immobiliare_id') ->on('a.ruolo', '=', 'b.ruolo') ->whereColumn('a.id', '<', 'b.id'); }) ->join('unita_immobiliari as ui', 'ui.id', '=', 'a.unita_immobiliare_id') ->whereIn('a.fonte', ['legacy_condomin', 'legacy_comproprietari']) ->whereIn('b.fonte', ['legacy_condomin', 'legacy_comproprietari']) ->whereRaw($periodiSiSovrappongonoSql) ->whereRaw($sameNormalizedNameSql) ->whereRaw('NOT (' . $sameComproprietarioSeriesSql . ')') ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->count() : 0; $compresenzeMultiIntestatario = Schema::hasTable('unita_immobiliare_nominativi') ? DB::table('unita_immobiliare_nominativi as a') ->join('unita_immobiliare_nominativi as b', function ($join) : void { $join->on('a.unita_immobiliare_id', '=', 'b.unita_immobiliare_id') ->on('a.ruolo', '=', 'b.ruolo') ->whereColumn('a.id', '<', 'b.id'); }) ->join('unita_immobiliari as ui', 'ui.id', '=', 'a.unita_immobiliare_id') ->whereIn('a.fonte', ['legacy_condomin', 'legacy_comproprietari']) ->whereIn('b.fonte', ['legacy_condomin', 'legacy_comproprietari']) ->whereRaw($periodiSiSovrappongonoSql) ->whereRaw('NOT (' . $sameNormalizedNameSql . ')') ->when($stabileId > 0, fn($q) => $q->where('ui.stabile_id', $stabileId)) ->count() : 0; $legacyYears = []; if (config('database.connections.gescon_import') && Schema::connection('gescon_import')->hasTable('condomin')) { $legacyYears = DB::connection('gescon_import') ->table('condomin') ->selectRaw('cod_stabile, MIN(legacy_year) as anno_min, MAX(legacy_year) as anno_max') ->whereNotNull('legacy_year') ->when($stabileId > 0, function ($q) use ($stabileId): void { $cod = DB::table('stabili')->where('id', $stabileId)->value('codice_stabile'); if (is_string($cod) && trim($cod) !== '') { $q->where('cod_stabile', trim($cod)); } }) ->groupBy('cod_stabile') ->limit(20) ->get() ->map(fn($r) => [ 'cod_stabile' => (string) $r->cod_stabile, 'anno_min' => (string) $r->anno_min, 'anno_max' => (string) $r->anno_max, ]) ->all(); } $this->info('--- QA Unita/Nominativi ---'); $this->line('Unita attive: ' . $totUnita); $this->line('Unita senza storico nominativi: ' . $unitaSenzaNominativi); $this->line('Ruoli con stabile mismatch: ' . $mismatchRuoliStabile . ($applyFix ? ' (fix applicato)' : '')); $this->line('Ruoli attivi senza cellulare: ' . $ruoliAttiviSenzaCell); $this->line('Duplicati periodi stesso nominativo: ' . $duplicatiPeriodiStessoNominativo); $this->line('Compresenze multi-intestatario contemporanee: ' . $compresenzeMultiIntestatario); if (! empty($legacyYears)) { $this->line('Anni legacy disponibili (condomin):'); foreach ($legacyYears as $row) { $this->line('- ' . $row['cod_stabile'] . ': ' . $row['anno_min'] . ' -> ' . $row['anno_max']); } } if ($unitaSenzaNominativi > 0 || $mismatchRuoliStabile > 0 || $duplicatiPeriodiStessoNominativo > 0) { $this->warn('QA completato con anomalie da lavorare.'); return self::INVALID; } $this->info('QA completato senza anomalie bloccanti.'); return self::SUCCESS; } }