'date', 'data_consenso_privacy' => 'datetime', 'consenso_privacy' => 'boolean', 'consenso_marketing' => 'boolean', 'attivo' => 'boolean', ]; /** * Relazione con unità immobiliari tramite pivot */ public function unitaImmobiliari(): BelongsToMany { return $this->belongsToMany(UnitaImmobiliare::class, 'persone_unita_relazioni', 'persona_id', 'unita_id') ->withPivot([ 'tipo_relazione', 'quota_relazione', 'data_inizio', 'data_fine', 'attivo', 'riceve_comunicazioni', 'riceve_convocazioni', 'vota_assemblea', 'note_relazione', ]) ->withTimestamps() ->wherePivot('attivo', true); } /** * Relazioni dirette con unità (per query più specifiche) */ public function relazioniUnita(): HasMany { return $this->hasMany(PersonaUnitaRelazione::class); } /** * Relazioni attive con unità */ public function relazioniUnitaAttive(): HasMany { return $this->relazioniUnita()->where('attivo', true); } /** * Email multiple */ public function emailMultiple(): HasMany { return $this->hasMany(PersonaEmailMultipla::class); } public function unitaRecapitiServizio(): HasMany { return $this->hasMany(UnitaRecapitoServizio::class, 'persona_id'); } /** * Società di cui è rappresentante legale */ public function societaRappresentate(): BelongsToMany { return $this->belongsToMany(Persona::class, 'rappresentanti_legali', 'rappresentante_id', 'societa_id') ->withPivot(['tipo_rappresentanza', 'data_inizio', 'data_fine', 'attivo']) ->wherePivot('attivo', true); } /** * Rappresentanti legali (se società) */ public function rappresentantiLegali(): BelongsToMany { return $this->belongsToMany(Persona::class, 'rappresentanti_legali', 'societa_id', 'rappresentante_id') ->withPivot(['tipo_rappresentanza', 'data_inizio', 'data_fine', 'attivo']) ->wherePivot('attivo', true); } /** * Audit delle modifiche */ public function auditModifiche(): HasMany { return $this->hasMany(PersonaAudit::class); } /** * SCOPE: Persone fisiche */ public function scopeFisiche($query) { return $query->where('tipologia', 'fisica'); } /** * SCOPE: Persone giuridiche */ public function scopeGiuridiche($query) { return $query->where('tipologia', 'giuridica'); } /** * SCOPE: Persone attive */ public function scopeAttive($query) { return $query->where('attivo', true); } /** * SCOPE: Ricerca per nome/cognome */ public function scopeRicercaNome($query, $termine) { return $query->where(function ($q) use ($termine) { $q->where('cognome', 'LIKE', "%{$termine}%") ->orWhere('nome', 'LIKE', "%{$termine}%") ->orWhere('ragione_sociale', 'LIKE', "%{$termine}%"); }); } /** * SCOPE: Ricerca per codice interno */ public function scopeRicercaCodice($query, $codice) { return $query->where('codice_interno', 'LIKE', "%{$codice}%"); } /** * ACCESSOR: Nome completo */ public function getNomeCompletoAttribute() { if ($this->tipologia === 'giuridica' && $this->ragione_sociale) { return $this->ragione_sociale; } return trim($this->cognome . ' ' . $this->nome); } /** * ACCESSOR: Nome per visualizzazione */ public function getNomeDisplayAttribute() { $nome = $this->nome_completo; if ($this->codice_interno) { $nome .= " ({$this->codice_interno})"; } return $nome; } /** * ACCESSOR: Identificativo univoco (CF o Codice Interno) */ public function getIdentificativoAttribute() { return $this->codice_fiscale ?: $this->codice_interno; } /** * MUTATOR: Normalizza telefono principale */ public function setTelefonoPrincipaleAttribute($value) { // Rimuovi spazi e caratteri speciali, mantieni solo numeri e + $this->attributes['telefono_principale'] = $value ? preg_replace('/[^\d+]/', '', $value) : null; } /** * MUTATOR: Normalizza codice fiscale */ public function setCodiceFiscaleAttribute($value) { $this->attributes['codice_fiscale'] = $value ? strtoupper(trim($value)) : null; } /** * Verifica duplicati prima del salvataggio */ public static function verificaDuplicati($datiPersona, $escludiId = null) { $duplicati = []; // 1️⃣ CONTROLLO CODICE FISCALE (Critico) if (! empty($datiPersona['codice_fiscale'])) { $query = static::where('codice_fiscale', $datiPersona['codice_fiscale']); if ($escludiId) { $query->where('id', '!=', $escludiId); } if ($existing = $query->first()) { $duplicati[] = [ 'tipo' => 'CRITICO', 'campo' => 'Codice Fiscale', 'persona_esistente' => $existing, 'azione' => 'BLOCCA_O_MERGE', ]; } } // 2️⃣ CONTROLLO TELEFONO PRINCIPALE (Critico) if (! empty($datiPersona['telefono_principale'])) { $telefono = preg_replace('/[^\d+]/', '', $datiPersona['telefono_principale']); $query = static::where('telefono_principale', $telefono); if ($escludiId) { $query->where('id', '!=', $escludiId); } if ($existing = $query->first()) { $duplicati[] = [ 'tipo' => 'CRITICO', 'campo' => 'Telefono Principale', 'persona_esistente' => $existing, 'azione' => 'BLOCCA_O_MERGE', ]; } } // 3️⃣ CONTROLLO EMAIL (Warning) if (! empty($datiPersona['email_principale'])) { $query = static::where('email_principale', $datiPersona['email_principale']); if ($escludiId) { $query->where('id', '!=', $escludiId); } if ($existing = $query->first()) { $duplicati[] = [ 'tipo' => 'WARNING', 'campo' => 'Email Principale', 'persona_esistente' => $existing, 'azione' => 'AVVISA', ]; } } // 4️⃣ CONTROLLO OMONIMIA (Warning) if (! empty($datiPersona['cognome']) && ! empty($datiPersona['nome']) && ! empty($datiPersona['data_nascita'])) { $query = static::where('cognome', $datiPersona['cognome']) ->where('nome', $datiPersona['nome']) ->where('data_nascita', $datiPersona['data_nascita']); if ($escludiId) { $query->where('id', '!=', $escludiId); } if ($existing = $query->first()) { $duplicati[] = [ 'tipo' => 'WARNING', 'campo' => 'Omonimia Completa', 'persona_esistente' => $existing, 'azione' => 'VERIFICA_MANUALE', ]; } } return $duplicati; } /** * Boot del model per eventi */ protected static function boot() { parent::boot(); // Genera codice interno automaticamente se non specificato static::creating(function ($persona) { if (empty($persona->codice_interno)) { $persona->codice_interno = static::generaCodiceUnivoco(); } }); // Audit delle modifiche static::updating(function ($persona) { $changes = $persona->getChanges(); $original = $persona->getOriginal(); foreach ($changes as $campo => $nuovoValore) { if ($campo !== 'updated_at') { PersonaAudit::registraModifica( $persona->id, $campo, $original[$campo] ?? null, $nuovoValore ); } } }); } /** * Cerca persona per identificativo (codice interno, CF, telefono) */ public static function cercaPerIdentificativo($identificativo) { return static::where('codice_interno', $identificativo) ->orWhere('codice_fiscale', $identificativo) ->orWhere('telefono_principale', $identificativo) ->first(); } /** * Genera codice univoco 8 caratteri */ public static function generaCodiceUnivoco() { $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; do { $codice = ''; for ($i = 0; $i < 8; $i++) { $codice .= $characters[rand(0, strlen($characters) - 1)]; } } while (static::where('codice_interno', $codice)->exists()); return $codice; } }