netgescon-master/docs/08-IMPLEMENTAZIONE-SISTEMA-CONTABILE-PRATICO.md
Michele Windows e68ee85a18 🚀 CHECKPOINT STABILE - Sistema Contabile Avanzato
📋 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
2025-07-26 15:11:19 +02:00

1042 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔧 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**
```php
// ⚠️ 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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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**
```bash
# 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**
```php
// 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! 💎✨