675 lines
23 KiB
Markdown
675 lines
23 KiB
Markdown
# 🏠 MODULO UNITÀ IMMOBILIARI AVANZATO
|
|
|
|
> **📋 Specifiche tecniche complete**
|
|
> Aggiornato: 15 Luglio 2025
|
|
> Base per: Sprint 3 Implementazione
|
|
|
|
## 🎯 **OBIETTIVI MODULO**
|
|
|
|
### **FUNZIONALITÀ INNOVATIVE**
|
|
- ✅ **Millesimi multipli** (proprietà, riscaldamento, ascensore, scale, etc.)
|
|
- 🔄 **Gestione subentri** automatici con storico
|
|
- 📊 **Calcolo ripartizioni** automatico per tipo spesa
|
|
- 🏗️ **Composizione unità** (unione/divisione automatica)
|
|
- 🔑 **Collegamento chiavi** e controlli accessi
|
|
- 📈 **Analytics occupazione** e andamento millesimi
|
|
|
|
## 🏗️ **STRUTTURA DATABASE**
|
|
|
|
### **Tabella unita_immobiliari (ESTENSIONE)**
|
|
```sql
|
|
-- Campi aggiuntivi per modulo avanzato
|
|
ALTER TABLE unita_immobiliari ADD COLUMN (
|
|
-- Millesimi dettagliati
|
|
millesimi_proprieta DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_riscaldamento DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_ascensore DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_scale DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_pulizie DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_custom_1 DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_custom_2 DECIMAL(8,4) DEFAULT 0,
|
|
millesimi_custom_3 DECIMAL(8,4) DEFAULT 0,
|
|
|
|
-- Dati tecnici avanzati
|
|
superficie_commerciale DECIMAL(8,2),
|
|
superficie_calpestabile DECIMAL(8,2),
|
|
superficie_balconi DECIMAL(8,2),
|
|
superficie_terrazzi DECIMAL(8,2),
|
|
numero_vani TINYINT,
|
|
numero_bagni TINYINT,
|
|
numero_balconi TINYINT,
|
|
classe_energetica VARCHAR(5),
|
|
anno_costruzione YEAR,
|
|
anno_ristrutturazione YEAR,
|
|
|
|
-- Stato e condizione
|
|
stato_conservazione ENUM('ottimo','buono','discreto','cattivo'),
|
|
necessita_lavori BOOLEAN DEFAULT FALSE,
|
|
note_tecniche TEXT,
|
|
|
|
-- Collegamento struttura fisica
|
|
struttura_fisica_id BIGINT UNSIGNED,
|
|
|
|
-- Automazioni
|
|
calcolo_automatico_millesimi BOOLEAN DEFAULT TRUE,
|
|
notifiche_subentri BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Metadati avanzati
|
|
created_by BIGINT UNSIGNED,
|
|
updated_by BIGINT UNSIGNED,
|
|
|
|
FOREIGN KEY (struttura_fisica_id) REFERENCES struttura_fisica_dettaglio(id),
|
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
|
FOREIGN KEY (updated_by) REFERENCES users(id)
|
|
);
|
|
```
|
|
|
|
### **Tabella subentri_unita**
|
|
```sql
|
|
CREATE TABLE subentri_unita (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
unita_immobiliare_id BIGINT UNSIGNED NOT NULL,
|
|
soggetto_precedente_id BIGINT UNSIGNED,
|
|
soggetto_nuovo_id BIGINT UNSIGNED NOT NULL,
|
|
|
|
-- Dati subentro
|
|
data_subentro DATE NOT NULL,
|
|
tipo_subentro ENUM('vendita','eredita','donazione','locazione','comodato') NOT NULL,
|
|
quota_precedente DECIMAL(5,4) DEFAULT 1.0000,
|
|
quota_nuova DECIMAL(5,4) DEFAULT 1.0000,
|
|
|
|
-- Documenti
|
|
numero_atto VARCHAR(100),
|
|
data_atto DATE,
|
|
notaio VARCHAR(200),
|
|
prezzo_vendita DECIMAL(12,2),
|
|
|
|
-- Stati
|
|
stato_subentro ENUM('proposto','in_corso','completato','annullato') DEFAULT 'proposto',
|
|
data_completamento TIMESTAMP NULL,
|
|
|
|
-- Automazioni
|
|
ripartizioni_aggiornate BOOLEAN DEFAULT FALSE,
|
|
comunicazioni_inviate BOOLEAN DEFAULT FALSE,
|
|
|
|
-- Note e allegati
|
|
note TEXT,
|
|
allegati JSON,
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
created_by BIGINT UNSIGNED,
|
|
|
|
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (soggetto_precedente_id) REFERENCES soggetti(id),
|
|
FOREIGN KEY (soggetto_nuovo_id) REFERENCES soggetti(id),
|
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
|
|
|
INDEX idx_subentri_unita (unita_immobiliare_id),
|
|
INDEX idx_subentri_data (data_subentro),
|
|
INDEX idx_subentri_stato (stato_subentro)
|
|
);
|
|
```
|
|
|
|
### **Tabella composizione_unita**
|
|
```sql
|
|
CREATE TABLE composizione_unita (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
-- Unità coinvolte
|
|
unita_originale_id BIGINT UNSIGNED, -- NULL se è una nuova composizione
|
|
unita_risultante_id BIGINT UNSIGNED NOT NULL,
|
|
|
|
-- Tipo operazione
|
|
tipo_operazione ENUM('unione','divisione','modifica') NOT NULL,
|
|
data_operazione DATE NOT NULL,
|
|
|
|
-- Dati operazione
|
|
superficie_trasferita DECIMAL(8,2),
|
|
millesimi_trasferiti DECIMAL(8,4),
|
|
vani_trasferiti TINYINT,
|
|
|
|
-- Calcoli automatici
|
|
millesimi_automatici BOOLEAN DEFAULT TRUE,
|
|
coefficiente_ripartizione DECIMAL(6,4) DEFAULT 1.0000,
|
|
|
|
-- Documenti
|
|
numero_pratica VARCHAR(100),
|
|
riferimento_catastale VARCHAR(200),
|
|
note_variazione TEXT,
|
|
|
|
-- Stati
|
|
stato_pratica ENUM('in_corso','approvata','respinta','completata') DEFAULT 'in_corso',
|
|
data_approvazione DATE,
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
created_by BIGINT UNSIGNED,
|
|
|
|
FOREIGN KEY (unita_originale_id) REFERENCES unita_immobiliari(id),
|
|
FOREIGN KEY (unita_risultante_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
|
|
|
INDEX idx_composizione_operazione (tipo_operazione, data_operazione),
|
|
INDEX idx_composizione_stato (stato_pratica)
|
|
);
|
|
```
|
|
|
|
### **Tabella ripartizioni_spese**
|
|
```sql
|
|
CREATE TABLE ripartizioni_spese (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
stabile_id BIGINT UNSIGNED NOT NULL,
|
|
|
|
-- Configurazione ripartizione
|
|
nome_ripartizione VARCHAR(200) NOT NULL,
|
|
descrizione TEXT,
|
|
tipo_millesimi ENUM('proprieta','riscaldamento','ascensore','scale','pulizie','custom_1','custom_2','custom_3') NOT NULL,
|
|
|
|
-- Criteri calcolo
|
|
includi_pertinenze BOOLEAN DEFAULT TRUE,
|
|
includi_locazioni BOOLEAN DEFAULT TRUE,
|
|
minimo_presenza DECIMAL(5,2) DEFAULT 0.00, -- % minima presenza per essere inclusi
|
|
|
|
-- Configurazione automatica
|
|
attiva BOOLEAN DEFAULT TRUE,
|
|
aggiornamento_automatico BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Validità temporale
|
|
data_inizio DATE NOT NULL,
|
|
data_fine DATE,
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
created_by BIGINT UNSIGNED,
|
|
|
|
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
|
|
|
UNIQUE KEY uk_ripartizioni_nome_stabile (stabile_id, nome_ripartizione),
|
|
INDEX idx_ripartizioni_tipo (tipo_millesimi),
|
|
INDEX idx_ripartizioni_attive (attiva, data_inizio, data_fine)
|
|
);
|
|
```
|
|
|
|
## 📱 **INTERFACCIA UTENTE**
|
|
|
|
### **Dashboard Unità Immobiliare**
|
|
```php
|
|
// File: resources/views/admin/unita/show.blade.php
|
|
@extends('layouts.app-universal-v2')
|
|
|
|
@section('content')
|
|
<div class="container-fluid">
|
|
<!-- Header con azioni rapide -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4><i class="fas fa-home"></i> {{ $unita->denominazione }}</h4>
|
|
<small>{{ $unita->stabile->denominazione }} - {{ $unita->piano }}° piano</small>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<h6>Millesimi Proprietà</h6>
|
|
<h3 class="text-primary">{{ number_format($unita->millesimi_proprieta, 4) }}‰</h3>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h6>Superficie Comm.</h6>
|
|
<h3 class="text-info">{{ number_format($unita->superficie_commerciale, 2) }} m²</h3>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h6>Proprietario Attuale</h6>
|
|
<h5>{{ $unita->proprietarioAttuale?->denominazione ?? 'Non assegnato' }}</h5>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<h6>Stato</h6>
|
|
<span class="badge badge-{{ $unita->getStatoBadgeColor() }}">
|
|
{{ $unita->stato_conservazione }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Navigation -->
|
|
<ul class="nav nav-tabs" id="unitaTabs" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" data-toggle="tab" href="#generale">
|
|
<i class="fas fa-info-circle"></i> Generale
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-toggle="tab" href="#millesimi">
|
|
<i class="fas fa-chart-pie"></i> Millesimi
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-toggle="tab" href="#subentri">
|
|
<i class="fas fa-exchange-alt"></i> Subentri
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-toggle="tab" href="#composizione">
|
|
<i class="fas fa-puzzle-piece"></i> Composizione
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-toggle="tab" href="#analytics">
|
|
<i class="fas fa-chart-line"></i> Analytics
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content mt-3">
|
|
<!-- Tab Generale -->
|
|
<div class="tab-pane fade show active" id="generale">
|
|
@include('admin.unita.tabs.generale')
|
|
</div>
|
|
|
|
<!-- Tab Millesimi -->
|
|
<div class="tab-pane fade" id="millesimi">
|
|
@include('admin.unita.tabs.millesimi')
|
|
</div>
|
|
|
|
<!-- Tab Subentri -->
|
|
<div class="tab-pane fade" id="subentri">
|
|
@include('admin.unita.tabs.subentri')
|
|
</div>
|
|
|
|
<!-- Tab Composizione -->
|
|
<div class="tab-pane fade" id="composizione">
|
|
@include('admin.unita.tabs.composizione')
|
|
</div>
|
|
|
|
<!-- Tab Analytics -->
|
|
<div class="tab-pane fade" id="analytics">
|
|
@include('admin.unita.tabs.analytics')
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
```
|
|
|
|
## 🔧 **MODELS ELOQUENT**
|
|
|
|
### **UnitaImmobiliare.php (Estensione)**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|
|
|
class UnitaImmobiliare extends Model
|
|
{
|
|
protected $table = 'unita_immobiliari';
|
|
|
|
protected $fillable = [
|
|
'stabile_id', 'denominazione', 'piano', 'interno',
|
|
'millesimi_proprieta', 'millesimi_riscaldamento', 'millesimi_ascensore',
|
|
'millesimi_scale', 'millesimi_pulizie', 'millesimi_custom_1',
|
|
'millesimi_custom_2', 'millesimi_custom_3',
|
|
'superficie_commerciale', '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 = [
|
|
'millesimi_proprieta' => 'decimal:4',
|
|
'millesimi_riscaldamento' => 'decimal:4',
|
|
'millesimi_ascensore' => 'decimal:4',
|
|
'millesimi_scale' => 'decimal:4',
|
|
'millesimi_pulizie' => 'decimal:4',
|
|
'millesimi_custom_1' => 'decimal:4',
|
|
'millesimi_custom_2' => 'decimal:4',
|
|
'millesimi_custom_3' => 'decimal:4',
|
|
'superficie_commerciale' => 'decimal:2',
|
|
'superficie_calpestabile' => 'decimal:2',
|
|
'superficie_balconi' => 'decimal:2',
|
|
'superficie_terrazzi' => 'decimal:2',
|
|
'necessita_lavori' => 'boolean',
|
|
'calcolo_automatico_millesimi' => 'boolean',
|
|
'notifiche_subentri' => 'boolean',
|
|
];
|
|
|
|
// === RELAZIONI ===
|
|
|
|
public function stabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
public function subentri(): HasMany
|
|
{
|
|
return $this->hasMany(SubentroUnita::class);
|
|
}
|
|
|
|
public function composizioni(): HasMany
|
|
{
|
|
return $this->hasMany(ComposizioneUnita::class, 'unita_risultante_id');
|
|
}
|
|
|
|
public function strutturaFisica(): BelongsTo
|
|
{
|
|
return $this->belongsTo(StrutturaFisicaDettaglio::class, 'struttura_fisica_id');
|
|
}
|
|
|
|
public function soggetti(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(Soggetto::class, 'soggetti_unita_immobiliari')
|
|
->withPivot(['quota', 'tipo_diritto', 'data_inizio', 'data_fine'])
|
|
->withTimestamps();
|
|
}
|
|
|
|
// === METODI UTILITÀ ===
|
|
|
|
public function proprietarioAttuale()
|
|
{
|
|
return $this->soggetti()
|
|
->wherePivot('tipo_diritto', 'proprietà')
|
|
->wherePivot('data_fine', null)
|
|
->first();
|
|
}
|
|
|
|
public function calcolaMillesimiAutomatici(): array
|
|
{
|
|
if (!$this->calcolo_automatico_millesimi) {
|
|
return [];
|
|
}
|
|
|
|
$totaleStabile = $this->stabile->unita()->sum('superficie_commerciale');
|
|
$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;
|
|
$coefficientePiano = $this->getCoefficientePiano();
|
|
$coefficienteEsposizione = $this->getCoefficieneEsposizione();
|
|
|
|
return round($base * $coefficientePiano * $coefficienteEsposizione /
|
|
$this->stabile->getTotaleMillesimiRiscaldamento() * 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
|
|
return round($this->superficie_commerciale * $coefficientePiano /
|
|
$this->stabile->getTotaleMillesimiAscensore() * 1000, 4);
|
|
}
|
|
|
|
private function calcolaMillesimiScale(): float
|
|
{
|
|
if ($this->piano <= 0) {
|
|
return round($this->superficie_commerciale * 0.5 /
|
|
$this->stabile->getTotaleMillesimiScale() * 1000, 4);
|
|
}
|
|
|
|
return round($this->superficie_commerciale /
|
|
$this->stabile->getTotaleMillesimiScale() * 1000, 4);
|
|
}
|
|
|
|
public function getStatoBadgeColor(): string
|
|
{
|
|
return match($this->stato_conservazione) {
|
|
'ottimo' => 'success',
|
|
'buono' => 'info',
|
|
'discreto' => 'warning',
|
|
'cattivo' => 'danger',
|
|
default => 'secondary'
|
|
};
|
|
}
|
|
|
|
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 ===
|
|
|
|
public function scopeConMillesimi($query, string $tipo = 'proprieta')
|
|
{
|
|
return $query->where("millesimi_{$tipo}", '>', 0);
|
|
}
|
|
|
|
public function scopeDelPiano($query, int $piano)
|
|
{
|
|
return $query->where('piano', $piano);
|
|
}
|
|
|
|
public function scopeConSuperficieMinima($query, float $minima)
|
|
{
|
|
return $query->where('superficie_commerciale', '>=', $minima);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🚀 **CONTROLLER AVANZATO**
|
|
|
|
### **UnitaImmobiliareController.php**
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\UnitaImmobiliare;
|
|
use App\Models\Stabile;
|
|
use App\Models\Soggetto;
|
|
use App\Models\SubentroUnita;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class UnitaImmobiliareController extends Controller
|
|
{
|
|
public function show(UnitaImmobiliare $unita)
|
|
{
|
|
$unita->load([
|
|
'stabile',
|
|
'soggetti',
|
|
'subentri.soggettoNuovo',
|
|
'subentri.soggettoPrecedente',
|
|
'composizioni',
|
|
'strutturaFisica'
|
|
]);
|
|
|
|
$analytics = $this->calcolaAnalytics($unita);
|
|
|
|
return view('admin.unita.show', compact('unita', 'analytics'));
|
|
}
|
|
|
|
public function ricalcolaMillesimi(UnitaImmobiliare $unita)
|
|
{
|
|
if (!$unita->calcolo_automatico_millesimi) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Calcolo automatico disabilitato per questa unità'
|
|
], 400);
|
|
}
|
|
|
|
$nuoviMillesimi = $unita->calcolaMillesimiAutomatici();
|
|
|
|
$unita->update($nuoviMillesimi);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Millesimi ricalcolati automaticamente',
|
|
'data' => $nuoviMillesimi
|
|
]);
|
|
}
|
|
|
|
public function creaSubentro(Request $request, UnitaImmobiliare $unita)
|
|
{
|
|
$request->validate([
|
|
'soggetto_nuovo_id' => 'required|exists:soggetti,id',
|
|
'data_subentro' => 'required|date',
|
|
'tipo_subentro' => 'required|in:vendita,eredita,donazione,locazione,comodato',
|
|
'quota' => 'required|numeric|min:0|max:1',
|
|
'numero_atto' => 'nullable|string|max:100',
|
|
'data_atto' => 'nullable|date',
|
|
'notaio' => 'nullable|string|max:200',
|
|
'prezzo_vendita' => 'nullable|numeric|min:0'
|
|
]);
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$nuovoSoggetto = Soggetto::findOrFail($request->soggetto_nuovo_id);
|
|
|
|
$subentro = $unita->generaSubentroAutomatico($nuovoSoggetto, $request->all());
|
|
|
|
// Aggiorna relazione soggetti_unita_immobiliari
|
|
$this->aggiornaProprietaUnita($unita, $subentro);
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Subentro creato con successo',
|
|
'subentro_id' => $subentro->id
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Errore durante la creazione del subentro: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function approvaSubentro(SubentroUnita $subentro)
|
|
{
|
|
if ($subentro->stato_subentro !== 'proposto') {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Il subentro deve essere in stato "proposto" per essere approvato'
|
|
], 400);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$subentro->update([
|
|
'stato_subentro' => 'completato',
|
|
'data_completamento' => now(),
|
|
'ripartizioni_aggiornate' => true
|
|
]);
|
|
|
|
// Completa il passaggio di proprietà
|
|
$this->completaSubentro($subentro);
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Subentro approvato e completato'
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Errore durante l\'approvazione: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
private function calcolaAnalytics(UnitaImmobiliare $unita): array
|
|
{
|
|
return [
|
|
'storico_subentri' => $unita->subentri()->count(),
|
|
'composizioni_totali' => $unita->composizioni()->count(),
|
|
'superficie_totale' => $unita->superficie_commerciale +
|
|
$unita->superficie_balconi +
|
|
$unita->superficie_terrazzi,
|
|
'percentuale_millesimi' => ($unita->millesimi_proprieta / 10), // Su base 100
|
|
'valore_catastale_stimato' => $this->stimaValoreCatastale($unita),
|
|
'trend_mercato' => $this->calcolaTrendMercato($unita)
|
|
];
|
|
}
|
|
|
|
private function completaSubentro(SubentroUnita $subentro): void
|
|
{
|
|
$unita = $subentro->unitaImmobiliare;
|
|
|
|
// Chiudi relazione precedente
|
|
if ($subentro->soggetto_precedente_id) {
|
|
$unita->soggetti()
|
|
->wherePivot('id', $subentro->soggetto_precedente_id)
|
|
->updateExistingPivot($subentro->soggetto_precedente_id, [
|
|
'data_fine' => $subentro->data_subentro
|
|
]);
|
|
}
|
|
|
|
// Crea nuova relazione
|
|
$unita->soggetti()->attach($subentro->soggetto_nuovo_id, [
|
|
'quota' => $subentro->quota_nuova,
|
|
'tipo_diritto' => $this->getTipoDirittoFromSubentro($subentro->tipo_subentro),
|
|
'data_inizio' => $subentro->data_subentro,
|
|
'data_fine' => null
|
|
]);
|
|
}
|
|
|
|
private function getTipoDirittoFromSubentro(string $tipoSubentro): string
|
|
{
|
|
return match($tipoSubentro) {
|
|
'vendita', 'eredita', 'donazione' => 'proprietà',
|
|
'locazione' => 'locazione',
|
|
'comodato' => 'comodato',
|
|
default => 'proprietà'
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🎯 **PROSSIMI PASSI IMPLEMENTAZIONE**
|
|
|
|
### **Sprint 3: Unità Immobiliari Avanzate**
|
|
1. **Database**: Creare le nuove migrazioni
|
|
2. **Models**: Estendere UnitaImmobiliare e creare i nuovi model
|
|
3. **Controller**: Implementare UnitaImmobiliareController avanzato
|
|
4. **Views**: Creare dashboard completa con tab
|
|
5. **API**: Endpoint per calcoli automatici e subentri
|
|
6. **Testing**: Test completi funzionalità avanzate
|
|
|
|
### **Milestone Raggiunta**
|
|
✅ **Modulo Unità Immobiliari Avanzato** - Specifiche Complete
|
|
🎯 **Ready for Implementation** - Sprint 3 Fase 2
|