# 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 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 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 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 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 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 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 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 '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 '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 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 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 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 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 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 " 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**