netgescon-master/docs/08-IMPLEMENTAZIONE-SISTEMA-CONTABILE-PRATICO.md
Michele Windows e68ee85a18 🚀 CHECKPOINT STABILE - Sistema Contabile Avanzato
📋 AGGIUNTE PRINCIPALI:
- Sistema contabile partita doppia con gestioni multiple
- Documentazione implementazione completa
- Models Laravel: GestioneContabile, MovimentoPartitaDoppia
- Controller ContabilitaAvanzataController
- Migration sistema contabile completo
- Scripts automazione e trasferimento
- Manuali utente e checklist implementazione

📊 FILES PRINCIPALI:
- docs/10-IMPLEMENTAZIONE-CONTABILITA-PARTITA-DOPPIA-GESTIONI.md
- SPECIFICHE-SISTEMA-CONTABILE-COMPLETO.md
- netgescon-laravel/database/migrations/2025_07_20_100000_create_complete_accounting_system.php
- netgescon-laravel/app/Models/GestioneContabile.php

 CHECKPOINT SICURO PER ROLLBACK
2025-07-26 15:11:19 +02:00

35 KiB
Raw Permalink Blame History

🔧 IMPLEMENTAZIONE PRATICA SISTEMA CONTABILE CONDOMINIALE

📋 OVERVIEW

Manuale tecnico per l'implementazione del sistema contabile condominiale in partita doppia con gestioni amministrative (esercizi) basate su delibere assembleari.


🎯 PRINCIPI FONDAMENTALI IMPLEMENTATIVI

📅 GESTIONI vs ANNI SOLARI - PRINCIPIO CHIAVE

⚠️  FONDAMENTALE: La contabilità condominiale NON segue l'anno solare!

✅ GESTIONE AMMINISTRATIVA:
   - Inizio: Delibera assemblea (es: 01/01/2024)
   - Operazioni: Tutto l'anno + eventuali post 31/12
   - Fine: Approvazione bilancio assemblea successiva (es: 30/04/2025)
   
❌ ERRORE COMUNE: Chiudere automaticamente al 31/12
✅ CORRETTO: Chiudere solo alla delibera assemblea approvazione bilancio

🎯 CAMPO GESTIONE - IMPLEMENTAZIONE

// ⚠️ OGNI MOVIMENTO DEVE AVERE gestione_id (non anno!)
Schema::table('registrazioni_contabili', function (Blueprint $table) {
    $table->unsignedBigInteger('gestione_id')->after('id');
    $table->unsignedBigInteger('condominio_id')->after('gestione_id');
    
    // 📅 Date multiple per contabilità condominiale
    $table->date('data_operazione');       // Data effettiva operazione
    $table->date('data_registrazione');    // Data inserimento
    $table->date('data_competenza');       // Data competenza contabile
    $table->date('data_valuta')->nullable(); // Data valuta bancaria
});

🗃️ SCHEMA DATABASE IMPLEMENTATIVO

1 MIGRAZIONE: Gestioni Contabili

<?php
// File: database/migrations/2025_01_20_100000_create_gestioni_contabili_table.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('gestioni_contabili', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('condominio_id');
            
            // 📅 PERIODO GESTIONE
            $table->string('denominazione', 255);           // "Gestione 2024"
            $table->date('data_inizio');                     // Delibera assemblea
            $table->date('data_fine_prevista');             // Solitamente 31/12
            $table->date('data_chiusura_effettiva')->nullable(); // Approvazione reale
            
            // 📊 STATO GESTIONE  
            $table->enum('stato', ['aperta','chiusa_provvisoria','chiusa_definitiva'])
                  ->default('aperta');
            
            // 🏛️ ASSEMBLEA APPROVAZIONE
            $table->date('data_assemblea_approvazione')->nullable();
            $table->string('verbale_approvazione', 255)->nullable();
            
            // 💰 TOTALI GESTIONE (calcolati)
            $table->decimal('totale_entrate', 12, 4)->default(0);
            $table->decimal('totale_uscite', 12, 4)->default(0);
            $table->decimal('saldo_gestione', 12, 4)->default(0);
            
            $table->text('note_gestione')->nullable();
            $table->timestamps();
            $table->unsignedBigInteger('created_by')->nullable();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('condominio_id')->references('id')->on('stabili')->onDelete('cascade');
            $table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
            
            // 📊 INDICI
            $table->index(['condominio_id', 'data_inizio', 'data_fine_prevista']);
            $table->index(['stato']);
            $table->index(['data_chiusura_effettiva']);
            
            // ✅ CONSTRAINT UNICO
            $table->unique(['condominio_id', 'denominazione']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('gestioni_contabili');
    }
};

