# 🏢 TABELLA STABILI ## Edifici e condomini gestiti dall'amministratore --- ## 🎯 **CARATTERISTICHE TABELLA** ### **Scopo**: Tabella per tutti gli edifici gestiti dall'amministratore - ogni stabile appartiene a un singolo amministratore ### **Principio**: **Isolamento per amministratore** - ogni stabile ha `amministratore_id` per filtro multi-tenant --- ## 🗄️ **STRUTTURA TABELLA** ```sql CREATE TABLE stabili ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, amministratore_id BIGINT UNSIGNED NOT NULL, -- FK verso amministratori -- Identificazione stabile denominazione VARCHAR(255) NOT NULL, -- Nome condominio/edificio codice_interno VARCHAR(50) NULL, -- Codice interno amministratore -- Indirizzo completo indirizzo VARCHAR(255) NOT NULL, numero_civico VARCHAR(20) NULL, scala VARCHAR(50) NULL, cap VARCHAR(10) NOT NULL, comune_id INT UNSIGNED NULL, -- FK verso comuni_italiani provincia VARCHAR(2) NOT NULL, regione VARCHAR(50) NULL, -- Dati catastali foglio VARCHAR(20) NULL, particella VARCHAR(20) NULL, subalterno VARCHAR(20) NULL, categoria_catastale VARCHAR(10) NULL, classe VARCHAR(10) NULL, consistenza VARCHAR(50) NULL, rendita_catastale DECIMAL(10, 2) NULL, -- Caratteristiche edificio anno_costruzione YEAR NULL, numero_piani INT NULL, numero_unita INT DEFAULT 0, -- Calcolato automaticamente ascensore BOOLEAN DEFAULT FALSE, riscaldamento_centralizzato BOOLEAN DEFAULT FALSE, tipo_edificio ENUM('condominio', 'villetta', 'complesso', 'altro') DEFAULT 'condominio', -- Dati amministrativi codice_fiscale_condominio VARCHAR(16) NULL, -- CF del condominio partita_iva_condominio VARCHAR(11) NULL, data_nomina_amministratore DATE NULL, data_scadenza_mandato DATE NULL, periodicita_assemblea ENUM('annuale', 'semestrale') DEFAULT 'annuale', -- Configurazioni usa_millesimi BOOLEAN DEFAULT TRUE, valuta_default VARCHAR(3) DEFAULT 'EUR', configurazioni JSON NULL, -- Config specifiche stabile -- Stato is_active BOOLEAN DEFAULT TRUE, note TEXT NULL, -- Audit created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Foreign Keys FOREIGN KEY (amministratore_id) REFERENCES amministratori(id) ON DELETE CASCADE, FOREIGN KEY (comune_id) REFERENCES comuni_italiani(id) ON DELETE SET NULL, -- Indici INDEX idx_amministratore_id (amministratore_id), INDEX idx_comune_id (comune_id), INDEX idx_denominazione (denominazione), INDEX idx_active (is_active), INDEX idx_cap (cap), INDEX idx_provincia (provincia), INDEX idx_tipo_edificio (tipo_edificio), -- Indice composto per ricerche INDEX idx_admin_denominazione (amministratore_id, denominazione), INDEX idx_admin_active (amministratore_id, is_active) ); ``` --- ## 🏗️ **MIGRATION LARAVEL** ### **File**: `database/migrations/2024_08_05_000010_create_stabili_table.php` ```php id(); $table->foreignId('amministratore_id') ->constrained('amministratori') ->onDelete('cascade'); // Identificazione stabile $table->string('denominazione'); $table->string('codice_interno', 50)->nullable(); // Indirizzo completo $table->string('indirizzo'); $table->string('numero_civico', 20)->nullable(); $table->string('scala', 50)->nullable(); $table->string('cap', 10); $table->unsignedInteger('comune_id')->nullable(); $table->string('provincia', 2); $table->string('regione', 50)->nullable(); // Dati catastali $table->string('foglio', 20)->nullable(); $table->string('particella', 20)->nullable(); $table->string('subalterno', 20)->nullable(); $table->string('categoria_catastale', 10)->nullable(); $table->string('classe', 10)->nullable(); $table->string('consistenza', 50)->nullable(); $table->decimal('rendita_catastale', 10, 2)->nullable(); // Caratteristiche edificio $table->year('anno_costruzione')->nullable(); $table->integer('numero_piani')->nullable(); $table->integer('numero_unita')->default(0); $table->boolean('ascensore')->default(false); $table->boolean('riscaldamento_centralizzato')->default(false); $table->enum('tipo_edificio', ['condominio', 'villetta', 'complesso', 'altro']) ->default('condominio'); // Dati amministrativi $table->string('codice_fiscale_condominio', 16)->nullable(); $table->string('partita_iva_condominio', 11)->nullable(); $table->date('data_nomina_amministratore')->nullable(); $table->date('data_scadenza_mandato')->nullable(); $table->enum('periodicita_assemblea', ['annuale', 'semestrale']) ->default('annuale'); // Configurazioni $table->boolean('usa_millesimi')->default(true); $table->string('valuta_default', 3)->default('EUR'); $table->json('configurazioni')->nullable(); // Stato $table->boolean('is_active')->default(true); $table->text('note')->nullable(); $table->timestamps(); // Foreign Keys $table->foreign('comune_id') ->references('id') ->on('comuni_italiani') ->onDelete('set null'); // Indici $table->index(['amministratore_id']); $table->index(['comune_id']); $table->index(['denominazione']); $table->index(['is_active']); $table->index(['cap']); $table->index(['provincia']); $table->index(['tipo_edificio']); // Indici composti $table->index(['amministratore_id', 'denominazione'], 'idx_admin_denominazione'); $table->index(['amministratore_id', 'is_active'], 'idx_admin_active'); }); } public function down(): void { Schema::dropIfExists('stabili'); } }; ``` --- ## 📊 **MODEL ELOQUENT** ### **File**: `app/Models/Stabile.php` ```php 'boolean', 'riscaldamento_centralizzato' => 'boolean', 'usa_millesimi' => 'boolean', 'is_active' => 'boolean', 'data_nomina_amministratore' => 'date', 'data_scadenza_mandato' => 'date', 'rendita_catastale' => 'decimal:2', 'configurazioni' => 'array', 'numero_piani' => 'integer', 'numero_unita' => 'integer', ]; // Relazioni public function amministratore(): BelongsTo { return $this->belongsTo(Amministratore::class); } public function comune(): BelongsTo { return $this->belongsTo(ComuneItaliano::class, 'comune_id'); } public function unitaImmobiliari(): HasMany { return $this->hasMany(UnitaImmobiliare::class); } public function gestioni(): HasMany { return $this->hasMany(Gestione::class); } public function documenti(): HasMany { return $this->hasMany(Documento::class); } public function soggetti(): HasMany { return $this->hasMany(Soggetto::class); } // Scope automatico per amministratore corrente protected static function booted(): void { static::addGlobalScope('amministratore', function (Builder $builder) { $user = auth()->user(); if ($user && $user->amministratore_id) { $builder->where('amministratore_id', $user->amministratore_id); } }); } // Scope public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } public function scopeByTipo(Builder $query, string $tipo): Builder { return $query->where('tipo_edificio', $tipo); } public function scopeByProvincia(Builder $query, string $provincia): Builder { return $query->where('provincia', $provincia); } public function scopeConAscensore(Builder $query): Builder { return $query->where('ascensore', true); } public function scopeConRiscaldamentoCentralizzato(Builder $query): Builder { return $query->where('riscaldamento_centralizzato', true); } // Accessors public function getIndirizzoCompletoAttribute(): string { $parts = array_filter([ $this->indirizzo, $this->numero_civico, $this->scala ? "Scala {$this->scala}" : null, ]); return implode(', ', $parts); } public function getLocalitaAttribute(): string { $parts = array_filter([ $this->cap, $this->comune?->denominazione, $this->provincia ? "({$this->provincia})" : null, ]); return implode(' ', $parts); } public function getIndirizzoFormattato(): string { return "{$this->indirizzo_completo}\n{$this->localita}"; } public function getMandatoScadutoAttribute(): bool { return $this->data_scadenza_mandato && $this->data_scadenza_mandato->isPast(); } public function getGiorniAllaScadenzaAttribute(): ?int { if (!$this->data_scadenza_mandato) { return null; } return now()->diffInDays($this->data_scadenza_mandato, false); } // Helper Methods public function updateNumeroUnita(): void { $count = $this->unitaImmobiliari()->count(); $this->update(['numero_unita' => $count]); } public function getGestioneAttiva(): ?Gestione { return $this->gestioni() ->where('anno_gestione', now()->year) ->where('is_active', true) ->first(); } public function hasGestioneAttiva(): bool { return $this->getGestioneAttiva() !== null; } public function canDelete(): bool { return $this->unitaImmobiliari()->count() === 0 && $this->gestioni()->count() === 0; } public function getStatistiche(): array { return [ 'numero_unita' => $this->numero_unita, 'gestioni_totali' => $this->gestioni()->count(), 'gestioni_attive' => $this->gestioni()->where('is_active', true)->count(), 'documenti_totali' => $this->documenti()->count(), 'soggetti_coinvolti' => $this->soggetti()->count(), ]; } // Observer Events protected static function boot(): void { parent::boot(); static::creating(function ($stabile) { // Assegna automaticamente l'amministratore corrente se non specificato if (!$stabile->amministratore_id && auth()->check()) { $stabile->amministratore_id = auth()->user()->amministratore_id; } }); static::saved(function ($stabile) { // Aggiorna conteggio unità dopo salvataggio $stabile->updateNumeroUnita(); }); } } ``` --- ## 🌱 **SEEDER DATI DEMO** ### **File**: `database/seeders/StabiliSeeder.php` ```php first(); $roma = ComuneItaliano::where('denominazione', 'Roma')->first(); $torino = ComuneItaliano::where('denominazione', 'Torino')->first(); foreach ($amministratori as $admin) { // Stabile 1 - Condominio grande Stabile::create([ 'amministratore_id' => $admin->id, 'denominazione' => 'Condominio Bella Vista', 'codice_interno' => 'BV001', 'indirizzo' => 'Via Roma', 'numero_civico' => '123', 'cap' => $admin->id === 1 ? '20100' : ($admin->id === 2 ? '00100' : '10100'), 'comune_id' => $admin->id === 1 ? $milano?->id : ($admin->id === 2 ? $roma?->id : $torino?->id), 'provincia' => $admin->id === 1 ? 'MI' : ($admin->id === 2 ? 'RM' : 'TO'), 'regione' => $admin->id === 1 ? 'Lombardia' : ($admin->id === 2 ? 'Lazio' : 'Piemonte'), 'foglio' => '123', 'particella' => '456', 'categoria_catastale' => 'A/2', 'classe' => '3', 'rendita_catastale' => 15000.00, 'anno_costruzione' => 1985, 'numero_piani' => 5, 'ascensore' => true, 'riscaldamento_centralizzato' => true, 'tipo_edificio' => 'condominio', 'data_nomina_amministratore' => now()->subMonths(6), 'data_scadenza_mandato' => now()->addMonths(6), 'periodicita_assemblea' => 'annuale', 'usa_millesimi' => true, 'is_active' => true, ]); // Stabile 2 - Villetta Stabile::create([ 'amministratore_id' => $admin->id, 'denominazione' => 'Villa dei Fiori', 'codice_interno' => 'VF001', 'indirizzo' => 'Via Dante Alighieri', 'numero_civico' => '45', 'cap' => $admin->id === 1 ? '20100' : ($admin->id === 2 ? '00100' : '10100'), 'comune_id' => $admin->id === 1 ? $milano?->id : ($admin->id === 2 ? $roma?->id : $torino?->id), 'provincia' => $admin->id === 1 ? 'MI' : ($admin->id === 2 ? 'RM' : 'TO'), 'regione' => $admin->id === 1 ? 'Lombardia' : ($admin->id === 2 ? 'Lazio' : 'Piemonte'), 'foglio' => '789', 'particella' => '012', 'categoria_catastale' => 'A/7', 'classe' => '4', 'rendita_catastale' => 8000.00, 'anno_costruzione' => 2000, 'numero_piani' => 2, 'ascensore' => false, 'riscaldamento_centralizzato' => false, 'tipo_edificio' => 'villetta', 'data_nomina_amministratore' => now()->subYear(), 'data_scadenza_mandato' => now()->addYear(), 'periodicita_assemblea' => 'annuale', 'usa_millesimi' => false, 'is_active' => true, ]); // Stabile 3 - Complesso residenziale if ($admin->piano_abbonamento !== 'base') { Stabile::create([ 'amministratore_id' => $admin->id, 'denominazione' => 'Residenza Park View', 'codice_interno' => 'PV001', 'indirizzo' => 'Viale Europa', 'numero_civico' => '200', 'scala' => 'A-B-C', 'cap' => $admin->id === 1 ? '20100' : ($admin->id === 2 ? '00100' : '10100'), 'comune_id' => $admin->id === 1 ? $milano?->id : ($admin->id === 2 ? $roma?->id : $torino?->id), 'provincia' => $admin->id === 1 ? 'MI' : ($admin->id === 2 ? 'RM' : 'TO'), 'regione' => $admin->id === 1 ? 'Lombardia' : ($admin->id === 2 ? 'Lazio' : 'Piemonte'), 'anno_costruzione' => 2010, 'numero_piani' => 8, 'ascensore' => true, 'riscaldamento_centralizzato' => true, 'tipo_edificio' => 'complesso', 'codice_fiscale_condominio' => '80123456789', 'data_nomina_amministratore' => now()->subMonths(3), 'data_scadenza_mandato' => now()->addMonths(9), 'periodicita_assemblea' => 'annuale', 'usa_millesimi' => true, 'configurazioni' => [ 'portierato' => true, 'videosorveglianza' => true, 'piscina' => true, 'palestra' => false, ], 'is_active' => true, ]); } } $totalStabili = Stabile::count(); $this->command->info("✅ Creati {$totalStabili} stabili per tutti gli amministratori"); // Mostra riassunto per amministratore foreach ($amministratori as $admin) { $count = $admin->stabili()->count(); $this->command->info("Admin {$admin->codice_univoco}: {$count} stabili"); } } } ``` --- ## 🔍 **QUERY PATTERNS** ### **Query Base per Amministratore**: ```php // Controller - Lista stabili dell'amministratore corrente public function index() { $stabili = Stabile::with(['comune', 'unitaImmobiliari']) ->active() ->orderBy('denominazione') ->paginate(20); return view('stabili.index', compact('stabili')); } // Query con filtri public function search(Request $request) { $query = Stabile::query(); if ($request->filled('denominazione')) { $query->where('denominazione', 'like', "%{$request->denominazione}%"); } if ($request->filled('provincia')) { $query->where('provincia', $request->provincia); } if ($request->filled('tipo_edificio')) { $query->byTipo($request->tipo_edificio); } $stabili = $query->with('comune')->paginate(20); return view('stabili.index', compact('stabili')); } ``` ### **Statistiche per Dashboard**: ```php // Controller Dashboard public function dashboard() { $amministratore = auth()->user()->amministratore; $stats = [ 'stabili_totali' => $amministratore->stabili()->count(), 'stabili_attivi' => $amministratore->stabili()->active()->count(), 'unita_totali' => $amministratore->stabili() ->withCount('unitaImmobiliari') ->get() ->sum('unita_immobiliari_count'), 'mandati_in_scadenza' => $amministratore->stabili() ->whereDate('data_scadenza_mandato', '<=', now()->addDays(30)) ->count(), ]; return view('dashboard', compact('stats')); } ``` --- ## 🎛️ **FORM VALIDATION** ### **File**: `app/Http/Requests/StabileRequest.php` ```php check(); } public function rules(): array { $stabileId = $this->route('stabile')?->id; return [ 'denominazione' => 'required|string|max:255', 'codice_interno' => [ 'nullable', 'string', 'max:50', Rule::unique('stabili') ->where('amministratore_id', auth()->user()->amministratore_id) ->ignore($stabileId) ], 'indirizzo' => 'required|string|max:255', 'numero_civico' => 'nullable|string|max:20', 'cap' => 'required|string|size:5', 'provincia' => 'required|string|size:2', 'comune_id' => 'nullable|exists:comuni_italiani,id', 'tipo_edificio' => 'required|in:condominio,villetta,complesso,altro', 'anno_costruzione' => 'nullable|integer|min:1800|max:' . (date('Y') + 5), 'numero_piani' => 'nullable|integer|min:1|max:100', 'ascensore' => 'boolean', 'riscaldamento_centralizzato' => 'boolean', 'data_nomina_amministratore' => 'nullable|date', 'data_scadenza_mandato' => 'nullable|date|after:data_nomina_amministratore', 'codice_fiscale_condominio' => 'nullable|string|size:16', 'partita_iva_condominio' => 'nullable|string|size:11', 'rendita_catastale' => 'nullable|numeric|min:0', ]; } public function messages(): array { return [ 'denominazione.required' => 'La denominazione è obbligatoria.', 'indirizzo.required' => 'L\'indirizzo è obbligatorio.', 'cap.required' => 'Il CAP è obbligatorio.', 'cap.size' => 'Il CAP deve essere di 5 cifre.', 'provincia.required' => 'La provincia è obbligatoria.', 'codice_interno.unique' => 'Questo codice interno è già utilizzato per un altro stabile.', 'data_scadenza_mandato.after' => 'La data scadenza deve essere successiva alla data nomina.', ]; } } ``` --- ## 🎯 **UTILIZZO PATTERN** ### **Nel Controller**: ```php // Sempre filtrato per amministratore corrente grazie al Global Scope $stabili = Stabile::all(); // Solo stabili dell'amministratore loggato // Query specifiche $condomini = Stabile::byTipo('condominio')->get(); $stabiliMilano = Stabile::byProvincia('MI')->get(); $conAscensore = Stabile::conAscensore()->get(); ``` ### **Nei Blade Templates**: ```blade {{-- Lista stabili --}} @foreach($stabili as $stabile)

{{ $stabile->denominazione }}

{{ $stabile->indirizzo_formattato }}

{{ $stabile->numero_unita }} unità @if($stabile->mandato_scaduto) Mandato scaduto @elseif($stabile->giorni_alla_scadenza <= 30) Scade tra {{ $stabile->giorni_alla_scadenza }} giorni @endif
@endforeach ``` --- **🎯 TABELLA STABILI DEFINITA** **Con isolamento per amministratore, dati catastali completi e gestione mandati** **📖 Prossimo: `gestioni.md` per collegare i periodi amministrativi agli stabili**