1083 lines
35 KiB
Markdown
1083 lines
35 KiB
Markdown
# 👤 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
|
|
<?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('soggetti', function (Blueprint $table) {
|
|
$table->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
|
|
<?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_soggetti', function (Blueprint $table) {
|
|
$table->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
|
|
<?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 Soggetto extends Model
|
|
{
|
|
protected $fillable = [
|
|
'stabile_id',
|
|
'tipo_soggetto',
|
|
'categoria_soggetto',
|
|
'nome',
|
|
'cognome',
|
|
'codice_fiscale',
|
|
'data_nascita',
|
|
'luogo_nascita',
|
|
'ragione_sociale',
|
|
'partita_iva',
|
|
'codice_fiscale_azienda',
|
|
'forma_giuridica',
|
|
'telefono',
|
|
'cellulare',
|
|
'email',
|
|
'pec',
|
|
'indirizzo_residenza',
|
|
'citta_residenza',
|
|
'cap_residenza',
|
|
'provincia_residenza',
|
|
'indirizzo_domicilio',
|
|
'citta_domicilio',
|
|
'cap_domicilio',
|
|
'provincia_domicilio',
|
|
'iban',
|
|
'banca',
|
|
'is_active',
|
|
'note',
|
|
'privacy_consenso',
|
|
'data_consenso_privacy',
|
|
'configurazioni',
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_nascita' => '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
|
|
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use App\Models\Soggetto;
|
|
use App\Models\UnitaImmobiliare;
|
|
use App\Models\Stabile;
|
|
use Faker\Factory as Faker;
|
|
|
|
class SoggettiSeeder extends Seeder
|
|
{
|
|
public function run(): void
|
|
{
|
|
$faker = Faker::create('it_IT');
|
|
$stabili = Stabile::all();
|
|
|
|
foreach ($stabili as $stabile) {
|
|
$this->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)
|
|
<div class="card">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3>{{ $soggetto->denominazione_computed }}</h3>
|
|
<p class="text-sm text-gray-600">
|
|
{{ ucfirst($soggetto->categoria_soggetto) }} •
|
|
{{ $soggetto->is_persona_fisica ? 'Persona fisica' : 'Persona giuridica' }}
|
|
</p>
|
|
<p class="text-sm">{{ $soggetto->indirizzo_completo }}</p>
|
|
<p class="text-sm">{{ $soggetto->contatto_preferenziale }}</p>
|
|
</div>
|
|
|
|
<div class="text-right">
|
|
@if($soggetto->is_proprietario)
|
|
<span class="badge badge-green">Proprietario</span>
|
|
<p class="text-xs mt-1">{{ $soggetto->calcolaTotaleMillesimi() }}/1000</p>
|
|
@elseif($soggetto->is_inquilino)
|
|
<span class="badge badge-blue">Inquilino</span>
|
|
<p class="text-xs mt-1">€{{ number_format($soggetto->calcolaTotaleCanoniMensili(), 2) }}/mese</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<p class="text-sm font-medium">Unità collegate:</p>
|
|
@foreach($soggetto->getUnitaAttive() as $unita)
|
|
<span class="inline-block bg-gray-100 rounded px-2 py-1 text-xs mr-1 mb-1">
|
|
{{ $unita->interno }} ({{ $unita->pivot->quota_proprieta }}%)
|
|
</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
```
|
|
|
|
---
|
|
|
|
**🎯 TABELLE SOGGETTI + UNITA-SOGGETTI COMPLETATE**
|
|
**Con anagrafica completa, relazioni many-to-many e gestione contratti**
|
|
|
|
**📖 Hai ora la struttura completa: amministratori → stabili → gestioni → unità → soggetti**
|
|
**Prossimo step consigliato: `contabilita.md` per movimenti finanziari collegati alle gestioni**
|