2 MIGRAZIONE: Piano Conti Mastri

<?php
// File: database/migrations/2025_01_20_100001_create_piano_conti_mastri_table.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('piano_conti_mastri', function (Blueprint $table) {
            $table->id();
            
            // 📊 IDENTIFICAZIONE MASTRO
            $table->string('codice_mastro', 10)->unique();     // "100", "200"
            $table->string('denominazione', 255);              // "ENTRATE"
            $table->enum('tipo_mastro', ['ATTIVO','PASSIVO','COSTI','RICAVI']);
            
            // 🎨 VISUALIZZAZIONE
            $table->string('colore_hex', 7)->default('#6c757d');
            $table->string('icona', 50)->default('fas fa-folder');
            $table->smallInteger('ordine_visualizzazione')->default(0);
            
            $table->text('descrizione')->nullable();
            $table->boolean('attivo')->default(true);
            $table->timestamps();
            
            // 📊 INDICI
            $table->index(['tipo_mastro', 'ordine_visualizzazione']);
            $table->index(['attivo']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('piano_conti_mastri');
    }
};

3 MIGRAZIONE: Piano Conti Conti

<?php
// File: database/migrations/2025_01_20_100002_create_piano_conti_conti_table.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('piano_conti_conti', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('mastro_id');
            
            // 📂 IDENTIFICAZIONE CONTO  
            $table->string('codice_conto', 15)->unique();       // "101", "201.1"
            $table->string('denominazione', 255);               // "Rate Condominiali"
            
            // 🎯 CONFIGURAZIONE CONTABILE
            $table->enum('tipo_saldo', ['DARE','AVERE']);
            $table->boolean('ripartizione_automatica')->default(true);
            $table->string('tabella_millesimale_default', 50)->nullable();
            
            // 🎨 VISUALIZZAZIONE
            $table->string('colore_hex', 7)->nullable();        // Eredita da mastro
            $table->string('icona', 50)->nullable();
            $table->smallInteger('ordine_visualizzazione')->default(0);
            
            $table->text('descrizione')->nullable();
            $table->boolean('attivo')->default(true);
            $table->timestamps();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('mastro_id')->references('id')->on('piano_conti_mastri')->onDelete('cascade');
            
            // 📊 INDICI
            $table->index(['mastro_id', 'ordine_visualizzazione']);
            $table->index(['ripartizione_automatica']);
            $table->index(['attivo']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('piano_conti_conti');
    }
};

4 MIGRAZIONE: Piano Conti Sottoconti

<?php
// File: database/migrations/2025_01_20_100003_create_piano_conti_sottoconti_table.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('piano_conti_sottoconti', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('conto_id');
            
            // 📄 IDENTIFICAZIONE SOTTOCONTO
            $table->string('codice_sottoconto', 20)->unique();  // "101.01"
            $table->string('denominazione', 255);               // "Rate Ordinarie"
            
            // 🎯 CONFIGURAZIONE SPECIFICA
            $table->string('ripartizione_specifica', 100)->nullable(); // "SOLO_SCALA_A"
            $table->decimal('percentuale_ripartizione', 5, 2)->nullable();
            $table->decimal('importo_fisso', 10, 2)->nullable();
            
            // 📊 NATURA CONTABILE
            $table->enum('tipo_saldo', ['DARE','AVERE'])->nullable(); // Eredita da conto
            $table->boolean('deducibile_fiscale')->default(false);
            
            $table->smallInteger('ordine_visualizzazione')->default(0);
            $table->text('descrizione')->nullable();
            $table->boolean('attivo')->default(true);
            $table->timestamps();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('conto_id')->references('id')->on('piano_conti_conti')->onDelete('cascade');
            
            // 📊 INDICI
            $table->index(['conto_id', 'ordine_visualizzazione']);
            $table->index(['ripartizione_specifica']);
            $table->index(['attivo']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('piano_conti_sottoconti');
    }
};

