netgescon-day0/docs/91-database/02-tabelle-collegate/piano_conti.md

33 KiB
Raw Permalink Blame History

📊 PIANO DEI CONTI - Struttura Contabile Condominiale

🎯 OVERVIEW

Il Piano dei Conti rappresenta la struttura contabile standardizzata per la gestione condominiale secondo la normativa italiana. Ogni conto è collegato ai millesimi di ripartizione e consente la generazione automatica di mastrini e chiusure per gestione.


🗄️ STRUTTURA MYSQL

CREATE TABLE piano_conti (
    -- Identificativi
    id                          BIGINT PRIMARY KEY AUTO_INCREMENT,
    codice_conto               VARCHAR(20) NOT NULL,                    -- Codice gerarchico (es: 100.001)
    denominazione              VARCHAR(255) NOT NULL,                   -- Nome del conto
    
    -- Struttura Gerarchica
    padre_id                   BIGINT NULL,                             -- FK piano_conti.id (struttura ad albero)
    livello                    TINYINT NOT NULL DEFAULT 1,              -- Livello gerarchico (1-5)
    tipo_conto                 ENUM('ATTIVO','PASSIVO','RICAVO','COSTO','PATRIMONIO') NOT NULL,
    sottotipo                  VARCHAR(50) NULL,                        -- Sottocategoria specifica
    
    -- Gestione Multi-Tenant
    gestione_id                BIGINT NOT NULL,                         -- FK gestioni.id
    
    -- Configurazione Contabile
    natura_saldo               ENUM('DARE','AVERE') NOT NULL,           -- Natura normale del saldo
    utilizzabile_registrazioni BOOLEAN DEFAULT TRUE,                    -- Se utilizzabile per registrazioni dirette
    richiede_dettaglio         BOOLEAN DEFAULT FALSE,                   -- Se richiede sottoconto obbligatorio
    
    -- Ripartizione Condominiale
    ripartizione_default       ENUM('MILLESIMI_GENERALI','MILLESIMI_SCALE','MILLESIMI_ASCENSORE','MILLESIMI_RISCALDAMENTO','PARTI_UGUALI','PERSONALIZZATA') DEFAULT 'MILLESIMI_GENERALI',
    ripartizione_personalizzata JSON NULL,                              -- Configurazione ripartizione custom
    
    -- Normativa e Compliance
    art_1131_cc                BOOLEAN DEFAULT FALSE,                   -- Art. 1131 Cod. Civile (obblighi amministratore)
    art_1133_cc                BOOLEAN DEFAULT FALSE,                   -- Art. 1133 Cod. Civile (manutenzione ordinaria)
    art_1134_cc                BOOLEAN DEFAULT FALSE,                   -- Art. 1134 Cod. Civile (innovazioni)
    categoria_tributi          VARCHAR(50) NULL,                        -- Categoria per dichiarazioni fiscali
    
    -- Automazioni e Calcoli
    formula_automatica         TEXT NULL,                               -- Formula per calcoli automatici
    soglia_attenzione          DECIMAL(12,2) NULL,                      -- Soglia per alert
    budget_annuale             DECIMAL(12,2) NULL,                      -- Budget previsto per l'anno
    
    -- Integrazione Sistema
    codice_importazione        VARCHAR(50) NULL,                        -- Codice per importazioni esterne
    conto_collegato_id         BIGINT NULL,                             -- FK piano_conti.id (conto di contropartita)
    
    -- Stato e Configurazione
    attivo                     BOOLEAN DEFAULT TRUE,
    chiuso_esercizio           BOOLEAN DEFAULT FALSE,                   -- Se chiuso per l'esercizio corrente
    note                       TEXT NULL,
    
    -- Audit
    created_at                 TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at                 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    created_by                 BIGINT NULL,                             -- FK users.id
    updated_by                 BIGINT NULL,                             -- FK users.id
    
    -- Indici
    UNIQUE KEY unique_codice_gestione (codice_conto, gestione_id),
    INDEX idx_gestione (gestione_id),
    INDEX idx_padre (padre_id),
    INDEX idx_tipo_conto (tipo_conto),
    INDEX idx_ripartizione (ripartizione_default),
    INDEX idx_attivo (attivo),
    INDEX idx_livello (livello),
    
    -- Foreign Keys
    FOREIGN KEY (gestione_id) REFERENCES gestioni(id) ON DELETE CASCADE,
    FOREIGN KEY (padre_id) REFERENCES piano_conti(id) ON DELETE SET NULL,
    FOREIGN KEY (conto_collegato_id) REFERENCES piano_conti(id) ON DELETE SET NULL,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
    FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
    
    -- Constraints
    CONSTRAINT chk_livello CHECK (livello BETWEEN 1 AND 5),
    CONSTRAINT chk_soglia_positiva CHECK (soglia_attenzione >= 0),
    CONSTRAINT chk_budget_positivo CHECK (budget_annuale >= 0),
    CONSTRAINT chk_padre_diverso CHECK (padre_id != id)
);

