netgescon-master/docs/moduli/02-MODULO-UNITA-IMMOBILIARI.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

🔄 Versione: 2025.07.19-1644
🎯 Sistema pronto per Git distribuito
2025-07-19 16:44:47 +02:00

642 lines
28 KiB
Markdown

# 🏠 MODULO UNITÀ IMMOBILIARI - Specifiche Complete
## 📋 OVERVIEW
Il **Modulo Unità Immobiliari** gestisce l'anagrafica completa delle singole unità immobiliari all'interno di ogni stabile, i collegamenti con proprietari/inquilini, la gestione delle quote millesimali e lo storico delle proprietà.
---
## 🎯 OBIETTIVI DEL MODULO
### ✅ Funzionalità Core
- **Anagrafica Unità Complete**: Tutti i dati tecnici e amministrativi
- **Gestione Proprietari**: Collegamento con persone fisiche/giuridiche
- **Quote Millesimali**: Gestione millesimi per ogni tabella
- **Storico Proprietà**: Tracking completo passaggi di proprietà
- **Classificazioni Multiple**: Tipologie uso, categorie catastali, ecc.
### 🔗 Integrazione Sistema
- **Collegamento Stabile**: Ogni unità appartiene a uno stabile
- **Base Contabile**: Fondamento per ripartizioni spese
- **Gestione Documenti**: Archivio contratti, atti, planimetrie
- **API Complete**: Endpoint per app mobile e integrazioni
---
## 💾 STRUTTURA DATABASE
### 📊 Tabella: `unita_immobiliari`
```sql
CREATE TABLE unita_immobiliari (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
-- Identificazione Unità
codice_unita VARCHAR(20) NOT NULL COMMENT 'Es: A01, B12, Box-05',
denominazione VARCHAR(255) COMMENT 'Nome descrittivo unità',
piano VARCHAR(10) COMMENT 'Piano (T, 1, 2, S1, ecc.)',
interno VARCHAR(10) COMMENT 'Numero interno',
-- Tipologia e Uso
tipologia_unita ENUM('appartamento', 'box', 'cantina', 'locale_commerciale', 'ufficio', 'posto_auto', 'altro') NOT NULL,
destinazione_uso ENUM('residenziale', 'commerciale', 'ufficio', 'deposito', 'garage', 'altro') NOT NULL,
categoria_catastale VARCHAR(5) COMMENT 'A/1, A/2, C/1, ecc.',
classe_catastale VARCHAR(5),
-- Dati Tecnici
superficie_commerciale DECIMAL(8,2) COMMENT 'mq',
superficie_calpestabile DECIMAL(8,2) COMMENT 'mq',
superficie_balconi DECIMAL(8,2) COMMENT 'mq',
superficie_terrazzi DECIMAL(8,2) COMMENT 'mq',
numero_vani DECIMAL(3,1),
numero_bagni TINYINT,
numero_balconi TINYINT,
altezza_media DECIMAL(3,2) COMMENT 'metri',
-- Dati Catastali
foglio VARCHAR(10),
particella VARCHAR(10),
subalterno VARCHAR(10),
rendita_catastale DECIMAL(10,2),
valore_catastale DECIMAL(12,2),
-- Caratteristiche Tecniche
riscaldamento ENUM('autonomo', 'centralizzato', 'condominiale', 'assente') DEFAULT 'centralizzato',
condizionamento BOOLEAN DEFAULT FALSE,
ascensore BOOLEAN DEFAULT FALSE,
balcone BOOLEAN DEFAULT FALSE,
terrazzo BOOLEAN DEFAULT FALSE,
giardino BOOLEAN DEFAULT FALSE,
posto_auto BOOLEAN DEFAULT FALSE,
classe_energetica ENUM('A4', 'A3', 'A2', 'A1', 'B', 'C', 'D', 'E', 'F', 'G'),
-- Stato Unità
stato ENUM('attiva', 'venduta', 'sfittata', 'in_ristrutturazione', 'disabitata') DEFAULT 'attiva',
data_costruzione DATE,
data_ultima_ristrutturazione DATE,
-- Note e Descrizioni
descrizione TEXT,
note_amministrative TEXT,
caratteristiche_speciali TEXT,
-- Timestamps e Audit
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT UNSIGNED,
updated_by BIGINT UNSIGNED,
-- Indici
INDEX idx_stabile_codice (stabile_id, codice_unita),
INDEX idx_tipologia (tipologia_unita),
INDEX idx_stato (stato),
INDEX idx_catastale (foglio, particella, subalterno),
-- Foreign Keys
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
-- Constraints
UNIQUE KEY uk_stabile_codice_unita (stabile_id, codice_unita)
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 📊 Tabella: `unita_proprieta`
```sql
CREATE TABLE unita_proprieta (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
unita_id BIGINT UNSIGNED NOT NULL,
persona_id BIGINT UNSIGNED NOT NULL,
-- Tipo Proprietà
tipo_proprieta ENUM('proprieta', 'usufrutto', 'nuda_proprieta', 'diritto_superficie', 'altro') DEFAULT 'proprieta',
quota_proprieta DECIMAL(8,6) NOT NULL DEFAULT 1.000000 COMMENT 'Quota di proprietà (es: 0.5 = 50%)',
-- Periodo Validità
data_inizio DATE NOT NULL,
data_fine DATE NULL,
-- Dati Acquisizione
titolo_acquisizione ENUM('acquisto', 'successione', 'donazione', 'permuta', 'altro'),
atto_notarile VARCHAR(255),
data_atto DATE,
notaio VARCHAR(255),
prezzo_acquisto DECIMAL(12,2),
-- Stato Attuale
attivo BOOLEAN DEFAULT TRUE,
note TEXT,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT UNSIGNED,
-- Indici
INDEX idx_unita_attivo (unita_id, attivo),
INDEX idx_persona_periodo (persona_id, data_inizio, data_fine),
INDEX idx_data_atto (data_atto),
-- Foreign Keys
FOREIGN KEY (unita_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
FOREIGN KEY (persona_id) REFERENCES persone(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
### 📊 Tabella: `unita_millesimi`
```sql
CREATE TABLE unita_millesimi (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
unita_id BIGINT UNSIGNED NOT NULL,
tabella_millesimale_id BIGINT UNSIGNED NOT NULL,
-- Valori Millesimali
millesimi DECIMAL(10,6) NOT NULL COMMENT 'Valore millesimale (es: 25.500000)',
percentuale DECIMAL(8,6) GENERATED ALWAYS AS (millesimi / 1000) STORED COMMENT 'Percentuale automatica',
-- Validità Temporale
data_inizio DATE NOT NULL,
data_fine DATE NULL,
-- Approvazione
approvato BOOLEAN DEFAULT FALSE,
data_approvazione DATE NULL,
verbale_approvazione VARCHAR(255),
-- Note
note TEXT,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by BIGINT UNSIGNED,
-- Indici
INDEX idx_unita_tabella (unita_id, tabella_millesimale_id),
INDEX idx_tabella_periodo (tabella_millesimale_id, data_inizio, data_fine),
INDEX idx_approvato (approvato),
-- Foreign Keys
FOREIGN KEY (unita_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
FOREIGN KEY (tabella_millesimale_id) REFERENCES tabelle_millesimali(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
-- Constraint unico per periodo
UNIQUE KEY uk_unita_tabella_periodo (unita_id, tabella_millesimale_id, data_inizio)
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
---
## 🔧 CONTROLLER LARAVEL
### 📝 UnitaImmobiliareController
```php
<?php
namespace App\Http\Controllers;
use App\Models\UnitaImmobiliare;
use App\Models\Stabile;
use App\Http\Requests\UnitaImmobiliareRequest;
use App\Services\UnitaImmobiliareService;
use Illuminate\Http\Request;
class UnitaImmobiliareController extends Controller
{
protected $unitaService;
public function __construct(UnitaImmobiliareService $unitaService)
{
$this->unitaService = $unitaService;
$this->middleware('auth');
$this->middleware('permission:unita.view')->only(['index', 'show']);
$this->middleware('permission:unita.create')->only(['create', 'store']);
$this->middleware('permission:unita.edit')->only(['edit', 'update']);
$this->middleware('permission:unita.delete')->only(['destroy']);
}
/**
* Lista unità per stabile
*/
public function index(Request $request, Stabile $stabile)
{
$this->authorize('view', $stabile);
$unita = $this->unitaService->getUnitaByStabile($stabile, $request->all());
$statistiche = $this->unitaService->getStatisticheStabile($stabile);
return view('unita.index', compact('stabile', 'unita', 'statistiche'));
}
/**
* Form creazione nuova unità
*/
public function create(Stabile $stabile)
{
$this->authorize('update', $stabile);
return view('unita.create', compact('stabile'));
}
/**
* Salvataggio nuova unità
*/
public function store(UnitaImmobiliareRequest $request, Stabile $stabile)
{
$this->authorize('update', $stabile);
try {
$unita = $this->unitaService->createUnita($stabile, $request->validated());
return redirect()->route('unita.show', [$stabile, $unita])
->with('success', 'Unità immobiliare creata con successo!');
} catch (\Exception $e) {
return back()->withInput()
->with('error', 'Errore durante la creazione: ' . $e->getMessage());
}
}
/**
* Visualizzazione dettaglio unità
*/
public function show(Stabile $stabile, UnitaImmobiliare $unita)
{
$this->authorize('view', $stabile);
$unita->load(['proprieta.persona', 'millesimi.tabella', 'documenti']);
$dashboard = $this->unitaService->getDashboardUnita($unita);
return view('unita.show', compact('stabile', 'unita', 'dashboard'));
}
/**
* Form modifica unità
*/
public function edit(Stabile $stabile, UnitaImmobiliare $unita)
{
$this->authorize('update', $stabile);
return view('unita.edit', compact('stabile', 'unita'));
}
/**
* Aggiornamento unità
*/
public function update(UnitaImmobiliareRequest $request, Stabile $stabile, UnitaImmobiliare $unita)
{
$this->authorize('update', $stabile);
try {
$this->unitaService->updateUnita($unita, $request->validated());
return redirect()->route('unita.show', [$stabile, $unita])
->with('success', 'Unità immobiliare aggiornata con successo!');
} catch (\Exception $e) {
return back()->withInput()
->with('error', 'Errore durante l\'aggiornamento: ' . $e->getMessage());
}
}
/**
* Gestione proprietari unità
*/
public function proprieta(Stabile $stabile, UnitaImmobiliare $unita)
{
$this->authorize('view', $stabile);
$proprieta = $unita->proprieta()->with('persona')->get();
$persone = $this->unitaService->getPersoneDisponibili($stabile);
return view('unita.proprieta', compact('stabile', 'unita', 'proprieta', 'persone'));
}
/**
* Gestione millesimi unità
*/
public function millesimi(Stabile $stabile, UnitaImmobiliare $unita)
{
$this->authorize('view', $stabile);
$millesimi = $unita->millesimi()->with('tabella')->get();
$tabelle = $stabile->tabelleMillesimali()->get();
return view('unita.millesimi', compact('stabile', 'unita', 'millesimi', 'tabelle'));
}
}
```
---
## 🎨 INTERFACCE UTENTE
### 📋 Lista Unità (`unita/index.blade.php`)
```html
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('stabili.index') }}">Stabili</a></li>
<li class="breadcrumb-item"><a href="{{ route('stabili.show', $stabile) }}">{{ $stabile->denominazione }}</a></li>
<li class="breadcrumb-item active">Unità Immobiliari</li>
</ol>
</nav>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h3>🏠 Unità Immobiliari - {{ $stabile->denominazione }}</h3>
@can('update', $stabile)
<a href="{{ route('unita.create', $stabile) }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Nuova Unità
</a>
@endcan
</div>
<div class="card-body">
<!-- Statistiche Rapide -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card border-primary">
<div class="card-body text-center">
<h4 class="text-primary">{{ $statistiche['totale_unita'] }}</h4>
<p class="mb-0">Unità Totali</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-success">
<div class="card-body text-center">
<h4 class="text-success">{{ $statistiche['unita_attive'] }}</h4>
<p class="mb-0">Unità Attive</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-info">
<div class="card-body text-center">
<h4 class="text-info">{{ number_format($statistiche['superficie_totale'], 0) }} mq</h4>
<p class="mb-0">Superficie Totale</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-warning">
<div class="card-body text-center">
<h4 class="text-warning">{{ $statistiche['proprieta_attive'] }}</h4>
<p class="mb-0">Proprietari</p>
</div>
</div>
</div>
</div>
<!-- Filtri di Ricerca -->
<form method="GET" class="mb-4">
<div class="row">
<div class="col-md-2">
<input type="text" name="search" class="form-control"
placeholder="Cerca unità..."
value="{{ request('search') }}">
</div>
<div class="col-md-2">
<select name="tipologia" class="form-control">
<option value="">Tutte le tipologie</option>
<option value="appartamento" {{ request('tipologia') == 'appartamento' ? 'selected' : '' }}>Appartamento</option>
<option value="box" {{ request('tipologia') == 'box' ? 'selected' : '' }}>Box</option>
<option value="cantina" {{ request('tipologia') == 'cantina' ? 'selected' : '' }}>Cantina</option>
<option value="posto_auto" {{ request('tipologia') == 'posto_auto' ? 'selected' : '' }}>Posto Auto</option>
</select>
</div>
<div class="col-md-2">
<select name="piano" class="form-control">
<option value="">Tutti i piani</option>
@foreach($statistiche['piani'] as $piano)
<option value="{{ $piano }}" {{ request('piano') == $piano ? 'selected' : '' }}>
Piano {{ $piano }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<select name="stato" class="form-control">
<option value="">Tutti gli stati</option>
<option value="attiva" {{ request('stato') == 'attiva' ? 'selected' : '' }}>Attiva</option>
<option value="venduta" {{ request('stato') == 'venduta' ? 'selected' : '' }}>Venduta</option>
<option value="sfittata" {{ request('stato') == 'sfittata' ? 'selected' : '' }}>Sfittata</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-primary">
<i class="fas fa-search"></i> Cerca
</button>
</div>
</div>
</form>
<!-- Tabella Unità -->
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Codice</th>
<th>Tipologia</th>
<th>Piano</th>
<th>Superficie</th>
<th>Proprietario</th>
<th>Millesimi</th>
<th>Stato</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@forelse($unita as $unita_item)
<tr>
<td>
<strong>{{ $unita_item->codice_unita }}</strong>
@if($unita_item->denominazione)
<br><small class="text-muted">{{ $unita_item->denominazione }}</small>
@endif
</td>
<td>
<span class="badge badge-secondary">{{ ucfirst($unita_item->tipologia_unita) }}</span>
@if($unita_item->categoria_catastale)
<br><small class="text-muted">Cat. {{ $unita_item->categoria_catastale }}</small>
@endif
</td>
<td>{{ $unita_item->piano ?? '-' }}</td>
<td>
@if($unita_item->superficie_commerciale)
{{ number_format($unita_item->superficie_commerciale, 0) }} mq
@if($unita_item->numero_vani)
<br><small class="text-muted">{{ $unita_item->numero_vani }} vani</small>
@endif
@else
-
@endif
</td>
<td>
@if($unita_item->proprietari->count() > 0)
@foreach($unita_item->proprietari as $proprietario)
<div>{{ $proprietario->nome_completo }}</div>
@if($proprietario->pivot->quota_proprieta < 1)
<small class="text-muted">{{ number_format($proprietario->pivot->quota_proprieta * 100, 1) }}%</small>
@endif
@endforeach
@else
<em class="text-muted">Nessun proprietario</em>
@endif
</td>
<td>
@if($unita_item->millesimi_totali)
{{ number_format($unita_item->millesimi_totali, 3) }}‰
@else
<em class="text-muted">Non definiti</em>
@endif
</td>
<td>
@switch($unita_item->stato)
@case('attiva')
<span class="badge badge-success">Attiva</span>
@break
@case('venduta')
<span class="badge badge-info">Venduta</span>
@break
@case('sfittata')
<span class="badge badge-warning">Sfittata</span>
@break
@case('in_ristrutturazione')
<span class="badge badge-secondary">In Ristrutturazione</span>
@break
@default
<span class="badge badge-light">{{ ucfirst($unita_item->stato) }}</span>
@endswitch
</td>
<td>
<div class="btn-group" role="group">
@can('view', $stabile)
<a href="{{ route('unita.show', [$stabile, $unita_item]) }}"
class="btn btn-sm btn-info" title="Visualizza">
<i class="fas fa-eye"></i>
</a>
@endcan
@can('update', $stabile)
<a href="{{ route('unita.edit', [$stabile, $unita_item]) }}"
class="btn btn-sm btn-warning" title="Modifica">
<i class="fas fa-edit"></i>
</a>
@endcan
@can('update', $stabile)
<a href="{{ route('unita.proprieta', [$stabile, $unita_item]) }}"
class="btn btn-sm btn-success" title="Proprietari">
<i class="fas fa-users"></i>
</a>
@endcan
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center">
<em>Nessuna unità immobiliare trovata</em>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
{{ $unita->links() }}
</div>
</div>
</div>
</div>
</div>
@endsection
```
---
## 🔄 WORKFLOW OPERATIVO
### 📝 Processo Creazione Unità
1. **Accesso da Stabile**: Admin accede dalla vista dettaglio stabile
2. **Form Guidato**: Wizard step-by-step per dati completi
3. **Validazione Avanzata**: Controllo unicità codice e coerenza dati
4. **Salvataggio Atomico**: Insert unità + configurazioni iniziali
5. **Collegamento Automatico**: Link con tabelle millesimali default
### 📊 Gestione Proprietari
1. **Aggiunta Proprietario**: Selezione da anagrafica o creazione nuova
2. **Definizione Quote**: Specificazione percentuali proprietà
3. **Validazione Totali**: Controllo che la somma quote = 100%
4. **Storico Automatico**: Tracking passaggi proprietà
5. **Documenti Collegati**: Upload atti, contratti, planimetrie
### 🧮 Gestione Millesimi
1. **Collegamento Tabelle**: Assegnazione millesimi per ogni tabella
2. **Validazione Matematica**: Controllo coerenza totali = 1000‰
3. **Approvazione Formale**: Workflow approvazione assemblea
4. **Storico Modifiche**: Versioning millesimi nel tempo
5. **Calcoli Automatici**: Ripartizioni spese in base a millesimi
---
## 🔒 BUSINESS RULES
### ✅ Regole Validazione
- **Codice Unità Univoco**: Per stabile (es: A01, B12, Box-05)
- **Quote Proprietà**: Somma deve essere = 100% per unità
- **Millesimi Coerenti**: Valori devono essere > 0 e matematicamente corretti
- **Superfici Logiche**: Superficie calpestabile ≤ commerciale
- **Dati Catastali**: Se presenti, devono essere completi e validi
### 🔄 Automazioni
- **Aggiornamento Contatori**: Numero unità in tabella stabili
- **Calcolo Superficie**: Totali automatici per piano/tipologia
- **Notificazioni**: Alert per modifiche importanti
- **Backup Differenziale**: Salvataggio automatico modifiche critiche
---
## 📈 METRICHE E REPORTISTICA
### 📊 KPI Principali
- Numero unità per tipologia
- Superficie totale per piano
- Distribuzione millesimi
- Percentuale unità con proprietari definiti
- Tempo medio gestione pratica proprietà
### 📋 Report Automatici
- Registro unità immobiliari
- Tabella proprietari attuali
- Distribuzione millesimi per tabella
- Analisi superfici e rendite
- Report modifiche periodiche
---
## 🚀 PROSSIMI SVILUPPI
### 🔄 Fase 2 - Miglioramenti
- Import massivo unità da Excel/CAD
- Gestione contratti locazione
- Integrazione con visure catastali
- Calcolo IMU/TASI automatico
### 🎯 Fase 3 - Avanzate
- Planimetrie interattive
- Timeline visuale modifiche
- Analisi predittiva valori
- App mobile proprietari
---
*Documento tecnico Modulo Unità Immobiliari | Versione 1.0 | Luglio 2025*