5 MIGRAZIONE: Registrazioni Contabili

<?php
// File: database/migrations/2025_01_20_100004_create_registrazioni_contabili_table.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('registrazioni_contabili', function (Blueprint $table) {
            $table->id();
            
            // 🎯 COLLEGAMENTO GESTIONE (FONDAMENTALE!)
            $table->unsignedBigInteger('gestione_id');
            $table->unsignedBigInteger('condominio_id');
            
            // 📅 DATE MOVIMENTO  
            $table->date('data_operazione');                // Data effettiva
            $table->date('data_registrazione');             // Data inserimento
            $table->date('data_competenza');                // Data competenza
            $table->date('data_valuta')->nullable();        // Data valuta bancaria
            
            // 📋 IDENTIFICAZIONE MOVIMENTO
            $table->string('numero_registrazione', 20);    // Progressivo per gestione
            $table->string('causale', 500);                // Descrizione
            $table->string('riferimento_documento', 255)->nullable();
            
            // 💰 IMPORTO TOTALE
            $table->decimal('importo_totale', 12, 4);
            
            // 🎯 CLASSIFICAZIONE
            $table->enum('tipo_movimento', ['ENTRATA','USCITA','GIROCONTO']);
            $table->string('categoria_movimento', 100)->nullable();
            $table->string('sottocategoria', 100)->nullable();
            
            // 🏦 DATI BANCARI
            $table->string('conto_corrente', 50)->nullable();
            $table->string('numero_assegno', 20)->nullable();
            $table->string('cro_bonifico', 50)->nullable();
            
            // 👥 SOGGETTI COINVOLTI  
            $table->unsignedBigInteger('fornitore_id')->nullable();
            $table->unsignedBigInteger('cliente_id')->nullable();
            
            // ⚙️ STATO E CONTROLLI
            $table->enum('stato', ['bozza','confermata','ripartita','chiusa'])->default('bozza');
            $table->boolean('ripartita')->default(false);
            $table->boolean('riconciliata')->default(false);
            
            // 🔄 RIPARTIZIONE
            $table->boolean('ripartizione_automatica')->default(true);
            $table->string('tabella_millesimale_usata', 50)->nullable();
            
            // 📎 ALLEGATI E NOTE
            $table->smallInteger('numero_allegati')->default(0);
            $table->text('note_interne')->nullable();
            $table->text('note_pubbliche')->nullable();
            
            $table->timestamps();
            $table->unsignedBigInteger('created_by');
            $table->unsignedBigInteger('updated_by')->nullable();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('gestione_id')->references('id')->on('gestioni_contabili')->onDelete('restrict');
            $table->foreign('condominio_id')->references('id')->on('stabili')->onDelete('cascade');
            $table->foreign('fornitore_id')->references('id')->on('persone')->onDelete('set null');
            $table->foreign('cliente_id')->references('id')->on('persone')->onDelete('set null');
            $table->foreign('created_by')->references('id')->on('users')->onDelete('restrict');
            
            // 📊 INDICI
            $table->index(['gestione_id', 'data_operazione']);
            $table->index(['condominio_id', 'gestione_id']);
            $table->index(['numero_registrazione', 'gestione_id']);
            $table->index(['tipo_movimento', 'categoria_movimento']);
            $table->index(['stato', 'ripartita']);
            
            // ✅ CONSTRAINTS
            $table->unique(['gestione_id', 'numero_registrazione']);
            $table->checkRaw('importo_totale > 0');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('registrazioni_contabili');
    }
};

