# 👤 TABELLA SOGGETTI ## Anagrafica persone e aziende collegate 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**: Anagrafica unificata di proprietari, inquilini, fornitori e altri soggetti collegati agli stabili ### **Principio**: **Collegamento via stabile** - ogni soggetto appartiene a uno stabile → isolamento automatico per amministratore --- ## 🗄️ **STRUTTURA TABELLA SOGGETTI** ```sql CREATE TABLE soggetti ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, stabile_id BIGINT UNSIGNED NOT NULL, -- FK verso stabili -- Tipo soggetto tipo_soggetto ENUM('persona_fisica', 'persona_giuridica') DEFAULT 'persona_fisica', categoria_soggetto ENUM('proprietario', 'inquilino', 'fornitore', 'amministratore_precedente', 'altro') DEFAULT 'proprietario', -- Dati anagrafici persona fisica nome VARCHAR(255) NULL, cognome VARCHAR(255) NULL, codice_fiscale VARCHAR(16) NULL, data_nascita DATE NULL, luogo_nascita VARCHAR(255) NULL, -- Dati persona giuridica ragione_sociale VARCHAR(255) NULL, partita_iva VARCHAR(11) NULL, codice_fiscale_azienda VARCHAR(16) NULL, forma_giuridica VARCHAR(100) NULL, -- SRL, SPA, SNC, ecc -- Contatti telefono VARCHAR(20) NULL, cellulare VARCHAR(20) NULL, email VARCHAR(255) NULL, pec VARCHAR(255) NULL, -- Indirizzo residenza/sede indirizzo_residenza TEXT NULL, citta_residenza VARCHAR(255) NULL, cap_residenza VARCHAR(10) NULL, provincia_residenza VARCHAR(2) NULL, -- Indirizzo domicilio (se diverso) indirizzo_domicilio TEXT NULL, citta_domicilio VARCHAR(255) NULL, cap_domicilio VARCHAR(10) NULL, provincia_domicilio VARCHAR(2) NULL, -- Dati bancari iban VARCHAR(34) NULL, banca VARCHAR(255) NULL, -- Configurazioni is_active BOOLEAN DEFAULT TRUE, note TEXT NULL, privacy_consenso BOOLEAN DEFAULT FALSE, data_consenso_privacy TIMESTAMP NULL, configurazioni JSON NULL, -- 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_tipo_soggetto (tipo_soggetto), INDEX idx_categoria_soggetto (categoria_soggetto), INDEX idx_codice_fiscale (codice_fiscale), INDEX idx_partita_iva (partita_iva), INDEX idx_email (email), INDEX idx_active (is_active), -- Indici composti INDEX idx_stabile_categoria (stabile_id, categoria_soggetto), INDEX idx_nome_cognome (nome, cognome), INDEX idx_ragione_sociale (ragione_sociale) ); ``` --- ## 🔗 **TABELLA PIVOT UNITA-SOGGETTI** ```sql CREATE TABLE unita_soggetti ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unita_immobiliare_id BIGINT UNSIGNED NOT NULL, soggetto_id BIGINT UNSIGNED NOT NULL, -- Tipo rapporto tipo_rapporto ENUM('proprietario', 'inquilino', 'usufruttuario', 'nudo_proprietario', 'amministratore') DEFAULT 'proprietario', -- Quote e date quota_proprieta DECIMAL(5, 2) DEFAULT 100.00, -- Percentuale proprietà data_inizio DATE NOT NULL, data_fine DATE NULL, -- Contratto inquilino canone_mensile DECIMAL(8, 2) NULL, -- Solo per inquilini deposito_cauzionale DECIMAL(8, 2) NULL, tipo_contratto ENUM('libero', 'concordato', 'transitorio', 'studenti') NULL, durata_contratto_mesi INT NULL, -- 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 (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE, FOREIGN KEY (soggetto_id) REFERENCES soggetti(id) ON DELETE CASCADE, -- Indici INDEX idx_unita_immobiliare_id (unita_immobiliare_id), INDEX idx_soggetto_id (soggetto_id), INDEX idx_tipo_rapporto (tipo_rapporto), INDEX idx_active (is_active), INDEX idx_data_inizio (data_inizio), INDEX idx_data_fine (data_fine), -- Indici composti INDEX idx_unita_soggetto (unita_immobiliare_id, soggetto_id), INDEX idx_unita_rapporto (unita_immobiliare_id, tipo_rapporto), -- Vincolo: un soggetto può avere un solo rapporto attivo per unità UNIQUE KEY unique_unita_soggetto_attivo (unita_immobiliare_id, soggetto_id, is_active) ); ``` --- ## 🏗️ **MIGRATION LARAVEL** ### **File**: `database/migrations/2024_08_05_000040_create_soggetti_table.php` ```php id(); $table->foreignId('stabile_id') ->constrained('stabili') ->onDelete('cascade'); // Tipo soggetto $table->enum('tipo_soggetto', ['persona_fisica', 'persona_giuridica']) ->default('persona_fisica'); $table->enum('categoria_soggetto', [ 'proprietario', 'inquilino', 'fornitore', 'amministratore_precedente', 'altro' ])->default('proprietario'); // Dati anagrafici persona fisica $table->string('nome')->nullable(); $table->string('cognome')->nullable(); $table->string('codice_fiscale', 16)->nullable(); $table->date('data_nascita')->nullable(); $table->string('luogo_nascita')->nullable(); // Dati persona giuridica $table->string('ragione_sociale')->nullable(); $table->string('partita_iva', 11)->nullable(); $table->string('codice_fiscale_azienda', 16)->nullable(); $table->string('forma_giuridica', 100)->nullable(); // Contatti $table->string('telefono', 20)->nullable(); $table->string('cellulare', 20)->nullable(); $table->string('email')->nullable(); $table->string('pec')->nullable(); // Indirizzo residenza/sede $table->text('indirizzo_residenza')->nullable(); $table->string('citta_residenza')->nullable(); $table->string('cap_residenza', 10)->nullable(); $table->string('provincia_residenza', 2)->nullable(); // Indirizzo domicilio $table->text('indirizzo_domicilio')->nullable(); $table->string('citta_domicilio')->nullable(); $table->string('cap_domicilio', 10)->nullable(); $table->string('provincia_domicilio', 2)->nullable(); // Dati bancari $table->string('iban', 34)->nullable(); $table->string('banca')->nullable(); // Configurazioni $table->boolean('is_active')->default(true); $table->text('note')->nullable(); $table->boolean('privacy_consenso')->default(false); $table->timestamp('data_consenso_privacy')->nullable(); $table->json('configurazioni')->nullable(); $table->timestamps(); // Indici $table->index(['stabile_id']); $table->index(['tipo_soggetto']); $table->index(['categoria_soggetto']); $table->index(['codice_fiscale']); $table->index(['partita_iva']); $table->index(['email']); $table->index(['is_active']); // Indici composti $table->index(['stabile_id', 'categoria_soggetto'], 'idx_stabile_categoria'); $table->index(['nome', 'cognome'], 'idx_nome_cognome'); $table->index(['ragione_sociale']); }); } public function down(): void { Schema::dropIfExists('soggetti'); } }; ``` ### **File**: `database/migrations/2024_08_05_000041_create_unita_soggetti_table.php` ```php id(); $table->foreignId('unita_immobiliare_id') ->constrained('unita_immobiliari') ->onDelete('cascade'); $table->foreignId('soggetto_id') ->constrained('soggetti') ->onDelete('cascade'); // Tipo rapporto $table->enum('tipo_rapporto', [ 'proprietario', 'inquilino', 'usufruttuario', 'nudo_proprietario', 'amministratore' ])->default('proprietario'); // Quote e date $table->decimal('quota_proprieta', 5, 2)->default(100.00); $table->date('data_inizio'); $table->date('data_fine')->nullable(); // Contratto inquilino $table->decimal('canone_mensile', 8, 2)->nullable(); $table->decimal('deposito_cauzionale', 8, 2)->nullable(); $table->enum('tipo_contratto', ['libero', 'concordato', 'transitorio', 'studenti']) ->nullable(); $table->integer('durata_contratto_mesi')->nullable(); // Stato $table->boolean('is_active')->default(true); $table->text('note')->nullable(); $table->timestamps(); // Indici $table->index(['unita_immobiliare_id']); $table->index(['soggetto_id']); $table->index(['tipo_rapporto']); $table->index(['is_active']); $table->index(['data_inizio']); $table->index(['data_fine']); // Indici composti $table->index(['unita_immobiliare_id', 'soggetto_id'], 'idx_unita_soggetto'); $table->index(['unita_immobiliare_id', 'tipo_rapporto'], 'idx_unita_rapporto'); }); } public function down(): void { Schema::dropIfExists('unita_soggetti'); } }; ``` --- ## 📊 **MODEL ELOQUENT** ### **File**: `app/Models/Soggetto.php` ```php 'date', 'is_active' => 'boolean', 'privacy_consenso' => 'boolean', 'data_consenso_privacy' => 'datetime', 'configurazioni' => 'array', ]; // Relazioni public function stabile(): BelongsTo { return $this->belongsTo(Stabile::class); } public function unitaImmobiliari(): BelongsToMany { return $this->belongsToMany(UnitaImmobiliare::class, 'unita_soggetti') ->withPivot([ 'tipo_rapporto', 'quota_proprieta', 'data_inizio', 'data_fine', 'canone_mensile', 'deposito_cauzionale', 'tipo_contratto', 'durata_contratto_mesi', 'is_active', 'note' ]) ->withTimestamps(); } public function unitaProprieta(): BelongsToMany { return $this->unitaImmobiliari()->wherePivot('tipo_rapporto', 'proprietario'); } public function unitaInquilinoIn(): BelongsToMany { return $this->unitaImmobiliari()->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_soggetto', $tipo); } public function scopeByCategoria(Builder $query, string $categoria): Builder { return $query->where('categoria_soggetto', $categoria); } public function scopeProprietari(Builder $query): Builder { return $query->where('categoria_soggetto', 'proprietario'); } public function scopeInquilini(Builder $query): Builder { return $query->where('categoria_soggetto', 'inquilino'); } public function scopeFornitori(Builder $query): Builder { return $query->where('categoria_soggetto', 'fornitore'); } public function scopePersoneFisiche(Builder $query): Builder { return $query->where('tipo_soggetto', 'persona_fisica'); } public function scopePersoneGiuridiche(Builder $query): Builder { return $query->where('tipo_soggetto', 'persona_giuridica'); } public function scopeSearch(Builder $query, string $term): Builder { return $query->where(function ($q) use ($term) { $q->where('nome', 'like', "%{$term}%") ->orWhere('cognome', 'like', "%{$term}%") ->orWhere('ragione_sociale', 'like', "%{$term}%") ->orWhere('codice_fiscale', 'like', "%{$term}%") ->orWhere('email', 'like', "%{$term}%"); }); } // Accessors public function getDenominazioneComputedAttribute(): string { if ($this->tipo_soggetto === 'persona_fisica') { return trim("{$this->nome} {$this->cognome}"); } else { return $this->ragione_sociale ?: 'Azienda senza nome'; } } public function getCodiceIdentificativoAttribute(): string { return $this->codice_fiscale ?: $this->codice_fiscale_azienda ?: $this->partita_iva ?: ''; } public function getIndirizzoCompletoAttribute(): string { $indirizzo = $this->indirizzo_residenza ?: $this->indirizzo_domicilio; $citta = $this->citta_residenza ?: $this->citta_domicilio; $cap = $this->cap_residenza ?: $this->cap_domicilio; $provincia = $this->provincia_residenza ?: $this->provincia_domicilio; $parts = array_filter([ $indirizzo, $cap, $citta, $provincia ? "({$provincia})" : null, ]); return implode(', ', $parts); } public function getContattoPreferenzialeAttribute(): string { return $this->cellulare ?: $this->telefono ?: $this->email ?: ''; } public function getIsPersonaFisicaAttribute(): bool { return $this->tipo_soggetto === 'persona_fisica'; } public function getIsPersonaGiuridicaAttribute(): bool { return $this->tipo_soggetto === 'persona_giuridica'; } public function getIsProprietarioAttribute(): bool { return $this->categoria_soggetto === 'proprietario'; } public function getIsInquilinoAttribute(): bool { return $this->categoria_soggetto === 'inquilino'; } public function getIsFornitoreAttribute(): bool { return $this->categoria_soggetto === 'fornitore'; } public function getEtaAttribute(): ?int { if (!$this->data_nascita) { return null; } return $this->data_nascita->age; } // Helper Methods public function hasPrivacyConsenso(): bool { return $this->privacy_consenso && $this->data_consenso_privacy; } public function aggiornaPrivacyConsenso(bool $consenso = true): void { $this->update([ 'privacy_consenso' => $consenso, 'data_consenso_privacy' => $consenso ? now() : null, ]); } public function getUnitaAttive(): \Illuminate\Database\Eloquent\Collection { return $this->unitaImmobiliari() ->wherePivot('is_active', true) ->wherePivot('data_fine', null) ->get(); } public function getUnitaProprietaAttive(): \Illuminate\Database\Eloquent\Collection { return $this->unitaProprieta() ->wherePivot('is_active', true) ->wherePivot('data_fine', null) ->get(); } public function getContrattiAffittoAttivi(): \Illuminate\Database\Eloquent\Collection { return $this->unitaInquilinoIn() ->wherePivot('is_active', true) ->wherePivot('data_fine', null) ->get(); } public function calcolaTotaleCanoniMensili(): float { return $this->unitaInquilinoIn() ->wherePivot('is_active', true) ->wherePivot('data_fine', null) ->sum('unita_soggetti.canone_mensile') ?: 0.0; } public function calcolaTotaleMillesimi(): float { $unitaAttive = $this->getUnitaProprietaAttive(); return $unitaAttive->sum(function ($unita) { $quota = $unita->pivot->quota_proprieta / 100; return $unita->millesimi_generali * $quota; }); } public function associaUnita(UnitaImmobiliare $unita, array $datiRapporto): void { $datiPivot = array_merge([ 'tipo_rapporto' => 'proprietario', 'quota_proprieta' => 100.00, 'data_inizio' => now()->toDateString(), 'is_active' => true, ], $datiRapporto); $this->unitaImmobiliari()->attach($unita->id, $datiPivot); // Aggiorna stato unità $unita->aggiornaStatoOccupazione( $datiPivot['tipo_rapporto'] === 'proprietario' ? 'occupata_proprietario' : 'occupata_inquilino' ); } public function rimuoviUnita(UnitaImmobiliare $unita): void { $this->unitaImmobiliari()->updateExistingPivot($unita->id, [ 'is_active' => false, 'data_fine' => now()->toDateString() ]); // Controlla se l'unità ha altri soggetti attivi $altriSoggettiAttivi = $unita->soggetti() ->wherePivot('is_active', true) ->wherePivot('id', '!=', $this->id) ->exists(); if (!$altriSoggettiAttivi) { $unita->aggiornaStatoOccupazione('libera'); } } public function canDelete(): bool { return $this->unitaImmobiliari()->count() === 0 && $this->contabilitaRegistrazioni()->count() === 0; } // Observer Events protected static function boot(): void { parent::boot(); static::creating(function ($soggetto) { // Converti codice fiscale in maiuscolo if ($soggetto->codice_fiscale) { $soggetto->codice_fiscale = strtoupper($soggetto->codice_fiscale); } if ($soggetto->codice_fiscale_azienda) { $soggetto->codice_fiscale_azienda = strtoupper($soggetto->codice_fiscale_azienda); } // Normalizza email if ($soggetto->email) { $soggetto->email = strtolower($soggetto->email); } if ($soggetto->pec) { $soggetto->pec = strtolower($soggetto->pec); } }); static::updating(function ($soggetto) { // Stesse normalizzazioni per update if ($soggetto->isDirty('codice_fiscale') && $soggetto->codice_fiscale) { $soggetto->codice_fiscale = strtoupper($soggetto->codice_fiscale); } if ($soggetto->isDirty('email') && $soggetto->email) { $soggetto->email = strtolower($soggetto->email); } }); } } ``` --- ## 🌱 **SEEDER DATI DEMO** ### **File**: `database/seeders/SoggettiSeeder.php` ```php createSoggettiPerStabile($stabile, $faker); } $totalSoggetti = Soggetto::count(); $this->command->info("✅ Creati {$totalSoggetti} soggetti per tutti gli stabili"); // Mostra riassunto per amministratore $stabili->groupBy('amministratore_id')->each(function ($stabiliGroup) { $admin = $stabiliGroup->first()->amministratore; $soggettiCount = Soggetto::whereIn('stabile_id', $stabiliGroup->pluck('id'))->count(); $this->command->info("Admin {$admin->codice_univoco}: {$soggettiCount} soggetti"); }); } private function createSoggettiPerStabile(Stabile $stabile, $faker): void { $unita = $stabile->unitaImmobiliari; foreach ($unita as $unita) { $this->createSoggettiPerUnita($unita, $faker); } // Aggiungi alcuni fornitori per lo stabile $this->createFornitori($stabile, $faker); } private function createSoggettiPerUnita(UnitaImmobiliare $unita, $faker): void { // Crea proprietario $proprietario = $this->createProprietario($unita, $faker); // 30% probabilità di avere inquilino if (rand(1, 100) <= 30) { $this->createInquilino($unita, $faker); } // Se è una coppia, 60% probabilità di secondo proprietario if (rand(1, 100) <= 60) { $this->createCoProprietario($unita, $faker, $proprietario); } } private function createProprietario(UnitaImmobiliare $unita, $faker): Soggetto { $isPersonaGiuridica = rand(1, 100) <= 15; // 15% aziende if ($isPersonaGiuridica) { $soggetto = Soggetto::create([ 'stabile_id' => $unita->stabile_id, 'tipo_soggetto' => 'persona_giuridica', 'categoria_soggetto' => 'proprietario', 'ragione_sociale' => $faker->company, 'partita_iva' => $this->generatePartitaIva(), 'codice_fiscale_azienda' => $this->generateCodiceFiscaleAzienda(), 'forma_giuridica' => $faker->randomElement(['SRL', 'SPA', 'SNC', 'SAS']), 'telefono' => $faker->phoneNumber, 'email' => $faker->companyEmail, 'pec' => str_replace('@', '@pec.', $faker->companyEmail), 'indirizzo_residenza' => $faker->streetAddress, 'citta_residenza' => $faker->city, 'cap_residenza' => $faker->postcode, 'provincia_residenza' => $faker->stateAbbr, 'iban' => $faker->iban('IT'), 'banca' => $faker->randomElement(['Intesa Sanpaolo', 'UniCredit', 'BNL', 'Monte Paschi']), 'privacy_consenso' => true, 'data_consenso_privacy' => now(), 'is_active' => true, ]); } else { $soggetto = Soggetto::create([ 'stabile_id' => $unita->stabile_id, 'tipo_soggetto' => 'persona_fisica', 'categoria_soggetto' => 'proprietario', 'nome' => $faker->firstName, 'cognome' => $faker->lastName, 'codice_fiscale' => $this->generateCodiceFiscale(), 'data_nascita' => $faker->dateTimeBetween('-80 years', '-25 years')->format('Y-m-d'), 'luogo_nascita' => $faker->city, 'telefono' => $faker->phoneNumber, 'cellulare' => $faker->mobileNumber, 'email' => $faker->email, 'indirizzo_residenza' => $faker->streetAddress, 'citta_residenza' => $faker->city, 'cap_residenza' => $faker->postcode, 'provincia_residenza' => $faker->stateAbbr, 'iban' => $faker->iban('IT'), 'banca' => $faker->randomElement(['Intesa Sanpaolo', 'UniCredit', 'BNL', 'Monte Paschi']), 'privacy_consenso' => true, 'data_consenso_privacy' => now(), 'is_active' => true, ]); } // Associa alla unità come proprietario $soggetto->associaUnita($unita, [ 'tipo_rapporto' => 'proprietario', 'quota_proprieta' => 100.00, 'data_inizio' => $faker->dateTimeBetween('-5 years', 'now')->format('Y-m-d'), ]); return $soggetto; } private function createCoProprietario(UnitaImmobiliare $unita, $faker, Soggetto $proprietarioPrincipale): void { $soggetto = Soggetto::create([ 'stabile_id' => $unita->stabile_id, 'tipo_soggetto' => 'persona_fisica', 'categoria_soggetto' => 'proprietario', 'nome' => $faker->firstName, 'cognome' => $proprietarioPrincipale->cognome, // Stesso cognome (coniuge) 'codice_fiscale' => $this->generateCodiceFiscale(), 'data_nascita' => $faker->dateTimeBetween('-80 years', '-25 years')->format('Y-m-d'), 'luogo_nascita' => $faker->city, 'telefono' => $proprietarioPrincipale->telefono, // Stesso telefono 'cellulare' => $faker->mobileNumber, 'email' => $faker->email, 'indirizzo_residenza' => $proprietarioPrincipale->indirizzo_residenza, 'citta_residenza' => $proprietarioPrincipale->citta_residenza, 'cap_residenza' => $proprietarioPrincipale->cap_residenza, 'provincia_residenza' => $proprietarioPrincipale->provincia_residenza, 'privacy_consenso' => true, 'data_consenso_privacy' => now(), 'is_active' => true, ]); // Aggiorna quote proprietà $unita->soggetti()->updateExistingPivot($proprietarioPrincipale->id, [ 'quota_proprieta' => 50.00 ]); // Associa il co-proprietario $soggetto->associaUnita($unita, [ 'tipo_rapporto' => 'proprietario', 'quota_proprieta' => 50.00, 'data_inizio' => $faker->dateTimeBetween('-5 years', 'now')->format('Y-m-d'), ]); } private function createInquilino(UnitaImmobiliare $unita, $faker): void { $soggetto = Soggetto::create([ 'stabile_id' => $unita->stabile_id, 'tipo_soggetto' => 'persona_fisica', 'categoria_soggetto' => 'inquilino', 'nome' => $faker->firstName, 'cognome' => $faker->lastName, 'codice_fiscale' => $this->generateCodiceFiscale(), 'data_nascita' => $faker->dateTimeBetween('-65 years', '-20 years')->format('Y-m-d'), 'luogo_nascita' => $faker->city, 'telefono' => $faker->phoneNumber, 'cellulare' => $faker->mobileNumber, 'email' => $faker->email, 'indirizzo_residenza' => $faker->streetAddress, 'citta_residenza' => $faker->city, 'cap_residenza' => $faker->postcode, 'provincia_residenza' => $faker->stateAbbr, 'privacy_consenso' => true, 'data_consenso_privacy' => now(), 'is_active' => true, ]); // Calcola canone basato su superficie $canone = $unita->superficie_totale ? ($unita->superficie_totale * rand(8, 15)) : rand(500, 1200); $soggetto->associaUnita($unita, [ 'tipo_rapporto' => 'inquilino', 'quota_proprieta' => 0.00, 'data_inizio' => $faker->dateTimeBetween('-2 years', 'now')->format('Y-m-d'), 'canone_mensile' => $canone, 'deposito_cauzionale' => $canone * rand(2, 3), 'tipo_contratto' => $faker->randomElement(['libero', 'concordato', 'transitorio']), 'durata_contratto_mesi' => $faker->randomElement([48, 60, 18, 24]), ]); } private function createFornitori(Stabile $stabile, $faker): void { $fornitori = [ ['Ditta Pulizie Verde', 'pulizie'], ['Elettricista Rossi', 'elettrico'], ['Idraulico Bianchi', 'idraulico'], ['Giardinaggio Fiori', 'giardinaggio'], ['Ascensori Veloci SRL', 'ascensori'], ]; foreach ($fornitori as [$nome, $tipo]) { Soggetto::create([ 'stabile_id' => $stabile->id, 'tipo_soggetto' => 'persona_giuridica', 'categoria_soggetto' => 'fornitore', 'ragione_sociale' => $nome, 'partita_iva' => $this->generatePartitaIva(), 'telefono' => $faker->phoneNumber, 'email' => strtolower(str_replace(' ', '', $nome)) . '@example.com', 'indirizzo_residenza' => $faker->streetAddress, 'citta_residenza' => $faker->city, 'cap_residenza' => $faker->postcode, 'provincia_residenza' => $faker->stateAbbr, 'configurazioni' => ['tipo_servizio' => $tipo], 'is_active' => true, ]); } } private function generateCodiceFiscale(): string { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return substr(str_shuffle($chars), 0, 16); } private function generateCodiceFiscaleAzienda(): string { return $this->generateCodiceFiscale(); } private function generatePartitaIva(): string { return str_pad(rand(10000000000, 99999999999), 11, '0', STR_PAD_LEFT); } } ``` --- ## 🔍 **QUERY PATTERNS** ### **Dashboard Soggetti**: ```php // Controller - Overview soggetti per stabile public function dashboard() { $stats = [ 'proprietari_totali' => Soggetto::proprietari()->count(), 'inquilini_totali' => Soggetti::inquilini()->count(), 'fornitori_totali' => Soggetto::fornitori()->count(), 'persone_fisiche' => Soggetto::personeFisiche()->count(), 'persone_giuridiche' => Soggetto::personeGiuridiche()->count(), ]; // Soggetti con più unità $soggettiMultipli = Soggetto::has('unitaImmobiliari', '>', 1) ->with(['unitaImmobiliari']) ->limit(10) ->get(); return view('soggetti.dashboard', compact('stats', 'soggettiMultipli')); } ``` ### **Ricerca Avanzata**: ```php // Controller - Ricerca soggetti con filtri public function search(Request $request) { $query = Soggetto::with(['stabile', 'unitaImmobiliari']); if ($request->filled('search')) { $query->search($request->search); } if ($request->filled('categoria')) { $query->byCategoria($request->categoria); } if ($request->filled('tipo')) { $query->byTipo($request->tipo); } if ($request->filled('stabile_id')) { $query->where('stabile_id', $request->stabile_id); } $soggetti = $query->orderBy('cognome') ->orderBy('nome') ->orderBy('ragione_sociale') ->paginate(20); return view('soggetti.search', compact('soggetti')); } ``` --- ## 🎯 **UTILIZZO PATTERN** ### **Nel Controller**: ```php // Proprietari di uno stabile $proprietari = Soggetto::proprietari() ->where('stabile_id', $stabile->id) ->with('unitaProprietaAttive') ->get(); // Inquilini con contratti attivi $inquilini = Soggetto::inquilini() ->whereHas('unitaInquilinoIn', function($q) { $q->wherePivot('is_active', true); }) ->get(); // Totale canoni mensili $totaleCanoni = Soggetto::inquilini() ->get() ->sum(function($soggetto) { return $soggetto->calcolaTotaleCanoniMensili(); }); ``` ### **Nei Blade Templates**: ```blade {{-- Card soggetto --}} @foreach($soggetti as $soggetto)
{{ ucfirst($soggetto->categoria_soggetto) }} • {{ $soggetto->is_persona_fisica ? 'Persona fisica' : 'Persona giuridica' }}
{{ $soggetto->indirizzo_completo }}
{{ $soggetto->contatto_preferenziale }}
{{ $soggetto->calcolaTotaleMillesimi() }}/1000
@elseif($soggetto->is_inquilino) Inquilino€{{ number_format($soggetto->calcolaTotaleCanoniMensili(), 2) }}/mese
@endifUnitĂ collegate:
@foreach($soggetto->getUnitaAttive() as $unita) {{ $unita->interno }} ({{ $unita->pivot->quota_proprieta }}%) @endforeach