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

276 lines
10 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Builder;
use App\Models\Persona;
use App\Models\PersonaUnitaRelazione;
use App\Models\RubricaRuolo;
use App\Models\RubricaUniversale;
class GesconSyncRubricaRuoli extends Command
{
protected $signature = 'gescon:sync-rubrica-ruoli
{--stabile= : Filtra per stabile_id (dominio)}
{--limit=5000 : Max relazioni da processare}
{--create-missing : Se manca un contatto in rubrica_universale, lo crea da Persona (richiede --apply)}
{--apply : Esegue realmente l\'upsert. Senza flag è solo report}';
protected $description = 'Sincronizza ruoli/relazioni da persone_unita_relazioni verso rubrica_ruoli (con match CF/email/telefono).';
public function handle(): int
{
if (!Schema::hasTable('rubrica_universale') || !Schema::hasTable('rubrica_ruoli')) {
$this->error('Tabelle rubrica_universale/rubrica_ruoli non disponibili.');
return 1;
}
$apply = (bool)$this->option('apply');
$createMissing = (bool) $this->option('create-missing');
$stabileFilter = $this->option('stabile');
$limit = (int)$this->option('limit');
if ($limit < 100) $limit = 100;
if ($limit > 20000) $limit = 20000;
$query = PersonaUnitaRelazione::with(['persona', 'unitaImmobiliare.stabile'])
->orderBy('id');
if ($stabileFilter) {
$query->whereHas('unitaImmobiliare', function ($q) use ($stabileFilter) {
$q->where('stabile_id', $stabileFilter);
});
}
$total = (clone $query)->count();
if ($total === 0) {
$this->info('Nessuna relazione da sincronizzare.');
return 0;
}
$processed = $inserted = $updated = $skipped = 0;
$query->limit($limit)->chunkById(200, function ($chunk) use (&$processed, &$inserted, &$updated, &$skipped, $apply, $createMissing) {
foreach ($chunk as $relazione) {
$processed++;
$persona = $relazione->persona;
$unita = $relazione->unitaImmobiliare;
$stabile = $unita?->stabile;
if (!$persona || !$unita || !$stabile) {
$skipped++;
continue;
}
$rubrica = $this->trovaRubricaPerPersona($persona, $stabile->amministratore_id ?? null, $apply && $createMissing);
if (!$rubrica) {
$skipped++;
continue;
}
$ruoloStandard = $this->mapRuolo($relazione->tipo_relazione);
$match = [
'rubrica_id' => $rubrica->id,
'stabile_id' => $stabile->id,
'unita_immobiliare_id' => $unita->id,
'ruolo_standard' => $ruoloStandard,
];
$payload = [
'is_attivo' => $relazione->isAttiva(),
'is_preferito' => $relazione->isAttiva(),
'meta' => [
'percentuale_possesso' => $relazione->quota_relazione,
'tipo_relazione_orig' => $relazione->tipo_relazione,
'persona_id_orig' => $persona->id,
],
];
if (!$apply) {
RubricaRuolo::where($match)->exists() ? $updated++ : $inserted++;
continue;
}
$record = RubricaRuolo::updateOrCreate($match, $payload);
$record->wasRecentlyCreated ? $inserted++ : $updated++;
}
});
$this->info(($apply ? 'Sync eseguito' : 'Dry-run') . " - processati: {$processed}, inseriti: {$inserted}, aggiornati: {$updated}, saltati: {$skipped}");
return 0;
}
protected function trovaRubricaPerPersona(Persona $persona, $amministratoreId = null, bool $createIfMissing = false): ?RubricaUniversale
{
if (!$this->personaHasRubricaIdentity($persona)) {
return null;
}
$query = $this->rubricaScopedQuery($amministratoreId);
$cf = trim((string) ($persona->codice_fiscale ?? ''));
if ($cf) {
$found = (clone $query)->where('codice_fiscale', $cf)->first();
if ($found) return $found;
}
$piva = trim((string) ($persona->partita_iva ?? ''));
if ($piva) {
$found = (clone $query)->where('partita_iva', $piva)->first();
if ($found) return $found;
}
foreach ($this->collectPersonaEmails($persona) as $email) {
$found = (clone $query)->whereRaw('LOWER(email) = ?', [$email])->first();
if ($found) {
return $found;
}
}
foreach ($this->collectPersonaPhones($persona) as $phone) {
$found = (clone $query)->where(function ($q) use ($phone) {
$q->where('telefono_ufficio', $phone)
->orWhere('telefono_cellulare', $phone)
->orWhere('telefono_casa', $phone);
})->first();
if ($found) {
return $found;
}
}
$ragioneSociale = trim((string) ($persona->ragione_sociale ?? ''));
if ($ragioneSociale !== '') {
$found = (clone $query)
->whereRaw('LOWER(ragione_sociale) = ?', [mb_strtolower($ragioneSociale)])
->first();
if ($found) {
return $found;
}
}
$nome = trim((string) ($persona->nome ?? ''));
$cognome = trim((string) ($persona->cognome ?? ''));
if ($nome !== '' && $cognome !== '') {
$found = (clone $query)
->whereRaw('LOWER(nome) = ?', [mb_strtolower($nome)])
->whereRaw('LOWER(cognome) = ?', [mb_strtolower($cognome)])
->first();
if ($found) {
return $found;
}
}
if (!$createIfMissing) {
return null;
}
$tipo = $persona->tipologia === 'giuridica' ? 'persona_giuridica' : 'persona_fisica';
$now = now();
return RubricaUniversale::query()->create([
'amministratore_id' => $amministratoreId ? (int) $amministratoreId : null,
'nome' => $tipo === 'persona_fisica' ? ($persona->nome ?? null) : null,
'cognome' => $tipo === 'persona_fisica' ? ($persona->cognome ?? null) : null,
'ragione_sociale' => $tipo === 'persona_giuridica' ? ($persona->ragione_sociale ?? $persona->nome_completo) : null,
'tipo_contatto' => $tipo,
'categoria' => 'condomino',
'stato' => 'attivo',
'codice_fiscale' => $cf ?: null,
'partita_iva' => $persona->partita_iva ?: null,
'email' => $persona->email_principale ?? null,
'pec' => $persona->email_pec ?? null,
'telefono_ufficio' => $persona->telefono_principale ?? null,
'telefono_cellulare' => $persona->telefono_secondario ?? null,
'note' => $persona->note ?? null,
'data_inserimento' => $now,
'data_ultima_modifica' => $now,
]);
}
private function rubricaScopedQuery($amministratoreId = null): Builder
{
$query = RubricaUniversale::query();
if (! Schema::hasColumn('rubrica_universale', 'amministratore_id') || $amministratoreId === null) {
return $query;
}
return $query
->where(function ($inner) use ($amministratoreId) {
$inner->where('amministratore_id', (int) $amministratoreId)
->orWhereNull('amministratore_id');
})
->orderByRaw('CASE WHEN amministratore_id = ? THEN 0 WHEN amministratore_id IS NULL THEN 1 ELSE 2 END', [(int) $amministratoreId]);
}
/** @return array<int, string> */
private function collectPersonaEmails(Persona $persona): array
{
$emails = [];
$primary = trim((string) ($persona->email_principale ?? ''));
if ($primary !== '') {
$emails[] = mb_strtolower($primary);
}
if (Schema::hasTable('persone_email_multiple')) {
foreach ($persona->emailMultiple()->pluck('email')->all() as $email) {
$candidate = mb_strtolower(trim((string) $email));
if ($candidate !== '') {
$emails[] = $candidate;
}
}
}
return array_values(array_unique($emails));
}
/** @return array<int, string> */
private function collectPersonaPhones(Persona $persona): array
{
$phones = [];
foreach ([$persona->telefono_principale, $persona->telefono_secondario] as $value) {
$candidate = trim((string) $value);
if ($candidate !== '') {
$phones[] = $candidate;
}
}
return array_values(array_unique($phones));
}
private function personaHasRubricaIdentity(Persona $persona): bool
{
$vals = [
trim((string) ($persona->nome ?? '')),
trim((string) ($persona->cognome ?? '')),
trim((string) ($persona->ragione_sociale ?? '')),
trim((string) ($persona->nome_completo ?? '')),
trim((string) ($persona->codice_fiscale ?? '')),
trim((string) ($persona->partita_iva ?? '')),
trim((string) ($persona->email_principale ?? '')),
trim((string) ($persona->email_pec ?? '')),
trim((string) ($persona->telefono_principale ?? '')),
trim((string) ($persona->telefono_secondario ?? '')),
];
foreach ($vals as $v) {
if ($v !== '') {
return true;
}
}
return false;
}
protected function mapRuolo(?string $tipo): string
{
$tipo = strtolower((string)$tipo);
return match ($tipo) {
'inquilino' => 'inquilino',
'proprietario', 'comproprietario', 'nudo_proprietario', 'usufruttuario', 'titolare' => 'condomino',
default => 'condomino',
};
}
}