6 MIGRAZIONE: Movimenti Contabili (DARE/AVERE)

<?php
// File: database/migrations/2025_01_20_100005_create_movimenti_contabili_table.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('movimenti_contabili', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('registrazione_id');
            
            // 📊 PARTITA DOPPIA
            $table->unsignedBigInteger('sottoconto_id');
            
            // 💰 IMPORTI DARE/AVERE
            $table->decimal('importo_dare', 12, 4)->default(0);
            $table->decimal('importo_avere', 12, 4)->default(0);
            
            // 📋 DETTAGLI MOVIMENTO
            $table->string('descrizione', 500)->nullable();
            $table->decimal('quantita', 10, 3)->nullable();
            $table->decimal('prezzo_unitario', 10, 4)->nullable();
            
            // 🎯 RIPARTIZIONE
            $table->boolean('da_ripartire')->default(true);
            $table->string('tabella_millesimale', 50)->nullable();
            
            $table->timestamp('created_at')->useCurrent();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('registrazione_id')->references('id')->on('registrazioni_contabili')->onDelete('cascade');
            $table->foreign('sottoconto_id')->references('id')->on('piano_conti_sottoconti')->onDelete('restrict');
            
            // 📊 INDICI
            $table->index(['registrazione_id']);
            $table->index(['sottoconto_id']);
            $table->index(['da_ripartire']);
            
            // ✅ CONSTRAINT PARTITA DOPPIA
            $table->checkRaw('(importo_dare > 0 AND importo_avere = 0) OR (importo_dare = 0 AND importo_avere > 0)');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('movimenti_contabili');
    }
};

7 MIGRAZIONE: Ripartizioni Condomini

<?php
// File: database/migrations/2025_01_20_100006_create_ripartizioni_condomini_table.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('ripartizioni_condomini', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('registrazione_id');
            $table->unsignedBigInteger('movimento_id');
            $table->unsignedBigInteger('unita_immobiliare_id');
            
            // 💰 IMPORTO RIPARTITO
            $table->decimal('importo_ripartito', 12, 4);
            
            // 📊 CALCOLO RIPARTIZIONE
            $table->decimal('millesimi_utilizzati', 8, 4);
            $table->string('tabella_millesimale', 50);
            
            // 👥 IMPUTAZIONE
            $table->unsignedBigInteger('persona_id')->nullable();
            $table->enum('tipo_imputazione', ['proprietario','inquilino','delegato'])->default('proprietario');
            
            // 📅 PERIODO
            $table->date('data_competenza_inizio')->nullable();
            $table->date('data_competenza_fine')->nullable();
            
            // ⚙️ STATO
            $table->enum('stato', ['calcolata','confermata','fatturata','pagata'])->default('calcolata');
            
            $table->timestamp('created_at')->useCurrent();
            
            // 🔗 FOREIGN KEYS
            $table->foreign('registrazione_id')->references('id')->on('registrazioni_contabili')->onDelete('cascade');
            $table->foreign('movimento_id')->references('id')->on('movimenti_contabili')->onDelete('cascade');
            $table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('cascade');
            $table->foreign('persona_id')->references('id')->on('persone')->onDelete('set null');
            
            // 📊 INDICI
            $table->index(['registrazione_id']);
            $table->index(['unita_immobiliare_id']);
            $table->index(['persona_id', 'stato']);
            $table->index(['data_competenza_inizio', 'data_competenza_fine']);
            
            // ✅ CONSTRAINTS
            $table->checkRaw('importo_ripartito > 0');
            $table->checkRaw('millesimi_utilizzati > 0 AND millesimi_utilizzati <= 1000');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('ripartizioni_condomini');
    }
};

🏗️ MODELS ELOQUENT

1 Model: GestioneContabile

