error('Tabella rubrica_universale non disponibile.'); return self::FAILURE; } if (! Schema::hasTable('rubrica_contatti_canali')) { $this->error('Tabella rubrica_contatti_canali non disponibile.'); return self::FAILURE; } $apply = (bool) $this->option('apply'); $limit = max(1, min((int) ($this->option('limit') ?? 5000), 50000)); $adminId = (int) ($this->option('admin-id') ?: 0); $stabile = trim((string) ($this->option('stabile') ?? '')); $query = RubricaUniversale::query()->orderBy('id'); if ($adminId > 0 && Schema::hasColumn('rubrica_universale', 'amministratore_id')) { $query->where('amministratore_id', $adminId); } if ($stabile !== '') { $rubricaIds = $this->resolveScopedRubricaIds($stabile); if ($rubricaIds === []) { $this->warn('Nessuna rubrica trovata per lo stabile richiesto.'); return self::SUCCESS; } $query->whereIn('id', $rubricaIds); } $records = $query->limit($limit)->get(); if ($records->isEmpty()) { $this->info('Nessuna rubrica da bonificare.'); return self::SUCCESS; } $stats = [ 'analizzate' => 0, 'rubriche_aggiornate' => 0, 'canali_creati' => 0, 'canali_aggiornati' => 0, 'saltate' => 0, ]; foreach ($records as $rubrica) { $stats['analizzate']++; $contactSet = $this->extractStructuredContacts($rubrica); if ($contactSet['channels'] === [] && $contactSet['updates'] === []) { $stats['saltate']++; continue; } if ($apply && $contactSet['updates'] !== []) { $rubrica->fill($contactSet['updates']); $rubrica->save(); $stats['rubriche_aggiornate']++; } elseif ($contactSet['updates'] !== []) { $stats['rubriche_aggiornate']++; } foreach ($contactSet['channels'] as $channel) { $existing = RubricaContattoCanale::query() ->where('rubrica_id', (int) $rubrica->id) ->where('tipo', $channel['tipo']) ->where('valore', $channel['valore']) ->whereNull('stabile_id') ->whereNull('unita_immobiliare_id') ->first(); if (! $apply) { if ($existing instanceof RubricaContattoCanale) { $stats['canali_aggiornati']++; } else { $stats['canali_creati']++; } continue; } if ($existing instanceof RubricaContattoCanale) { $existing->etichetta = $channel['etichetta']; $existing->is_principale = $channel['is_principale']; $existing->meta = $channel['meta']; $existing->save(); $stats['canali_aggiornati']++; continue; } RubricaContattoCanale::query()->create([ 'rubrica_id' => (int) $rubrica->id, 'tipo' => $channel['tipo'], 'etichetta' => $channel['etichetta'], 'valore' => $channel['valore'], 'is_principale' => $channel['is_principale'], 'meta' => $channel['meta'], ]); $stats['canali_creati']++; } } $mode = $apply ? 'apply' : 'dry-run'; $this->info('Bonifica rubrica canali completata (' . $mode . '): ' . json_encode($stats, JSON_UNESCAPED_UNICODE)); return self::SUCCESS; } /** @return array */ private function resolveScopedRubricaIds(string $stabileCode): array { if (! Schema::hasTable('stabili')) { return []; } $trimmed = ltrim($stabileCode, '0'); $stabileIds = Stabile::query() ->where(function (Builder $query) use ($stabileCode, $trimmed): void { $query->where('codice_stabile', $stabileCode); if ($trimmed !== '' && $trimmed !== $stabileCode) { $query->orWhere('codice_stabile', $trimmed); } }) ->pluck('id') ->map(fn($id) => (int) $id) ->all(); if ($stabileIds === []) { return []; } $rubricaIds = RubricaContattoCanale::query() ->whereIn('stabile_id', $stabileIds) ->pluck('rubrica_id') ->map(fn($id) => (int) $id) ->all(); if (Schema::hasTable('rubrica_ruoli')) { $roleRubricaIds = \DB::table('rubrica_ruoli') ->whereIn('stabile_id', $stabileIds) ->pluck('rubrica_id') ->map(fn($id) => (int) $id) ->all(); $rubricaIds = array_merge($rubricaIds, $roleRubricaIds); } $stabileRubricaIds = Stabile::query() ->whereIn('id', $stabileIds) ->whereNotNull('rubrica_id') ->pluck('rubrica_id') ->map(fn($id) => (int) $id) ->all(); $rubricaIds = array_merge($rubricaIds, $stabileRubricaIds); $rubricaIds = array_values(array_unique(array_filter($rubricaIds, fn(int $id): bool => $id > 0))); return $rubricaIds; } /** @return array{updates: array, channels: array>} */ private function extractStructuredContacts(RubricaUniversale $rubrica): array { $emails = $this->uniqueEmails([ ...$this->splitEmails((string) ($rubrica->email ?? '')), ]); $pecs = $this->uniqueEmails([ ...$this->splitEmails((string) ($rubrica->pec ?? '')), ]); $mobilePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_cellulare ?? '')), 'cellulare'); $officePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_ufficio ?? '')), 'telefono'); $homePhones = $this->normalizePhones($this->splitPhones((string) ($rubrica->telefono_casa ?? '')), 'telefono'); $primaryEmail = $emails[0] ?? $this->normalizeEmail((string) ($rubrica->email ?? '')); $primaryPec = $pecs[0] ?? $this->normalizeEmail((string) ($rubrica->pec ?? '')); $primaryMobile = $mobilePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_cellulare ?? '')); $primaryOffice = $officePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_ufficio ?? '')); $primaryHome = $homePhones[0]['value'] ?? $this->normalizePhoneDisplay((string) ($rubrica->telefono_casa ?? '')); $updates = []; if ($primaryEmail !== $this->normalizeEmail((string) ($rubrica->email ?? ''))) { $updates['email'] = $primaryEmail; } if ($primaryPec !== $this->normalizeEmail((string) ($rubrica->pec ?? ''))) { $updates['pec'] = $primaryPec; } if ($primaryMobile !== $this->normalizePhoneDisplay((string) ($rubrica->telefono_cellulare ?? ''))) { $updates['telefono_cellulare'] = $primaryMobile; [$e164, $prefix, $national] = PhoneNumber::splitItaly($primaryMobile); $updates['prefisso_cellulare'] = $prefix; $updates['telefono_cellulare_nazionale'] = $national; } if ($primaryOffice !== $this->normalizePhoneDisplay((string) ($rubrica->telefono_ufficio ?? ''))) { $updates['telefono_ufficio'] = $primaryOffice; } if ($primaryHome !== $this->normalizePhoneDisplay((string) ($rubrica->telefono_casa ?? ''))) { $updates['telefono_casa'] = $primaryHome; } $channels = []; foreach ($emails as $index => $email) { $channels[] = $this->buildChannel('email', $email, $index === 0 ? 'Email primaria' : 'Email aggiuntiva', $index === 0, 'rubrica.email'); } foreach ($pecs as $index => $email) { $channels[] = $this->buildChannel('pec', $email, $index === 0 ? 'PEC primaria' : 'PEC aggiuntiva', $index === 0, 'rubrica.pec'); } foreach ($mobilePhones as $index => $phone) { $channels[] = $this->buildChannel('cellulare', $phone['value'], $index === 0 ? 'Cellulare principale' : 'Cellulare aggiuntivo', $index === 0, 'rubrica.telefono_cellulare'); } foreach ($officePhones as $index => $phone) { $channels[] = $this->buildChannel('telefono', $phone['value'], $index === 0 ? 'Telefono ufficio' : 'Telefono aggiuntivo', $index === 0, 'rubrica.telefono_ufficio'); } foreach ($homePhones as $index => $phone) { $channels[] = $this->buildChannel('telefono', $phone['value'], $index === 0 ? 'Telefono casa' : 'Telefono casa aggiuntivo', false, 'rubrica.telefono_casa'); } return [ 'updates' => $updates, 'channels' => $this->deduplicateChannels($channels), ]; } /** @return array */ private function splitEmails(string $value): array { $raw = trim($value); if ($raw === '') { return []; } $parts = preg_split('/[;,\n\r]+/', $raw) ?: []; return array_values(array_filter(array_map(fn(string $part): ?string => $this->normalizeEmail($part), $parts))); } /** @return array */ private function splitPhones(string $value): array { $raw = trim($value); if ($raw === '') { return []; } if (! preg_match('/[;,\n\r\/|]/', $raw)) { return [$raw]; } $parts = preg_split('/[;,\n\r\/|]+/', $raw) ?: []; return array_values(array_filter(array_map(fn(string $part): ?string => $this->normalizePhoneDisplay($part), $parts))); } private function normalizeEmail(?string $value): ?string { $normalized = strtolower(trim((string) $value)); return filter_var($normalized, FILTER_VALIDATE_EMAIL) ? $normalized : null; } private function normalizePhoneDisplay(?string $value): ?string { $raw = trim((string) $value); if ($raw === '') { return null; } $clean = preg_replace('/[^0-9+]/', '', $raw); return is_string($clean) && $clean !== '' ? $clean : null; } /** @param array $values * @return array */ private function normalizePhones(array $values, string $defaultType): array { $result = []; foreach ($values as $value) { $clean = $this->normalizePhoneDisplay($value); if ($clean === null) { continue; } $digits = PhoneNumber::normalizeDigits($clean); if ($digits === '') { continue; } $type = $defaultType; if (str_starts_with($digits, '3')) { $type = 'cellulare'; } elseif (str_starts_with($digits, '0')) { $type = 'telefono'; } $normalizedValue = $type === 'cellulare' ? (PhoneNumber::toE164Italy($clean) ?? $clean) : $clean; $result[] = [ 'value' => $normalizedValue, 'type' => $type, ]; } $unique = []; $seen = []; foreach ($result as $row) { $key = $row['type'] . '|' . PhoneNumber::normalizeDigits($row['value']); if (isset($seen[$key])) { continue; } $seen[$key] = true; $unique[] = $row; } return $unique; } /** @param array $values * @return array */ private function uniqueEmails(array $values): array { $unique = []; foreach ($values as $value) { $normalized = $this->normalizeEmail($value); if ($normalized === null || in_array($normalized, $unique, true)) { continue; } $unique[] = $normalized; } return $unique; } /** @return array */ private function buildChannel(string $tipo, string $valore, string $etichetta, bool $isPrincipale, string $source): array { return [ 'tipo' => $tipo, 'valore' => $valore, 'etichetta' => $etichetta, 'is_principale' => $isPrincipale, 'meta' => [ 'source' => $source, ], ]; } /** @param array> $channels * @return array> */ private function deduplicateChannels(array $channels): array { $result = []; $seen = []; foreach ($channels as $channel) { $key = $channel['tipo'] . '|'; if (in_array($channel['tipo'], ['telefono', 'cellulare'], true)) { $key .= PhoneNumber::normalizeDigits((string) $channel['valore']); } else { $key .= strtolower((string) $channel['valore']); } if (isset($seen[$key])) { if (! empty($channel['is_principale'])) { $result[$seen[$key]]['is_principale'] = true; } continue; } $seen[$key] = count($result); $result[] = $channel; } return $result; } }