netgescon-day0/app/Console/Commands/GesconBonificaRubricaCanaliCommand.php

401 lines
15 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use App\Models\RubricaContattoCanale;
use App\Models\RubricaUniversale;
use App\Models\Stabile;
use App\Support\PhoneNumber;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
class GesconBonificaRubricaCanaliCommand extends Command
{
protected $signature = 'gescon:bonifica-rubrica-canali
{--stabile= : Codice stabile da bonificare}
{--admin-id= : Limita la bonifica a un amministratore}
{--limit=5000 : Numero massimo di anagrafiche da analizzare}
{--apply : Applica realmente le modifiche}';
protected $description = 'Normalizza telefoni, email e PEC multi-valore della rubrica e li materializza in rubrica_contatti_canali.';
public function handle(): int
{
if (! Schema::hasTable('rubrica_universale')) {
$this->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<int, int> */
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<string,mixed>, channels: array<int,array<string,mixed>>} */
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<int, string> */
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<int, string> */
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<int, string> $values
* @return array<int, array{value:string,type:string}>
*/
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<int, string> $values
* @return array<int, string>
*/
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<string,mixed> */
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<int,array<string,mixed>> $channels
* @return array<int,array<string,mixed>>
*/
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;
}
}