<?php
// File: app/Models/GestioneContabile.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class GestioneContabile extends Model
{
    use HasFactory;

    protected $table = 'gestioni_contabili';

    protected $fillable = [
        'condominio_id',
        'denominazione',
        'data_inizio',
        'data_fine_prevista',
        'data_chiusura_effettiva',
        'stato',
        'data_assemblea_approvazione',
        'verbale_approvazione',
        'totale_entrate',
        'totale_uscite',
        'saldo_gestione',
        'note_gestione',
        'created_by',
    ];

    protected $casts = [
        'data_inizio' => 'date',
        'data_fine_prevista' => 'date',
        'data_chiusura_effettiva' => 'date',
        'data_assemblea_approvazione' => 'date',
        'totale_entrate' => 'decimal:4',
        'totale_uscite' => 'decimal:4',
        'saldo_gestione' => 'decimal:4',
    ];

    // === RELATIONSHIPS ===

    public function condominio(): BelongsTo
    {
        return $this->belongsTo(Stabile::class, 'condominio_id');
    }

    public function registrazioni(): HasMany
    {
        return $this->hasMany(RegistrazioneContabile::class, 'gestione_id');
    }

    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by');
    }

    // === SCOPES ===

    public function scopeAperte($query)
    {
        return $query->where('stato', 'aperta');
    }

    public function scopeChiuse($query)
    {
        return $query->where('stato', 'chiusa_definitiva');
    }

    // === METHODS ===

    public function isAperta(): bool
    {
        return $this->stato === 'aperta';
    }

    public function calcolaTotali(): void
    {
        $entrate = $this->registrazioni()
            ->where('tipo_movimento', 'ENTRATA')
            ->where('stato', '!=', 'bozza')
            ->sum('importo_totale');

        $uscite = $this->registrazioni()
            ->where('tipo_movimento', 'USCITA')
            ->where('stato', '!=', 'bozza')
            ->sum('importo_totale');

        $this->update([
            'totale_entrate' => $entrate,
            'totale_uscite' => $uscite,
            'saldo_gestione' => $entrate - $uscite,
        ]);
    }
}

2 Model: RegistrazioneContabile

<?php
// File: app/Models/RegistrazioneContabile.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class RegistrazioneContabile extends Model
{
    use HasFactory;

    protected $table = 'registrazioni_contabili';

    protected $fillable = [
        'gestione_id',
        'condominio_id',
        'data_operazione',
        'data_registrazione',
        'data_competenza',
        'data_valuta',
        'numero_registrazione',
        'causale',
        'riferimento_documento',
        'importo_totale',
        'tipo_movimento',
        'categoria_movimento',
        'sottocategoria',
        'conto_corrente',
        'numero_assegno',
        'cro_bonifico',
        'fornitore_id',
        'cliente_id',
        'stato',
        'ripartita',
        'riconciliata',
        'ripartizione_automatica',
        'tabella_millesimale_usata',
        'numero_allegati',
        'note_interne',
        'note_pubbliche',
        'created_by',
        'updated_by',
    ];

    protected $casts = [
        'data_operazione' => 'date',
        'data_registrazione' => 'date',
        'data_competenza' => 'date',
        'data_valuta' => 'date',
        'importo_totale' => 'decimal:4',
        'ripartita' => 'boolean',
        'riconciliata' => 'boolean',
        'ripartizione_automatica' => 'boolean',
        'numero_allegati' => 'integer',
    ];

    // === RELATIONSHIPS ===

    public function gestione(): BelongsTo
    {
        return $this->belongsTo(GestioneContabile::class, 'gestione_id');
    }

    public function condominio(): BelongsTo
    {
        return $this->belongsTo(Stabile::class, 'condominio_id');
    }

    public function movimenti(): HasMany
    {
        return $this->hasMany(MovimentoContabile::class, 'registrazione_id');
    }

    public function ripartizioni(): HasMany
    {
        return $this->hasMany(RipartizioneCondomino::class, 'registrazione_id');
    }

    public function fornitore(): BelongsTo
    {
        return $this->belongsTo(Persona::class, 'fornitore_id');
    }

    public function cliente(): BelongsTo
    {
        return $this->belongsTo(Persona::class, 'cliente_id');
    }

    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by');
    }

    // === METHODS ===

    public function verificaPartitaDoppia(): bool
    {
        $totaleDare = $this->movimenti()->sum('importo_dare');
        $totaleAvere = $this->movimenti()->sum('importo_avere');
        
        return abs($totaleDare - $totaleAvere) < 0.01; // Tolleranza centesimi
    }

    public function generaNumeroRegistrazione(): string
    {
        $ultimoNumero = self::where('gestione_id', $this->gestione_id)
            ->max('numero_registrazione');
        
        return str_pad(intval($ultimoNumero) + 1, 6, '0', STR_PAD_LEFT);
    }

    public function ripartisciAutomaticamente(): void
    {
        foreach ($this->movimenti()->where('da_ripartire', true)->get() as $movimento) {
            $movimento->ripartisci();
        }
        
        $this->update(['ripartita' => true]);
    }
}

