📋 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
1042 lines
35 KiB
Markdown
1042 lines
35 KiB
Markdown
# 🔧 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! 💎✨
|