📋 MIGRATION LARAVEL

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('piano_conti', function (Blueprint $table) {
            // Identificativi
            $table->id();
            $table->string('codice_conto', 20);
            $table->string('denominazione');
            
            // Struttura Gerarchica
            $table->foreignId('padre_id')->nullable()->constrained('piano_conti')->onDelete('set null');
            $table->tinyInteger('livello')->default(1);
            $table->enum('tipo_conto', ['ATTIVO','PASSIVO','RICAVO','COSTO','PATRIMONIO']);
            $table->string('sottotipo', 50)->nullable();
            
            // Multi-Tenant
            $table->foreignId('gestione_id')->constrained('gestioni')->onDelete('cascade');
            
            // Configurazione Contabile
            $table->enum('natura_saldo', ['DARE','AVERE']);
            $table->boolean('utilizzabile_registrazioni')->default(true);
            $table->boolean('richiede_dettaglio')->default(false);
            
            // Ripartizione
            $table->enum('ripartizione_default', [
                'MILLESIMI_GENERALI',
                'MILLESIMI_SCALE', 
                'MILLESIMI_ASCENSORE',
                'MILLESIMI_RISCALDAMENTO',
                'PARTI_UGUALI',
                'PERSONALIZZATA'
            ])->default('MILLESIMI_GENERALI');
            $table->json('ripartizione_personalizzata')->nullable();
            
            // Normativa
            $table->boolean('art_1131_cc')->default(false);
            $table->boolean('art_1133_cc')->default(false);
            $table->boolean('art_1134_cc')->default(false);
            $table->string('categoria_tributi', 50)->nullable();
            
            // Automazioni
            $table->text('formula_automatica')->nullable();
            $table->decimal('soglia_attenzione', 12, 2)->nullable();
            $table->decimal('budget_annuale', 12, 2)->nullable();
            
            // Sistema
            $table->string('codice_importazione', 50)->nullable();
            $table->foreignId('conto_collegato_id')->nullable()->constrained('piano_conti')->onDelete('set null');
            
            // Stato
            $table->boolean('attivo')->default(true);
            $table->boolean('chiuso_esercizio')->default(false);
            $table->text('note')->nullable();
            
            // Audit
            $table->timestamps();
            $table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
            $table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
            
            // Indici
            $table->unique(['codice_conto', 'gestione_id'], 'unique_codice_gestione');
            $table->index(['gestione_id']);
            $table->index(['padre_id']);
            $table->index(['tipo_conto']);
            $table->index(['ripartizione_default']);
            $table->index(['attivo']);
            $table->index(['livello']);
            
            // Constraints
            $table->check('livello >= 1 AND livello <= 5');
            $table->check('soglia_attenzione >= 0');
            $table->check('budget_annuale >= 0');
        });
        
        // Trigger per impedire autoreferenza padre
        DB::unprepared('
            CREATE TRIGGER trg_piano_conti_no_self_parent 
            BEFORE INSERT ON piano_conti 
            FOR EACH ROW 
            BEGIN 
                IF NEW.padre_id = NEW.id THEN 
                    SIGNAL SQLSTATE "45000" SET MESSAGE_TEXT = "Un conto non può essere padre di se stesso"; 
                END IF; 
            END
        ');
        
        DB::unprepared('
            CREATE TRIGGER trg_piano_conti_no_self_parent_update 
            BEFORE UPDATE ON piano_conti 
            FOR EACH ROW 
            BEGIN 
                IF NEW.padre_id = NEW.id THEN 
                    SIGNAL SQLSTATE "45000" SET MESSAGE_TEXT = "Un conto non può essere padre di se stesso"; 
                END IF; 
            END
        ');
    }

    public function down()
    {
        DB::unprepared('DROP TRIGGER IF EXISTS trg_piano_conti_no_self_parent');
        DB::unprepared('DROP TRIGGER IF EXISTS trg_piano_conti_no_self_parent_update');
        Schema::dropIfExists('piano_conti');
    }
};

