401 lines
14 KiB
PHP
401 lines
14 KiB
PHP
<?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;
|
|
}
|
|
} |