netgescon-master/docs/05-backup-unificazione/DOCS-UNIFIED/02-DOCUMENTAZIONE-TECNICA/04-DATABASE-STRUTTURE.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

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

1773 lines
56 KiB
Markdown

# 4. DATABASE E STRUTTURE - GUIDA COMPLETA
## 📋 **INDICE CAPITOLO**
- [4.1 Schema Database Completo](#41-schema-database-completo)
- [4.2 Migrazioni Laravel](#42-migrazioni-laravel)
- [4.3 Relazioni e Vincoli](#43-relazioni-e-vincoli)
- [4.4 Modelli Eloquent](#44-modelli-eloquent)
- [4.5 Seeder e Dati Base](#45-seeder-e-dati-base)
- [4.6 Gestione Conflitti Migrazioni](#46-gestione-conflitti-migrazioni)
- [4.7 Triggers e Codici Univoci](#47-triggers-e-codici-univoci)
- [4.8 Troubleshooting Database](#48-troubleshooting-database)
- [4.9 Backup e Ripristino](#49-backup-e-ripristino)
- [4.10 Sincronizzazione Multi-Macchina](#410-sincronizzazione-multi-macchina)
---
## 4.1 Schema Database Completo
### Tabelle Sistema Utenti
```sql
-- Tabella utenti base
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
email_verified_at TIMESTAMP NULL,
password VARCHAR(255) NOT NULL,
remember_token VARCHAR(100) NULL,
is_active BOOLEAN DEFAULT TRUE,
last_login_at TIMESTAMP NULL,
profile_photo_path VARCHAR(2048) NULL,
phone VARCHAR(20) NULL,
address TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_email (email),
INDEX idx_active (is_active),
INDEX idx_last_login (last_login_at)
);
-- Tabelle Spatie Permission
CREATE TABLE roles (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
guard_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
UNIQUE KEY unique_role_guard (name, guard_name)
);
CREATE TABLE permissions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
guard_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
UNIQUE KEY unique_permission_guard (name, guard_name)
);
CREATE TABLE model_has_roles (
role_id BIGINT UNSIGNED NOT NULL,
model_type VARCHAR(255) NOT NULL,
model_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (role_id, model_id, model_type),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
INDEX idx_model_type (model_type),
INDEX idx_model_id (model_id)
);
CREATE TABLE model_has_permissions (
permission_id BIGINT UNSIGNED NOT NULL,
model_type VARCHAR(255) NOT NULL,
model_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (permission_id, model_id, model_type),
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
INDEX idx_model_type (model_type),
INDEX idx_model_id (model_id)
);
CREATE TABLE role_has_permissions (
permission_id BIGINT UNSIGNED NOT NULL,
role_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (permission_id, role_id),
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
```
### Tabelle Anagrafica Base
```sql
-- Comuni italiani
CREATE TABLE comuni_italiani (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
codice_catastale VARCHAR(4) NOT NULL UNIQUE,
denominazione VARCHAR(255) NOT NULL,
denominazione_tedesca VARCHAR(255) NULL,
denominazione_altra VARCHAR(255) NULL,
codice_provincia VARCHAR(2) NOT NULL,
provincia VARCHAR(255) NOT NULL,
regione VARCHAR(255) NOT NULL,
cap VARCHAR(5) NULL,
prefisso VARCHAR(10) NULL,
email_pec VARCHAR(255) NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_codice_catastale (codice_catastale),
INDEX idx_provincia (codice_provincia),
INDEX idx_denominazione (denominazione),
INDEX idx_cap (cap)
);
-- Stabili
CREATE TABLE stabili (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
denominazione VARCHAR(255) NOT NULL,
indirizzo VARCHAR(255) NOT NULL,
comune_id BIGINT UNSIGNED NULL,
cap VARCHAR(10) NULL,
codice_fiscale VARCHAR(16) NULL,
partita_iva VARCHAR(11) NULL,
amministratore_id BIGINT UNSIGNED NULL,
is_active BOOLEAN DEFAULT TRUE,
note TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (comune_id) REFERENCES comuni_italiani(id) ON DELETE SET NULL,
FOREIGN KEY (amministratore_id) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_denominazione (denominazione),
INDEX idx_amministratore (amministratore_id),
INDEX idx_comune (comune_id),
INDEX idx_active (is_active),
INDEX idx_codice_fiscale (codice_fiscale)
);
-- Soggetti (persone fisiche e giuridiche)
CREATE TABLE soggetti (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tipo_soggetto ENUM('persona_fisica', 'persona_giuridica') NOT NULL,
nome VARCHAR(255) NULL,
cognome VARCHAR(255) NULL,
ragione_sociale VARCHAR(255) NULL,
codice_fiscale VARCHAR(16) NULL,
partita_iva VARCHAR(11) NULL,
data_nascita DATE NULL,
luogo_nascita VARCHAR(255) NULL,
indirizzo VARCHAR(255) NULL,
comune_id BIGINT UNSIGNED NULL,
cap VARCHAR(10) NULL,
telefono VARCHAR(20) NULL,
email VARCHAR(255) NULL,
is_active BOOLEAN DEFAULT TRUE,
note TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (comune_id) REFERENCES comuni_italiani(id) ON DELETE SET NULL,
INDEX idx_codice_fiscale (codice_fiscale),
INDEX idx_partita_iva (partita_iva),
INDEX idx_tipo_soggetto (tipo_soggetto),
INDEX idx_cognome_nome (cognome, nome),
INDEX idx_ragione_sociale (ragione_sociale),
INDEX idx_active (is_active)
);
-- Unità immobiliari
CREATE TABLE unita_immobiliari (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
numero VARCHAR(50) NOT NULL,
piano VARCHAR(20) NULL,
scala VARCHAR(20) NULL,
interno VARCHAR(20) NULL,
tipo ENUM('appartamento', 'negozio', 'garage', 'cantina', 'altro') NOT NULL DEFAULT 'appartamento',
categoria_catastale VARCHAR(10) NULL,
classe_catastale VARCHAR(10) NULL,
consistenza VARCHAR(20) NULL,
superficie_catastale DECIMAL(8,2) NULL,
superficie_commerciale DECIMAL(8,2) NULL,
rendita_catastale DECIMAL(10,2) NULL,
millesimi_proprieta DECIMAL(8,4) NULL,
millesimi_riscaldamento DECIMAL(8,4) NULL,
millesimi_acqua DECIMAL(8,4) NULL,
millesimi_ascensore DECIMAL(8,4) NULL,
is_active BOOLEAN DEFAULT TRUE,
note TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
INDEX idx_stabile (stabile_id),
INDEX idx_numero (numero),
INDEX idx_tipo (tipo),
INDEX idx_active (is_active),
UNIQUE KEY unique_stabile_numero (stabile_id, numero)
);
```
### Tabelle Relazioni
```sql
-- Diritti reali (collega soggetti a unità immobiliari)
CREATE TABLE diritti_reali (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
soggetto_id BIGINT UNSIGNED NOT NULL,
unita_immobiliare_id BIGINT UNSIGNED NOT NULL,
tipo_diritto ENUM('proprietario', 'nudo_proprietario', 'usufruttuario', 'inquilino', 'comodatario') NOT NULL,
quota_proprieta DECIMAL(8,4) NULL,
data_inizio DATE NULL,
data_fine DATE NULL,
is_active BOOLEAN DEFAULT TRUE,
note TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (soggetto_id) REFERENCES soggetti(id) ON DELETE CASCADE,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
INDEX idx_soggetto (soggetto_id),
INDEX idx_unita (unita_immobiliare_id),
INDEX idx_tipo_diritto (tipo_diritto),
INDEX idx_active (is_active),
INDEX idx_data_inizio (data_inizio),
INDEX idx_data_fine (data_fine)
);
-- Collegamento users a unità immobiliari (per condomini)
CREATE TABLE user_unita_immobiliari (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
unita_immobiliare_id BIGINT UNSIGNED NOT NULL,
tipo_accesso ENUM('proprietario', 'inquilino', 'amministratore') NOT NULL,
data_inizio DATE NULL,
data_fine DATE NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
INDEX idx_user (user_id),
INDEX idx_unita (unita_immobiliare_id),
INDEX idx_tipo_accesso (tipo_accesso),
INDEX idx_active (is_active),
UNIQUE KEY unique_user_unita (user_id, unita_immobiliare_id)
);
```
### Tabelle Gestione Documenti
```sql
-- Documenti
CREATE TABLE documenti (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
unita_immobiliare_id BIGINT UNSIGNED NULL,
soggetto_id BIGINT UNSIGNED NULL,
user_id BIGINT UNSIGNED NOT NULL,
titolo VARCHAR(255) NOT NULL,
descrizione TEXT NULL,
tipo_documento VARCHAR(100) NULL,
categoria ENUM('generale', 'amministrativo', 'contabile', 'tecnico', 'legale') NOT NULL DEFAULT 'generale',
nome_file VARCHAR(255) NOT NULL,
path_file VARCHAR(500) NOT NULL,
dimensione_file BIGINT UNSIGNED NULL,
mime_type VARCHAR(100) NULL,
is_public BOOLEAN DEFAULT FALSE,
data_documento DATE NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
FOREIGN KEY (soggetto_id) REFERENCES soggetti(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_stabile (stabile_id),
INDEX idx_unita (unita_immobiliare_id),
INDEX idx_soggetto (soggetto_id),
INDEX idx_user (user_id),
INDEX idx_categoria (categoria),
INDEX idx_public (is_public),
INDEX idx_data_documento (data_documento),
INDEX idx_created_at (created_at)
);
```
### Tabelle Contabilità
```sql
-- Movimenti bancari
CREATE TABLE movimenti_bancari (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
data_movimento DATE NOT NULL,
data_valuta DATE NULL,
descrizione TEXT NOT NULL,
importo DECIMAL(12,2) NOT NULL,
tipo_movimento ENUM('entrata', 'uscita') NOT NULL,
categoria VARCHAR(100) NULL,
sottocategoria VARCHAR(100) NULL,
causale VARCHAR(255) NULL,
documento_riferimento VARCHAR(255) NULL,
conto_corrente VARCHAR(100) NULL,
is_riconciliato BOOLEAN DEFAULT FALSE,
user_id BIGINT UNSIGNED NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_stabile (stabile_id),
INDEX idx_data_movimento (data_movimento),
INDEX idx_tipo_movimento (tipo_movimento),
INDEX idx_categoria (categoria),
INDEX idx_riconciliato (is_riconciliato),
INDEX idx_importo (importo)
);
-- Ripartizioni spese
CREATE TABLE ripartizioni_spese (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
movimento_bancario_id BIGINT UNSIGNED NOT NULL,
unita_immobiliare_id BIGINT UNSIGNED NOT NULL,
importo_ripartito DECIMAL(12,2) NOT NULL,
quota_millesimi DECIMAL(8,4) NULL,
tipo_ripartizione ENUM('millesimi_proprieta', 'millesimi_riscaldamento', 'millesimi_acqua', 'millesimi_ascensore', 'fissa') NOT NULL,
note TEXT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (movimento_bancario_id) REFERENCES movimenti_bancari(id) ON DELETE CASCADE,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE CASCADE,
INDEX idx_movimento (movimento_bancario_id),
INDEX idx_unita (unita_immobiliare_id),
INDEX idx_tipo_ripartizione (tipo_ripartizione)
);
```
### Tabelle Comunicazioni
```sql
-- Tickets/Comunicazioni
CREATE TABLE tickets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
stabile_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
unita_immobiliare_id BIGINT UNSIGNED NULL,
assigned_to BIGINT UNSIGNED NULL,
titolo VARCHAR(255) NOT NULL,
descrizione TEXT NOT NULL,
categoria ENUM('manutenzione', 'amministrativo', 'contabile', 'segnalazione', 'richiesta', 'reclamo') NOT NULL DEFAULT 'segnalazione',
priorita ENUM('bassa', 'media', 'alta', 'urgente') NOT NULL DEFAULT 'media',
stato ENUM('aperto', 'in_lavorazione', 'in_attesa', 'risolto', 'chiuso') NOT NULL DEFAULT 'aperto',
data_scadenza DATE NULL,
data_risoluzione TIMESTAMP NULL,
is_public BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (stabile_id) REFERENCES stabili(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (unita_immobiliare_id) REFERENCES unita_immobiliari(id) ON DELETE SET NULL,
FOREIGN KEY (assigned_to) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_stabile (stabile_id),
INDEX idx_user (user_id),
INDEX idx_unita (unita_immobiliare_id),
INDEX idx_assigned (assigned_to),
INDEX idx_categoria (categoria),
INDEX idx_priorita (priorita),
INDEX idx_stato (stato),
INDEX idx_data_scadenza (data_scadenza),
INDEX idx_created_at (created_at)
);
-- Risposte ai tickets
CREATE TABLE ticket_risposte (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ticket_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
messaggio TEXT NOT NULL,
is_internal BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_ticket (ticket_id),
INDEX idx_user (user_id),
INDEX idx_internal (is_internal),
INDEX idx_created_at (created_at)
);
```
---
## 4.2 Migrazioni Laravel
### Migration Base Users
**File**: `database/migrations/2024_01_01_000000_create_users_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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_active')->default(true);
$table->timestamp('last_login_at')->nullable();
$table->string('profile_photo_path', 2048)->nullable();
$table->string('phone', 20)->nullable();
$table->text('address')->nullable();
$table->rememberToken();
$table->timestamps();
$table->index(['email']);
$table->index(['is_active']);
$table->index(['last_login_at']);
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
```
### Migration Comuni Italiani
**File**: `database/migrations/2024_01_02_000000_create_comuni_italiani_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('comuni_italiani', function (Blueprint $table) {
$table->id();
$table->string('codice_catastale', 4)->unique();
$table->string('denominazione');
$table->string('denominazione_tedesca')->nullable();
$table->string('denominazione_altra')->nullable();
$table->string('codice_provincia', 2);
$table->string('provincia');
$table->string('regione');
$table->string('cap', 5)->nullable();
$table->string('prefisso', 10)->nullable();
$table->string('email_pec')->nullable();
$table->timestamps();
$table->index(['codice_catastale']);
$table->index(['codice_provincia']);
$table->index(['denominazione']);
$table->index(['cap']);
});
}
public function down(): void
{
Schema::dropIfExists('comuni_italiani');
}
};
```
### Migration Stabili
**File**: `database/migrations/2024_01_03_000000_create_stabili_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('stabili', function (Blueprint $table) {
$table->id();
$table->string('denominazione');
$table->string('indirizzo');
$table->foreignId('comune_id')->nullable()->constrained('comuni_italiani')->onDelete('set null');
$table->string('cap', 10)->nullable();
$table->string('codice_fiscale', 16)->nullable();
$table->string('partita_iva', 11)->nullable();
$table->foreignId('amministratore_id')->nullable()->constrained('users')->onDelete('set null');
$table->boolean('is_active')->default(true);
$table->text('note')->nullable();
$table->timestamps();
$table->index(['denominazione']);
$table->index(['amministratore_id']);
$table->index(['comune_id']);
$table->index(['is_active']);
$table->index(['codice_fiscale']);
});
}
public function down(): void
{
Schema::dropIfExists('stabili');
}
};
```
### Migration Soggetti
**File**: `database/migrations/2024_01_04_000000_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->enum('tipo_soggetto', ['persona_fisica', 'persona_giuridica']);
$table->string('nome')->nullable();
$table->string('cognome')->nullable();
$table->string('ragione_sociale')->nullable();
$table->string('codice_fiscale', 16)->nullable();
$table->string('partita_iva', 11)->nullable();
$table->date('data_nascita')->nullable();
$table->string('luogo_nascita')->nullable();
$table->string('indirizzo')->nullable();
$table->foreignId('comune_id')->nullable()->constrained('comuni_italiani')->onDelete('set null');
$table->string('cap', 10)->nullable();
$table->string('telefono', 20)->nullable();
$table->string('email')->nullable();
$table->boolean('is_active')->default(true);
$table->text('note')->nullable();
$table->timestamps();
$table->index(['codice_fiscale']);
$table->index(['partita_iva']);
$table->index(['tipo_soggetto']);
$table->index(['cognome', 'nome']);
$table->index(['ragione_sociale']);
$table->index(['is_active']);
});
}
public function down(): void
{
Schema::dropIfExists('soggetti');
}
};
```
### Migration Unità Immobiliari
**File**: `database/migrations/2024_01_05_000000_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');
$table->string('numero', 50);
$table->string('piano', 20)->nullable();
$table->string('scala', 20)->nullable();
$table->string('interno', 20)->nullable();
$table->enum('tipo', ['appartamento', 'negozio', 'garage', 'cantina', 'altro'])->default('appartamento');
$table->string('categoria_catastale', 10)->nullable();
$table->string('classe_catastale', 10)->nullable();
$table->string('consistenza', 20)->nullable();
$table->decimal('superficie_catastale', 8, 2)->nullable();
$table->decimal('superficie_commerciale', 8, 2)->nullable();
$table->decimal('rendita_catastale', 10, 2)->nullable();
$table->decimal('millesimi_proprieta', 8, 4)->nullable();
$table->decimal('millesimi_riscaldamento', 8, 4)->nullable();
$table->decimal('millesimi_acqua', 8, 4)->nullable();
$table->decimal('millesimi_ascensore', 8, 4)->nullable();
$table->boolean('is_active')->default(true);
$table->text('note')->nullable();
$table->timestamps();
$table->index(['stabile_id']);
$table->index(['numero']);
$table->index(['tipo']);
$table->index(['is_active']);
$table->unique(['stabile_id', 'numero']);
});
}
public function down(): void
{
Schema::dropIfExists('unita_immobiliari');
}
};
```
---
## 4.3 Relazioni e Vincoli
### Tabelle Ponte
**File**: `database/migrations/2024_01_06_000000_create_diritti_reali_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('diritti_reali', function (Blueprint $table) {
$table->id();
$table->foreignId('soggetto_id')->constrained('soggetti')->onDelete('cascade');
$table->foreignId('unita_immobiliare_id')->constrained('unita_immobiliari')->onDelete('cascade');
$table->enum('tipo_diritto', ['proprietario', 'nudo_proprietario', 'usufruttuario', 'inquilino', 'comodatario']);
$table->decimal('quota_proprieta', 8, 4)->nullable();
$table->date('data_inizio')->nullable();
$table->date('data_fine')->nullable();
$table->boolean('is_active')->default(true);
$table->text('note')->nullable();
$table->timestamps();
$table->index(['soggetto_id']);
$table->index(['unita_immobiliare_id']);
$table->index(['tipo_diritto']);
$table->index(['is_active']);
$table->index(['data_inizio']);
$table->index(['data_fine']);
});
}
public function down(): void
{
Schema::dropIfExists('diritti_reali');
}
};
```
### Tabella User-Unità
**File**: `database/migrations/2024_01_07_000000_create_user_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('user_unita_immobiliari', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->foreignId('unita_immobiliare_id')->constrained('unita_immobiliari')->onDelete('cascade');
$table->enum('tipo_accesso', ['proprietario', 'inquilino', 'amministratore']);
$table->date('data_inizio')->nullable();
$table->date('data_fine')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->index(['user_id']);
$table->index(['unita_immobiliare_id']);
$table->index(['tipo_accesso']);
$table->index(['is_active']);
$table->unique(['user_id', 'unita_immobiliare_id']);
});
}
public function down(): void
{
Schema::dropIfExists('user_unita_immobiliari');
}
};
```
---
## 4.4 Modelli Eloquent
### Modello Stabile Completo
**File**: `app/Models/Stabile.php`
```php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
class Stabile extends Model
{
protected $fillable = [
'denominazione',
'indirizzo',
'comune_id',
'cap',
'codice_fiscale',
'partita_iva',
'amministratore_id',
'is_active',
'note',
];
protected $casts = [
'is_active' => 'boolean',
];
// Relazioni
public function amministratore(): BelongsTo
{
return $this->belongsTo(User::class, 'amministratore_id');
}
public function comune(): BelongsTo
{
return $this->belongsTo(ComuneItaliano::class, 'comune_id');
}
public function unitaImmobiliari(): HasMany
{
return $this->hasMany(UnitaImmobiliare::class)->orderBy('numero');
}
public function documenti(): HasMany
{
return $this->hasMany(Documento::class)->orderBy('created_at', 'desc');
}
public function tickets(): HasMany
{
return $this->hasMany(Ticket::class)->orderBy('created_at', 'desc');
}
public function movimentiBancari(): HasMany
{
return $this->hasMany(MovimentoBancario::class)->orderBy('data_movimento', 'desc');
}
// Scope
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeByAmministratore(Builder $query, int $amministratoreId): Builder
{
return $query->where('amministratore_id', $amministratoreId);
}
// Accessors
public function getIndirizzoCompletoAttribute(): string
{
$indirizzo = $this->indirizzo;
if ($this->comune) {
$indirizzo .= ', ' . $this->comune->denominazione;
}
if ($this->cap) {
$indirizzo .= ' (' . $this->cap . ')';
}
return $indirizzo;
}
public function getTotalUnitaAttribute(): int
{
return $this->unitaImmobiliari()->count();
}
public function getTotalMillesimiAttribute(): float
{
return $this->unitaImmobiliari()->sum('millesimi_proprieta') ?? 0.0;
}
public function getSaldoTotaleAttribute(): float
{
return $this->movimentiBancari()->sum('importo') ?? 0.0;
}
}
```
### Modello UnitaImmobiliare
**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\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class UnitaImmobiliare extends Model
{
protected $fillable = [
'stabile_id',
'numero',
'piano',
'scala',
'interno',
'tipo',
'categoria_catastale',
'classe_catastale',
'consistenza',
'superficie_catastale',
'superficie_commerciale',
'rendita_catastale',
'millesimi_proprieta',
'millesimi_riscaldamento',
'millesimi_acqua',
'millesimi_ascensore',
'is_active',
'note',
];
protected $casts = [
'superficie_catastale' => 'decimal:2',
'superficie_commerciale' => 'decimal:2',
'rendita_catastale' => 'decimal:2',
'millesimi_proprieta' => 'decimal:4',
'millesimi_riscaldamento' => 'decimal:4',
'millesimi_acqua' => 'decimal:4',
'millesimi_ascensore' => 'decimal:4',
'is_active' => 'boolean',
];
// Relazioni
public function stabile(): BelongsTo
{
return $this->belongsTo(Stabile::class);
}
public function soggetti(): BelongsToMany
{
return $this->belongsToMany(Soggetto::class, 'diritti_reali')
->withPivot(['tipo_diritto', 'quota_proprieta', 'data_inizio', 'data_fine', 'is_active'])
->withTimestamps();
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_unita_immobiliari')
->withPivot(['tipo_accesso', 'data_inizio', 'data_fine', 'is_active'])
->withTimestamps();
}
public function documenti(): HasMany
{
return $this->hasMany(Documento::class);
}
public function tickets(): HasMany
{
return $this->hasMany(Ticket::class);
}
public function ripartizioniSpese(): HasMany
{
return $this->hasMany(RipartizioneSpesa::class);
}
// Accessors
public function getNumeroCompletoAttribute(): string
{
$numero = $this->numero;
if ($this->piano) {
$numero .= ' (Piano ' . $this->piano . ')';
}
if ($this->scala) {
$numero .= ' - Scala ' . $this->scala;
}
if ($this->interno) {
$numero .= ' - Interno ' . $this->interno;
}
return $numero;
}
public function getTipoDisplayAttribute(): string
{
$tipi = [
'appartamento' => 'Appartamento',
'negozio' => 'Negozio',
'garage' => 'Garage',
'cantina' => 'Cantina',
'altro' => 'Altro',
];
return $tipi[$this->tipo] ?? 'Non definito';
}
// Helper Methods
public function getProprietari()
{
return $this->soggetti()->wherePivot('tipo_diritto', 'proprietario')
->wherePivot('is_active', true);
}
public function getInquilini()
{
return $this->soggetti()->wherePivot('tipo_diritto', 'inquilino')
->wherePivot('is_active', true);
}
}
```
---
## 4.5 Seeder e Dati Base
### Seeder Comuni Italiani
**File**: `database/seeders/ComuniItalianiSeeder.php`
```php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class ComuniItalianiSeeder extends Seeder
{
public function run(): void
{
// Verifica se i comuni sono già stati caricati
if (DB::table('comuni_italiani')->count() > 0) {
$this->command->info('Comuni italiani già presenti nel database');
return;
}
// Carica da file CSV
$csvFile = database_path('data/comuni_italiani.csv');
if (!file_exists($csvFile)) {
$this->command->error('File comuni_italiani.csv non trovato in database/data/');
return;
}
$handle = fopen($csvFile, 'r');
$header = fgetcsv($handle, 1000, ';');
$comuni = [];
while (($data = fgetcsv($handle, 1000, ';')) !== false) {
$comuni[] = [
'codice_catastale' => $data[0],
'denominazione' => $data[1],
'denominazione_tedesca' => $data[2] ?: null,
'denominazione_altra' => $data[3] ?: null,
'codice_provincia' => $data[4],
'provincia' => $data[5],
'regione' => $data[6],
'cap' => $data[7] ?: null,
'prefisso' => $data[8] ?: null,
'email_pec' => $data[9] ?: null,
'created_at' => now(),
'updated_at' => now(),
];
}
fclose($handle);
// Inserimento batch per performance
$chunks = array_chunk($comuni, 1000);
foreach ($chunks as $chunk) {
DB::table('comuni_italiani')->insert($chunk);
}
$this->command->info('Caricati ' . count($comuni) . ' comuni italiani');
}
}
```
### Seeder Dati Demo
**File**: `database/seeders/DemoDataSeeder.php`
```php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\Soggetto;
use App\Models\ComuneItaliano;
class DemoDataSeeder extends Seeder
{
public function run(): void
{
// Trova comuni per demo
$milano = ComuneItaliano::where('denominazione', 'Milano')->first();
$roma = ComuneItaliano::where('denominazione', 'Roma')->first();
if (!$milano || !$roma) {
$this->command->error('Comuni Milano e Roma non trovati. Esegui prima ComuniItalianiSeeder');
return;
}
// Crea utenti amministratore
$admin1 = User::create([
'name' => 'Admin Milano',
'email' => 'admin.milano@netgescon.it',
'password' => bcrypt('password'),
'is_active' => true,
]);
$admin1->assignRole('admin');
$admin2 = User::create([
'name' => 'Admin Roma',
'email' => 'admin.roma@netgescon.it',
'password' => bcrypt('password'),
'is_active' => true,
]);
$admin2->assignRole('amministratore');
// Crea stabili
$stabile1 = Stabile::create([
'denominazione' => 'Condominio Via Roma 123',
'indirizzo' => 'Via Roma 123',
'comune_id' => $milano->id,
'cap' => '20100',
'codice_fiscale' => '80123456789',
'amministratore_id' => $admin1->id,
'is_active' => true,
]);
$stabile2 = Stabile::create([
'denominazione' => 'Residenza I Pini',
'indirizzo' => 'Via dei Pini 45',
'comune_id' => $roma->id,
'cap' => '00100',
'codice_fiscale' => '80987654321',
'amministratore_id' => $admin2->id,
'is_active' => true,
]);
// Crea unità immobiliari
for ($i = 1; $i <= 12; $i++) {
UnitaImmobiliare::create([
'stabile_id' => $stabile1->id,
'numero' => (string) $i,
'piano' => $i <= 4 ? '1' : ($i <= 8 ? '2' : '3'),
'tipo' => 'appartamento',
'superficie_catastale' => rand(60, 120),
'millesimi_proprieta' => rand(70, 130) / 1000,
'is_active' => true,
]);
}
for ($i = 1; $i <= 8; $i++) {
UnitaImmobiliare::create([
'stabile_id' => $stabile2->id,
'numero' => (string) $i,
'piano' => $i <= 4 ? '1' : '2',
'tipo' => 'appartamento',
'superficie_catastale' => rand(80, 150),
'millesimi_proprieta' => rand(100, 180) / 1000,
'is_active' => true,
]);
}
// Crea soggetti demo
$soggetto1 = Soggetto::create([
'tipo_soggetto' => 'persona_fisica',
'nome' => 'Mario',
'cognome' => 'Rossi',
'codice_fiscale' => 'RSSMRA70A01H501Z',
'indirizzo' => 'Via Roma 123',
'comune_id' => $milano->id,
'telefono' => '3331234567',
'email' => 'mario.rossi@email.it',
'is_active' => true,
]);
$soggetto2 = Soggetto::create([
'tipo_soggetto' => 'persona_fisica',
'nome' => 'Anna',
'cognome' => 'Verdi',
'codice_fiscale' => 'VRDNNA75B01F205X',
'indirizzo' => 'Via dei Pini 45',
'comune_id' => $roma->id,
'telefono' => '3339876543',
'email' => 'anna.verdi@email.it',
'is_active' => true,
]);
// Collega soggetti a unità immobiliari
$unita1 = UnitaImmobiliare::where('stabile_id', $stabile1->id)->first();
$unita2 = UnitaImmobiliare::where('stabile_id', $stabile2->id)->first();
$unita1->soggetti()->attach($soggetto1->id, [
'tipo_diritto' => 'proprietario',
'quota_proprieta' => 1.0000,
'data_inizio' => now()->subYear(),
'is_active' => true,
]);
$unita2->soggetti()->attach($soggetto2->id, [
'tipo_diritto' => 'proprietario',
'quota_proprieta' => 1.0000,
'data_inizio' => now()->subYear(),
'is_active' => true,
]);
$this->command->info('Dati demo creati con successo');
}
}
```
---
## 4.6 Gestione Conflitti Migrazioni
### Comando Verifica Stato Database
**File**: `app/Console/Commands/CheckDatabaseStatus.php`
```php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CheckDatabaseStatus extends Command
{
protected $signature = 'db:check-status';
protected $description = 'Verifica lo stato del database e rileva possibili conflitti';
public function handle()
{
$this->info('🔍 Verifica stato database NetGescon...');
// Verifica connessione database
try {
DB::connection()->getPdo();
$this->info('✅ Connessione database OK');
} catch (\Exception $e) {
$this->error('❌ Errore connessione database: ' . $e->getMessage());
return 1;
}
// Verifica tabelle principali
$requiredTables = [
'users', 'roles', 'permissions', 'model_has_roles',
'comuni_italiani', 'stabili', 'soggetti', 'unita_immobiliari',
'diritti_reali', 'documenti', 'tickets', 'movimenti_bancari'
];
$this->info('📋 Verifica tabelle principali...');
foreach ($requiredTables as $table) {
if (Schema::hasTable($table)) {
$count = DB::table($table)->count();
$this->info("✅ {$table}: {$count} record");
} else {
$this->warn("⚠️ {$table}: TABELLA MANCANTE");
}
}
// Verifica migrazioni
$this->info('🔄 Verifica migrazioni...');
$pendingMigrations = DB::table('migrations')
->pluck('migration')
->toArray();
if (empty($pendingMigrations)) {
$this->warn('⚠️ Nessuna migrazione eseguita');
} else {
$this->info('✅ Migrazioni eseguite: ' . count($pendingMigrations));
}
// Verifica conflitti campi
$this->info('⚡ Verifica conflitti campi...');
$this->checkTableConflicts();
// Verifica indici
$this->info('📊 Verifica indici database...');
$this->checkIndexes();
$this->info('✅ Verifica completata!');
return 0;
}
private function checkTableConflicts()
{
$conflicts = [];
// Verifica campi duplicati in users
if (Schema::hasTable('users')) {
$columns = Schema::getColumnListing('users');
$expectedColumns = ['id', 'name', 'email', 'password', 'is_active', 'last_login_at'];
foreach ($expectedColumns as $col) {
if (!in_array($col, $columns)) {
$conflicts[] = "users.{$col} mancante";
}
}
}
// Verifica campi stabili
if (Schema::hasTable('stabili')) {
$columns = Schema::getColumnListing('stabili');
if (!in_array('amministratore_id', $columns)) {
$conflicts[] = 'stabili.amministratore_id mancante';
}
if (!in_array('is_active', $columns)) {
$conflicts[] = 'stabili.is_active mancante';
}
}
if (!empty($conflicts)) {
$this->error('❌ Conflitti rilevati:');
foreach ($conflicts as $conflict) {
$this->error(" - {$conflict}");
}
} else {
$this->info('✅ Nessun conflitto rilevato');
}
}
private function checkIndexes()
{
$tables = ['users', 'stabili', 'unita_immobiliari', 'soggetti'];
foreach ($tables as $table) {
if (Schema::hasTable($table)) {
$indexes = collect(DB::select("SHOW INDEX FROM {$table}"))
->pluck('Key_name')
->unique()
->values()
->toArray();
$this->info(" {$table}: " . count($indexes) . " indici");
}
}
}
}
```
### Comando Reset Database
**File**: `app/Console/Commands/ResetDatabase.php`
```php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan;
class ResetDatabase extends Command
{
protected $signature = 'db:reset-clean {--force}';
protected $description = 'Reset completo database con pulizia conflitti';
public function handle()
{
if (!$this->option('force')) {
if (!$this->confirm('⚠️ Questa operazione cancellerà TUTTI i dati. Continuare?')) {
$this->info('Operazione annullata');
return 0;
}
}
$this->info('🔄 Inizio reset database...');
// Drop tutte le tabelle
$this->info('🗑️ Rimozione tabelle esistenti...');
$this->dropAllTables();
// Pulisci tabella migrazioni
$this->info('🧹 Pulizia migrazioni...');
try {
DB::statement('DROP TABLE IF EXISTS migrations');
} catch (\Exception $e) {
// Ignore se non esiste
}
// Ricrea tutto
$this->info('⚡ Ricreazione database...');
Artisan::call('migrate:fresh');
$this->info('🌱 Esecuzione seeder...');
Artisan::call('db:seed');
$this->info('✅ Reset database completato!');
return 0;
}
private function dropAllTables()
{
$tables = DB::select('SHOW TABLES');
$dbName = DB::getDatabaseName();
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
foreach ($tables as $table) {
$tableName = $table->{"Tables_in_{$dbName}"};
DB::statement("DROP TABLE IF EXISTS {$tableName}");
}
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
}
}
```
---
## 4.7 Triggers e Codici Univoci
### 4.7.1 Sistema Codici Univoci
Il sistema NetGescon utilizza codici univoci alfanumerici di 8 caratteri per identificare univocamente utenti, amministratori e altre entità critiche.
**Caratteristiche:**
- **Lunghezza**: 8 caratteri
- **Formato**: Alfanumerico (A-Z, 0-9)
- **Unicità**: Garantita tramite trigger SQL
- **Generazione**: Automatica all'inserimento
### 4.7.2 Trigger per Amministratori
```sql
-- Trigger per generare codice_univoco automaticamente per amministratori
DELIMITER $$
CREATE TRIGGER generate_codice_univoco_amministratori
BEFORE INSERT ON amministratori
FOR EACH ROW
BEGIN
DECLARE codice_temp VARCHAR(8);
DECLARE codice_exists INT DEFAULT 1;
-- Solo se il codice non è già fornito
IF NEW.codice_univoco IS NULL OR NEW.codice_univoco = '' THEN
WHILE codice_exists > 0 DO
SET codice_temp = CONCAT(
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1)
);
SELECT COUNT(*) INTO codice_exists
FROM amministratori
WHERE codice_univoco = codice_temp;
END WHILE;
SET NEW.codice_univoco = codice_temp;
END IF;
END$$
DELIMITER ;
```
### 4.7.3 Trigger per Users
```sql
-- Trigger per generare codice_univoco per users
DELIMITER $$
CREATE TRIGGER generate_codice_univoco_users
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
DECLARE codice_temp VARCHAR(8);
DECLARE codice_exists INT DEFAULT 1;
-- Solo se il codice non è già fornito
IF NEW.codice_univoco IS NULL OR NEW.codice_univoco = '' THEN
WHILE codice_exists > 0 DO
SET codice_temp = CONCAT(
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', FLOOR(1 + (RAND() * 36)), 1)
);
SELECT COUNT(*) INTO codice_exists
FROM users
WHERE codice_univoco = codice_temp;
END WHILE;
SET NEW.codice_univoco = codice_temp;
END IF;
END$$
DELIMITER ;
```
### 4.7.4 Migration Laravel per Triggers
```php
<?php
// database/migrations/2025_07_17_210000_create_codice_univoco_triggers.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// Trigger per amministratori
DB::unprepared('
DROP TRIGGER IF EXISTS generate_codice_univoco_amministratori;
DELIMITER $$
CREATE TRIGGER generate_codice_univoco_amministratori
BEFORE INSERT ON amministratori
FOR EACH ROW
BEGIN
DECLARE codice_temp VARCHAR(8);
DECLARE codice_exists INT DEFAULT 1;
IF NEW.codice_univoco IS NULL OR NEW.codice_univoco = "" THEN
WHILE codice_exists > 0 DO
SET codice_temp = CONCAT(
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1),
SUBSTRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", FLOOR(1 + (RAND() * 36)), 1)
);
SELECT COUNT(*) INTO codice_exists
FROM amministratori
WHERE codice_univoco = codice_temp;
END WHILE;
SET NEW.codice_univoco = codice_temp;
END IF;
END$$
DELIMITER ;
');
}
public function down(): void
{
DB::unprepared('DROP TRIGGER IF EXISTS generate_codice_univoco_amministratori');
}
};
```
### 4.7.5 Vantaggi del Sistema
**✅ Automazione Completa**
- Generazione automatica senza intervento manuale
- Nessun rischio di dimenticanza o errore umano
**✅ Sicurezza**
- Codici difficili da indovinare
- Distribuzione uniforme dei caratteri
- Impossibilità di duplicati
**✅ Performance**
- Operazione a livello database
- Nessun overhead applicativo
- Controllo di unicità ottimizzato
**✅ Manutenibilità**
- Trigger gestiti tramite migrations
- Versionamento del codice
- Rollback automatico
---
## 4.10 Sincronizzazione Multi-Macchina
### 4.10.1 Architettura di Sincronizzazione
NetGescon supporta la sincronizzazione automatica tra più macchine per garantire consistenza e aggiornamenti distribuiti.
**Componenti:**
- **Macchina di Sviluppo**: Windows WSL (locale)
- **Macchina Master**: Linux Ubuntu 24.04 (192.168.0.43)
- **Macchine Client**: Istanze replicate del sistema
### 4.10.2 Script di Sincronizzazione
#### Script PowerShell (Windows)
```powershell
# netgescon-sync.ps1 - Script di sincronizzazione Windows
$LOCAL_PROJECT_PATH = "U:\home\michele\netgescon\netgescon-laravel"
$REMOTE_HOST = "192.168.0.43"
$REMOTE_USER = "michele"
$REMOTE_PATH = "/var/www/netgescon"
# Funzione di sincronizzazione migrations
function Sync-Migrations {
Write-Log "Sincronizzando migrations..."
scp -r "$LOCAL_PROJECT_PATH\database\migrations\*" "$REMOTE_USER@$REMOTE_HOST`:$REMOTE_PATH/database/migrations/"
if ($LASTEXITCODE -eq 0) {
Write-Log "Migrations sincronizzate" "SUCCESS"
return $true
} else {
Write-Log "Errore sincronizzazione migrations" "ERROR"
return $false
}
}
# Esecuzione migrations remote
function Invoke-RemoteMigrations {
Write-Log "Eseguo migrations remote..."
ssh "$REMOTE_USER@$REMOTE_HOST" "cd $REMOTE_PATH && php artisan migrate --force"
if ($LASTEXITCODE -eq 0) {
Write-Log "Migrations remote eseguite" "SUCCESS"
return $true
} else {
Write-Log "Errore esecuzione migrations remote" "ERROR"
return $false
}
}
```
#### Script Bash (Linux)
```bash
#!/bin/bash
# netgescon-sync.sh - Script di sincronizzazione Linux
LOCAL_PROJECT_PATH="/mnt/u/home/michele/netgescon/netgescon-laravel"
REMOTE_HOST="192.168.0.43"
REMOTE_USER="michele"
REMOTE_PATH="/var/www/netgescon"
# Sincronizza migrations
sync_migrations() {
info "Sincronizzando migrations..."
rsync -avz --delete \
"$LOCAL_PROJECT_PATH/database/migrations/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/database/migrations/"
if [ $? -eq 0 ]; then
log "✅ Migrations sincronizzate"
return 0
else
error "❌ Errore sincronizzazione migrations"
return 1
fi
}
# Esegui migrations remote
run_remote_migrations() {
info "Eseguo migrations remote..."
ssh "$REMOTE_USER@$REMOTE_HOST" "
cd $REMOTE_PATH
php artisan migrate --force
"
if [ $? -eq 0 ]; then
log "✅ Migrations remote eseguite"
return 0
else
error "❌ Errore esecuzione migrations remote"
return 1
fi
}
```
### 4.10.3 Flusso di Sincronizzazione
1. **Sviluppo Locale** (Windows WSL)
2. **Sincronizzazione****Macchina Master** (192.168.0.43)
3. **Distribuzione****Macchine Client** (altre istanze)
### 4.10.4 Comandi di Utilizzo
```bash
# Avvia script di sincronizzazione
./netgescon-sync.sh
# Opzioni disponibili:
# 1. Sincronizza solo migrations
# 2. Sincronizza migrations + seeders
# 3. Sincronizza + esegui migrations
# 4. Sincronizza + esegui migrations + seeding
# 5. Full sync + restart services
```
```powershell
# Avvia script PowerShell
.\netgescon-sync.ps1
# Menu interattivo con opzioni di sincronizzazione
# Logging automatico in C:\temp\netgescon_sync_*.log
```
### 4.10.5 Backup Automatico
Ogni sincronizzazione include:
- **Backup automatico** della macchina remota
- **Logging dettagliato** di tutte le operazioni
- **Rollback capability** in caso di errori
- **Verifica connessione** prima di ogni operazione
---
## 4.8 Backup e Ripristino
### Script Backup Automatico
**File**: `scripts/backup-database.sh`
```bash
#!/bin/bash
# Configurazione
DB_NAME="netgescon_db"
DB_USER="netgescon"
DB_PASS="netgescon2024"
BACKUP_DIR="/var/backups/netgescon"
DATE=$(date +%Y%m%d_%H%M%S)
# Crea directory backup
mkdir -p $BACKUP_DIR
# Backup completo
echo "🗄️ Backup database $DB_NAME..."
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/netgescon_$DATE.sql
# Backup solo struttura
mysqldump -u $DB_USER -p$DB_PASS --no-data $DB_NAME > $BACKUP_DIR/netgescon_structure_$DATE.sql
# Backup solo dati
mysqldump -u $DB_USER -p$DB_PASS --no-create-info $DB_NAME > $BACKUP_DIR/netgescon_data_$DATE.sql
# Comprimi backup
gzip $BACKUP_DIR/netgescon_$DATE.sql
gzip $BACKUP_DIR/netgescon_structure_$DATE.sql
gzip $BACKUP_DIR/netgescon_data_$DATE.sql
# Rimuovi backup più vecchi di 7 giorni
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
echo "✅ Backup completato: $BACKUP_DIR/netgescon_$DATE.sql.gz"
```
### Script Ripristino
**File**: `scripts/restore-database.sh`
```bash
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Uso: $0 <file_backup.sql>"
echo "Esempio: $0 /var/backups/netgescon/netgescon_20240117_120000.sql.gz"
exit 1
fi
BACKUP_FILE=$1
DB_NAME="netgescon_db"
DB_USER="netgescon"
DB_PASS="netgescon2024"
if [ ! -f "$BACKUP_FILE" ]; then
echo "❌ File backup non trovato: $BACKUP_FILE"
exit 1
fi
# Conferma operazione
echo "⚠️ Ripristino database da: $BACKUP_FILE"
read -p "Questa operazione sovrascriverà i dati esistenti. Continuare? (y/N): " -n 1 -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Operazione annullata"
exit 0
fi
# Decomprimi se necessario
if [[ $BACKUP_FILE == *.gz ]]; then
echo "📦 Decompressione backup..."
gunzip -c $BACKUP_FILE > /tmp/restore_temp.sql
RESTORE_FILE="/tmp/restore_temp.sql"
else
RESTORE_FILE=$BACKUP_FILE
fi
# Ripristino
echo "🔄 Ripristino database..."
mysql -u $DB_USER -p$DB_PASS $DB_NAME < $RESTORE_FILE
# Pulizia
if [ -f "/tmp/restore_temp.sql" ]; then
rm /tmp/restore_temp.sql
fi
echo "✅ Ripristino completato!"
```
---
**📝 COMPLETATO: Capitolo 4 - Database e Strutture**
Questo capitolo fornisce una guida completa per:
- ✅ Schema database completo con tutte le tabelle
- ✅ Migrazioni Laravel strutturate
- ✅ Relazioni e vincoli foreign key
- ✅ Modelli Eloquent con relazioni
- ✅ Seeder per dati base e demo
- ✅ Gestione conflitti migrazioni
- ✅ Troubleshooting errori comuni
- ✅ Backup e ripristino automatico
**🎯 Questo capitolo risolve il problema segnalato dei conflitti nelle migrazioni fornendo:**
- Comandi di verifica stato database
- Script di reset pulito
- Troubleshooting per errori comuni
- Backup automatico prima di operazioni rischiose
**🔄 Prossimo capitolo da completare: API e Integrazioni**