🏗️ ELOQUENT MODEL

<?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;
use App\Traits\MultiTenantTrait;

/**
 * Piano dei Conti Model
 * 
 * Gestisce la struttura contabile condominiale con supporto per:
 * - Struttura gerarchica ad albero
 * - Ripartizione millesimi automatica
 * - Conformità normativa Codice Civile
 * - Mastrini e chiusure automatiche
 */
class PianoConto extends Model
{
    use HasFactory, MultiTenantTrait;

    protected $table = 'piano_conti';

    protected $fillable = [
        'codice_conto',
        'denominazione',
        'padre_id',
        'livello',
        'tipo_conto',
        'sottotipo',
        'gestione_id',
        'natura_saldo',
        'utilizzabile_registrazioni',
        'richiede_dettaglio',
        'ripartizione_default',
        'ripartizione_personalizzata',
        'art_1131_cc',
        'art_1133_cc',
        'art_1134_cc',
        'categoria_tributi',
        'formula_automatica',
        'soglia_attenzione',
        'budget_annuale',
        'codice_importazione',
        'conto_collegato_id',
        'attivo',
        'chiuso_esercizio',
        'note',
        'created_by',
        'updated_by'
    ];

    protected $casts = [
        'attivo' => 'boolean',
        'utilizzabile_registrazioni' => 'boolean',
        'richiede_dettaglio' => 'boolean',
        'chiuso_esercizio' => 'boolean',
        'art_1131_cc' => 'boolean',
        'art_1133_cc' => 'boolean',
        'art_1134_cc' => 'boolean',
        'ripartizione_personalizzata' => 'array',
        'soglia_attenzione' => 'decimal:2',
        'budget_annuale' => 'decimal:2',
        'livello' => 'integer'
    ];

    // ==================== RELATIONSHIPS ====================

    /**
     * Gestione di appartenenza (Multi-tenant)
     */
    public function gestione(): BelongsTo
    {
        return $this->belongsTo(Gestione::class);
    }

    /**
     * Conto padre (struttura gerarchica)
     */
    public function padre(): BelongsTo
    {
        return $this->belongsTo(PianoConto::class, 'padre_id');
    }

    /**
     * Conti figli
     */
    public function figli(): HasMany
    {
        return $this->hasMany(PianoConto::class, 'padre_id');
    }

    /**
     * Conto collegato per contropartite automatiche
     */
    public function contoCollegato(): BelongsTo
    {
        return $this->belongsTo(PianoConto::class, 'conto_collegato_id');
    }

    /**
     * Registrazioni contabili
     */
    public function registrazioniContabili(): HasMany
    {
        return $this->hasMany(RegistrazioneContabile::class, 'conto_id');
    }

    /**
     * Movimenti mastrino
     */
    public function movimentiMastrino(): HasMany
    {
        return $this->hasMany(MovimentoMastrino::class, 'conto_id');
    }

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