🎯 SEEDER PIANO CONTI STANDARD

<?php
// File: database/seeders/PianoContiSeeder.php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\PianoContiMastro;
use App\Models\PianoContiConto;
use App\Models\PianoContiSottoconto;

class PianoContiSeeder extends Seeder
{
    public function run(): void
    {
        // 🏛️ MASTRI
        $mastroEntrate = PianoContiMastro::create([
            'codice_mastro' => '100',
            'denominazione' => 'ENTRATE',
            'tipo_mastro' => 'RICAVI',
            'colore_hex' => '#28a745',
            'icona' => 'fas fa-arrow-down',
            'ordine_visualizzazione' => 1,
        ]);

        $mastroSpese = PianoContiMastro::create([
            'codice_mastro' => '200',
            'denominazione' => 'SPESE AMMINISTRATIVE',
            'tipo_mastro' => 'COSTI',
            'colore_hex' => '#dc3545',
            'icona' => 'fas fa-arrow-up',
            'ordine_visualizzazione' => 2,
        ]);

        $mastroManutenzioni = PianoContiMastro::create([
            'codice_mastro' => '300',
            'denominazione' => 'MANUTENZIONI',
            'tipo_mastro' => 'COSTI',
            'colore_hex' => '#fd7e14',
            'icona' => 'fas fa-tools',
            'ordine_visualizzazione' => 3,
        ]);

        $mastroFondi = PianoContiMastro::create([
            'codice_mastro' => '400',
            'denominazione' => 'FONDI E LIQUIDITÀ',
            'tipo_mastro' => 'ATTIVO',
            'colore_hex' => '#6f42c1',
            'icona' => 'fas fa-piggy-bank',
            'ordine_visualizzazione' => 4,
        ]);

        // 📂 CONTI - ENTRATE
        $contoRate = PianoContiConto::create([
            'mastro_id' => $mastroEntrate->id,
            'codice_conto' => '101',
            'denominazione' => 'Rate Condominiali',
            'tipo_saldo' => 'AVERE',
            'ripartizione_automatica' => false, // Le rate non si ripartiscono
            'ordine_visualizzazione' => 1,
        ]);

        $contoAltriRicavi = PianoContiConto::create([
            'mastro_id' => $mastroEntrate->id,
            'codice_conto' => '102',
            'denominazione' => 'Altri Ricavi',
            'tipo_saldo' => 'AVERE',
            'ripartizione_automatica' => false,
            'ordine_visualizzazione' => 2,
        ]);

        // 📂 CONTI - SPESE
        $contoPulizie = PianoContiConto::create([
            'mastro_id' => $mastroSpese->id,
            'codice_conto' => '201',
            'denominazione' => 'Pulizie',
            'tipo_saldo' => 'DARE',
            'ripartizione_automatica' => true,
            'tabella_millesimale_default' => 'GENERALE',
            'ordine_visualizzazione' => 1,
        ]);

        $contoEnergia = PianoContiConto::create([
            'mastro_id' => $mastroSpese->id,
            'codice_conto' => '202',
            'denominazione' => 'Energia Elettrica',
            'tipo_saldo' => 'DARE',
            'ripartizione_automatica' => true,
            'tabella_millesimale_default' => 'GENERALE',
            'ordine_visualizzazione' => 2,
        ]);

        // 📄 SOTTOCONTI - RATE
        PianoContiSottoconto::create([
            'conto_id' => $contoRate->id,
            'codice_sottoconto' => '101.01',
            'denominazione' => 'Rate Ordinarie',
            'tipo_saldo' => 'AVERE',
            'ordine_visualizzazione' => 1,
        ]);

        PianoContiSottoconto::create([
            'conto_id' => $contoRate->id,
            'codice_sottoconto' => '101.02',
            'denominazione' => 'Rate Straordinarie',
            'tipo_saldo' => 'AVERE',
            'ordine_visualizzazione' => 2,
        ]);

        // 📄 SOTTOCONTI - PULIZIE
        PianoContiSottoconto::create([
            'conto_id' => $contoPulizie->id,
            'codice_sottoconto' => '201.01',
            'denominazione' => 'Pulizie Scale Scala A',
            'tipo_saldo' => 'DARE',
            'ripartizione_specifica' => 'SCALA_A',
            'ordine_visualizzazione' => 1,
        ]);

        PianoContiSottoconto::create([
            'conto_id' => $contoPulizie->id,
            'codice_sottoconto' => '201.02',
            'denominazione' => 'Pulizie Scale Scala B', 
            'tipo_saldo' => 'DARE',
            'ripartizione_specifica' => 'SCALA_B',
            'ordine_visualizzazione' => 2,
        ]);
    }
}

