error('Tabelle richieste non disponibili.'); return self::FAILURE; } $apply = (bool) $this->option('apply'); $limit = max(1, min((int) ($this->option('limit') ?? 5000), 50000)); $stabile = trim((string) ($this->option('stabile') ?? '')); $query = DB::table('persone_unita_relazioni as pur') ->join('persone as p', 'p.id', '=', 'pur.persona_id') ->join('unita_immobiliari as ui', 'ui.id', '=', 'pur.unita_id') ->join('stabili as s', 's.id', '=', 'ui.stabile_id') ->select([ 'p.id as persona_id', 'p.nome', 'p.cognome', 'p.ragione_sociale', 'p.telefono_principale', 'p.telefono_secondario', 'p.email_principale', 'p.email_pec', 'p.note', 's.id as stabile_id', 's.codice_stabile', 's.amministratore_id', ]) ->distinct() ->orderBy('s.codice_stabile') ->orderBy('p.id') ->limit($limit); if ($stabile !== '') { $trimmed = ltrim($stabile, '0'); $query->where(function ($builder) use ($stabile, $trimmed): void { $builder->where('s.codice_stabile', $stabile); if ($trimmed !== '' && $trimmed !== $stabile) { $builder->orWhere('s.codice_stabile', $trimmed); } }); } $rows = $query->get(); if ($rows->isEmpty()) { $this->info('Nessuna persona da bonificare.'); return self::SUCCESS; } $stats = [ 'processed' => 0, 'persona_updated' => 0, 'rubrica_updated' => 0, 'no_match' => 0, 'phone_conflicts' => 0, ]; foreach ($rows as $row) { $stats['processed']++; $identityLabel = trim((string) ($row->ragione_sociale ?: trim(($row->nome ?? '') . ' ' . ($row->cognome ?? '')))); $tokenKey = $this->normalizeTokenSetKey($identityLabel); if ($tokenKey === null) { $stats['no_match']++; continue; } $rubriche = RubricaUniversale::query() ->when((int) ($row->amministratore_id ?? 0) > 0, fn($builder) => $builder->where(function ($query) use ($row): void { $query->where('amministratore_id', (int) $row->amministratore_id) ->orWhereNull('amministratore_id'); })) ->get() ->filter(fn(RubricaUniversale $rubrica): bool => $this->normalizeTokenSetKey($this->buildRubricaIdentityLabel($rubrica)) === $tokenKey) ->sortByDesc(fn(RubricaUniversale $rubrica): int => $this->scoreRubrica($rubrica)) ->values(); if ($rubriche->isEmpty()) { $stats['no_match']++; continue; } /** @var RubricaUniversale $best */ $best = $rubriche->first(); $canonicalRubricaId = $this->extractRubricaIdFromPersonaNote((string) ($row->note ?? '')); $canonicalRubrica = $canonicalRubricaId > 0 ? RubricaUniversale::query()->find($canonicalRubricaId) : null; if (! $canonicalRubrica instanceof RubricaUniversale) { $canonicalRubrica = $best; } $personaUpdates = $this->buildPersonaUpdates($row, $best, $stats); $rubricaUpdates = $this->buildRubricaUpdates($canonicalRubrica, $best); if ($apply && $personaUpdates !== []) { try { DB::table('persone')->where('id', (int) $row->persona_id)->update($personaUpdates + ['updated_at' => now()]); $stats['persona_updated']++; } catch (\Throwable $e) { $this->warn('Skip persona #' . (int) $row->persona_id . ': ' . $e->getMessage()); } } elseif (! $apply && $personaUpdates !== []) { $stats['persona_updated']++; } if ($apply && $rubricaUpdates !== []) { $canonicalRubrica->fill($rubricaUpdates); $canonicalRubrica->save(); $stats['rubrica_updated']++; } elseif (! $apply && $rubricaUpdates !== []) { $stats['rubrica_updated']++; } foreach ($rubriche as $candidateRubrica) { if (! $candidateRubrica instanceof RubricaUniversale || (int) $candidateRubrica->id === (int) $best->id) { continue; } $candidateUpdates = $this->buildRubricaUpdates($candidateRubrica, $best); if ($apply && $candidateUpdates !== []) { $candidateRubrica->fill($candidateUpdates); $candidateRubrica->save(); $stats['rubrica_updated']++; } elseif (! $apply && $candidateUpdates !== []) { $stats['rubrica_updated']++; } } } $mode = $apply ? 'Bonifica eseguita' : 'Dry-run bonifica'; $this->info($mode . ': ' . json_encode($stats, JSON_UNESCAPED_UNICODE)); return self::SUCCESS; } private function buildRubricaIdentityLabel(RubricaUniversale $rubrica): string { return trim(implode(' ', array_filter([ (string) ($rubrica->nome ?? ''), (string) ($rubrica->cognome ?? ''), (string) ($rubrica->ragione_sociale ?? ''), ]))); } private function normalizeTokenSetKey(?string $value): ?string { $tokens = preg_split('/\s+/', Str::upper(trim((string) $value))) ?: []; $tokens = array_values(array_filter(array_map(function (string $token): string { return (string) preg_replace('/[^A-Z0-9]+/u', '', $token); }, $tokens))); if ($tokens === []) { return null; } sort($tokens, SORT_STRING); return implode('|', $tokens); } private function normalizePhone(?string $value): ?string { $normalized = preg_replace('/[^\d+]/', '', (string) ($value ?? '')); return is_string($normalized) && $normalized !== '' ? $normalized : null; } private function normalizeEmail(?string $value): ?string { $normalized = strtolower(trim((string) ($value ?? ''))); $normalized = preg_replace('/\s+/', '', $normalized); return is_string($normalized) && $normalized !== '' ? $normalized : null; } private function scoreRubrica(RubricaUniversale $rubrica): int { $score = 0; foreach (['telefono_cellulare', 'telefono_ufficio', 'telefono_casa', 'email', 'pec', 'codice_fiscale', 'partita_iva', 'riferimento_stabile', 'riferimento_unita'] as $field) { if (filled($rubrica->{$field})) { $score += in_array($field, ['telefono_cellulare', 'email', 'codice_fiscale', 'partita_iva'], true) ? 10 : 4; } } return $score; } private function extractRubricaIdFromPersonaNote(string $note): int { if (preg_match('/rubrica_id=(\d+)/', $note, $matches)) { return (int) ($matches[1] ?? 0); } return 0; } /** * @param object $row * @return array */ private function buildPersonaUpdates(object $row, RubricaUniversale $best, array &$stats): array { $updates = []; $bestMobile = $this->normalizePhone((string) ($best->telefono_cellulare ?: $best->telefono_ufficio ?: $best->telefono_casa)); $bestOffice = $this->normalizePhone((string) ($best->telefono_ufficio ?: $best->telefono_casa)); $bestEmail = $this->normalizeEmail((string) ($best->email ?? '')); $bestPec = $this->normalizeEmail((string) ($best->pec ?? '')); if (! filled($row->telefono_principale) && $bestMobile !== null && ! $this->phoneExistsOnOtherPersona($bestMobile, (int) $row->persona_id)) { $updates['telefono_principale'] = $bestMobile; } elseif (! filled($row->telefono_principale) && $bestMobile !== null) { $stats['phone_conflicts']++; } if (! filled($row->telefono_secondario) && $bestOffice !== null && $bestOffice !== ($updates['telefono_principale'] ?? $row->telefono_principale)) { $updates['telefono_secondario'] = $bestOffice; } if (! filled($row->email_principale) && $bestEmail !== null) { $updates['email_principale'] = $bestEmail; } if (! filled($row->email_pec) && $bestPec !== null) { $updates['email_pec'] = $bestPec; } return $updates; } private function phoneExistsOnOtherPersona(string $phone, int $personaId): bool { return DB::table('persone') ->where('telefono_principale', $phone) ->where('id', '!=', $personaId) ->exists(); } /** * @return array */ private function buildRubricaUpdates(RubricaUniversale $canonical, RubricaUniversale $best): array { $updates = []; foreach (['telefono_cellulare', 'telefono_ufficio', 'telefono_casa', 'email', 'pec', 'codice_fiscale', 'partita_iva', 'prefisso_cellulare', 'telefono_cellulare_nazionale'] as $field) { if (! filled($canonical->{$field}) && filled($best->{$field})) { $updates[$field] = $best->{$field}; } } return $updates; } }