    /**
     * Utente modificatore
     */
    public function updatedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'updated_by');
    }

    // ==================== SCOPES ====================

    /**
     * Solo conti attivi
     */
    public function scopeAttivi($query)
    {
        return $query->where('attivo', true);
    }

    /**
     * Solo conti utilizzabili per registrazioni
     */
    public function scopeUtilizzabili($query)
    {
        return $query->where('utilizzabile_registrazioni', true)->where('attivo', true);
    }

    /**
     * Filtra per tipo conto
     */
    public function scopeTipoConto($query, string $tipo)
    {
        return $query->where('tipo_conto', $tipo);
    }

    /**
     * Conti di primo livello (radici)
     */
    public function scopeRadici($query)
    {
        return $query->whereNull('padre_id');
    }

    /**
     * Conti foglia (senza figli)
     */
    public function scopeFoglie($query)
    {
        return $query->whereDoesntHave('figli');
    }

    /**
     * Filtro per normativa Codice Civile
     */
    public function scopeNormativa($query, string $articolo)
    {
        return $query->where("art_{$articolo}_cc", true);
    }

    // ==================== BUSINESS METHODS ====================

    /**
     * Genera codice conto automatico
     */
    public static function generaCodiceConto(int $gestioneId, ?int $padreId = null): string
    {
        if (!$padreId) {
            // Primo livello: trova prossimo numero disponibile
            $ultimoCodice = static::where('gestione_id', $gestioneId)
                ->whereNull('padre_id')
                ->orderBy('codice_conto', 'desc')
                ->value('codice_conto');
            
            $prossimoNumero = $ultimoCodice ? (int)explode('.', $ultimoCodice)[0] + 1 : 100;
            return sprintf('%03d', $prossimoNumero);
        }
        
        // Livelli successivi: estende codice padre
        $padre = static::find($padreId);
        $ultimoFiglio = static::where('padre_id', $padreId)
            ->orderBy('codice_conto', 'desc')
            ->value('codice_conto');
        
        if (!$ultimoFiglio) {
            return $padre->codice_conto . '.001';
        }
        
        $partiCodice = explode('.', $ultimoFiglio);
        $ultimoSuffisso = (int)end($partiCodice);
        $nuovoSuffisso = sprintf('%03d', $ultimoSuffisso + 1);
        
        return $padre->codice_conto . '.' . $nuovoSuffisso;
    }

    /**
     * Calcola saldo del conto per periodo
     */
    public function calcolaSaldo(\Carbon\Carbon $dataInizio, \Carbon\Carbon $dataFine): array
    {
        $registrazioni = $this->registrazioniContabili()
            ->whereBetween('data_registrazione', [$dataInizio, $dataFine])
            ->get();
        
        $totaleDare = $registrazioni->sum('importo_dare');
        $totaleAvere = $registrazioni->sum('importo_avere');
        
        $saldo = ($this->natura_saldo === 'DARE') 
            ? $totaleDare - $totaleAvere
            : $totaleAvere - $totaleDare;
        
        return [
            'dare' => $totaleDare,
            'avere' => $totaleAvere,
            'saldo' => $saldo,
            'natura_saldo' => $saldo >= 0 ? $this->natura_saldo : 
                ($this->natura_saldo === 'DARE' ? 'AVERE' : 'DARE')
        ];
    }

    /**
     * Genera mastrino del conto
     */
    public function generaMastrino(\Carbon\Carbon $dataInizio, \Carbon\Carbon $dataFine): array
    {
        $movimenti = $this->registrazioniContabili()
            ->with(['contabilita', 'contabilita.gestione', 'contabilita.unitaImmobiliare'])
            ->whereBetween('data_registrazione', [$dataInizio, $dataFine])
            ->orderBy('data_registrazione')
            ->orderBy('numero_registrazione')
            ->get();
        
        $saldoProgressivo = 0;
        $mastrino = [];
        
        foreach ($movimenti as $movimento) {
            $importoMovimento = ($this->natura_saldo === 'DARE') 
                ? $movimento->importo_dare - $movimento->importo_avere
                : $movimento->importo_avere - $movimento->importo_dare;
            
            $saldoProgressivo += $importoMovimento;
            
            $mastrino[] = [
                'data' => $movimento->data_registrazione,
                'descrizione' => $movimento->contabilita->descrizione,
                'documento' => $movimento->contabilita->numero_documento,
                'dare' => $movimento->importo_dare,
                'avere' => $movimento->importo_avere,
                'saldo_progressivo' => $saldoProgressivo,
                'unita_immobiliare' => $movimento->contabilita->unitaImmobiliare?->denominazione
            ];
        }
        
        return $mastrino;
    }

    /**
     * Calcola ripartizione per unità immobiliari
     */
    public function calcolaRipartizione(float $importoTotale): array
    {
        $gestione = $this->gestione;
        $unitaImmobiliari = $gestione->stabile->unitaImmobiliari()->attive()->get();
        
        $ripartizione = [];
        
        switch ($this->ripartizione_default) {
            case 'MILLESIMI_GENERALI':
                $totaleMillesimi = $unitaImmobiliari->sum('millesimi_generali');
                foreach ($unitaImmobiliari as $unita) {
                    $quota = ($unita->millesimi_generali / $totaleMillesimi) * $importoTotale;
                    $ripartizione[$unita->id] = round($quota, 2);
                }
                break;
                
            case 'MILLESIMI_SCALE':
                $totaleMillesimi = $unitaImmobiliari->sum('millesimi_scale');
                foreach ($unitaImmobiliari as $unita) {
                    $quota = ($unita->millesimi_scale / $totaleMillesimi) * $importoTotale;
                    $ripartizione[$unita->id] = round($quota, 2);
                }
                break;
                
            case 'MILLESIMI_ASCENSORE':
                $totaleMillesimi = $unitaImmobiliari->sum('millesimi_ascensore');
                foreach ($unitaImmobiliari as $unita) {
                    $quota = ($unita->millesimi_ascensore / $totaleMillesimi) * $importoTotale;
                    $ripartizione[$unita->id] = round($quota, 2);
                }
                break;
                
            case 'MILLESIMI_RISCALDAMENTO':
                $totaleMillesimi = $unitaImmobiliari->sum('millesimi_riscaldamento');
                foreach ($unitaImmobiliari as $unita) {
                    $quota = ($unita->millesimi_riscaldamento / $totaleMillesimi) * $importoTotale;
                    $ripartizione[$unita->id] = round($quota, 2);
                }
                break;
                
            case 'PARTI_UGUALI':
                $numeroUnita = $unitaImmobiliari->count();
                $quotaUguale = $importoTotale / $numeroUnita;
                foreach ($unitaImmobiliari as $unita) {
                    $ripartizione[$unita->id] = round($quotaUguale, 2);
                }
                break;
                
            case 'PERSONALIZZATA':
                if ($this->ripartizione_personalizzata) {
                    foreach ($unitaImmobiliari as $unita) {
                        $percentuale = $this->ripartizione_personalizzata[$unita->id] ?? 0;
                        $quota = ($percentuale / 100) * $importoTotale;
                        $ripartizione[$unita->id] = round($quota, 2);
                    }
                }
                break;
        }
        
        return $ripartizione;
    }

    /**
     * Verifica se conto supera soglia attenzione
     */
    public function verificaSogliaAttenzione(): bool
    {
        if (!$this->soglia_attenzione) {
            return false;
        }
        
        $saldoCorrente = $this->calcolaSaldo(
            now()->startOfYear(),
            now()
        )['saldo'];
        
        return abs($saldoCorrente) > $this->soglia_attenzione;
    }

    /**
     * Ottieni gerarchia completa del conto
     */
    public function getPercorsoCompleto(): string
    {
        $percorso = [$this->denominazione];
        $padre = $this->padre;
        
        while ($padre) {
            array_unshift($percorso, $padre->denominazione);
            $padre = $padre->padre;
        }
        
        return implode(' > ', $percorso);
    }

    /**
     * Chiudi conto per esercizio
     */
    public function chiudiEsercizio(\Carbon\Carbon $dataChiusura, string $nota = null): bool
    {
        if ($this->chiuso_esercizio) {
            return false; // Già chiuso
        }
        
        // Calcola saldo finale
        $saldoFinale = $this->calcolaSaldo(
            $this->gestione->data_inizio,
            $dataChiusura
        );
        
        // Crea registrazione di chiusura se necessario
        if ($saldoFinale['saldo'] != 0) {
            $this->creaRegistrazioneChiusura($dataChiusura, $saldoFinale['saldo'], $nota);
        }
        
        $this->update([
            'chiuso_esercizio' => true,
            'updated_by' => auth()->id()
        ]);
        
        return true;
    }

    /**
     * Crea registrazione di chiusura
     */
    private function creaRegistrazioneChiusura(\Carbon\Carbon $dataChiusura, float $saldo, ?string $nota): void
    {
        // Trova o crea conto "Risultato di gestione"
        $contoRisultato = static::where('gestione_id', $this->gestione_id)
            ->where('codice_conto', 'LIKE', '%RISULTATO%')
            ->first();
        
        if (!$contoRisultato) {
            $contoRisultato = static::create([
                'codice_conto' => '999.001',
                'denominazione' => 'Risultato di gestione',
                'gestione_id' => $this->gestione_id,
                'tipo_conto' => 'PATRIMONIO',
                'natura_saldo' => 'AVERE',
                'utilizzabile_registrazioni' => false,
                'created_by' => auth()->id()
            ]);
        }
        
        // Crea registrazione di chiusura
        Contabilita::create([
            'gestione_id' => $this->gestione_id,
            'data_registrazione' => $dataChiusura,
            'numero_documento' => 'CHIUSURA-' . $this->codice_conto,
            'descrizione' => "Chiusura esercizio - {$this->denominazione}" . ($nota ? " - {$nota}" : ''),
            'importo_totale' => abs($saldo),
            'tipo_movimento' => 'CHIUSURA_ESERCIZIO',
            'registrazioni' => [
                [
                    'conto_id' => $this->id,
                    'importo_dare' => $saldo < 0 ? abs($saldo) : 0,
                    'importo_avere' => $saldo > 0 ? abs($saldo) : 0,
                ],
                [
                    'conto_id' => $contoRisultato->id,
                    'importo_dare' => $saldo > 0 ? abs($saldo) : 0,
                    'importo_avere' => $saldo < 0 ? abs($saldo) : 0,
                ]
            ],
            'created_by' => auth()->id()
        ]);
    }

    // ==================== VALIDAZIONE ====================

    /**
     * Valida struttura gerarchica
     */
    public function validaGerarchia(): array
    {
        $errori = [];
        
        // Verifica livello coerente con padre
        if ($this->padre_id) {
            $padre = $this->padre;
            if ($padre && $this->livello !== $padre->livello + 1) {
                $errori[] = "Livello {$this->livello} non coerente con padre (livello {$padre->livello})";
            }
        } else {
            if ($this->livello !== 1) {
                $errori[] = "Conto radice deve essere di livello 1";
            }
        }
        
        // Verifica codice conto
        $partiCodice = explode('.', $this->codice_conto);
        if (count($partiCodice) !== $this->livello) {
            $errori[] = "Codice conto non coerente con livello {$this->livello}";
        }
        
        return $errori;
    }

    // ==================== EVENTI MODEL ====================

    protected static function booted()
    {
        // Auto-genera codice se non fornito
        static::creating(function ($pianoConto) {
            if (!$pianoConto->codice_conto) {
                $pianoConto->codice_conto = static::generaCodiceConto(
                    $pianoConto->gestione_id,
                    $pianoConto->padre_id
                );
            }
            
            // Calcola livello automaticamente
            if ($pianoConto->padre_id) {
                $padre = static::find($pianoConto->padre_id);
                $pianoConto->livello = $padre ? $padre->livello + 1 : 1;
            } else {
                $pianoConto->livello = 1;
            }
            
            $pianoConto->created_by = auth()->id();
        });
        
        static::updating(function ($pianoConto) {
            $pianoConto->updated_by = auth()->id();
        });
        
        // Applica filtro multi-tenant
        static::addGlobalScope(new \App\Scopes\MultiTenantScope);
    }
}