⚙️ SCRIPT AUTOMATIZZAZIONE

1 Command: Setup Sistema Contabile

<?php
// File: app/Console/Commands/SetupSistemaContabile.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Stabile;
use App\Models\GestioneContabile;
use Illuminate\Support\Facades\DB;

class SetupSistemaContabile extends Command
{
    protected $signature = 'contabilita:setup {condominio_id} {anno}';
    protected $description = 'Setup sistema contabile per condominio';

    public function handle()
    {
        $condominioId = $this->argument('condominio_id');
        $anno = $this->argument('anno');

        $condominio = Stabile::findOrFail($condominioId);
        
        $this->info("🏢 Setup contabilità per: {$condominio->denominazione}");

        DB::transaction(function () use ($condominio, $anno) {
            
            // 1⃣ Crea gestione contabile
            $gestione = GestioneContabile::create([
                'condominio_id' => $condominio->id,
                'denominazione' => "Gestione {$anno}",
                'data_inizio' => "{$anno}-01-01",
                'data_fine_prevista' => "{$anno}-12-31",
                'stato' => 'aperta',
                'created_by' => 1,
            ]);

            $this->info("✅ Gestione creata: {$gestione->denominazione}");

            // 2⃣ Popola piano conti se vuoto
            if (!\App\Models\PianoContiMastro::exists()) {
                $this->call('db:seed', ['--class' => 'PianoContiSeeder']);
                $this->info("✅ Piano conti popolato");
            }

            // 3⃣ Verifica tabelle millesimali
            $unitaConProblem = $condominio->unitaImmobiliari()
                ->where('millesimi', '<=', 0)
                ->count();

            if ($unitaConProblem > 0) {
                $this->warn("⚠️  {$unitaConProblem} unità senza millesimi corretti");
            }

            $this->info("🎉 Setup completato!");
        });

        return Command::SUCCESS;
    }
}

2 Command: Verifica Partita Doppia

<?php
// File: app/Console/Commands/VerificaPartitaDoppia.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\GestioneContabile;
use App\Models\RegistrazioneContabile;

class VerificaPartitaDoppia extends Command
{
    protected $signature = 'contabilita:verifica {gestione_id?}';
    protected $description = 'Verifica quadratura partita doppia';

