# 📊 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** ```sql 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 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 '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** ```sql -- 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** ```sql -- 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** ```php // 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**?