🎯 PIANO DEI CONTI STANDARD CONDOMINIALE

STRUTTURA PREDEFINITA

-- INSERIMENTO PIANO DEI CONTI STANDARD PER CONDOMINI
-- Conforme alle normative italiane per amministrazione condominiale

-- LIVELLO 1: MACRO-CATEGORIE
INSERT INTO piano_conti (codice_conto, denominazione, tipo_conto, natura_saldo, livello, gestione_id) VALUES
('100', 'ATTIVITÀ', 'ATTIVO', 'DARE', 1, :gestione_id),
('200', 'PASSIVITÀ', 'PASSIVO', 'AVERE', 1, :gestione_id),
('300', 'RICAVI', 'RICAVO', 'AVERE', 1, :gestione_id),
('400', 'COSTI ORDINARI', 'COSTO', 'DARE', 1, :gestione_id),
('500', 'COSTI STRAORDINARI', 'COSTO', 'DARE', 1, :gestione_id),
('900', 'PATRIMONIO E RISULTATO', 'PATRIMONIO', 'AVERE', 1, :gestione_id);

-- LIVELLO 2: CATEGORIE PRINCIPALI

-- 100 - ATTIVITÀ
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id) VALUES
('100.001', 'Disponibilità Liquide', (SELECT id FROM piano_conti WHERE codice_conto='100'), 'ATTIVO', 'DARE', 2, :gestione_id),
('100.002', 'Crediti verso Condomini', (SELECT id FROM piano_conti WHERE codice_conto='100'), 'ATTIVO', 'DARE', 2, :gestione_id),
('100.003', 'Fondi di Cassa', (SELECT id FROM piano_conti WHERE codice_conto='100'), 'ATTIVO', 'DARE', 2, :gestione_id);

