📋 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
1248 lines
46 KiB
Markdown
1248 lines
46 KiB
Markdown
# 📋 SPECIFICHE COMPLETE SISTEMA CONTABILE NETGESCON
|
|
|
|
## 🗃️ **MIGRAZIONI DATABASE**
|
|
|
|
### **File: `database/migrations/2025_07_23_100000_create_sistema_contabile_completo.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
use Illuminate\Database\Migrations\Migration;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
return new class extends Migration
|
|
{
|
|
public function up(): void
|
|
{
|
|
// 1. PIANO DEI CONTI MASTERPLAN
|
|
if (!Schema::hasTable('piano_conti_masterplan')) {
|
|
Schema::create('piano_conti_masterplan', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('codice_conto', 10)->unique();
|
|
$table->string('descrizione_conto');
|
|
$table->enum('tipologia_conto', ['attivo', 'passivo', 'ricavo', 'costo', 'patrimoniale']);
|
|
$table->string('categoria_contabile', 50)->nullable();
|
|
$table->boolean('ripartibile')->default(true);
|
|
$table->json('default_ripartizioni')->nullable();
|
|
$table->boolean('attivo')->default(true);
|
|
$table->timestamps();
|
|
|
|
$table->index(['tipologia_conto', 'categoria_contabile']);
|
|
});
|
|
}
|
|
|
|
// 2. GESTIONI CONTABILI
|
|
if (!Schema::hasTable('gestioni_contabili')) {
|
|
Schema::create('gestioni_contabili', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->char('codice_gestione', 8)->unique();
|
|
$table->unsignedBigInteger('stabile_id');
|
|
$table->unsignedBigInteger('esercizio_contabile_id');
|
|
$table->string('denominazione');
|
|
$table->text('descrizione')->nullable();
|
|
$table->enum('tipologia', ['ordinaria', 'riscaldamento', 'straordinaria', 'fondo_lavori', 'fondo_riserva']);
|
|
$table->enum('stato', ['attiva', 'chiusa', 'sospesa'])->default('attiva');
|
|
$table->date('data_apertura');
|
|
$table->date('data_chiusura')->nullable();
|
|
$table->decimal('budget_previsto', 12, 2)->default(0);
|
|
$table->decimal('fondo_cassa_iniziale', 12, 2)->default(0);
|
|
$table->json('regole_ripartizione')->nullable();
|
|
$table->unsignedBigInteger('tabella_millesimale_id')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
|
|
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
|
|
$table->foreign('esercizio_contabile_id')->references('id')->on('esercizi_contabili')->onDelete('cascade');
|
|
$table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('set null');
|
|
|
|
$table->index(['stabile_id', 'tipologia', 'stato']);
|
|
$table->unique(['stabile_id', 'esercizio_contabile_id', 'tipologia'], 'unique_gestione_per_esercizio');
|
|
});
|
|
}
|
|
|
|
// 3. MOVIMENTI PARTITA DOPPIA
|
|
if (!Schema::hasTable('movimenti_partita_doppia')) {
|
|
Schema::create('movimenti_partita_doppia', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->char('codice_movimento', 12)->unique();
|
|
$table->unsignedBigInteger('stabile_id');
|
|
$table->unsignedBigInteger('gestione_contabile_id');
|
|
$table->unsignedBigInteger('esercizio_contabile_id');
|
|
|
|
$table->date('data_movimento');
|
|
$table->date('data_registrazione')->default(DB::raw('CURRENT_DATE'));
|
|
$table->string('descrizione');
|
|
$table->text('causale_dettagliata')->nullable();
|
|
$table->text('note_interne')->nullable();
|
|
|
|
$table->string('tipo_documento', 50)->nullable();
|
|
$table->string('numero_documento')->nullable();
|
|
$table->date('data_documento')->nullable();
|
|
$table->unsignedBigInteger('fornitore_id')->nullable();
|
|
$table->unsignedBigInteger('documento_id')->nullable();
|
|
|
|
$table->string('numero_protocollo', 20)->nullable();
|
|
$table->integer('progressivo_anno')->nullable();
|
|
|
|
$table->enum('stato_movimento', ['bozza', 'da_verificare', 'verificato', 'confermato', 'chiuso'])->default('bozza');
|
|
$table->enum('tipologia_registrazione', ['ordinaria', 'straordinaria', 'chiusura', 'apertura', 'rettifica'])->default('ordinaria');
|
|
|
|
$table->decimal('importo_lordo', 12, 2);
|
|
$table->decimal('importo_iva', 12, 2)->default(0);
|
|
$table->decimal('importo_ritenute', 12, 2)->default(0);
|
|
$table->decimal('importo_netto', 12, 2);
|
|
$table->json('dettagli_fiscali')->nullable();
|
|
|
|
$table->boolean('ripartito')->default(false);
|
|
$table->json('ripartizione_millesimale')->nullable();
|
|
$table->unsignedBigInteger('tabella_millesimale_utilizzata')->nullable();
|
|
|
|
$table->unsignedBigInteger('creato_da');
|
|
$table->unsignedBigInteger('verificato_da')->nullable();
|
|
$table->unsignedBigInteger('confermato_da')->nullable();
|
|
$table->timestamp('data_verifica')->nullable();
|
|
$table->timestamp('data_conferma')->nullable();
|
|
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
|
|
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
|
|
$table->foreign('gestione_contabile_id')->references('id')->on('gestioni_contabili')->onDelete('cascade');
|
|
$table->foreign('esercizio_contabile_id')->references('id')->on('esercizi_contabili')->onDelete('cascade');
|
|
$table->foreign('fornitore_id')->references('id')->on('fornitori')->onDelete('set null');
|
|
$table->foreign('tabella_millesimale_utilizzata')->references('id')->on('tabelle_millesimali')->onDelete('set null');
|
|
$table->foreign('creato_da')->references('id')->on('users')->onDelete('cascade');
|
|
$table->foreign('verificato_da')->references('id')->on('users')->onDelete('set null');
|
|
$table->foreign('confermato_da')->references('id')->on('users')->onDelete('set null');
|
|
|
|
$table->index(['stabile_id', 'data_movimento']);
|
|
$table->index(['gestione_contabile_id', 'stato_movimento']);
|
|
$table->index(['esercizio_contabile_id', 'tipologia_registrazione']);
|
|
$table->index(['numero_protocollo']);
|
|
$table->index(['progressivo_anno', 'stabile_id']);
|
|
});
|
|
}
|
|
|
|
// 4. RIGHE CONTABILI (DARE/AVERE)
|
|
if (!Schema::hasTable('righe_contabili')) {
|
|
Schema::create('righe_contabili', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->unsignedBigInteger('movimento_id');
|
|
$table->string('codice_conto', 10);
|
|
$table->string('descrizione_riga');
|
|
$table->enum('dare_avere', ['dare', 'avere']);
|
|
$table->decimal('importo', 12, 2);
|
|
$table->unsignedBigInteger('unita_immobiliare_id')->nullable();
|
|
$table->decimal('quota_millesimale', 10, 4)->nullable();
|
|
$table->text('note_riga')->nullable();
|
|
$table->timestamps();
|
|
|
|
$table->foreign('movimento_id')->references('id')->on('movimenti_partita_doppia')->onDelete('cascade');
|
|
$table->foreign('codice_conto')->references('codice_conto')->on('piano_conti_masterplan')->onDelete('cascade');
|
|
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('set null');
|
|
|
|
$table->index(['movimento_id', 'dare_avere']);
|
|
$table->index(['codice_conto']);
|
|
});
|
|
}
|
|
|
|
// 5. RATE CONDOMINIALI
|
|
if (!Schema::hasTable('rate_condominiali')) {
|
|
Schema::create('rate_condominiali', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->char('codice_rata', 12)->unique();
|
|
$table->unsignedBigInteger('stabile_id');
|
|
$table->unsignedBigInteger('gestione_contabile_id');
|
|
$table->unsignedBigInteger('unita_immobiliare_id');
|
|
$table->unsignedBigInteger('soggetto_id');
|
|
|
|
$table->string('tipo_rata', 50);
|
|
$table->date('data_scadenza');
|
|
$table->decimal('importo_dovuto', 10, 2);
|
|
$table->decimal('importo_pagato', 10, 2)->default(0);
|
|
$table->decimal('importo_residuo', 10, 2);
|
|
|
|
$table->enum('stato_pagamento', ['da_pagare', 'parzialmente_pagata', 'pagata', 'insoluta', 'stornata'])->default('da_pagare');
|
|
$table->date('data_primo_pagamento')->nullable();
|
|
$table->date('data_ultimo_pagamento')->nullable();
|
|
|
|
$table->decimal('millesimi_applicati', 10, 4);
|
|
$table->unsignedBigInteger('tabella_millesimale_id');
|
|
|
|
$table->decimal('interessi_mora', 10, 2)->default(0);
|
|
$table->date('data_decorrenza_mora')->nullable();
|
|
$table->decimal('percentuale_mora', 5, 2)->default(0);
|
|
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
|
|
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
|
|
$table->foreign('gestione_contabile_id')->references('id')->on('gestioni_contabili')->onDelete('cascade');
|
|
$table->foreign('unita_immobiliare_id')->references('id')->on('unita_immobiliari')->onDelete('cascade');
|
|
$table->foreign('soggetto_id')->references('id')->on('soggetti')->onDelete('cascade');
|
|
$table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('cascade');
|
|
|
|
$table->index(['stabile_id', 'data_scadenza']);
|
|
$table->index(['gestione_contabile_id', 'stato_pagamento']);
|
|
$table->index(['soggetto_id', 'stato_pagamento']);
|
|
});
|
|
}
|
|
|
|
// 6. PAGAMENTI RATE
|
|
if (!Schema::hasTable('pagamenti_rate')) {
|
|
Schema::create('pagamenti_rate', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->char('codice_pagamento', 12)->unique();
|
|
$table->unsignedBigInteger('rata_id');
|
|
$table->date('data_pagamento');
|
|
$table->decimal('importo_pagamento', 10, 2);
|
|
$table->string('modalita_pagamento', 50);
|
|
$table->string('riferimento_pagamento')->nullable();
|
|
$table->text('note_pagamento')->nullable();
|
|
$table->unsignedBigInteger('movimento_bancario_id')->nullable();
|
|
$table->timestamps();
|
|
|
|
$table->foreign('rata_id')->references('id')->on('rate_condominiali')->onDelete('cascade');
|
|
$table->index(['rata_id', 'data_pagamento']);
|
|
});
|
|
}
|
|
|
|
// 7. DOCUMENTI CONTABILI
|
|
if (!Schema::hasTable('documenti_contabili')) {
|
|
Schema::create('documenti_contabili', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->char('codice_documento', 12)->unique();
|
|
$table->unsignedBigInteger('stabile_id');
|
|
$table->unsignedBigInteger('movimento_id')->nullable();
|
|
|
|
$table->string('tipo_documento', 50);
|
|
$table->string('numero_documento');
|
|
$table->date('data_documento');
|
|
$table->string('oggetto');
|
|
$table->text('descrizione')->nullable();
|
|
|
|
$table->string('file_path')->nullable();
|
|
$table->string('file_originale')->nullable();
|
|
$table->string('mime_type')->nullable();
|
|
$table->bigInteger('file_size')->nullable();
|
|
|
|
$table->string('numero_protocollo')->nullable();
|
|
$table->date('data_protocollo')->nullable();
|
|
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
|
|
$table->foreign('stabile_id')->references('id')->on('stabili')->onDelete('cascade');
|
|
$table->foreign('movimento_id')->references('id')->on('movimenti_partita_doppia')->onDelete('set null');
|
|
|
|
$table->index(['stabile_id', 'tipo_documento']);
|
|
$table->index(['numero_protocollo']);
|
|
});
|
|
}
|
|
|
|
// 8. AUDIT CONTABILITA
|
|
if (!Schema::hasTable('audit_contabilita')) {
|
|
Schema::create('audit_contabilita', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('tabella_interessata');
|
|
$table->unsignedBigInteger('record_id');
|
|
$table->string('azione');
|
|
$table->json('dati_precedenti')->nullable();
|
|
$table->json('dati_nuovi')->nullable();
|
|
$table->unsignedBigInteger('user_id');
|
|
$table->string('ip_address')->nullable();
|
|
$table->string('user_agent')->nullable();
|
|
$table->timestamps();
|
|
|
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
|
$table->index(['tabella_interessata', 'record_id']);
|
|
$table->index(['user_id', 'created_at']);
|
|
});
|
|
}
|
|
}
|
|
|
|
public function down(): void
|
|
{
|
|
Schema::dropIfExists('audit_contabilita');
|
|
Schema::dropIfExists('documenti_contabili');
|
|
Schema::dropIfExists('pagamenti_rate');
|
|
Schema::dropIfExists('rate_condominiali');
|
|
Schema::dropIfExists('righe_contabili');
|
|
Schema::dropIfExists('movimenti_partita_doppia');
|
|
Schema::dropIfExists('gestioni_contabili');
|
|
Schema::dropIfExists('piano_conti_masterplan');
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ **MODELS DA CREARE**
|
|
|
|
### **File: `app/Models/GestioneContabile.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class GestioneContabile extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $table = 'gestioni_contabili';
|
|
|
|
protected $fillable = [
|
|
'codice_gestione', 'stabile_id', 'esercizio_contabile_id',
|
|
'denominazione', 'descrizione', 'tipologia', 'stato',
|
|
'data_apertura', 'data_chiusura', 'budget_previsto',
|
|
'fondo_cassa_iniziale', 'regole_ripartizione', 'tabella_millesimale_id'
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_apertura' => 'date',
|
|
'data_chiusura' => 'date',
|
|
'budget_previsto' => 'decimal:2',
|
|
'fondo_cassa_iniziale' => 'decimal:2',
|
|
'regole_ripartizione' => 'json'
|
|
];
|
|
|
|
protected static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::creating(function ($model) {
|
|
if (!$model->codice_gestione) {
|
|
$model->codice_gestione = static::generateCodiceGestione();
|
|
}
|
|
});
|
|
}
|
|
|
|
public static function generateCodiceGestione(): string
|
|
{
|
|
do {
|
|
$codice = 'GES' . sprintf('%05d', rand(10000, 99999));
|
|
} while (static::where('codice_gestione', $codice)->exists());
|
|
|
|
return $codice;
|
|
}
|
|
|
|
// Relazioni
|
|
public function stabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
public function esercizioContabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(EsercizioContabile::class, 'esercizio_contabile_id');
|
|
}
|
|
|
|
public function tabellaMillesimale(): BelongsTo
|
|
{
|
|
return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_id');
|
|
}
|
|
|
|
public function movimenti(): HasMany
|
|
{
|
|
return $this->hasMany(MovimentoPartitaDoppia::class, 'gestione_contabile_id');
|
|
}
|
|
|
|
public function rate(): HasMany
|
|
{
|
|
return $this->hasMany(RataCondominiale::class, 'gestione_contabile_id');
|
|
}
|
|
|
|
// Scopes
|
|
public function scopeAttive($query)
|
|
{
|
|
return $query->where('stato', 'attiva');
|
|
}
|
|
|
|
public function scopeByTipologia($query, $tipologia)
|
|
{
|
|
return $query->where('tipologia', $tipologia);
|
|
}
|
|
|
|
// Business Logic
|
|
public function calcolaSaldoContabile(): float
|
|
{
|
|
$entrate = $this->movimenti()
|
|
->whereHas('righeContabili', function($q) {
|
|
$q->where('dare_avere', 'avere');
|
|
})
|
|
->sum('importo_netto');
|
|
|
|
$uscite = $this->movimenti()
|
|
->whereHas('righeContabili', function($q) {
|
|
$q->where('dare_avere', 'dare');
|
|
})
|
|
->sum('importo_netto');
|
|
|
|
return $entrate - $uscite;
|
|
}
|
|
|
|
public function isChiudibile(): bool
|
|
{
|
|
$movimentiNonConfermati = $this->movimenti()
|
|
->whereIn('stato_movimento', ['bozza', 'da_verificare'])
|
|
->count();
|
|
|
|
return $movimentiNonConfermati === 0;
|
|
}
|
|
|
|
public function chiudiGestione(): bool
|
|
{
|
|
if (!$this->isChiudibile()) {
|
|
return false;
|
|
}
|
|
|
|
$this->update([
|
|
'stato' => 'chiusa',
|
|
'data_chiusura' => now(),
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
```
|
|
|
|
### **File: `app/Models/MovimentoPartitaDoppia.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class MovimentoPartitaDoppia extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $table = 'movimenti_partita_doppia';
|
|
|
|
protected $fillable = [
|
|
'codice_movimento', 'stabile_id', 'gestione_contabile_id', 'esercizio_contabile_id',
|
|
'data_movimento', 'data_registrazione', 'descrizione', 'causale_dettagliata',
|
|
'note_interne', 'tipo_documento', 'numero_documento', 'data_documento',
|
|
'fornitore_id', 'documento_id', 'numero_protocollo', 'progressivo_anno',
|
|
'stato_movimento', 'tipologia_registrazione', 'importo_lordo', 'importo_iva',
|
|
'importo_ritenute', 'importo_netto', 'dettagli_fiscali', 'ripartito',
|
|
'ripartizione_millesimale', 'tabella_millesimale_utilizzata',
|
|
'creato_da', 'verificato_da', 'confermato_da', 'data_verifica', 'data_conferma'
|
|
];
|
|
|
|
protected $casts = [
|
|
'data_movimento' => 'date',
|
|
'data_registrazione' => 'date',
|
|
'data_documento' => 'date',
|
|
'data_verifica' => 'datetime',
|
|
'data_conferma' => 'datetime',
|
|
'importo_lordo' => 'decimal:2',
|
|
'importo_iva' => 'decimal:2',
|
|
'importo_ritenute' => 'decimal:2',
|
|
'importo_netto' => 'decimal:2',
|
|
'dettagli_fiscali' => 'json',
|
|
'ripartito' => 'boolean',
|
|
'ripartizione_millesimale' => 'json'
|
|
];
|
|
|
|
protected static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::creating(function ($model) {
|
|
if (!$model->codice_movimento) {
|
|
$model->codice_movimento = static::generateCodiceMovimento();
|
|
}
|
|
|
|
if (!$model->progressivo_anno) {
|
|
$model->progressivo_anno = static::getNextProgressivo($model->stabile_id);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static function generateCodiceMovimento(): string
|
|
{
|
|
do {
|
|
$codice = 'MOV' . sprintf('%09d', rand(100000000, 999999999));
|
|
} while (static::where('codice_movimento', $codice)->exists());
|
|
|
|
return $codice;
|
|
}
|
|
|
|
public static function getNextProgressivo($stabile_id): int
|
|
{
|
|
$anno = date('Y');
|
|
$ultimo = static::where('stabile_id', $stabile_id)
|
|
->whereYear('data_registrazione', $anno)
|
|
->max('progressivo_anno');
|
|
|
|
return ($ultimo ?? 0) + 1;
|
|
}
|
|
|
|
// Relazioni
|
|
public function stabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Stabile::class);
|
|
}
|
|
|
|
public function gestioneContabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(GestioneContabile::class, 'gestione_contabile_id');
|
|
}
|
|
|
|
public function esercizioContabile(): BelongsTo
|
|
{
|
|
return $this->belongsTo(EsercizioContabile::class, 'esercizio_contabile_id');
|
|
}
|
|
|
|
public function fornitore(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Fornitore::class);
|
|
}
|
|
|
|
public function righeContabili(): HasMany
|
|
{
|
|
return $this->hasMany(RigaContabile::class, 'movimento_id');
|
|
}
|
|
|
|
public function creatoBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'creato_da');
|
|
}
|
|
|
|
public function verificatoBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'verificato_da');
|
|
}
|
|
|
|
public function confermatoBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'confermato_da');
|
|
}
|
|
|
|
// Scopes
|
|
public function scopeConfermati($query)
|
|
{
|
|
return $query->where('stato_movimento', 'confermato');
|
|
}
|
|
|
|
public function scopeByGestione($query, $gestione_id)
|
|
{
|
|
return $query->where('gestione_contabile_id', $gestione_id);
|
|
}
|
|
|
|
public function scopeByPeriodo($query, $data_inizio, $data_fine)
|
|
{
|
|
return $query->whereBetween('data_movimento', [$data_inizio, $data_fine]);
|
|
}
|
|
|
|
// Business Logic
|
|
public function verificaQuadratura(): bool
|
|
{
|
|
$totaleDare = $this->righeContabili()->where('dare_avere', 'dare')->sum('importo');
|
|
$totaleAvere = $this->righeContabili()->where('dare_avere', 'avere')->sum('importo');
|
|
|
|
return abs($totaleDare - $totaleAvere) < 0.01;
|
|
}
|
|
|
|
public function confermaMovimento($user_id): bool
|
|
{
|
|
if (!$this->verificaQuadratura()) {
|
|
return false;
|
|
}
|
|
|
|
$this->update([
|
|
'stato_movimento' => 'confermato',
|
|
'confermato_da' => $user_id,
|
|
'data_conferma' => now(),
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
public function creaRigheStandard($conto_dare, $conto_avere): void
|
|
{
|
|
// Riga in DARE
|
|
$this->righeContabili()->create([
|
|
'codice_conto' => $conto_dare,
|
|
'descrizione_riga' => $this->descrizione,
|
|
'dare_avere' => 'dare',
|
|
'importo' => $this->importo_netto,
|
|
]);
|
|
|
|
// Riga in AVERE
|
|
$this->righeContabili()->create([
|
|
'codice_conto' => $conto_avere,
|
|
'descrizione_riga' => $this->descrizione,
|
|
'dare_avere' => 'avere',
|
|
'importo' => $this->importo_netto,
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### **File: `app/Models/RigaContabile.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
class RigaContabile extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $table = 'righe_contabili';
|
|
|
|
protected $fillable = [
|
|
'movimento_id', 'codice_conto', 'descrizione_riga',
|
|
'dare_avere', 'importo', 'unita_immobiliare_id',
|
|
'quota_millesimale', 'note_riga'
|
|
];
|
|
|
|
protected $casts = [
|
|
'importo' => 'decimal:2',
|
|
'quota_millesimale' => 'decimal:4'
|
|
];
|
|
|
|
// Relazioni
|
|
public function movimento(): BelongsTo
|
|
{
|
|
return $this->belongsTo(MovimentoPartitaDoppia::class, 'movimento_id');
|
|
}
|
|
|
|
public function pianoConti(): BelongsTo
|
|
{
|
|
return $this->belongsTo(PianoContiMasterplan::class, 'codice_conto', 'codice_conto');
|
|
}
|
|
|
|
public function unitaImmobiliare(): BelongsTo
|
|
{
|
|
return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id');
|
|
}
|
|
|
|
// Scopes
|
|
public function scopeDare($query)
|
|
{
|
|
return $query->where('dare_avere', 'dare');
|
|
}
|
|
|
|
public function scopeAvere($query)
|
|
{
|
|
return $query->where('dare_avere', 'avere');
|
|
}
|
|
|
|
public function scopeByConto($query, $codice_conto)
|
|
{
|
|
return $query->where('codice_conto', $codice_conto);
|
|
}
|
|
}
|
|
```
|
|
|
|
### **File: `app/Models/PianoContiMasterplan.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class PianoContiMasterplan extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $table = 'piano_conti_masterplan';
|
|
|
|
protected $fillable = [
|
|
'codice_conto', 'descrizione_conto', 'tipologia_conto',
|
|
'categoria_contabile', 'ripartibile', 'default_ripartizioni', 'attivo'
|
|
];
|
|
|
|
protected $casts = [
|
|
'ripartibile' => 'boolean',
|
|
'attivo' => 'boolean',
|
|
'default_ripartizioni' => 'json'
|
|
];
|
|
|
|
// Relazioni
|
|
public function righeContabili(): HasMany
|
|
{
|
|
return $this->hasMany(RigaContabile::class, 'codice_conto', 'codice_conto');
|
|
}
|
|
|
|
// Scopes
|
|
public function scopeAttivi($query)
|
|
{
|
|
return $query->where('attivo', true);
|
|
}
|
|
|
|
public function scopeByTipologia($query, $tipologia)
|
|
{
|
|
return $query->where('tipologia_conto', $tipologia);
|
|
}
|
|
|
|
public function scopeByCategoria($query, $categoria)
|
|
{
|
|
return $query->where('categoria_contabile', $categoria);
|
|
}
|
|
|
|
public function scopeRipartibili($query)
|
|
{
|
|
return $query->where('ripartibile', true);
|
|
}
|
|
|
|
// Metodi helper
|
|
public static function getContiByCategoria($categoria)
|
|
{
|
|
return static::attivi()->byCategoria($categoria)->get();
|
|
}
|
|
|
|
public static function getContiCosti()
|
|
{
|
|
return static::attivi()->byTipologia('costo')->get();
|
|
}
|
|
|
|
public static function getContiRicavi()
|
|
{
|
|
return static::attivi()->byTipologia('ricavo')->get();
|
|
}
|
|
|
|
public static function getContiPatrimoniali()
|
|
{
|
|
return static::attivi()->whereIn('tipologia_conto', ['attivo', 'passivo', 'patrimoniale'])->get();
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎛️ **CONTROLLER DA CREARE**
|
|
|
|
### **File: `app/Http/Controllers/Admin/ContabilitaAvanzataController.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\GestioneContabile;
|
|
use App\Models\MovimentoPartitaDoppia;
|
|
use App\Models\RigaContabile;
|
|
use App\Models\PianoContiMasterplan;
|
|
use App\Models\RataCondominiale;
|
|
use App\Models\EsercizioContabile;
|
|
use App\Models\Stabile;
|
|
use App\Models\Fornitore;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Carbon\Carbon;
|
|
|
|
class ContabilitaAvanzataController extends Controller
|
|
{
|
|
public function dashboard()
|
|
{
|
|
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
|
|
|
|
$stats = $this->calcolaStatisticheDashboard($amministratore_id);
|
|
|
|
$ultimiMovimenti = MovimentoPartitaDoppia::with([
|
|
'stabile', 'gestioneContabile', 'fornitore', 'righeContabili.pianoConti'
|
|
])
|
|
->whereHas('stabile', function($q) use ($amministratore_id) {
|
|
$q->where('amministratore_id', $amministratore_id);
|
|
})
|
|
->orderBy('data_registrazione', 'desc')
|
|
->limit(10)
|
|
->get();
|
|
|
|
$gestioniAttive = GestioneContabile::with(['stabile', 'esercizioContabile'])
|
|
->whereHas('stabile', function($q) use ($amministratore_id) {
|
|
$q->where('amministratore_id', $amministratore_id);
|
|
})
|
|
->where('stato', 'attiva')
|
|
->get();
|
|
|
|
return view('admin.contabilita.dashboard', compact(
|
|
'stats', 'ultimiMovimenti', 'gestioniAttive'
|
|
));
|
|
}
|
|
|
|
public function movimenti(Request $request)
|
|
{
|
|
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
|
|
|
|
$query = MovimentoPartitaDoppia::with([
|
|
'stabile', 'gestioneContabile', 'fornitore', 'righeContabili.pianoConti'
|
|
])
|
|
->whereHas('stabile', function($q) use ($amministratore_id) {
|
|
$q->where('amministratore_id', $amministratore_id);
|
|
});
|
|
|
|
// Filtri
|
|
if ($request->stabile_id) {
|
|
$query->where('stabile_id', $request->stabile_id);
|
|
}
|
|
|
|
if ($request->gestione_id) {
|
|
$query->where('gestione_contabile_id', $request->gestione_id);
|
|
}
|
|
|
|
if ($request->stato) {
|
|
$query->where('stato_movimento', $request->stato);
|
|
}
|
|
|
|
if ($request->data_da && $request->data_a) {
|
|
$query->whereBetween('data_movimento', [$request->data_da, $request->data_a]);
|
|
}
|
|
|
|
$movimenti = $query->orderBy('data_registrazione', 'desc')->paginate(25);
|
|
|
|
$stabili = Stabile::where('amministratore_id', $amministratore_id)->get();
|
|
$gestioni = GestioneContabile::whereIn('stabile_id', $stabili->pluck('id'))->get();
|
|
|
|
return view('admin.contabilita.movimenti.index', compact('movimenti', 'stabili', 'gestioni'));
|
|
}
|
|
|
|
public function creaMovimento()
|
|
{
|
|
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
|
|
|
|
$stabili = Stabile::where('amministratore_id', $amministratore_id)->get();
|
|
$fornitori = Fornitore::where('amministratore_id', $amministratore_id)->get();
|
|
$pianoConti = PianoContiMasterplan::attivi()->get();
|
|
|
|
return view('admin.contabilita.movimenti.create', compact('stabili', 'fornitori', 'pianoConti'));
|
|
}
|
|
|
|
public function salvaMovimento(Request $request)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'stabile_id' => 'required|exists:stabili,id',
|
|
'gestione_contabile_id' => 'required|exists:gestioni_contabili,id',
|
|
'data_movimento' => 'required|date',
|
|
'descrizione' => 'required|string|max:255',
|
|
'importo_lordo' => 'required|numeric|min:0.01',
|
|
'importo_netto' => 'required|numeric|min:0.01',
|
|
'righe' => 'required|array|min:2',
|
|
'righe.*.codice_conto' => 'required|exists:piano_conti_masterplan,codice_conto',
|
|
'righe.*.dare_avere' => 'required|in:dare,avere',
|
|
'righe.*.importo' => 'required|numeric|min:0.01',
|
|
'righe.*.descrizione_riga' => 'required|string|max:255',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json(['errors' => $validator->errors()], 422);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
// Verifica quadratura dare/avere
|
|
$totaleDare = collect($request->righe)->where('dare_avere', 'dare')->sum('importo');
|
|
$totaleAvere = collect($request->righe)->where('dare_avere', 'avere')->sum('importo');
|
|
|
|
if (abs($totaleDare - $totaleAvere) > 0.01) {
|
|
return response()->json([
|
|
'error' => 'Le righe contabili non sono in quadratura. Dare: ' . $totaleDare . ', Avere: ' . $totaleAvere
|
|
], 422);
|
|
}
|
|
|
|
// Crea movimento
|
|
$movimento = MovimentoPartitaDoppia::create([
|
|
'stabile_id' => $request->stabile_id,
|
|
'gestione_contabile_id' => $request->gestione_contabile_id,
|
|
'esercizio_contabile_id' => $this->getEsercizioAttivo($request->stabile_id),
|
|
'data_movimento' => $request->data_movimento,
|
|
'descrizione' => $request->descrizione,
|
|
'causale_dettagliata' => $request->causale_dettagliata,
|
|
'note_interne' => $request->note_interne,
|
|
'tipo_documento' => $request->tipo_documento,
|
|
'numero_documento' => $request->numero_documento,
|
|
'data_documento' => $request->data_documento,
|
|
'fornitore_id' => $request->fornitore_id,
|
|
'importo_lordo' => $request->importo_lordo,
|
|
'importo_iva' => $request->importo_iva ?? 0,
|
|
'importo_ritenute' => $request->importo_ritenute ?? 0,
|
|
'importo_netto' => $request->importo_netto,
|
|
'creato_da' => Auth::id(),
|
|
'stato_movimento' => 'bozza',
|
|
]);
|
|
|
|
// Crea righe contabili
|
|
foreach ($request->righe as $riga) {
|
|
RigaContabile::create([
|
|
'movimento_id' => $movimento->id,
|
|
'codice_conto' => $riga['codice_conto'],
|
|
'descrizione_riga' => $riga['descrizione_riga'],
|
|
'dare_avere' => $riga['dare_avere'],
|
|
'importo' => $riga['importo'],
|
|
'note_riga' => $riga['note_riga'] ?? null,
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Movimento contabile creato con successo',
|
|
'movimento_id' => $movimento->id
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
DB::rollback();
|
|
return response()->json(['error' => 'Errore nel salvataggio: ' . $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
public function confermaMovimento($id)
|
|
{
|
|
$movimento = MovimentoPartitaDoppia::findOrFail($id);
|
|
|
|
if (!$movimento->verificaQuadratura()) {
|
|
return response()->json(['error' => 'Il movimento non è in quadratura'], 422);
|
|
}
|
|
|
|
if ($movimento->confermaMovimento(Auth::id())) {
|
|
return response()->json(['success' => true, 'message' => 'Movimento confermato']);
|
|
}
|
|
|
|
return response()->json(['error' => 'Errore nella conferma'], 500);
|
|
}
|
|
|
|
private function calcolaStatisticheDashboard($amministratore_id)
|
|
{
|
|
$stabiliIds = Stabile::where('amministratore_id', $amministratore_id)->pluck('id');
|
|
|
|
$meseCorrente = Carbon::now()->startOfMonth();
|
|
|
|
return [
|
|
'movimenti_mese' => MovimentoPartitaDoppia::whereIn('stabile_id', $stabiliIds)
|
|
->where('data_registrazione', '>=', $meseCorrente)
|
|
->count(),
|
|
|
|
'entrate_mese' => MovimentoPartitaDoppia::whereIn('stabile_id', $stabiliIds)
|
|
->where('data_registrazione', '>=', $meseCorrente)
|
|
->whereHas('righeContabili', function($q) {
|
|
$q->where('dare_avere', 'avere')
|
|
->whereHas('pianoConti', function($sq) {
|
|
$sq->where('tipologia_conto', 'ricavo');
|
|
});
|
|
})
|
|
->sum('importo_netto'),
|
|
|
|
'uscite_mese' => MovimentoPartitaDoppia::whereIn('stabile_id', $stabiliIds)
|
|
->where('data_registrazione', '>=', $meseCorrente)
|
|
->whereHas('righeContabili', function($q) {
|
|
$q->where('dare_avere', 'dare')
|
|
->whereHas('pianoConti', function($sq) {
|
|
$sq->where('tipologia_conto', 'costo');
|
|
});
|
|
})
|
|
->sum('importo_netto'),
|
|
|
|
'saldo_gestioni' => GestioneContabile::whereIn('stabile_id', $stabiliIds)
|
|
->where('stato', 'attiva')
|
|
->get()
|
|
->sum(function($gestione) {
|
|
return $gestione->calcolaSaldoContabile();
|
|
}),
|
|
];
|
|
}
|
|
|
|
private function getEsercizioAttivo($stabile_id)
|
|
{
|
|
$esercizio = EsercizioContabile::where('stabile_id', $stabile_id)
|
|
->where('stato', 'aperto')
|
|
->where('tipologia', 'ordinaria')
|
|
->first();
|
|
|
|
return $esercizio ? $esercizio->id : null;
|
|
}
|
|
|
|
public function getGestioniByStabile($stabile_id)
|
|
{
|
|
$gestioni = GestioneContabile::where('stabile_id', $stabile_id)
|
|
->where('stato', 'attiva')
|
|
->with('esercizioContabile')
|
|
->get();
|
|
|
|
return response()->json($gestioni);
|
|
}
|
|
|
|
public function verificaQuadratura(Request $request)
|
|
{
|
|
$righe = $request->righe ?? [];
|
|
|
|
$totaleDare = collect($righe)->where('dare_avere', 'dare')->sum('importo');
|
|
$totaleAvere = collect($righe)->where('dare_avere', 'avere')->sum('importo');
|
|
$differenza = abs($totaleDare - $totaleAvere);
|
|
|
|
return response()->json([
|
|
'in_quadratura' => $differenza < 0.01,
|
|
'totale_dare' => $totaleDare,
|
|
'totale_avere' => $totaleAvere,
|
|
'differenza' => $differenza,
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🌱 **SEEDER DA CREARE**
|
|
|
|
### **File: `database/seeders/PianoContiSeeder.php`**
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use App\Models\PianoContiMasterplan;
|
|
|
|
class PianoContiSeeder extends Seeder
|
|
{
|
|
public function run(): void
|
|
{
|
|
$conti = [
|
|
// CONTI PATRIMONIALI - ATTIVO
|
|
[
|
|
'codice_conto' => '1001',
|
|
'descrizione_conto' => 'Cassa',
|
|
'tipologia_conto' => 'attivo',
|
|
'categoria_contabile' => 'liquidita',
|
|
'ripartibile' => false,
|
|
],
|
|
[
|
|
'codice_conto' => '1002',
|
|
'descrizione_conto' => 'Banca c/c ordinario',
|
|
'tipologia_conto' => 'attivo',
|
|
'categoria_contabile' => 'liquidita',
|
|
'ripartibile' => false,
|
|
],
|
|
[
|
|
'codice_conto' => '1201',
|
|
'descrizione_conto' => 'Crediti vs condòmini per rate',
|
|
'tipologia_conto' => 'attivo',
|
|
'categoria_contabile' => 'crediti',
|
|
'ripartibile' => false,
|
|
],
|
|
|
|
// CONTI PATRIMONIALI - PASSIVO
|
|
[
|
|
'codice_conto' => '2001',
|
|
'descrizione_conto' => 'Debiti vs fornitori',
|
|
'tipologia_conto' => 'passivo',
|
|
'categoria_contabile' => 'debiti',
|
|
'ripartibile' => false,
|
|
],
|
|
[
|
|
'codice_conto' => '2101',
|
|
'descrizione_conto' => 'Fondo di riserva',
|
|
'tipologia_conto' => 'passivo',
|
|
'categoria_contabile' => 'fondi',
|
|
'ripartibile' => false,
|
|
],
|
|
|
|
// CONTI ECONOMICI - RICAVI
|
|
[
|
|
'codice_conto' => '5001',
|
|
'descrizione_conto' => 'Quote ordinarie',
|
|
'tipologia_conto' => 'ricavo',
|
|
'categoria_contabile' => 'quote_condominiali',
|
|
'ripartibile' => false,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '5002',
|
|
'descrizione_conto' => 'Quote straordinarie',
|
|
'tipologia_conto' => 'ricavo',
|
|
'categoria_contabile' => 'quote_condominiali',
|
|
'ripartibile' => false,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
|
|
// CONTI ECONOMICI - COSTI AMMINISTRAZIONE
|
|
[
|
|
'codice_conto' => '6001',
|
|
'descrizione_conto' => 'Compenso amministratore',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'amministrazione',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '6002',
|
|
'descrizione_conto' => 'Spese postali e telefoniche',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'amministrazione',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
|
|
// CONTI ECONOMICI - PULIZIA E IGIENE
|
|
[
|
|
'codice_conto' => '6101',
|
|
'descrizione_conto' => 'Pulizia scale e parti comuni',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'pulizia',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['scale' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '6102',
|
|
'descrizione_conto' => 'Materiali di pulizia',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'pulizia',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['scale' => 100]),
|
|
],
|
|
|
|
// CONTI ECONOMICI - MANUTENZIONE
|
|
[
|
|
'codice_conto' => '6201',
|
|
'descrizione_conto' => 'Manutenzione ordinaria ascensore',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'manutenzione',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['ascensore' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '6202',
|
|
'descrizione_conto' => 'Manutenzione impianto elettrico',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'manutenzione',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
|
|
// CONTI ECONOMICI - UTENZE
|
|
[
|
|
'codice_conto' => '6301',
|
|
'descrizione_conto' => 'Energia elettrica',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'utenze',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '6302',
|
|
'descrizione_conto' => 'Gas',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'utenze',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['riscaldamento' => 100]),
|
|
],
|
|
[
|
|
'codice_conto' => '6303',
|
|
'descrizione_conto' => 'Acqua',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'utenze',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
|
|
// CONTI ECONOMICI - ASSICURAZIONI
|
|
[
|
|
'codice_conto' => '6501',
|
|
'descrizione_conto' => 'Assicurazione globale fabbricati',
|
|
'tipologia_conto' => 'costo',
|
|
'categoria_contabile' => 'assicurazioni',
|
|
'ripartibile' => true,
|
|
'default_ripartizioni' => json_encode(['generale' => 100]),
|
|
],
|
|
];
|
|
|
|
foreach ($conti as $conto) {
|
|
PianoContiMasterplan::updateOrCreate(
|
|
['codice_conto' => $conto['codice_conto']],
|
|
$conto
|
|
);
|
|
}
|
|
|
|
$this->command->info('Piano dei conti popolato con ' . count($conti) . ' voci');
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛤️ **ROUTES DA AGGIUNGERE**
|
|
|
|
### **File: `routes/admin.php` (aggiungere alla fine)**
|
|
|
|
```php
|
|
// === CONTABILITÀ AVANZATA ===
|
|
Route::prefix('contabilita-avanzata')->name('contabilita_avanzata.')->group(function () {
|
|
Route::get('/', [ContabilitaAvanzataController::class, 'dashboard'])->name('dashboard');
|
|
|
|
// Movimenti
|
|
Route::get('/movimenti', [ContabilitaAvanzataController::class, 'movimenti'])->name('movimenti.index');
|
|
Route::get('/movimenti/create', [ContabilitaAvanzataController::class, 'creaMovimento'])->name('movimenti.create');
|
|
Route::post('/movimenti', [ContabilitaAvanzataController::class, 'salvaMovimento'])->name('movimenti.store');
|
|
Route::post('/movimenti/{id}/conferma', [ContabilitaAvanzataController::class, 'confermaMovimento'])->name('movimenti.conferma');
|
|
|
|
// API
|
|
Route::get('/api/gestioni/{stabile_id}', [ContabilitaAvanzataController::class, 'getGestioniByStabile'])->name('api.gestioni');
|
|
Route::post('/api/verifica-quadratura', [ContabilitaAvanzataController::class, 'verificaQuadratura'])->name('api.quadratura');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📜 **COMANDI ARTISAN DA ESEGUIRE**
|
|
|
|
```bash
|
|
# 1. Eseguire la migrazione
|
|
php artisan migrate --path=database/migrations/2025_07_23_100000_create_sistema_contabile_completo.php
|
|
|
|
# 2. Eseguire il seeder
|
|
php artisan db:seed --class=PianoContiSeeder
|
|
|
|
# 3. Clearing cache
|
|
php artisan config:clear
|
|
php artisan cache:clear
|
|
php artisan route:clear
|
|
|
|
# 4. Ottimizzazioni
|
|
php artisan config:cache
|
|
php artisan route:cache
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 **COSA PREFERISCI?**
|
|
|
|
**OPZIONE A:** Creo l'utente Gitea per me così gestisco tutto direttamente ✅ **CONSIGLIATO**
|
|
|
|
**OPZIONE B:** Tu segui queste specifiche manualmente step-by-step
|
|
|
|
Fammi sapere quale opzione preferisci! Se scegli l'OPZIONE A ti guido nella creazione dell'utente Gitea, se scegli l'OPZIONE B possiamo procedere step-by-step con l'implementazione manuale.
|
|
|
|
La **partita doppia** è già pronta con quadrature automatiche! 💎
|