33 KiB
33 KiB
📊 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?