-- 200 - PASSIVITÀ  
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id) VALUES
('200.001', 'Debiti verso Fornitori', (SELECT id FROM piano_conti WHERE codice_conto='200'), 'PASSIVO', 'AVERE', 2, :gestione_id),
('200.002', 'Fondi per Lavori Futuri', (SELECT id FROM piano_conti WHERE codice_conto='200'), 'PASSIVO', 'AVERE', 2, :gestione_id),
('200.003', 'Depositi Cauzionali', (SELECT id FROM piano_conti WHERE codice_conto='200'), 'PASSIVO', 'AVERE', 2, :gestione_id);

-- 300 - RICAVI
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, ripartizione_default) VALUES
('300.001', 'Quote Condominiali Ordinarie', (SELECT id FROM piano_conti WHERE codice_conto='300'), 'RICAVO', 'AVERE', 2, :gestione_id, 'MILLESIMI_GENERALI'),
('300.002', 'Quote Condominiali Straordinarie', (SELECT id FROM piano_conti WHERE codice_conto='300'), 'RICAVO', 'AVERE', 2, :gestione_id, 'MILLESIMI_GENERALI'),
('300.003', 'Interessi di Mora', (SELECT id FROM piano_conti WHERE codice_conto='300'), 'RICAVO', 'AVERE', 2, :gestione_id, 'PERSONALIZZATA');

