netgescon-master/netgescon-laravel/app/Http/Controllers/Admin/RiconciliazioniController.php

370 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\{
Stabile,
MovimentoBancario,
TransazioneContabile,
RiconciliazioneBancaria,
ContoCorrente
};
/**
* ========================================
* CONTROLLER RICONCILIAZIONE BANCARIA
* Sistema avanzato matching automatico/manuale
* ========================================
*/
class RiconciliazioniController extends Controller
{
/**
* Dashboard riconciliazioni
*/
public function index()
{
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
// Statistiche
$stats = [
'movimenti_non_riconciliati' => MovimentoBancario::whereHas('contoCorrente.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('riconciliato', false)->count(),
'differenze_trovate' => RiconciliazioneBancaria::whereHas('contoCorrente.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('stato', 'con_differenze')->count(),
'importo_non_riconciliato' => MovimentoBancario::whereHas('contoCorrente.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->where('riconciliato', false)->sum('importo'),
];
// Conti correnti attivi
$contiCorrenti = ContoCorrente::whereHas('stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->with('stabile')->get();
// Ultime riconciliazioni
$ultimeRiconciliazioni = RiconciliazioneBancaria::whereHas('contoCorrente.stabile', function($q) use ($amministratore_id) {
$q->where('amministratore_id', $amministratore_id);
})->with(['contoCorrente.stabile', 'utente'])
->orderBy('data_riconciliazione', 'desc')
->take(10)
->get();
return view('admin.riconciliazioni.index', compact(
'stats', 'contiCorrenti', 'ultimeRiconciliazioni'
));
}
/**
* Avvia processo di riconciliazione per un conto
*/
public function create(Request $request)
{
$request->validate([
'conto_corrente_id' => 'required|exists:conti_correnti,id',
'data_da' => 'required|date',
'data_a' => 'required|date|after_or_equal:data_da'
]);
$conto = ContoCorrente::with('stabile')->find($request->conto_corrente_id);
// Verifica autorizzazioni
$amministratore_id = Auth::user()->amministratore->id_amministratore ?? null;
if ($conto->stabile->amministratore_id !== $amministratore_id) {
abort(403);
}
// Movimenti bancari non riconciliati nel periodo
$movimentiBancari = MovimentoBancario::where('conto_corrente_id', $conto->id)
->whereBetween('data_valuta', [$request->data_da, $request->data_a])
->where('riconciliato', false)
->orderBy('data_valuta')
->get();
// Transazioni contabili nel periodo
$transazioniContabili = TransazioneContabile::where('stabile_id', $conto->stabile_id)
->whereBetween('data_registrazione', [$request->data_da, $request->data_a])
->where('riconciliata', false)
->whereHas('righe', function($q) {
$q->whereHas('conto', function($q2) {
$q2->where('tipo_conto', 'banca');
});
})
->with(['righe.conto'])
->orderBy('data_registrazione')
->get();
// Matching automatico preliminare
$matchingSuggeriti = $this->eseguiMatchingAutomatico($movimentiBancari, $transazioniContabili);
return view('admin.riconciliazioni.create', compact(
'conto', 'movimentiBancari', 'transazioniContabili', 'matchingSuggeriti'
));
}
/**
* Salva riconciliazione
*/
public function store(Request $request)
{
$request->validate([
'conto_corrente_id' => 'required|exists:conti_correnti,id',
'data_da' => 'required|date',
'data_a' => 'required|date',
'saldo_iniziale' => 'required|numeric',
'saldo_finale' => 'required|numeric',
'matching' => 'required|array',
'matching.*.movimento_bancario_id' => 'required|exists:movimenti_bancari,id',
'matching.*.transazione_contabile_id' => 'nullable|exists:transazioni_contabili,id',
'matching.*.tipo_matching' => 'required|in:automatico,manuale,non_trovato',
'differenze_non_giustificate' => 'nullable|array'
]);
DB::beginTransaction();
try {
$conto = ContoCorrente::find($request->conto_corrente_id);
// Crea record riconciliazione
$riconciliazione = RiconciliazioneBancaria::create([
'conto_corrente_id' => $request->conto_corrente_id,
'data_riconciliazione' => now(),
'periodo_da' => $request->data_da,
'periodo_a' => $request->data_a,
'saldo_iniziale_bancario' => $request->saldo_iniziale,
'saldo_finale_bancario' => $request->saldo_finale,
'saldo_iniziale_contabile' => $this->calcolaSaldoContabile($conto, $request->data_da, true),
'saldo_finale_contabile' => $this->calcolaSaldoContabile($conto, $request->data_a, false),
'stato' => 'in_progress',
'utente_id' => Auth::id()
]);
$movimentiRiconciliati = 0;
$importoRiconciliato = 0;
$differenzeNonGiustificate = [];
// Processa ogni matching
foreach ($request->matching as $match) {
$movimentoBancario = MovimentoBancario::find($match['movimento_bancario_id']);
if ($match['tipo_matching'] === 'automatico' || $match['tipo_matching'] === 'manuale') {
if (isset($match['transazione_contabile_id'])) {
$transazione = TransazioneContabile::find($match['transazione_contabile_id']);
// Marca come riconciliati
$movimentoBancario->update([
'riconciliato' => true,
'transazione_contabile_id' => $transazione->id,
'riconciliazione_id' => $riconciliazione->id,
'tipo_matching' => $match['tipo_matching']
]);
$transazione->update(['riconciliata' => true]);
$movimentiRiconciliati++;
$importoRiconciliato += abs($movimentoBancario->importo);
}
} else {
// Movimento non trovato - possibile differenza
$differenzeNonGiustificate[] = [
'movimento_bancario_id' => $movimentoBancario->id,
'importo' => $movimentoBancario->importo,
'descrizione' => $movimentoBancario->descrizione,
'data' => $movimentoBancario->data_valuta
];
}
}
// Calcola stato finale
$stato = 'completata';
if (count($differenzeNonGiustificate) > 0) {
$stato = 'con_differenze';
}
$differenzaSaldi = abs($riconciliazione->saldo_finale_bancario - $riconciliazione->saldo_finale_contabile);
if ($differenzaSaldi > 0.01) {
$stato = 'con_differenze';
}
// Aggiorna riconciliazione
$riconciliazione->update([
'movimenti_riconciliati' => $movimentiRiconciliati,
'importo_riconciliato' => $importoRiconciliato,
'differenze_non_giustificate' => json_encode($differenzeNonGiustificate),
'differenza_saldi' => $differenzaSaldi,
'stato' => $stato,
'note_riconciliazione' => $request->note ?? null
]);
DB::commit();
$message = "Riconciliazione completata. Movimenti riconciliati: {$movimentiRiconciliati}";
if ($stato === 'con_differenze') {
$message .= " - ATTENZIONE: Sono presenti differenze da verificare.";
}
return redirect()
->route('admin.riconciliazioni.show', $riconciliazione)
->with('success', $message);
} catch (\Exception $e) {
DB::rollback();
return back()
->withInput()
->withErrors(['error' => 'Errore durante la riconciliazione: ' . $e->getMessage()]);
}
}
/**
* Mostra dettagli riconciliazione
*/
public function show(RiconciliazioneBancaria $riconciliazione)
{
$riconciliazione->load([
'contoCorrente.stabile',
'movimentiBancari.transazioneContabile',
'utente'
]);
$differenzeNonGiustificate = json_decode($riconciliazione->differenze_non_giustificate, true) ?? [];
return view('admin.riconciliazioni.show', compact(
'riconciliazione', 'differenzeNonGiustificate'
));
}
/**
* API: Matching automatico in tempo reale
*/
public function matchingAutomatico(Request $request)
{
$request->validate([
'movimento_bancario_id' => 'required|exists:movimenti_bancari,id',
'conto_corrente_id' => 'required|exists:conti_correnti,id',
'data_da' => 'required|date',
'data_a' => 'required|date'
]);
$movimentoBancario = MovimentoBancario::find($request->movimento_bancario_id);
// Criteri di matching automatico
$transazioniCandidati = TransazioneContabile::whereHas('stabile.contiCorrenti', function($q) use ($request) {
$q->where('id', $request->conto_corrente_id);
})
->whereBetween('data_registrazione', [$request->data_da, $request->data_a])
->where('riconciliata', false)
->where(function($q) use ($movimentoBancario) {
// Matching per importo esatto
$q->where('importo_totale', abs($movimentoBancario->importo))
// Matching per numero documento
->orWhere('numero_documento', 'LIKE', '%' . $movimentoBancario->numero_operazione . '%')
// Matching per descrizione
->orWhere('descrizione', 'LIKE', '%' . substr($movimentoBancario->descrizione, 0, 20) . '%');
})
->with(['fornitore', 'righe.conto'])
->get();
// Calcola score di matching
$candidatiConScore = $transazioniCandidati->map(function($transazione) use ($movimentoBancario) {
$score = 0;
// Score per importo
if (abs($transazione->importo_totale - abs($movimentoBancario->importo)) < 0.01) {
$score += 50;
} elseif (abs($transazione->importo_totale - abs($movimentoBancario->importo)) < 10) {
$score += 20;
}
// Score per data
$diffGiorni = abs($transazione->data_registrazione->diffInDays($movimentoBancario->data_valuta));
if ($diffGiorni <= 1) {
$score += 30;
} elseif ($diffGiorni <= 7) {
$score += 15;
}
// Score per descrizione
if (stripos($movimentoBancario->descrizione, $transazione->numero_documento) !== false) {
$score += 25;
}
if ($transazione->fornitore && stripos($movimentoBancario->descrizione, $transazione->fornitore->ragione_sociale) !== false) {
$score += 20;
}
$transazione->matching_score = $score;
return $transazione;
})->sortByDesc('matching_score');
return response()->json([
'candidati' => $candidatiConScore->take(5)->values(),
'movimento_bancario' => $movimentoBancario
]);
}
/**
* Esegue matching automatico preliminare
*/
private function eseguiMatchingAutomatico($movimentiBancari, $transazioniContabili)
{
$matching = [];
foreach ($movimentiBancari as $movimento) {
$miglioreMatch = null;
$migliorScore = 0;
foreach ($transazioniContabili as $transazione) {
$score = 0;
// Matching per importo esatto
if (abs($transazione->importo_totale - abs($movimento->importo)) < 0.01) {
$score += 60;
}
// Matching per data (più vicine = score più alto)
$diffGiorni = abs($transazione->data_registrazione->diffInDays($movimento->data_valuta));
if ($diffGiorni <= 1) {
$score += 30;
} elseif ($diffGiorni <= 3) {
$score += 15;
}
// Matching per numero documento
if ($transazione->numero_documento && stripos($movimento->descrizione, $transazione->numero_documento) !== false) {
$score += 20;
}
if ($score > $migliorScore && $score >= 70) { // Soglia minima per matching automatico
$migliorScore = $score;
$miglioreMatch = $transazione;
}
}
$matching[] = [
'movimento_bancario' => $movimento,
'transazione_suggerita' => $miglioreMatch,
'score' => $migliorScore,
'tipo_suggerimento' => $migliorScore >= 80 ? 'automatico' : 'manuale'
];
}
return $matching;
}
/**
* Calcola saldo contabile alla data
*/
private function calcolaSaldoContabile($conto, $data, $iniziale = false)
{
// Implementazione calcolo saldo contabile dal piano dei conti
// Basato sulle transazioni registrate fino alla data specificata
return 0; // Placeholder
}
}