netgescon-day0/docs/91-database/02-tabelle-collegate/unita-immobiliari.md

832 lines
28 KiB
Markdown

# 🏠 TABELLA UNITA IMMOBILIARI
## Appartamenti, locali e box collegati agli stabili
---
## 🔑 **CONNESSIONE DATABASE**
### **Credenziali NetGescon**:
```bash
# Connessione MySQL
mysql -u netgescon_user -p netgescon
# Password: NetGescon2024!
# Test connessione Laravel
cd /var/www/netgescon
php artisan tinker
DB::connection()->getPdo();
```
---
## 🎯 **CARATTERISTICHE TABELLA**
### **Scopo**:
Tabella per tutti gli appartamenti, locali, box e cantine appartenenti agli stabili
### **Principio**:
**Collegamento via stabile** - ogni unità appartiene a uno stabile → isolamento automatico per amministratore
---
## 🗄️ **STRUTTURA TABELLA**
```sql
CREATE TABLE unita_immobiliari (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL, -- FK verso stabili
-- Identificazione unità
interno VARCHAR(20) NOT NULL, -- A1, B12, Box 5, ecc
piano INT NULL, -- Piano (0=terra, -1=seminterrato)
scala VARCHAR(10) NULL, -- A, B, C
denominazione VARCHAR(255) NULL, -- Appartamento al 2° piano
codice_interno VARCHAR(50) NULL, -- Codice amministratore
-- Tipologia unità
tipo_unita ENUM('appartamento', 'locale_commerciale', 'box', 'cantina', 'mansarda', 'soffitta', 'altro') DEFAULT 'appartamento',
destinazione_uso ENUM('abitazione', 'commerciale', 'ufficio', 'deposito', 'altro') DEFAULT 'abitazione',
-- Dati catastali
foglio VARCHAR(20) NULL,
particella VARCHAR(20) NULL,
subalterno VARCHAR(20) NOT NULL, -- Obbligatorio per unità
categoria_catastale VARCHAR(10) NULL, -- A/2, A/3, C/1, ecc
classe VARCHAR(10) NULL,
consistenza VARCHAR(50) NULL, -- Vani, mq, mc
superficie_commerciale DECIMAL(8, 2) NULL, -- mq
superficie_calpestabile DECIMAL(8, 2) NULL, -- mq
rendita_catastale DECIMAL(8, 2) NULL, -- Euro
-- Caratteristiche fisiche
numero_vani INT NULL,
numero_bagni INT NULL,
numero_balconi INT NULL,
has_terrazzo BOOLEAN DEFAULT FALSE,
has_giardino BOOLEAN DEFAULT FALSE,
has_cantina BOOLEAN DEFAULT FALSE,
has_box BOOLEAN DEFAULT FALSE,
-- Millesimi
millesimi_generali DECIMAL(8, 4) DEFAULT 0.0000, -- /1000
millesimi_riscaldamento DECIMAL(8, 4) DEFAULT 0.0000,
millesimi_ascensore DECIMAL(8, 4) DEFAULT 0.0000,
millesimi_scala DECIMAL(8, 4) DEFAULT 0.0000,
millesimi_personalizzati JSON NULL, -- Altri tipi millesimi
-- Stato occupazione
stato_occupazione ENUM('libera', 'occupata_proprietario', 'occupata_inquilino', 'sfitto', 'vendita') DEFAULT 'libera',
data_ultimo_cambio_stato DATE NULL,
-- Configurazioni
is_active BOOLEAN DEFAULT TRUE,
note TEXT NULL,
configurazioni JSON NULL, -- Config specifiche unità
-- Audit
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Foreign Keys
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
-- Indici
INDEX idx_stabile_id (stabile_id),
INDEX idx_interno (interno),
INDEX idx_piano (piano),
INDEX idx_tipo_unita (tipo_unita),
INDEX idx_categoria_catastale (categoria_catastale),
INDEX idx_stato_occupazione (stato_occupazione),
INDEX idx_active (is_active),
INDEX idx_subalterno (subalterno),
-- Indici composti
INDEX idx_stabile_interno (stabile_id, interno),
INDEX idx_stabile_piano (stabile_id, piano),
INDEX idx_catastale (foglio, particella, subalterno),
-- Vincolo unicità: interno univoco per stabile
UNIQUE KEY unique_stabile_interno (stabile_id, interno),
-- Vincolo unicità: subalterno univoco per stabile
UNIQUE KEY unique_stabile_subalterno (stabile_id, subalterno)
);
```
---
## 🏗️ **MIGRATION LARAVEL**
### **File**: `database/migrations/2024_08_05_000030_create_unita_immobiliari_table.php`
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('unita_immobiliari', function (Blueprint $table) {
$table->id();
$table->foreignId('stabile_id')
->constrained('stabili')
->onDelete('cascade');
// Identificazione unità
$table->string('interno', 20);
$table->integer('piano')->nullable();
$table->string('scala', 10)->nullable();
$table->string('denominazione')->nullable();
$table->string('codice_interno', 50)->nullable();
// Tipologia unità
$table->enum('tipo_unita', [
'appartamento', 'locale_commerciale', 'box',
'cantina', 'mansarda', 'soffitta', 'altro'
])->default('appartamento');
$table->enum('destinazione_uso', [
'abitazione', 'commerciale', 'ufficio', 'deposito', 'altro'
])->default('abitazione');
// Dati catastali
$table->string('foglio', 20)->nullable();
$table->string('particella', 20)->nullable();
$table->string('subalterno', 20);
$table->string('categoria_catastale', 10)->nullable();
$table->string('classe', 10)->nullable();
$table->string('consistenza', 50)->nullable();
$table->decimal('superficie_commerciale', 8, 2)->nullable();
$table->decimal('superficie_calpestabile', 8, 2)->nullable();
$table->decimal('rendita_catastale', 8, 2)->nullable();
// Caratteristiche fisiche
$table->integer('numero_vani')->nullable();
$table->integer('numero_bagni')->nullable();
$table->integer('numero_balconi')->nullable();
$table->boolean('has_terrazzo')->default(false);
$table->boolean('has_giardino')->default(false);
$table->boolean('has_cantina')->default(false);
$table->boolean('has_box')->default(false);
// Millesimi
$table->decimal('millesimi_generali', 8, 4)->default(0.0000);
$table->decimal('millesimi_riscaldamento', 8, 4)->default(0.0000);
$table->decimal('millesimi_ascensore', 8, 4)->default(0.0000);
$table->decimal('millesimi_scala', 8, 4)->default(0.0000);
$table->json('millesimi_personalizzati')->nullable();
// Stato occupazione
$table->enum('stato_occupazione', [
'libera', 'occupata_proprietario', 'occupata_inquilino', 'sfitto', 'vendita'
])->default('libera');
$table->date('data_ultimo_cambio_stato')->nullable();
// Configurazioni
$table->boolean('is_active')->default(true);
$table->text('note')->nullable();
$table->json('configurazioni')->nullable();
$table->timestamps();
// Indici
$table->index(['stabile_id']);
$table->index(['interno']);
$table->index(['piano']);
$table->index(['tipo_unita']);
$table->index(['categoria_catastale']);
$table->index(['stato_occupazione']);
$table->index(['is_active']);
$table->index(['subalterno']);
// Indici composti
$table->index(['stabile_id', 'interno'], 'idx_stabile_interno');
$table->index(['stabile_id', 'piano'], 'idx_stabile_piano');
$table->index(['foglio', 'particella', 'subalterno'], 'idx_catastale');
// Vincoli unicità
$table->unique(['stabile_id', 'interno'], 'unique_stabile_interno');
$table->unique(['stabile_id', 'subalterno'], 'unique_stabile_subalterno');
});
}
public function down(): void
{
Schema::dropIfExists('unita_immobiliari');
}
};
```
---
## 📊 **MODEL ELOQUENT**
### **File**: `app/Models/UnitaImmobiliare.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
class UnitaImmobiliare extends Model
{
protected $fillable = [
'stabile_id',
'interno',
'piano',
'scala',
'denominazione',
'codice_interno',
'tipo_unita',
'destinazione_uso',
'foglio',
'particella',
'subalterno',
'categoria_catastale',
'classe',
'consistenza',
'superficie_commerciale',
'superficie_calpestabile',
'rendita_catastale',
'numero_vani',
'numero_bagni',
'numero_balconi',
'has_terrazzo',
'has_giardino',
'has_cantina',
'has_box',
'millesimi_generali',
'millesimi_riscaldamento',
'millesimi_ascensore',
'millesimi_scala',
'millesimi_personalizzati',
'stato_occupazione',
'data_ultimo_cambio_stato',
'is_active',
'note',
'configurazioni',
];
protected $casts = [
'piano' => 'integer',
'superficie_commerciale' => 'decimal:2',
'superficie_calpestabile' => 'decimal:2',
'rendita_catastale' => 'decimal:2',
'numero_vani' => 'integer',
'numero_bagni' => 'integer',
'numero_balconi' => 'integer',
'has_terrazzo' => 'boolean',
'has_giardino' => 'boolean',
'has_cantina' => 'boolean',
'has_box' => 'boolean',
'millesimi_generali' => 'decimal:4',
'millesimi_riscaldamento' => 'decimal:4',
'millesimi_ascensore' => 'decimal:4',
'millesimi_scala' => 'decimal:4',
'millesimi_personalizzati' => 'array',
'data_ultimo_cambio_stato' => 'date',
'is_active' => 'boolean',
'configurazioni' => 'array',
];
// Relazioni
public function stabile(): BelongsTo
{
return $this->belongsTo(Stabile::class);
}
public function soggetti(): BelongsToMany
{
return $this->belongsToMany(Soggetto::class, 'unita_soggetti')
->withPivot([
'tipo_rapporto',
'quota_proprieta',
'data_inizio',
'data_fine',
'is_active'
])
->withTimestamps();
}
public function proprietari(): BelongsToMany
{
return $this->soggetti()->wherePivot('tipo_rapporto', 'proprietario');
}
public function inquilini(): BelongsToMany
{
return $this->soggetti()->wherePivot('tipo_rapporto', 'inquilino');
}
public function contabilitaRegistrazioni(): HasMany
{
return $this->hasMany(ContabilitaRegistrazione::class);
}
public function documenti(): HasMany
{
return $this->hasMany(Documento::class);
}
// Scope automatico per amministratore tramite stabile
protected static function booted(): void
{
static::addGlobalScope('amministratore', function (Builder $builder) {
$user = auth()->user();
if ($user && $user->amministratore_id) {
$builder->whereHas('stabile', function ($query) use ($user) {
$query->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_unita', $tipo);
}
public function scopeByPiano(Builder $query, int $piano): Builder
{
return $query->where('piano', $piano);
}
public function scopeByStato(Builder $query, string $stato): Builder
{
return $query->where('stato_occupazione', $stato);
}
public function scopeLibere(Builder $query): Builder
{
return $query->where('stato_occupazione', 'libera');
}
public function scopeOccupate(Builder $query): Builder
{
return $query->whereIn('stato_occupazione', ['occupata_proprietario', 'occupata_inquilino']);
}
public function scopeByMillesimi(Builder $query, string $tipo = 'generali'): Builder
{
$campo = "millesimi_{$tipo}";
return $query->where($campo, '>', 0);
}
// Accessors
public function getDenominazioneComputedAttribute(): string
{
return $this->denominazione ?: "{$this->tipo_unita} {$this->interno}";
}
public function getIndirizzoCompletoAttribute(): string
{
$parts = array_filter([
$this->stabile->denominazione,
$this->scala ? "Scala {$this->scala}" : null,
"Interno {$this->interno}",
$this->piano !== null ? "Piano {$this->piano}" : null,
]);
return implode(', ', $parts);
}
public function getIdentificativoCatastaleAttribute(): string
{
$parts = array_filter([
$this->foglio ? "F.{$this->foglio}" : null,
$this->particella ? "P.{$this->particella}" : null,
$this->subalterno ? "Sub.{$this->subalterno}" : null,
]);
return implode(' ', $parts);
}
public function getSuperficieTotaleAttribute(): ?float
{
return $this->superficie_commerciale ?: $this->superficie_calpestabile;
}
public function getIsAppartamentoAttribute(): bool
{
return $this->tipo_unita === 'appartamento';
}
public function getIsLocaleCommercialeAttribute(): bool
{
return $this->tipo_unita === 'locale_commerciale';
}
public function getIsBoxAttribute(): bool
{
return $this->tipo_unita === 'box';
}
public function getHasProprietarioAttribute(): bool
{
return $this->proprietari()->exists();
}
public function getHasInquilinoAttribute(): bool
{
return $this->inquilini()->exists();
}
public function getStatoColorAttribute(): string
{
return match($this->stato_occupazione) {
'libera' => 'gray',
'occupata_proprietario' => 'green',
'occupata_inquilino' => 'blue',
'sfitto' => 'yellow',
'vendita' => 'red',
default => 'gray'
};
}
// Helper Methods
public function calcolaMillesimiAutomatici(): void
{
if (!$this->superficie_totale) {
return;
}
$stabileSuperficieTotale = $this->stabile->unitaImmobiliari()
->whereNotNull('superficie_commerciale')
->sum('superficie_commerciale');
if ($stabileSuperficieTotale > 0) {
$millesimi = ($this->superficie_totale / $stabileSuperficieTotale) * 1000;
$this->update([
'millesimi_generali' => round($millesimi, 4)
]);
}
}
public function aggiornaStatoOccupazione(string $nuovoStato): bool
{
if ($this->stato_occupazione === $nuovoStato) {
return false;
}
$this->update([
'stato_occupazione' => $nuovoStato,
'data_ultimo_cambio_stato' => now()->toDateString()
]);
return true;
}
public function getProprietarioAttuale(): ?Soggetto
{
return $this->proprietari()
->wherePivot('is_active', true)
->wherePivot('data_fine', null)
->first();
}
public function getInquilinoAttuale(): ?Soggetto
{
return $this->inquilini()
->wherePivot('is_active', true)
->wherePivot('data_fine', null)
->first();
}
public function associaSoggetto(Soggetto $soggetto, string $tipoRapporto, array $datiAggiuntivi = []): void
{
$datiPivot = array_merge([
'tipo_rapporto' => $tipoRapporto,
'quota_proprieta' => $datiAggiuntivi['quota_proprieta'] ?? 100.00,
'data_inizio' => $datiAggiuntivi['data_inizio'] ?? now()->toDateString(),
'data_fine' => $datiAggiuntivi['data_fine'] ?? null,
'is_active' => $datiAggiuntivi['is_active'] ?? true,
], $datiAggiuntivi);
$this->soggetti()->attach($soggetto->id, $datiPivot);
// Aggiorna stato occupazione automaticamente
if ($tipoRapporto === 'proprietario') {
$this->aggiornaStatoOccupazione('occupata_proprietario');
} elseif ($tipoRapporto === 'inquilino') {
$this->aggiornaStatoOccupazione('occupata_inquilino');
}
}
public function rimuoviSoggetto(Soggetto $soggetto): void
{
$this->soggetti()->updateExistingPivot($soggetto->id, [
'is_active' => false,
'data_fine' => now()->toDateString()
]);
// Controlla se rimangono soggetti attivi
$hasProprietarioAttivo = $this->proprietari()
->wherePivot('is_active', true)
->exists();
$hasInquilinoAttivo = $this->inquilini()
->wherePivot('is_active', true)
->exists();
if (!$hasProprietarioAttivo && !$hasInquilinoAttivo) {
$this->aggiornaStatoOccupazione('libera');
}
}
public function canDelete(): bool
{
return $this->soggetti()->count() === 0 &&
$this->contabilitaRegistrazioni()->count() === 0;
}
// Observer Events
protected static function boot(): void
{
parent::boot();
static::creating(function ($unita) {
// Genera denominazione automatica se non specificata
if (!$unita->denominazione) {
$unita->denominazione = ucfirst($unita->tipo_unita) . " {$unita->interno}";
}
});
static::saved(function ($unita) {
// Aggiorna conteggio unità dello stabile
$unita->stabile->updateNumeroUnita();
// Ricalcola millesimi se superficie cambiata
if ($unita->wasChanged('superficie_commerciale')) {
$unita->calcolaMillesimiAutomatici();
}
});
static::deleted(function ($unita) {
// Aggiorna conteggio unità dello stabile
$unita->stabile->updateNumeroUnita();
});
}
}
```
---
## 🌱 **SEEDER DATI DEMO**
### **File**: `database/seeders/UnitaImmobiliariSeeder.php`
```php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\UnitaImmobiliare;
use App\Models\Stabile;
class UnitaImmobiliariSeeder extends Seeder
{
public function run(): void
{
$stabili = Stabile::all();
foreach ($stabili as $stabile) {
$this->createUnitaPerStabile($stabile);
}
$totalUnita = UnitaImmobiliare::count();
$this->command->info("✅ Create {$totalUnita} unità immobiliari per tutti gli stabili");
// Mostra riassunto per amministratore
$stabili->groupBy('amministratore_id')->each(function ($stabiliGroup) {
$admin = $stabiliGroup->first()->amministratore;
$unitaCount = UnitaImmobiliare::whereIn('stabile_id', $stabiliGroup->pluck('id'))->count();
$this->command->info("Admin {$admin->codice_univoco}: {$unitaCount} unità immobiliari");
});
}
private function createUnitaPerStabile(Stabile $stabile): void
{
$tipoEdificio = $stabile->tipo_edificio;
$numeroPiani = $stabile->numero_piani ?? 3;
if ($tipoEdificio === 'condominio') {
$this->createUnitaCondominio($stabile, $numeroPiani);
} elseif ($tipoEdificio === 'villetta') {
$this->createUnitaVilletta($stabile);
} elseif ($tipoEdificio === 'complesso') {
$this->createUnitaComplesso($stabile, $numeroPiani);
}
}
private function createUnitaCondominio(Stabile $stabile, int $numeroPiani): void
{
$subCount = 1;
// Appartamenti per piano
for ($piano = 1; $piano <= $numeroPiani; $piano++) {
$unitaPerPiano = $piano === 1 ? 3 : 4; // Piano terra meno unità
for ($unita = 1; $unita <= $unitaPerPiano; $unita++) {
$interno = "A{$piano}{$unita}";
UnitaImmobiliare::create([
'stabile_id' => $stabile->id,
'interno' => $interno,
'piano' => $piano,
'scala' => 'A',
'denominazione' => "Appartamento piano {$piano}",
'tipo_unita' => 'appartamento',
'destinazione_uso' => 'abitazione',
'foglio' => $stabile->foglio,
'particella' => $stabile->particella,
'subalterno' => str_pad($subCount++, 3, '0', STR_PAD_LEFT),
'categoria_catastale' => $piano <= 2 ? 'A/2' : 'A/3',
'classe' => '3',
'consistenza' => rand(4, 6) . ' vani',
'superficie_commerciale' => rand(60, 120),
'superficie_calpestabile' => rand(55, 100),
'rendita_catastale' => rand(300, 800),
'numero_vani' => rand(3, 5),
'numero_bagni' => rand(1, 2),
'numero_balconi' => rand(1, 3),
'has_terrazzo' => $piano === $numeroPiani && rand(0, 1),
'has_cantina' => rand(0, 1),
'millesimi_generali' => rand(80, 120),
'millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato ? rand(80, 120) : 0,
'millesimi_ascensore' => $stabile->ascensore && $piano > 1 ? rand(80, 120) : 0,
'stato_occupazione' => ['occupata_proprietario', 'occupata_inquilino', 'libera'][rand(0, 2)],
'is_active' => true,
]);
}
}
// Box/garage al piano seminterrato
if (rand(0, 1)) {
for ($box = 1; $box <= 8; $box++) {
UnitaImmobiliare::create([
'stabile_id' => $stabile->id,
'interno' => "Box{$box}",
'piano' => -1,
'denominazione' => "Box auto n. {$box}",
'tipo_unita' => 'box',
'destinazione_uso' => 'deposito',
'foglio' => $stabile->foglio,
'particella' => $stabile->particella,
'subalterno' => str_pad($subCount++, 3, '0', STR_PAD_LEFT),
'categoria_catastale' => 'C/6',
'superficie_commerciale' => rand(15, 25),
'superficie_calpestabile' => rand(15, 25),
'rendita_catastale' => rand(50, 150),
'millesimi_generali' => rand(5, 15),
'stato_occupazione' => ['occupata_proprietario', 'libera'][rand(0, 1)],
'is_active' => true,
]);
}
}
}
private function createUnitaVilletta(Stabile $stabile): void
{
// Villetta = un'unica unità
UnitaImmobiliare::create([
'stabile_id' => $stabile->id,
'interno' => 'VILLA',
'piano' => 0,
'denominazione' => 'Villa unifamiliare',
'tipo_unita' => 'appartamento',
'destinazione_uso' => 'abitazione',
'foglio' => $stabile->foglio,
'particella' => $stabile->particella,
'subalterno' => '001',
'categoria_catastale' => 'A/7',
'classe' => '4',
'consistenza' => '8 vani',
'superficie_commerciale' => rand(150, 250),
'superficie_calpestabile' => rand(130, 220),
'rendita_catastale' => rand(800, 1500),
'numero_vani' => rand(6, 8),
'numero_bagni' => rand(2, 3),
'numero_balconi' => rand(2, 4),
'has_terrazzo' => true,
'has_giardino' => true,
'has_cantina' => true,
'millesimi_generali' => 1000, // Unica unità = 1000/1000
'stato_occupazione' => 'occupata_proprietario',
'is_active' => true,
]);
}
private function createUnitaComplesso(Stabile $stabile, int $numeroPiani): void
{
$subCount = 1;
$scale = ['A', 'B', 'C'];
foreach ($scale as $scala) {
for ($piano = 1; $piano <= $numeroPiani; $piano++) {
for ($unita = 1; $unita <= 2; $unita++) {
$interno = "{$scala}{$piano}{$unita}";
UnitaImmobiliare::create([
'stabile_id' => $stabile->id,
'interno' => $interno,
'piano' => $piano,
'scala' => $scala,
'denominazione' => "Appartamento scala {$scala} piano {$piano}",
'tipo_unita' => 'appartamento',
'destinazione_uso' => 'abitazione',
'foglio' => $stabile->foglio,
'particella' => $stabile->particella,
'subalterno' => str_pad($subCount++, 3, '0', STR_PAD_LEFT),
'categoria_catastale' => 'A/2',
'classe' => '3',
'superficie_commerciale' => rand(70, 130),
'superficie_calpestabile' => rand(65, 115),
'rendita_catastale' => rand(400, 900),
'numero_vani' => rand(3, 5),
'numero_bagni' => rand(1, 2),
'numero_balconi' => rand(1, 2),
'has_terrazzo' => $piano === $numeroPiani && rand(0, 1),
'millesimi_generali' => rand(30, 50),
'millesimi_riscaldamento' => $stabile->riscaldamento_centralizzato ? rand(30, 50) : 0,
'millesimi_ascensore' => $stabile->ascensore && $piano > 1 ? rand(30, 50) : 0,
'millesimi_scala' => rand(15, 25),
'stato_occupazione' => ['occupata_proprietario', 'occupata_inquilino', 'libera'][rand(0, 2)],
'is_active' => true,
]);
}
}
}
// Locali commerciali al piano terra
for ($locale = 1; $locale <= 3; $locale++) {
UnitaImmobiliare::create([
'stabile_id' => $stabile->id,
'interno' => "LC{$locale}",
'piano' => 0,
'denominazione' => "Locale commerciale n. {$locale}",
'tipo_unita' => 'locale_commerciale',
'destinazione_uso' => 'commerciale',
'foglio' => $stabile->foglio,
'particella' => $stabile->particella,
'subalterno' => str_pad($subCount++, 3, '0', STR_PAD_LEFT),
'categoria_catastale' => 'C/1',
'superficie_commerciale' => rand(40, 80),
'superficie_calpestabile' => rand(35, 70),
'rendita_catastale' => rand(200, 500),
'numero_bagni' => 1,
'millesimi_generali' => rand(20, 40),
'stato_occupazione' => ['occupata_inquilino', 'sfitto'][rand(0, 1)],
'is_active' => true,
]);
}
}
}
```
---
## 🎯 **UTILIZZO PATTERN**
### **Query Base**:
```php
// Unità di uno stabile
$unita = UnitaImmobiliare::where('stabile_id', $stabile->id)
->orderBy('piano')
->orderBy('interno')
->get();
// Appartamenti occupati
$occupati = UnitaImmobiliare::occupate()
->byTipo('appartamento')
->with(['proprietari', 'inquilini'])
->get();
// Unità libere per affitto
$libere = UnitaImmobiliare::libere()
->with('stabile')
->get();
```
---
**🎯 TABELLA UNITÀ IMMOBILIARI COMPLETATA**
**Con dati catastali, millesimi e collegamenti per soggetti**
**📖 Prossimo: `soggetti.md` per proprietari e inquilini collegati alle unità**