-- 400 - COSTI ORDINARI
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, art_1133_cc) VALUES
('400.001', 'Manutenzione Ordinaria', (SELECT id FROM piano_conti WHERE codice_conto='400'), 'COSTO', 'DARE', 2, :gestione_id, TRUE),
('400.002', 'Pulizie e Igiene', (SELECT id FROM piano_conti WHERE codice_conto='400'), 'COSTO', 'DARE', 2, :gestione_id, TRUE),
('400.003', 'Servizi Condominiali', (SELECT id FROM piano_conti WHERE codice_conto='400'), 'COSTO', 'DARE', 2, :gestione_id, FALSE);

-- 500 - COSTI STRAORDINARI  
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, art_1134_cc) VALUES
('500.001', 'Innovazioni', (SELECT id FROM piano_conti WHERE codice_conto='500'), 'COSTO', 'DARE', 2, :gestione_id, TRUE),
('500.002', 'Manutenzione Straordinaria', (SELECT id FROM piano_conti WHERE codice_conto='500'), 'COSTO', 'DARE', 2, :gestione_id, FALSE),
('500.003', 'Lavori di Miglioria', (SELECT id FROM piano_conti WHERE codice_conto='500'), 'COSTO', 'DARE', 2, :gestione_id, TRUE);

-- LIVELLO 3: DETTAGLIO OPERATIVO

