📋 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
376 lines
14 KiB
PHP
376 lines
14 KiB
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 App\Models\TabellaMillesimale;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Carbon\Carbon;
|
|
|
|
/**
|
|
* Controller per la gestione contabile avanzata con partita doppia
|
|
*/
|
|
class ContabilitaAvanzataController extends Controller
|
|
{
|
|
/**
|
|
* Dashboard principale contabilità
|
|
*/
|
|
public function dashboard()
|
|
{
|
|
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
|
|
|
|
// Statistiche principali
|
|
$stats = $this->calcolaStatisticheDashboard($amministratore_id);
|
|
|
|
// Ultimi movimenti
|
|
$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();
|
|
|
|
// Gestioni attive
|
|
$gestioniAttive = GestioneContabile::with(['stabile', 'esercizioContabile'])
|
|
->whereHas('stabile', function($q) use ($amministratore_id) {
|
|
$q->where('amministratore_id', $amministratore_id);
|
|
})
|
|
->where('stato', 'attiva')
|
|
->get();
|
|
|
|
// Rate in scadenza
|
|
$rateInScadenza = RataCondominiale::with(['stabile', 'unitaImmobiliare', 'soggetto'])
|
|
->whereHas('stabile', function($q) use ($amministratore_id) {
|
|
$q->where('amministratore_id', $amministratore_id);
|
|
})
|
|
->where('stato_pagamento', '!=', 'pagata')
|
|
->where('data_scadenza', '<=', Carbon::now()->addDays(30))
|
|
->orderBy('data_scadenza')
|
|
->limit(15)
|
|
->get();
|
|
|
|
return view('admin.contabilita.dashboard', compact(
|
|
'stats',
|
|
'ultimiMovimenti',
|
|
'gestioniAttive',
|
|
'rateInScadenza'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Lista movimenti contabili
|
|
*/
|
|
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);
|
|
|
|
// Dati per i filtri
|
|
$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'));
|
|
}
|
|
|
|
/**
|
|
* Crea nuovo movimento
|
|
*/
|
|
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'));
|
|
}
|
|
|
|
/**
|
|
* Salva nuovo movimento
|
|
*/
|
|
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',
|
|
'tipo_documento' => 'nullable|string|max:50',
|
|
'numero_documento' => 'nullable|string|max:255',
|
|
'fornitore_id' => 'nullable|exists:fornitori,id',
|
|
'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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Conferma movimento
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Gestione delle rate
|
|
*/
|
|
public function rate(Request $request)
|
|
{
|
|
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
|
|
|
|
$query = RataCondominiale::with([
|
|
'stabile',
|
|
'gestioneContabile',
|
|
'unitaImmobiliare',
|
|
'soggetto'
|
|
])
|
|
->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->stato_pagamento) {
|
|
$query->where('stato_pagamento', $request->stato_pagamento);
|
|
}
|
|
|
|
if ($request->scadenza_da && $request->scadenza_a) {
|
|
$query->whereBetween('data_scadenza', [$request->scadenza_da, $request->scadenza_a]);
|
|
}
|
|
|
|
$rate = $query->orderBy('data_scadenza', 'desc')->paginate(25);
|
|
|
|
// Statistiche rate
|
|
$statsRate = [
|
|
'totale_dovuto' => $query->sum('importo_dovuto'),
|
|
'totale_incassato' => $query->sum('importo_pagato'),
|
|
'totale_residuo' => $query->sum('importo_residuo'),
|
|
'rate_insolute' => $query->where('stato_pagamento', 'insoluta')->count(),
|
|
];
|
|
|
|
$stabili = Stabile::where('amministratore_id', $amministratore_id)->get();
|
|
|
|
return view('admin.contabilita.rate.index', compact('rate', 'statsRate', 'stabili'));
|
|
}
|
|
|
|
/**
|
|
* Calcola statistiche per la dashboard
|
|
*/
|
|
private function calcolaStatisticheDashboard($amministratore_id)
|
|
{
|
|
$stabiliIds = Stabile::where('amministratore_id', $amministratore_id)->pluck('id');
|
|
|
|
$meseCorrente = Carbon::now()->startOfMonth();
|
|
$mesePrecedente = Carbon::now()->subMonth()->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'),
|
|
|
|
'rate_scadute' => RataCondominiale::whereIn('stabile_id', $stabiliIds)
|
|
->where('data_scadenza', '<', Carbon::now())
|
|
->where('stato_pagamento', '!=', 'pagata')
|
|
->count(),
|
|
|
|
'saldo_gestioni' => GestioneContabile::whereIn('stabile_id', $stabiliIds)
|
|
->where('stato', 'attiva')
|
|
->get()
|
|
->sum(function($gestione) {
|
|
return $gestione->calcolaSaldoContabile();
|
|
}),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ottiene l'esercizio contabile attivo per uno stabile
|
|
*/
|
|
private function getEsercizioAttivo($stabile_id)
|
|
{
|
|
$esercizio = EsercizioContabile::where('stabile_id', $stabile_id)
|
|
->where('stato', 'aperto')
|
|
->where('tipologia', 'ordinaria')
|
|
->first();
|
|
|
|
return $esercizio ? $esercizio->id : null;
|
|
}
|
|
|
|
/**
|
|
* API per ottenere gestioni di uno stabile
|
|
*/
|
|
public function getGestioniByStabile($stabile_id)
|
|
{
|
|
$gestioni = GestioneContabile::where('stabile_id', $stabile_id)
|
|
->where('stato', 'attiva')
|
|
->with('esercizioContabile')
|
|
->get();
|
|
|
|
return response()->json($gestioni);
|
|
}
|
|
|
|
/**
|
|
* API per verificare quadratura movimento
|
|
*/
|
|
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,
|
|
]);
|
|
}
|
|
}
|