    public function handle()
    {
        $gestioneId = $this->argument('gestione_id');
        
        if ($gestioneId) {
            $gestioni = [GestioneContabile::findOrFail($gestioneId)];
        } else {
            $gestioni = GestioneContabile::where('stato', 'aperta')->get();
        }

        foreach ($gestioni as $gestione) {
            $this->info("🔍 Verifica Gestione: {$gestione->denominazione}");
            
            $registrazioni = $gestione->registrazioni()->get();
            $errori = 0;

            foreach ($registrazioni as $registrazione) {
                if (!$registrazione->verificaPartitaDoppia()) {
                    $errori++;
                    $this->error("❌ Registrazione #{$registrazione->numero_registrazione}: partita doppia non bilanciata");
                }
            }

            if ($errori === 0) {
                $this->info("✅ Tutte le registrazioni sono bilanciate");
            } else {
                $this->error("⚠️  {$errori} registrazioni con errori di bilanciamento");
            }

            // Ricalcola totali gestione
            $gestione->calcolaTotali();
            $this->info("💰 Totale Entrate: €" . number_format($gestione->totale_entrate, 2));
            $this->info("💰 Totale Uscite: €" . number_format($gestione->totale_uscite, 2));
            $this->info("💰 Saldo: €" . number_format($gestione->saldo_gestione, 2));
        }

        return Command::SUCCESS;
    }
}

📋 CHECKLIST IMPLEMENTAZIONE

FASE 1: Database e Strutture

  • Eseguire migrazioni piano conti
  • Eseguire migrazioni registrazioni contabili
  • Popolare seeder piano conti standard
  • Verificare foreign keys e constraint

FASE 2: Models e Relationships

  • Implementare models Eloquent
  • Configurare relationships
  • Implementare scopes e methods
  • Aggiungere validazioni

FASE 3: Controllers e Routes

  • Controller gestioni contabili
  • Controller registrazioni
  • Controller piano conti
  • API endpoints

FASE 4: Business Logic

  • Sistema ripartizione automatica
  • Controllo partita doppia
  • Numerazione automatica
  • Calcolo totali gestione

FASE 5: Testing e Validazione

  • Unit tests models
  • Feature tests controllers
  • Test ripartizione
  • Test partita doppia

🎯 COMANDI ARTISAN DISPONIBILI

# Setup completo sistema contabile
php artisan contabilita:setup {condominio_id} {anno}

# Verifica partita doppia
php artisan contabilita:verifica {gestione_id?}

# Popola piano conti standard
php artisan db:seed --class=PianoContiSeeder

# Ricalcola totali gestioni
php artisan contabilita:ricalcola-totali

# Chiudi gestione contabile  
php artisan contabilita:chiudi {gestione_id}

🔧 INTEGRAZIONE CON SISTEMA ESISTENTE

📊 Adattamento EsercizioContabile → GestioneContabile

// File: database/migrations/2025_01_20_200000_migrate_esercizi_to_gestioni.php

use Illuminate\Database\Migrations\Migration;
use App\Models\EsercizioContabile;
use App\Models\GestioneContabile;

return new class extends Migration
{
    public function up(): void
    {
        // Migra dati esistenti
        EsercizioContabile::all()->each(function ($esercizio) {
            GestioneContabile::create([
                'condominio_id' => $esercizio->stabile_id,
                'denominazione' => $esercizio->descrizione,
                'data_inizio' => $esercizio->data_inizio,
                'data_fine_prevista' => $esercizio->data_fine,
                'stato' => $esercizio->stato === 'aperto' ? 'aperta' : 'chiusa_definitiva',
                'data_assemblea_approvazione' => $esercizio->data_approvazione,
                'created_by' => 1,
            ]);
        });
    }
};

Questo manuale fornisce tutto il necessario per implementare il sistema contabile condominiale basato su gestioni amministrative con controllo totale di ogni centesimo! 💎