-- Disponibilità Liquide (100.001)
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, utilizzabile_registrazioni) VALUES
('100.001.001', 'Conto Corrente Ordinario', (SELECT id FROM piano_conti WHERE codice_conto='100.001'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE),
('100.001.002', 'Conto Corrente Straordinario', (SELECT id FROM piano_conti WHERE codice_conto='100.001'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE),
('100.001.003', 'Cassa Contanti', (SELECT id FROM piano_conti WHERE codice_conto='100.001'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE);

-- Crediti verso Condomini (100.002)
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, utilizzabile_registrazioni, ripartizione_default) VALUES
('100.002.001', 'Rate Ordinarie da Incassare', (SELECT id FROM piano_conti WHERE codice_conto='100.002'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE, 'MILLESIMI_GENERALI'),
('100.002.002', 'Rate Straordinarie da Incassare', (SELECT id FROM piano_conti WHERE codice_conto='100.002'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE, 'MILLESIMI_GENERALI'),
('100.002.003', 'Morosità e Interessi', (SELECT id FROM piano_conti WHERE codice_conto='100.002'), 'ATTIVO', 'DARE', 3, :gestione_id, TRUE, 'PERSONALIZZATA');

-- Manutenzione Ordinaria (400.001) - Art. 1133 C.C.
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, utilizzabile_registrazioni, art_1133_cc, ripartizione_default) VALUES
('400.001.001', 'Elettricista', (SELECT id FROM piano_conti WHERE codice_conto='400.001'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, TRUE, 'MILLESIMI_GENERALI'),
('400.001.002', 'Idraulico', (SELECT id FROM piano_conti WHERE codice_conto='400.001'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, TRUE, 'MILLESIMI_GENERALI'),
('400.001.003', 'Ascensore', (SELECT id FROM piano_conti WHERE codice_conto='400.001'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, TRUE, 'MILLESIMI_ASCENSORE'),
('400.001.004', 'Riscaldamento', (SELECT id FROM piano_conti WHERE codice_conto='400.001'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, TRUE, 'MILLESIMI_RISCALDAMENTO');

-- Pulizie e Igiene (400.002)
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, utilizzabile_registrazioni, ripartizione_default) VALUES
('400.002.001', 'Pulizie Scale', (SELECT id FROM piano_conti WHERE codice_conto='400.002'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, 'MILLESIMI_SCALE'),
('400.002.002', 'Derattizzazione', (SELECT id FROM piano_conti WHERE codice_conto='400.002'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, 'MILLESIMI_GENERALI'),
('400.002.003', 'Disinfestazione', (SELECT id FROM piano_conti WHERE codice_conto='400.002'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, 'MILLESIMI_GENERALI');

-- Servizi Condominiali (400.003) - Art. 1131 C.C.
INSERT INTO piano_conti (codice_conto, denominazione, padre_id, tipo_conto, natura_saldo, livello, gestione_id, utilizzabile_registrazioni, art_1131_cc, ripartizione_default) VALUES
('400.003.001', 'Amministratore', (SELECT id FROM piano_conti WHERE codice_conto='400.003'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, TRUE, 'MILLESIMI_GENERALI'),
('400.003.002', 'Portiere', (SELECT id FROM piano_conti WHERE codice_conto='400.003'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, FALSE, 'MILLESIMI_GENERALI'),
('400.003.003', 'Assicurazioni', (SELECT id FROM piano_conti WHERE codice_conto='400.003'), 'COSTO', 'DARE', 3, :gestione_id, TRUE, FALSE, 'MILLESIMI_GENERALI');

📊 UTILIZZO E INTEGRAZIONE

Query Esempio - Mastrino Completo

-- Genera mastrino per conto specifico con saldi progressivi
SELECT 
    c.data_registrazione,
    c.numero_documento,
    c.descrizione,
    rc.importo_dare,
    rc.importo_avere,
    (@saldo := @saldo + (rc.importo_dare - rc.importo_avere)) AS saldo_progressivo,
    ui.denominazione AS unita_immobiliare
FROM contabilita c
INNER JOIN registrazioni_contabili rc ON c.id = rc.contabilita_id
LEFT JOIN unita_immobiliari ui ON c.unita_immobiliare_id = ui.id
CROSS JOIN (SELECT @saldo := 0) AS init
WHERE rc.conto_id = :conto_id
  AND c.gestione_id = :gestione_id  
  AND c.data_registrazione BETWEEN :data_inizio AND :data_fine
ORDER BY c.data_registrazione, c.numero_registrazione;

Business Logic - Ripartizione Automatica

// Esempio: Ripartire spesa di manutenzione ascensore
$contoAscensore = PianoConto::where('codice_conto', '400.001.003')->first();
$ripartizione = $contoAscensore->calcolaRipartizione(1500.00);

// Risultato automatico basato su millesimi_ascensore:
// [
//     '1' => 45.75,    // Unità 1: 30,5‰ × 1500€
//     '2' => 38.25,    // Unità 2: 25,5‰ × 1500€  
//     '3' => 66.00,    // Unità 3: 44‰ × 1500€
//     // ...
// ]

🚀 PROSSIMI STEP

Completato: Piano dei Conti con struttura gerarchica e normativa CC
🔄 Prossimo: Movimenti Bancari per riconciliazione automatica
Seguente: Fatture Elettroniche per integrazione fiscale

La struttura Piano dei Conti è ora completa e pronta per integrarsi con il sistema contabile centralizzato. Procediamo con i Movimenti Bancari?