📋 AGGIUNTE PRINCIPALI: - Sistema contabile partita doppia con gestioni multiple - Documentazione implementazione completa - Models Laravel: GestioneContabile, MovimentoPartitaDoppia - Controller ContabilitaAvanzataController - Migration sistema contabile completo - Scripts automazione e trasferimento - Manuali utente e checklist implementazione 📊 FILES PRINCIPALI: - docs/10-IMPLEMENTAZIONE-CONTABILITA-PARTITA-DOPPIA-GESTIONI.md - SPECIFICHE-SISTEMA-CONTABILE-COMPLETO.md - netgescon-laravel/database/migrations/2025_07_20_100000_create_complete_accounting_system.php - netgescon-laravel/app/Models/GestioneContabile.php ✅ CHECKPOINT SICURO PER ROLLBACK
35 KiB
35 KiB
🔧 IMPLEMENTAZIONE PRATICA SISTEMA CONTABILE CONDOMINIALE
📋 OVERVIEW
Manuale tecnico per l'implementazione del sistema contabile condominiale in partita doppia con gestioni amministrative (esercizi) basate su delibere assembleari.
🎯 PRINCIPI FONDAMENTALI IMPLEMENTATIVI
📅 GESTIONI vs ANNI SOLARI - PRINCIPIO CHIAVE
⚠️ FONDAMENTALE: La contabilità condominiale NON segue l'anno solare!
✅ GESTIONE AMMINISTRATIVA:
- Inizio: Delibera assemblea (es: 01/01/2024)
- Operazioni: Tutto l'anno + eventuali post 31/12
- Fine: Approvazione bilancio assemblea successiva (es: 30/04/2025)
❌ ERRORE COMUNE: Chiudere automaticamente al 31/12
✅ CORRETTO: Chiudere solo alla delibera assemblea approvazione bilancio
🎯 CAMPO GESTIONE - IMPLEMENTAZIONE
// ⚠️ OGNI MOVIMENTO DEVE AVERE gestione_id (non anno!)
Schema::table('registrazioni_contabili', function (Blueprint $table) {
$table->unsignedBigInteger('gestione_id')->after('id');
$table->unsignedBigInteger('condominio_id')->after('gestione_id');
// 📅 Date multiple per contabilità condominiale
$table->date('data_operazione'); // Data effettiva operazione
$table->date('data_registrazione'); // Data inserimento
$table->date('data_competenza'); // Data competenza contabile
$table->date('data_valuta')->nullable(); // Data valuta bancaria
});
🗃️ SCHEMA DATABASE IMPLEMENTATIVO
1️⃣ MIGRAZIONE: Gestioni Contabili
<?php
// File: database/migrations/2025_01_20_100000_create_gestioni_contabili_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('gestioni_contabili', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('condominio_id');
// 📅 PERIODO GESTIONE
$table->string('denominazione', 255); // "Gestione 2024"
$table->date('data_inizio'); // Delibera assemblea
$table->date('data_fine_prevista'); // Solitamente 31/12
$table->date('data_chiusura_effettiva')->nullable(); // Approvazione reale
// 📊 STATO GESTIONE
$table->enum('stato', ['aperta','chiusa_provvisoria','chiusa_definitiva'])
->default('aperta');
// 🏛️ ASSEMBLEA APPROVAZIONE
$table->date('data_assemblea_approvazione')->nullable();
$table->string('verbale_approvazione', 255)->nullable();
// 💰 TOTALI GESTIONE (calcolati)
$table->decimal('totale_entrate', 12, 4)->default(0);
$table->decimal('totale_uscite', 12, 4)->default(0);
$table->decimal('saldo_gestione', 12, 4)->default(0);
$table->text('note_gestione')->nullable();
$table->timestamps();
$table->unsignedBigInteger('created_by')->nullable();
// 🔗 FOREIGN KEYS
$table->foreign('condominio_id')->references('id')->on('stabili')->onDelete('cascade');
$table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
// 📊 INDICI
$table->index(['condominio_id', 'data_inizio', 'data_fine_prevista']);
$table->index(['stato']);
$table->index(['data_chiusura_effettiva']);
// ✅ CONSTRAINT UNICO
$table->unique(['condominio_id', 'denominazione']);
});
}
public function down(): void
{
Schema::dropIfExists('gestioni_contabili');
}
};
2️⃣ MIGRAZIONE: Piano Conti Mastri
<?php
// File: database/migrations/2025_01_20_100001_create_piano_conti_mastri_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('piano_conti_mastri', function (Blueprint $table) {
$table->id();
// 📊 IDENTIFICAZIONE MASTRO
$table->string('codice_mastro', 10)->unique(); // "100", "200"
$table->string('denominazione', 255); // "ENTRATE"
$table->enum('tipo_mastro', ['ATTIVO','PASSIVO','COSTI','RICAVI']);
// 🎨 VISUALIZZAZIONE
$table->string('colore_hex', 7)->default('#6c757d');
$table->string('icona', 50)->default('fas fa-folder');
$table->smallInteger('ordine_visualizzazione')->default(0);
$table->text('descrizione')->nullable();
$table->boolean('attivo')->default(true);
$table->timestamps();
// 📊 INDICI
$table->index(['tipo_mastro', 'ordine_visualizzazione']);
$table->index(['attivo']);
});
}
public function down(): void
{
Schema::dropIfExists('piano_conti_mastri');
}
};
3️⃣ MIGRAZIONE: Piano Conti Conti
<?php
// File: database/migrations/2025_01_20_100002_create_piano_conti_conti_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('piano_conti_conti', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('mastro_id');
// 📂 IDENTIFICAZIONE CONTO
$table->string('codice_conto', 15)->unique(); // "101", "201.1"
$table->string('denominazione', 255); // "Rate Condominiali"
// 🎯 CONFIGURAZIONE CONTABILE
$table->enum('tipo_saldo', ['DARE','AVERE']);
$table->boolean('ripartizione_automatica')->default(true);
$table->string('tabella_millesimale_default', 50)->nullable();
// 🎨 VISUALIZZAZIONE
$table->string('colore_hex', 7)->nullable(); // Eredita da mastro
$table->string('icona', 50)->nullable();
$table->smallInteger('ordine_visualizzazione')->default(0);
$table->text('descrizione')->nullable();
$table->boolean('attivo')->default(true);
$table->timestamps();
// 🔗 FOREIGN KEYS
$table->foreign('mastro_id')->references('id')->on('piano_conti_mastri')->onDelete('cascade');
// 📊 INDICI
$table->index(['mastro_id', 'ordine_visualizzazione']);
$table->index(['ripartizione_automatica']);
$table->index(['attivo']);
});
}
public function down(): void
{
Schema::dropIfExists('piano_conti_conti');
}
};
4️⃣ MIGRAZIONE: Piano Conti Sottoconti
<?php
// File: database/migrations/2025_01_20_100003_create_piano_conti_sottoconti_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('piano_conti_sottoconti', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('conto_id');
// 📄 IDENTIFICAZIONE SOTTOCONTO
$table->string('codice_sottoconto', 20)->unique(); // "101.01"
$table->string('denominazione', 255); // "Rate Ordinarie"
// 🎯 CONFIGURAZIONE SPECIFICA
$table->string('ripartizione_specifica', 100)->nullable(); // "SOLO_SCALA_A"
$table->decimal('percentuale_ripartizione', 5, 2)->nullable();
$table->decimal('importo_fisso', 10, 2)->nullable();
// 📊 NATURA CONTABILE
$table->enum('tipo_saldo', ['DARE','AVERE'])->nullable(); // Eredita da conto
$table->boolean('deducibile_fiscale')->default(false);
$table->smallInteger('ordine_visualizzazione')->default(0);
$table->text('descrizione')->nullable();
$table->boolean('attivo')->default(true);
$table->timestamps();
// 🔗 FOREIGN KEYS
$table->foreign('conto_id')->references('id')->on('piano_conti_conti')->onDelete('cascade');
// 📊 INDICI
$table->index(['conto_id', 'ordine_visualizzazione']);
$table->index(['ripartizione_specifica']);
$table->index(['attivo']);
});
}
public function down(): void
{
Schema::dropIfExists('piano_conti_sottoconti');
}
};
5️⃣ MIGRAZIONE: Registrazioni Contabili ⭐
<?php
// File: database/migrations/2025_01_20_100004_create_registrazioni_contabili_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('registrazioni_contabili', function (Blueprint $table) {
$table->id();
// 🎯 COLLEGAMENTO GESTIONE (FONDAMENTALE!)
$table->unsignedBigInteger('gestione_id');
$table->unsignedBigInteger('condominio_id');
// 📅 DATE MOVIMENTO
$table->date('data_operazione'); // Data effettiva
$table->date('data_registrazione'); // Data inserimento
$table->date('data_competenza'); // Data competenza
$table->date('data_valuta')->nullable(); // Data valuta bancaria
// 📋 IDENTIFICAZIONE MOVIMENTO
$table->string('numero_registrazione', 20); // Progressivo per gestione
$table->string('causale', 500); // Descrizione
$table->string('riferimento_documento', 255)->nullable();
// 💰 IMPORTO TOTALE
$table->decimal('importo_totale', 12, 4);
// 🎯 CLASSIFICAZIONE
$table->enum('tipo_movimento', ['ENTRATA','USCITA','GIROCONTO']);
$table->string('categoria_movimento', 100)->nullable();
$table->string('sottocategoria', 100)->nullable();
// 🏦 DATI BANCARI
$table->string('conto_corrente', 50)->nullable();
$table->string('numero_assegno', 20)->nullable();
$table->string('cro_bonifico', 50)->nullable();
// 👥 SOGGETTI COINVOLTI
$table->unsignedBigInteger('fornitore_id')->nullable();
$table->unsignedBigInteger('cliente_id')->nullable();
// ⚙️ STATO E CONTROLLI
$table->enum('stato', ['bozza','confermata','ripartita','chiusa'])->default('bozza');
$table->boolean('ripartita')->default(false);
$table->boolean('riconciliata')->default(false);
// 🔄 RIPARTIZIONE
$table->boolean('ripartizione_automatica')->default(true);
$table->string('tabella_millesimale_usata', 50)->nullable();
// 📎 ALLEGATI E NOTE
$table->smallInteger('numero_allegati')->default(0);
$table->text('note_interne')->nullable();
$table->text('note_pubbliche')->nullable();
$table->timestamps();
$table->unsignedBigInteger('created_by');
$table->unsignedBigInteger('updated_by')->nullable();
// 🔗 FOREIGN KEYS
$table->foreign('gestione_id')->references('id')->on('gestioni_contabili')->onDelete('restrict');
$table->foreign('condominio_id')->references('id')->on('stabili')->onDelete('cascade');
$table->foreign('fornitore_id')->references('id')->on('persone')->onDelete('set null');
$table->foreign('cliente_id')->references('id')->on('persone')->onDelete('set null');
$table->foreign('created_by')->references('id')->on('users')->onDelete('restrict');
// 📊 INDICI
$table->index(['gestione_id', 'data_operazione']);
$table->index(['condominio_id', 'gestione_id']);
$table->index(['numero_registrazione', 'gestione_id']);
$table->index(['tipo_movimento', 'categoria_movimento']);
$table->index(['stato', 'ripartita']);
// ✅ CONSTRAINTS
$table->unique(['gestione_id', 'numero_registrazione']);
$table->checkRaw('importo_totale > 0');
});
}
public function down(): void
{
Schema::dropIfExists('registrazioni_contabili');
}
};
6️⃣ MIGRAZIONE: Movimenti Contabili (DARE/AVERE)
<?php
// File: database/migrations/2025_01_20_100005_create_movimenti_contabili_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('movimenti_contabili', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('registrazione_id');
// 📊 PARTITA DOPPIA
$table->unsignedBigInteger('sottoconto_id');
// 💰 IMPORTI DARE/AVERE
$table->decimal('importo_dare', 12, 4)->default(0);
$table->decimal('importo_avere', 12, 4)->default(0);
// 📋 DETTAGLI MOVIMENTO
$table->string('descrizione', 500)->nullable();
$table->decimal('quantita', 10, 3)->nullable();
$table->decimal('prezzo_unitario', 10, 4)->nullable();
// 🎯 RIPARTIZIONE
$table->boolean('da_ripartire')->default(true);
$table->string('tabella_millesimale', 50)->nullable();
$table->timestamp('created_at')->useCurrent();
// 🔗 FOREIGN KEYS
$table->foreign('registrazione_id')->references('id')->on('registrazioni_contabili')->onDelete('cascade');
$table->foreign('sottoconto_id')->references('id')->on('piano_conti_sottoconti')->onDelete('restrict');
// 📊 INDICI
$table->index(['registrazione_id']);
$table->index(['sottoconto_id']);
$table->index(['da_ripartire']);
// ✅ CONSTRAINT PARTITA DOPPIA
$table->checkRaw('(importo_dare > 0 AND importo_avere = 0) OR (importo_dare = 0 AND importo_avere > 0)');
});
}
public function down(): void
{
Schema::dropIfExists('movimenti_contabili');
}
};
7️⃣ MIGRAZIONE: Ripartizioni Condomini
<?php
// File: database/migrations/2025_01_20_100006_create_ripartizioni_condomini_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('ripartizioni_condomini', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('registrazione_id');
$table->unsignedBigInteger('movimento_id');
$table->unsignedBigInteger('unita_immobiliare_id');
// 💰 IMPORTO RIPARTITO
$table->decimal('importo_ripartito', 12, 4);
// 📊 CALCOLO RIPARTIZIONE
$table->decimal('millesimi_utilizzati', 8, 4);
$table->string('tabella_millesimale', 50);
// 👥 IMPUTAZIONE
$table->unsignedBigInteger('persona_id')->nullable();
$table->enum('tipo_imputazione', ['proprietario','inquilino','delegato'])->default('proprietario');
// 📅 PERIODO
$table->date('data_competenza_inizio')->nullable();
$table->date('data_competenza_fine')->nullable();
// ⚙️ STATO
$table->enum('stato', ['calcolata','confermata','fatturata','pagata'])->default('calcolata');
$table->timestamp('created_at')->useCurrent();
// 🔗 FOREIGN KEYS
$table->foreign('registrazione_id')->references('id')->on('registrazioni_contabili')->onDelete('cascade');
$table->foreign('movimento_id')->references('id')->on('movimenti_contabili')->onDelete('cascade');
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('cascade');
$table->foreign('persona_id')->references('id')->on('persone')->onDelete('set null');
// 📊 INDICI
$table->index(['registrazione_id']);
$table->index(['unita_immobiliare_id']);
$table->index(['persona_id', 'stato']);
$table->index(['data_competenza_inizio', 'data_competenza_fine']);
// ✅ CONSTRAINTS
$table->checkRaw('importo_ripartito > 0');
$table->checkRaw('millesimi_utilizzati > 0 AND millesimi_utilizzati <= 1000');
});
}
public function down(): void
{
Schema::dropIfExists('ripartizioni_condomini');
}
};
🏗️ MODELS ELOQUENT
1️⃣ Model: GestioneContabile
<?php
// File: app/Models/GestioneContabile.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class GestioneContabile extends Model
{
use HasFactory;
protected $table = 'gestioni_contabili';
protected $fillable = [
'condominio_id',
'denominazione',
'data_inizio',
'data_fine_prevista',
'data_chiusura_effettiva',
'stato',
'data_assemblea_approvazione',
'verbale_approvazione',
'totale_entrate',
'totale_uscite',
'saldo_gestione',
'note_gestione',
'created_by',
];
protected $casts = [
'data_inizio' => 'date',
'data_fine_prevista' => 'date',
'data_chiusura_effettiva' => 'date',
'data_assemblea_approvazione' => 'date',
'totale_entrate' => 'decimal:4',
'totale_uscite' => 'decimal:4',
'saldo_gestione' => 'decimal:4',
];
// === RELATIONSHIPS ===
public function condominio(): BelongsTo
{
return $this->belongsTo(Stabile::class, 'condominio_id');
}
public function registrazioni(): HasMany
{
return $this->hasMany(RegistrazioneContabile::class, 'gestione_id');
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
// === SCOPES ===
public function scopeAperte($query)
{
return $query->where('stato', 'aperta');
}
public function scopeChiuse($query)
{
return $query->where('stato', 'chiusa_definitiva');
}
// === METHODS ===
public function isAperta(): bool
{
return $this->stato === 'aperta';
}
public function calcolaTotali(): void
{
$entrate = $this->registrazioni()
->where('tipo_movimento', 'ENTRATA')
->where('stato', '!=', 'bozza')
->sum('importo_totale');
$uscite = $this->registrazioni()
->where('tipo_movimento', 'USCITA')
->where('stato', '!=', 'bozza')
->sum('importo_totale');
$this->update([
'totale_entrate' => $entrate,
'totale_uscite' => $uscite,
'saldo_gestione' => $entrate - $uscite,
]);
}
}
2️⃣ Model: RegistrazioneContabile
<?php
// File: app/Models/RegistrazioneContabile.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class RegistrazioneContabile extends Model
{
use HasFactory;
protected $table = 'registrazioni_contabili';
protected $fillable = [
'gestione_id',
'condominio_id',
'data_operazione',
'data_registrazione',
'data_competenza',
'data_valuta',
'numero_registrazione',
'causale',
'riferimento_documento',
'importo_totale',
'tipo_movimento',
'categoria_movimento',
'sottocategoria',
'conto_corrente',
'numero_assegno',
'cro_bonifico',
'fornitore_id',
'cliente_id',
'stato',
'ripartita',
'riconciliata',
'ripartizione_automatica',
'tabella_millesimale_usata',
'numero_allegati',
'note_interne',
'note_pubbliche',
'created_by',
'updated_by',
];
protected $casts = [
'data_operazione' => 'date',
'data_registrazione' => 'date',
'data_competenza' => 'date',
'data_valuta' => 'date',
'importo_totale' => 'decimal:4',
'ripartita' => 'boolean',
'riconciliata' => 'boolean',
'ripartizione_automatica' => 'boolean',
'numero_allegati' => 'integer',
];
// === RELATIONSHIPS ===
public function gestione(): BelongsTo
{
return $this->belongsTo(GestioneContabile::class, 'gestione_id');
}
public function condominio(): BelongsTo
{
return $this->belongsTo(Stabile::class, 'condominio_id');
}
public function movimenti(): HasMany
{
return $this->hasMany(MovimentoContabile::class, 'registrazione_id');
}
public function ripartizioni(): HasMany
{
return $this->hasMany(RipartizioneCondomino::class, 'registrazione_id');
}
public function fornitore(): BelongsTo
{
return $this->belongsTo(Persona::class, 'fornitore_id');
}
public function cliente(): BelongsTo
{
return $this->belongsTo(Persona::class, 'cliente_id');
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
// === METHODS ===
public function verificaPartitaDoppia(): bool
{
$totaleDare = $this->movimenti()->sum('importo_dare');
$totaleAvere = $this->movimenti()->sum('importo_avere');
return abs($totaleDare - $totaleAvere) < 0.01; // Tolleranza centesimi
}
public function generaNumeroRegistrazione(): string
{
$ultimoNumero = self::where('gestione_id', $this->gestione_id)
->max('numero_registrazione');
return str_pad(intval($ultimoNumero) + 1, 6, '0', STR_PAD_LEFT);
}
public function ripartisciAutomaticamente(): void
{
foreach ($this->movimenti()->where('da_ripartire', true)->get() as $movimento) {
$movimento->ripartisci();
}
$this->update(['ripartita' => true]);
}
}
🎯 SEEDER PIANO CONTI STANDARD
<?php
// File: database/seeders/PianoContiSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\PianoContiMastro;
use App\Models\PianoContiConto;
use App\Models\PianoContiSottoconto;
class PianoContiSeeder extends Seeder
{
public function run(): void
{
// 🏛️ MASTRI
$mastroEntrate = PianoContiMastro::create([
'codice_mastro' => '100',
'denominazione' => 'ENTRATE',
'tipo_mastro' => 'RICAVI',
'colore_hex' => '#28a745',
'icona' => 'fas fa-arrow-down',
'ordine_visualizzazione' => 1,
]);
$mastroSpese = PianoContiMastro::create([
'codice_mastro' => '200',
'denominazione' => 'SPESE AMMINISTRATIVE',
'tipo_mastro' => 'COSTI',
'colore_hex' => '#dc3545',
'icona' => 'fas fa-arrow-up',
'ordine_visualizzazione' => 2,
]);
$mastroManutenzioni = PianoContiMastro::create([
'codice_mastro' => '300',
'denominazione' => 'MANUTENZIONI',
'tipo_mastro' => 'COSTI',
'colore_hex' => '#fd7e14',
'icona' => 'fas fa-tools',
'ordine_visualizzazione' => 3,
]);
$mastroFondi = PianoContiMastro::create([
'codice_mastro' => '400',
'denominazione' => 'FONDI E LIQUIDITÀ',
'tipo_mastro' => 'ATTIVO',
'colore_hex' => '#6f42c1',
'icona' => 'fas fa-piggy-bank',
'ordine_visualizzazione' => 4,
]);
// 📂 CONTI - ENTRATE
$contoRate = PianoContiConto::create([
'mastro_id' => $mastroEntrate->id,
'codice_conto' => '101',
'denominazione' => 'Rate Condominiali',
'tipo_saldo' => 'AVERE',
'ripartizione_automatica' => false, // Le rate non si ripartiscono
'ordine_visualizzazione' => 1,
]);
$contoAltriRicavi = PianoContiConto::create([
'mastro_id' => $mastroEntrate->id,
'codice_conto' => '102',
'denominazione' => 'Altri Ricavi',
'tipo_saldo' => 'AVERE',
'ripartizione_automatica' => false,
'ordine_visualizzazione' => 2,
]);
// 📂 CONTI - SPESE
$contoPulizie = PianoContiConto::create([
'mastro_id' => $mastroSpese->id,
'codice_conto' => '201',
'denominazione' => 'Pulizie',
'tipo_saldo' => 'DARE',
'ripartizione_automatica' => true,
'tabella_millesimale_default' => 'GENERALE',
'ordine_visualizzazione' => 1,
]);
$contoEnergia = PianoContiConto::create([
'mastro_id' => $mastroSpese->id,
'codice_conto' => '202',
'denominazione' => 'Energia Elettrica',
'tipo_saldo' => 'DARE',
'ripartizione_automatica' => true,
'tabella_millesimale_default' => 'GENERALE',
'ordine_visualizzazione' => 2,
]);
// 📄 SOTTOCONTI - RATE
PianoContiSottoconto::create([
'conto_id' => $contoRate->id,
'codice_sottoconto' => '101.01',
'denominazione' => 'Rate Ordinarie',
'tipo_saldo' => 'AVERE',
'ordine_visualizzazione' => 1,
]);
PianoContiSottoconto::create([
'conto_id' => $contoRate->id,
'codice_sottoconto' => '101.02',
'denominazione' => 'Rate Straordinarie',
'tipo_saldo' => 'AVERE',
'ordine_visualizzazione' => 2,
]);
// 📄 SOTTOCONTI - PULIZIE
PianoContiSottoconto::create([
'conto_id' => $contoPulizie->id,
'codice_sottoconto' => '201.01',
'denominazione' => 'Pulizie Scale Scala A',
'tipo_saldo' => 'DARE',
'ripartizione_specifica' => 'SCALA_A',
'ordine_visualizzazione' => 1,
]);
PianoContiSottoconto::create([
'conto_id' => $contoPulizie->id,
'codice_sottoconto' => '201.02',
'denominazione' => 'Pulizie Scale Scala B',
'tipo_saldo' => 'DARE',
'ripartizione_specifica' => 'SCALA_B',
'ordine_visualizzazione' => 2,
]);
}
}
⚙️ SCRIPT AUTOMATIZZAZIONE
1️⃣ Command: Setup Sistema Contabile
<?php
// File: app/Console/Commands/SetupSistemaContabile.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Stabile;
use App\Models\GestioneContabile;
use Illuminate\Support\Facades\DB;
class SetupSistemaContabile extends Command
{
protected $signature = 'contabilita:setup {condominio_id} {anno}';
protected $description = 'Setup sistema contabile per condominio';
public function handle()
{
$condominioId = $this->argument('condominio_id');
$anno = $this->argument('anno');
$condominio = Stabile::findOrFail($condominioId);
$this->info("🏢 Setup contabilità per: {$condominio->denominazione}");
DB::transaction(function () use ($condominio, $anno) {
// 1️⃣ Crea gestione contabile
$gestione = GestioneContabile::create([
'condominio_id' => $condominio->id,
'denominazione' => "Gestione {$anno}",
'data_inizio' => "{$anno}-01-01",
'data_fine_prevista' => "{$anno}-12-31",
'stato' => 'aperta',
'created_by' => 1,
]);
$this->info("✅ Gestione creata: {$gestione->denominazione}");
// 2️⃣ Popola piano conti se vuoto
if (!\App\Models\PianoContiMastro::exists()) {
$this->call('db:seed', ['--class' => 'PianoContiSeeder']);
$this->info("✅ Piano conti popolato");
}
// 3️⃣ Verifica tabelle millesimali
$unitaConProblem = $condominio->unitaImmobiliari()
->where('millesimi', '<=', 0)
->count();
if ($unitaConProblem > 0) {
$this->warn("⚠️ {$unitaConProblem} unità senza millesimi corretti");
}
$this->info("🎉 Setup completato!");
});
return Command::SUCCESS;
}
}
2️⃣ Command: Verifica Partita Doppia
<?php
// File: app/Console/Commands/VerificaPartitaDoppia.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\GestioneContabile;
use App\Models\RegistrazioneContabile;
class VerificaPartitaDoppia extends Command
{
protected $signature = 'contabilita:verifica {gestione_id?}';
protected $description = 'Verifica quadratura partita doppia';
public function handle()
{
$gestioneId = $this->argument('gestione_id');
if ($gestioneId) {
$gestioni = [GestioneContabile::findOrFail($gestioneId)];
} else {
$gestioni = GestioneContabile::where('stato', 'aperta')->get();
}
foreach ($gestioni as $gestione) {
$this->info("🔍 Verifica Gestione: {$gestione->denominazione}");
$registrazioni = $gestione->registrazioni()->get();
$errori = 0;
foreach ($registrazioni as $registrazione) {
if (!$registrazione->verificaPartitaDoppia()) {
$errori++;
$this->error("❌ Registrazione #{$registrazione->numero_registrazione}: partita doppia non bilanciata");
}
}
if ($errori === 0) {
$this->info("✅ Tutte le registrazioni sono bilanciate");
} else {
$this->error("⚠️ {$errori} registrazioni con errori di bilanciamento");
}
// Ricalcola totali gestione
$gestione->calcolaTotali();
$this->info("💰 Totale Entrate: €" . number_format($gestione->totale_entrate, 2));
$this->info("💰 Totale Uscite: €" . number_format($gestione->totale_uscite, 2));
$this->info("💰 Saldo: €" . number_format($gestione->saldo_gestione, 2));
}
return Command::SUCCESS;
}
}
📋 CHECKLIST IMPLEMENTAZIONE
✅ FASE 1: Database e Strutture
- Eseguire migrazioni piano conti
- Eseguire migrazioni registrazioni contabili
- Popolare seeder piano conti standard
- Verificare foreign keys e constraint
✅ FASE 2: Models e Relationships
- Implementare models Eloquent
- Configurare relationships
- Implementare scopes e methods
- Aggiungere validazioni
✅ FASE 3: Controllers e Routes
- Controller gestioni contabili
- Controller registrazioni
- Controller piano conti
- API endpoints
✅ FASE 4: Business Logic
- Sistema ripartizione automatica
- Controllo partita doppia
- Numerazione automatica
- Calcolo totali gestione
✅ FASE 5: Testing e Validazione
- Unit tests models
- Feature tests controllers
- Test ripartizione
- Test partita doppia
🎯 COMANDI ARTISAN DISPONIBILI
# Setup completo sistema contabile
php artisan contabilita:setup {condominio_id} {anno}
# Verifica partita doppia
php artisan contabilita:verifica {gestione_id?}
# Popola piano conti standard
php artisan db:seed --class=PianoContiSeeder
# Ricalcola totali gestioni
php artisan contabilita:ricalcola-totali
# Chiudi gestione contabile
php artisan contabilita:chiudi {gestione_id}
🔧 INTEGRAZIONE CON SISTEMA ESISTENTE
📊 Adattamento EsercizioContabile → GestioneContabile
// File: database/migrations/2025_01_20_200000_migrate_esercizi_to_gestioni.php
use Illuminate\Database\Migrations\Migration;
use App\Models\EsercizioContabile;
use App\Models\GestioneContabile;
return new class extends Migration
{
public function up(): void
{
// Migra dati esistenti
EsercizioContabile::all()->each(function ($esercizio) {
GestioneContabile::create([
'condominio_id' => $esercizio->stabile_id,
'denominazione' => $esercizio->descrizione,
'data_inizio' => $esercizio->data_inizio,
'data_fine_prevista' => $esercizio->data_fine,
'stato' => $esercizio->stato === 'aperto' ? 'aperta' : 'chiusa_definitiva',
'data_assemblea_approvazione' => $esercizio->data_approvazione,
'created_by' => 1,
]);
});
}
};
Questo manuale fornisce tutto il necessario per implementare il sistema contabile condominiale basato su gestioni amministrative con controllo totale di ogni centesimo! 💎✨