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

861 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📊 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
<?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
<?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**
```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**?