362 lines
10 KiB
PHP
Executable File
362 lines
10 KiB
PHP
Executable File
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||
|
||
class Persona extends Model
|
||
{
|
||
use HasFactory;
|
||
|
||
protected $table = 'persone';
|
||
|
||
protected $fillable = [
|
||
'codice_interno',
|
||
'tipologia',
|
||
'cognome',
|
||
'nome',
|
||
'codice_fiscale',
|
||
'partita_iva',
|
||
'data_nascita',
|
||
'luogo_nascita_comune_id',
|
||
'residenza_via',
|
||
'residenza_comune_id',
|
||
'telefono_principale',
|
||
'telefono_secondario',
|
||
'email_principale',
|
||
'email_pec',
|
||
'whatsapp',
|
||
'modalita_comunicazione_preferita',
|
||
'lingua_preferita',
|
||
'consenso_privacy',
|
||
'consenso_marketing',
|
||
'data_consenso_privacy',
|
||
'ragione_sociale',
|
||
'forma_giuridica',
|
||
'codice_fiscale_societa',
|
||
'partita_iva_societa',
|
||
'note',
|
||
'attivo'
|
||
];
|
||
|
||
protected $casts = [
|
||
'data_nascita' => '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);
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
}
|