555 lines
15 KiB
PHP
555 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class UnitaImmobiliare extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $table = 'unita_immobiliari';
|
|
|
|
protected $fillable = [
|
|
'stabile_id',
|
|
'tipo_utilizzo_id',
|
|
'numero_interno',
|
|
'palazzina',
|
|
'piano',
|
|
'scala',
|
|
'codice_interno',
|
|
'codice_catastale',
|
|
'foglio',
|
|
'particella',
|
|
'subalterno',
|
|
'categoria',
|
|
'classe',
|
|
'consistenza',
|
|
'rendita_catastale',
|
|
'superficie_catastale',
|
|
'superficie_commerciale',
|
|
'millesimi_proprieta',
|
|
'millesimi_riscaldamento',
|
|
'millesimi_ascensore',
|
|
'millesimi_scale',
|
|
'millesimi_pulizie',
|
|
'millesimi_acqua',
|
|
'millesimi_custom_1',
|
|
'millesimi_custom_2',
|
|
'millesimi_custom_3',
|
|
'nome_custom_1',
|
|
'nome_custom_2',
|
|
'nome_custom_3',
|
|
'stato',
|
|
'data_acquisto',
|
|
'valore_acquisto',
|
|
'note',
|
|
'documenti_path',
|
|
'attiva',
|
|
// Campi legacy per compatibilità
|
|
'interno',
|
|
'fabbricato',
|
|
'categoria_catastale',
|
|
'superficie',
|
|
'vani',
|
|
'indirizzo',
|
|
// Nuovi campi avanzati
|
|
'superficie_calpestabile',
|
|
'superficie_balconi',
|
|
'superficie_terrazzi',
|
|
'numero_vani',
|
|
'numero_bagni',
|
|
'numero_balconi',
|
|
'classe_energetica',
|
|
'anno_costruzione',
|
|
'anno_ristrutturazione',
|
|
'stato_conservazione',
|
|
'necessita_lavori',
|
|
'note_tecniche',
|
|
'struttura_fisica_id',
|
|
'calcolo_automatico_millesimi',
|
|
'notifiche_subentri',
|
|
'created_by',
|
|
'updated_by'
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_acquisto' => 'date',
|
|
'valore_acquisto' => 'decimal:2',
|
|
'rendita_catastale' => 'decimal:2',
|
|
'superficie_catastale' => 'decimal:2',
|
|
'superficie_commerciale' => 'decimal:2',
|
|
'millesimi_proprieta' => 'decimal:6',
|
|
'millesimi_riscaldamento' => 'decimal:6',
|
|
'millesimi_ascensore' => 'decimal:6',
|
|
'millesimi_scale' => 'decimal:6',
|
|
'millesimi_pulizie' => 'decimal:6',
|
|
'millesimi_acqua' => 'decimal:6',
|
|
'millesimi_custom_1' => 'decimal:6',
|
|
'millesimi_custom_2' => 'decimal:6',
|
|
'millesimi_custom_3' => 'decimal:6',
|
|
'attiva' => 'boolean',
|
|
// Legacy casts
|
|
'superficie' => 'decimal:2',
|
|
'vani' => 'decimal:2',
|
|
// Advanced casts
|
|
'superficie_calpestabile' => 'decimal:2',
|
|
'superficie_balconi' => 'decimal:2',
|
|
'superficie_terrazzi' => 'decimal:2',
|
|
'numero_vani' => 'integer',
|
|
'numero_bagni' => 'integer',
|
|
'numero_balconi' => 'integer',
|
|
'anno_costruzione' => 'integer',
|
|
'anno_ristrutturazione' => 'integer',
|
|
'necessita_lavori' => 'boolean',
|
|
'calcolo_automatico_millesimi' => 'boolean',
|
|
'notifiche_subentri' => 'boolean',
|
|
'created_at' => 'datetime',
|
|
'updated_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Relazione con Stabile
|
|
*/
|
|
public function stabile()
|
|
{
|
|
return $this->belongsTo(Stabile::class, 'stabile_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con il tipo di utilizzo
|
|
*/
|
|
public function tipoUtilizzo()
|
|
{
|
|
return $this->belongsTo(TipoUtilizzo::class);
|
|
}
|
|
|
|
/**
|
|
* Relazione con i diritti reali
|
|
*/
|
|
public function dirittiReali()
|
|
{
|
|
return $this->hasMany(DirittoReale::class);
|
|
}
|
|
|
|
/**
|
|
* Relazione con i contratti di locazione
|
|
*/
|
|
public function contrattiLocazione()
|
|
{
|
|
return $this->hasMany(ContrattoLocazione::class);
|
|
}
|
|
|
|
/**
|
|
* Relazione con le ripartizioni spese inquilini
|
|
*/
|
|
public function ripartizioniSpese()
|
|
{
|
|
return $this->hasMany(RipartizioneSpeseInquilini::class);
|
|
}
|
|
|
|
/**
|
|
* Relazione con Tickets (legacy)
|
|
*/
|
|
public function tickets()
|
|
{
|
|
return $this->hasMany(Ticket::class, 'unita_immobiliare_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con Proprietà (legacy)
|
|
*/
|
|
public function proprieta()
|
|
{
|
|
return $this->hasMany(Proprieta::class, 'unita_immobiliare_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con DettaglioRipartizioneSpese
|
|
*/
|
|
public function dettagliRipartizioneSpese()
|
|
{
|
|
return $this->hasMany(DettaglioRipartizioneSpese::class);
|
|
}
|
|
|
|
/**
|
|
* Relazione con Rate
|
|
*/
|
|
public function rate()
|
|
{
|
|
return $this->hasMany(Rata::class);
|
|
}
|
|
|
|
/**
|
|
* Rate attive (emesse o parzialmente pagate)
|
|
*/
|
|
public function rateAttive()
|
|
{
|
|
return $this->hasMany(Rata::class)->whereIn('stato', ['emessa', 'parzialmente_pagata', 'scaduta']);
|
|
}
|
|
|
|
/**
|
|
* Rate scadute
|
|
*/
|
|
public function rateScadute()
|
|
{
|
|
return $this->hasMany(Rata::class)->scadute();
|
|
}
|
|
|
|
// === NUOVE RELAZIONI AVANZATE ===
|
|
|
|
/**
|
|
* Relazione con i subentri
|
|
*/
|
|
public function subentri()
|
|
{
|
|
return $this->hasMany(SubentroUnita::class, 'unita_immobiliare_id', 'id')->orderBy('data_subentro', 'desc');
|
|
}
|
|
|
|
/**
|
|
* Ultimo subentro
|
|
*/
|
|
public function ultimoSubentro()
|
|
{
|
|
return $this->hasOne(SubentroUnita::class, 'unita_immobiliare_id', 'id')->latest('data_subentro');
|
|
}
|
|
|
|
/**
|
|
* Relazione con la composizione dell'unità
|
|
*/
|
|
public function composizione()
|
|
{
|
|
return $this->hasMany(ComposizioneUnita::class, 'unita_immobiliare_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con le ripartizioni spese specifiche
|
|
*/
|
|
public function ripartizioniSpeseSpecifiche()
|
|
{
|
|
return $this->hasMany(RipartizioneSpese::class, 'unita_immobiliare_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con la struttura fisica dettagliata
|
|
*/
|
|
public function strutturaFisica()
|
|
{
|
|
return $this->belongsTo(StrutturaFisicaDettaglio::class, 'struttura_fisica_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con l'utente che ha creato il record
|
|
*/
|
|
public function createdBy()
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by', 'id');
|
|
}
|
|
|
|
/**
|
|
* Relazione con l'utente che ha aggiornato il record
|
|
*/
|
|
public function updatedBy()
|
|
{
|
|
return $this->belongsTo(User::class, 'updated_by', 'id');
|
|
}
|
|
|
|
/**
|
|
* Accessor per il nome completo dell'unità
|
|
*/
|
|
public function getNomeCompletoAttribute()
|
|
{
|
|
$nome = '';
|
|
|
|
if ($this->palazzina) {
|
|
$nome .= "Palazzina {$this->palazzina} - ";
|
|
}
|
|
|
|
if ($this->scala) {
|
|
$nome .= "Scala {$this->scala} - ";
|
|
}
|
|
|
|
if ($this->piano) {
|
|
$nome .= "Piano {$this->piano} - ";
|
|
}
|
|
|
|
$numeroInterno = $this->numero_interno ?: $this->interno;
|
|
$nome .= "Interno {$numeroInterno}";
|
|
|
|
return $nome;
|
|
}
|
|
|
|
/**
|
|
* Accessor per i dati catastali
|
|
*/
|
|
public function getDatiCatastaliAttribute()
|
|
{
|
|
return [
|
|
'foglio' => $this->foglio,
|
|
'particella' => $this->particella,
|
|
'subalterno' => $this->subalterno,
|
|
'categoria' => $this->categoria ?: $this->categoria_catastale,
|
|
'classe' => $this->classe,
|
|
'consistenza' => $this->consistenza,
|
|
'rendita' => $this->rendita_catastale
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Accessor per tutti i millesimi
|
|
*/
|
|
public function getMillesimiAttribute()
|
|
{
|
|
return [
|
|
'proprieta' => $this->millesimi_proprieta,
|
|
'riscaldamento' => $this->millesimi_riscaldamento,
|
|
'ascensore' => $this->millesimi_ascensore,
|
|
'scale' => $this->millesimi_scale,
|
|
'acqua' => $this->millesimi_acqua,
|
|
'custom_1' => $this->millesimi_custom_1,
|
|
'custom_2' => $this->millesimi_custom_2,
|
|
'custom_3' => $this->millesimi_custom_3
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Metodo per ottenere i proprietari attuali
|
|
*/
|
|
public function getProprietariAttuali()
|
|
{
|
|
return $this->dirittiReali()
|
|
->where('tipo_diritto', 'proprieta')
|
|
->whereNull('data_fine')
|
|
->with('anagraficaCondominiale')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Metodo per ottenere gli inquilini attuali
|
|
*/
|
|
public function getInquiliniAttuali()
|
|
{
|
|
return $this->contrattiLocazione()
|
|
->where('stato', 'attivo')
|
|
->whereDate('data_inizio', '<=', now())
|
|
->where(function ($query) {
|
|
$query->whereNull('data_fine')
|
|
->orWhereDate('data_fine', '>=', now());
|
|
})
|
|
->with('anagraficaCondominiale')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Metodo per verificare se l'unità è in locazione
|
|
*/
|
|
public function isInLocazione()
|
|
{
|
|
return $this->contrattiLocazione()
|
|
->where('stato', 'attivo')
|
|
->whereDate('data_inizio', '<=', now())
|
|
->where(function ($query) {
|
|
$query->whereNull('data_fine')
|
|
->orWhereDate('data_fine', '>=', now());
|
|
})
|
|
->exists();
|
|
}
|
|
|
|
/**
|
|
* Metodo per ottenere la ripartizione spese attuale
|
|
*/
|
|
public function getRipartizioneSpese()
|
|
{
|
|
return $this->ripartizioniSpese()
|
|
->whereDate('data_inizio', '<=', now())
|
|
->where(function ($query) {
|
|
$query->whereNull('data_fine')
|
|
->orWhereDate('data_fine', '>=', now());
|
|
})
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Accessor per identificazione completa dell'unità (legacy)
|
|
*/
|
|
public function getIdentificazioneCompiletaAttribute()
|
|
{
|
|
$parts = [];
|
|
if ($this->fabbricato) $parts[] = 'Fabb. ' . $this->fabbricato;
|
|
if ($this->scala) $parts[] = 'Scala ' . $this->scala;
|
|
if ($this->piano) $parts[] = 'Piano ' . $this->piano;
|
|
if ($this->interno || $this->numero_interno) {
|
|
$interno = $this->numero_interno ?: $this->interno;
|
|
$parts[] = 'Int. ' . $interno;
|
|
}
|
|
return implode(', ', $parts) ?: 'N/A';
|
|
}
|
|
|
|
/**
|
|
* Accessor per l'indirizzo completo
|
|
*/
|
|
public function getIndirizzoCompletoAttribute()
|
|
{
|
|
return $this->indirizzo ?: $this->stabile->indirizzo_completo;
|
|
}
|
|
|
|
// === METODI AVANZATI ===
|
|
|
|
/**
|
|
* Ottieni il proprietario attuale
|
|
*/
|
|
public function proprietarioAttuale()
|
|
{
|
|
return $this->soggetti()
|
|
->wherePivot('tipo_diritto', 'proprietà')
|
|
->wherePivot('data_fine', null)
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Calcola automaticamente i millesimi
|
|
*/
|
|
public function calcolaMillesimiAutomatici(): array
|
|
{
|
|
if (!$this->calcolo_automatico_millesimi) {
|
|
return [];
|
|
}
|
|
|
|
$totaleStabile = $this->stabile->unita()->sum('superficie_commerciale');
|
|
if ($totaleStabile <= 0) {
|
|
return [];
|
|
}
|
|
|
|
$coefficiente = $this->superficie_commerciale / $totaleStabile * 1000;
|
|
|
|
return [
|
|
'millesimi_proprieta' => round($coefficiente, 4),
|
|
'millesimi_riscaldamento' => $this->calcolaMillesimiRiscaldamento(),
|
|
'millesimi_ascensore' => $this->calcolaMillesimiAscensore(),
|
|
'millesimi_scale' => $this->calcolaMillesimiScale(),
|
|
'millesimi_pulizie' => round($coefficiente, 4), // Stesso di proprietà di default
|
|
];
|
|
}
|
|
|
|
private function calcolaMillesimiRiscaldamento(): float
|
|
{
|
|
// Calcolo basato su superficie + coefficienti piano/esposizione
|
|
$base = $this->superficie_commerciale ?: 0;
|
|
$coefficientePiano = $this->getCoefficientePiano();
|
|
$coefficienteEsposizione = $this->getCoefficieneEsposizione();
|
|
|
|
$totaleRiscaldamento = $this->stabile->getTotaleMillesimiRiscaldamento();
|
|
if ($totaleRiscaldamento <= 0) return 0;
|
|
|
|
return round($base * $coefficientePiano * $coefficienteEsposizione / $totaleRiscaldamento * 1000, 4);
|
|
}
|
|
|
|
private function calcolaMillesimiAscensore(): float
|
|
{
|
|
if ($this->piano <= 0) {
|
|
return 0; // Piano terra e seminterrati non pagano ascensore
|
|
}
|
|
|
|
$coefficientePiano = max(1, $this->piano * 0.15 + 0.85); // Crescente per piano
|
|
$totaleAscensore = $this->stabile->getTotaleMillesimiAscensore();
|
|
if ($totaleAscensore <= 0) return 0;
|
|
|
|
return round(($this->superficie_commerciale ?: 0) * $coefficientePiano / $totaleAscensore * 1000, 4);
|
|
}
|
|
|
|
private function calcolaMillesimiScale(): float
|
|
{
|
|
$base = $this->superficie_commerciale ?: 0;
|
|
$totaleScale = $this->stabile->getTotaleMillesimiScale();
|
|
if ($totaleScale <= 0) return 0;
|
|
|
|
if ($this->piano <= 0) {
|
|
return round($base * 0.5 / $totaleScale * 1000, 4);
|
|
}
|
|
|
|
return round($base / $totaleScale * 1000, 4);
|
|
}
|
|
|
|
private function getCoefficientePiano(): float
|
|
{
|
|
// Coefficiente per piano (piano terra = 1.0, aumenta con l'altezza)
|
|
return max(0.8, 1.0 + ($this->piano * 0.1));
|
|
}
|
|
|
|
private function getCoefficieneEsposizione(): float
|
|
{
|
|
// Placeholder per coefficiente esposizione - implementare logica specifica
|
|
return 1.0;
|
|
}
|
|
|
|
/**
|
|
* Badge color per stato conservazione
|
|
*/
|
|
public function getStatoBadgeColor(): string
|
|
{
|
|
return match($this->stato_conservazione) {
|
|
'ottimo' => 'success',
|
|
'buono' => 'info',
|
|
'discreto' => 'warning',
|
|
'cattivo' => 'danger',
|
|
default => 'secondary'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Genera un subentro automatico
|
|
*/
|
|
public function generaSubentroAutomatico(Soggetto $nuovoSoggetto, array $datiSubentro): SubentroUnita
|
|
{
|
|
$proprietarioAttuale = $this->proprietarioAttuale();
|
|
|
|
return $this->subentri()->create([
|
|
'soggetto_precedente_id' => $proprietarioAttuale?->id,
|
|
'soggetto_nuovo_id' => $nuovoSoggetto->id,
|
|
'data_subentro' => $datiSubentro['data_subentro'],
|
|
'tipo_subentro' => $datiSubentro['tipo_subentro'],
|
|
'quota_nuova' => $datiSubentro['quota'] ?? 1.0000,
|
|
'numero_atto' => $datiSubentro['numero_atto'] ?? null,
|
|
'data_atto' => $datiSubentro['data_atto'] ?? null,
|
|
'notaio' => $datiSubentro['notaio'] ?? null,
|
|
'prezzo_vendita' => $datiSubentro['prezzo_vendita'] ?? null,
|
|
'created_by' => auth()->id()
|
|
]);
|
|
}
|
|
|
|
// === SCOPES AVANZATI ===
|
|
|
|
/**
|
|
* Scope per unità con millesimi del tipo specificato
|
|
*/
|
|
public function scopeConMillesimi($query, string $tipo = 'proprieta')
|
|
{
|
|
return $query->where("millesimi_{$tipo}", '>', 0);
|
|
}
|
|
|
|
/**
|
|
* Scope per unità del piano specificato
|
|
*/
|
|
public function scopeDelPiano($query, int $piano)
|
|
{
|
|
return $query->where('piano', $piano);
|
|
}
|
|
|
|
/**
|
|
* Scope per unità con superficie minima
|
|
*/
|
|
public function scopeConSuperficieMinima($query, float $minima)
|
|
{
|
|
return $query->where('superficie_commerciale', '>=', $minima);
|
|
}
|
|
|
|
/**
|
|
* Scope per unità che necessitano lavori
|
|
*/
|
|
public function scopeConLavoriNecessari($query)
|
|
{
|
|
return $query->where('necessita_lavori', true);
|
|
}
|
|
} |