whereHas('ripartizioneSpese.voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }); // Filtro per stabile if ($request->filled('stabile_id')) { $query->whereHas('ripartizioneSpese.voceSpesa', function($q) use ($request) { $q->where('stabile_id', $request->stabile_id); }); } // Filtro per unità immobiliare if ($request->filled('unita_immobiliare_id')) { $query->where('unita_immobiliare_id', $request->unita_immobiliare_id); } // Filtro per stato if ($request->filled('stato')) { $query->where('stato', $request->stato); } // Filtro per data if ($request->filled('data_da')) { $query->where('data_inizio', '>=', $request->data_da); } if ($request->filled('data_a')) { $query->where('data_inizio', '<=', $request->data_a); } $pianiRateizzazione = $query->orderBy('data_inizio', 'desc')->paginate(15); // Dati per i filtri $stabili = \App\Models\Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null) ->orderBy('denominazione') ->get(); return view('admin.piani-rateizzazione.index', compact('pianiRateizzazione', 'stabili')); } /** * Show the form for creating a new resource. */ public function create(Request $request) { $ripartizioneSpesa = null; $dettaglioRipartizione = null; // Se arriva da una ripartizione specifica if ($request->filled('ripartizione_spesa_id')) { $ripartizioneSpesa = RipartizioneSpese::with(['voceSpesa.stabile', 'dettagli.unitaImmobiliare']) ->whereHas('voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->findOrFail($request->ripartizione_spesa_id); } // Se arriva da un dettaglio specifico if ($request->filled('dettaglio_ripartizione_id')) { $dettaglioRipartizione = DettaglioRipartizioneSpese::with(['ripartizioneSpese.voceSpesa.stabile', 'unitaImmobiliare']) ->whereHas('ripartizioneSpese.voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->findOrFail($request->dettaglio_ripartizione_id); $ripartizioneSpesa = $dettaglioRipartizione->ripartizioneSpese; } // Ripartizioni disponibili $ripartizioni = RipartizioneSpese::with(['voceSpesa.stabile']) ->whereHas('voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->where('stato', 'confermata') ->orderBy('data_ripartizione', 'desc') ->get(); return view('admin.piani-rateizzazione.create', compact('ripartizioni', 'ripartizioneSpesa', 'dettaglioRipartizione')); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $request->validate([ 'ripartizione_spese_id' => 'required|exists:ripartizioni_spese,id', 'unita_immobiliare_id' => 'required|exists:unita_immobiliari,id', 'denominazione' => 'required|string|max:255', 'importo_totale' => 'required|numeric|min:0', 'numero_rate' => 'required|integer|min:1|max:60', 'data_inizio' => 'required|date', 'frequenza' => 'required|in:mensile,bimestrale,trimestrale,semestrale', 'importo_prima_rata' => 'nullable|numeric|min:0', 'note' => 'nullable|string', 'applica_interessi' => 'nullable|boolean', 'tasso_interesse' => 'nullable|numeric|min:0|max:100', ]); // Verifica che la ripartizione appartenga all'amministratore $ripartizioneSpesa = RipartizioneSpese::whereHas('voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->where('stato', 'confermata') ->findOrFail($request->ripartizione_spese_id); // Verifica che l'unità immobiliare appartenga al stabile della ripartizione $unitaImmobiliare = UnitaImmobiliare::where('id', $request->unita_immobiliare_id) ->where('stabile_id', $ripartizioneSpesa->voceSpesa->stabile_id) ->firstOrFail(); // Verifica che l'unità abbia un dettaglio nella ripartizione $dettaglioRipartizione = DettaglioRipartizioneSpese::where('ripartizione_spese_id', $request->ripartizione_spese_id) ->where('unita_immobiliare_id', $request->unita_immobiliare_id) ->firstOrFail(); // Verifica che l'importo totale non superi l'importo del dettaglio if ($request->importo_totale > $dettaglioRipartizione->importo) { return redirect()->back() ->withInput() ->with('error', 'L\'importo totale del piano non può superare l\'importo della ripartizione (' . number_format($dettaglioRipartizione->importo, 2) . ' €).'); } DB::beginTransaction(); try { // Crea il piano di rateizzazione $pianoRateizzazione = PianoRateizzazione::create([ 'codice_piano' => $this->generateCodicePiano(), 'ripartizione_spese_id' => $request->ripartizione_spese_id, 'unita_immobiliare_id' => $request->unita_immobiliare_id, 'denominazione' => $request->denominazione, 'importo_totale' => $request->importo_totale, 'numero_rate' => $request->numero_rate, 'data_inizio' => $request->data_inizio, 'frequenza' => $request->frequenza, 'importo_prima_rata' => $request->importo_prima_rata, 'note' => $request->note, 'applica_interessi' => $request->boolean('applica_interessi'), 'tasso_interesse' => $request->tasso_interesse, 'stato' => 'bozza', 'created_by' => Auth::id(), ]); // Calcola e crea le rate $this->calcolaRate($pianoRateizzazione); DB::commit(); return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('success', 'Piano di rateizzazione creato con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back() ->withInput() ->with('error', 'Errore durante la creazione del piano: ' . $e->getMessage()); } } /** * Display the specified resource. */ public function show(PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('view', $pianoRateizzazione); $pianoRateizzazione->load([ 'ripartizioneSpese.voceSpesa.stabile', 'unitaImmobiliare.anagraficaCondominiale.soggetto', 'rate' => function($query) { $query->orderBy('numero_rata'); }, 'createdBy', 'updatedBy' ]); return view('admin.piani-rateizzazione.show', compact('pianoRateizzazione')); } /** * Show the form for editing the specified resource. */ public function edit(PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('update', $pianoRateizzazione); // Solo i piani in bozza possono essere modificati if ($pianoRateizzazione->stato !== 'bozza') { return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('error', 'Impossibile modificare un piano già attivato.'); } $pianoRateizzazione->load([ 'ripartizioneSpese.voceSpesa.stabile', 'unitaImmobiliare', 'rate' => function($query) { $query->orderBy('numero_rata'); } ]); return view('admin.piani-rateizzazione.edit', compact('pianoRateizzazione')); } /** * Update the specified resource in storage. */ public function update(Request $request, PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('update', $pianoRateizzazione); // Solo i piani in bozza possono essere modificati if ($pianoRateizzazione->stato !== 'bozza') { return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('error', 'Impossibile modificare un piano già attivato.'); } $request->validate([ 'denominazione' => 'required|string|max:255', 'importo_totale' => 'required|numeric|min:0', 'numero_rate' => 'required|integer|min:1|max:60', 'data_inizio' => 'required|date', 'frequenza' => 'required|in:mensile,bimestrale,trimestrale,semestrale', 'importo_prima_rata' => 'nullable|numeric|min:0', 'note' => 'nullable|string', 'applica_interessi' => 'nullable|boolean', 'tasso_interesse' => 'nullable|numeric|min:0|max:100', ]); // Verifica che l'importo totale non superi l'importo del dettaglio $dettaglioRipartizione = DettaglioRipartizioneSpese::where('ripartizione_spese_id', $pianoRateizzazione->ripartizione_spese_id) ->where('unita_immobiliare_id', $pianoRateizzazione->unita_immobiliare_id) ->firstOrFail(); if ($request->importo_totale > $dettaglioRipartizione->importo) { return redirect()->back() ->withInput() ->with('error', 'L\'importo totale del piano non può superare l\'importo della ripartizione (' . number_format($dettaglioRipartizione->importo, 2) . ' €).'); } DB::beginTransaction(); try { // Aggiorna il piano $pianoRateizzazione->update([ 'denominazione' => $request->denominazione, 'importo_totale' => $request->importo_totale, 'numero_rate' => $request->numero_rate, 'data_inizio' => $request->data_inizio, 'frequenza' => $request->frequenza, 'importo_prima_rata' => $request->importo_prima_rata, 'note' => $request->note, 'applica_interessi' => $request->boolean('applica_interessi'), 'tasso_interesse' => $request->tasso_interesse, 'updated_by' => Auth::id(), ]); // Elimina le rate esistenti $pianoRateizzazione->rate()->delete(); // Ricalcola le rate $this->calcolaRate($pianoRateizzazione); DB::commit(); return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('success', 'Piano di rateizzazione aggiornato con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back() ->withInput() ->with('error', 'Errore durante l\'aggiornamento del piano: ' . $e->getMessage()); } } /** * Remove the specified resource from storage. */ public function destroy(PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('delete', $pianoRateizzazione); // Solo i piani in bozza possono essere eliminati if ($pianoRateizzazione->stato !== 'bozza') { return redirect()->route('admin.piani-rateizzazione.index') ->with('error', 'Impossibile eliminare un piano già attivato.'); } DB::beginTransaction(); try { // Elimina le rate $pianoRateizzazione->rate()->delete(); // Elimina il piano $pianoRateizzazione->delete(); DB::commit(); return redirect()->route('admin.piani-rateizzazione.index') ->with('success', 'Piano di rateizzazione eliminato con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->route('admin.piani-rateizzazione.index') ->with('error', 'Errore durante l\'eliminazione del piano: ' . $e->getMessage()); } } /** * Attiva un piano di rateizzazione */ public function attiva(PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('update', $pianoRateizzazione); if ($pianoRateizzazione->stato !== 'bozza') { return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('error', 'Il piano è già stato attivato.'); } // Verifica che ci siano rate if (!$pianoRateizzazione->rate()->exists()) { return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('error', 'Impossibile attivare un piano senza rate.'); } $pianoRateizzazione->update([ 'stato' => 'attivo', 'data_attivazione' => now(), 'attivato_by' => Auth::id(), ]); return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('success', 'Piano di rateizzazione attivato con successo.'); } /** * Sospende un piano di rateizzazione */ public function sospendi(PianoRateizzazione $pianoRateizzazione) { // Verifica autorizzazione $this->authorize('update', $pianoRateizzazione); if ($pianoRateizzazione->stato !== 'attivo') { return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('error', 'Il piano non è attivo.'); } $pianoRateizzazione->update([ 'stato' => 'sospeso', 'data_sospensione' => now(), 'sospeso_by' => Auth::id(), ]); return redirect()->route('admin.piani-rateizzazione.show', $pianoRateizzazione) ->with('success', 'Piano di rateizzazione sospeso con successo.'); } /** * Calcola le rate per un piano di rateizzazione */ private function calcolaRate(PianoRateizzazione $piano) { $dataScadenza = Carbon::parse($piano->data_inizio); $importoRata = $piano->importo_totale / $piano->numero_rate; $importoPrimaRata = $piano->importo_prima_rata ?? $importoRata; // Calcola gli interessi se applicabili $importoTotaleConInteressi = $piano->importo_totale; if ($piano->applica_interessi && $piano->tasso_interesse > 0) { $importoTotaleConInteressi = $piano->importo_totale * (1 + ($piano->tasso_interesse / 100)); $importoRata = $importoTotaleConInteressi / $piano->numero_rate; } for ($i = 1; $i <= $piano->numero_rate; $i++) { $importoCorrente = ($i === 1) ? $importoPrimaRata : $importoRata; // Aggiusta l'ultima rata per eventuali arrotondamenti if ($i === $piano->numero_rate) { $totaleRatePrecedenti = $importoPrimaRata + (($piano->numero_rate - 2) * $importoRata); $importoCorrente = $importoTotaleConInteressi - $totaleRatePrecedenti; } Rata::create([ 'piano_rateizzazione_id' => $piano->id, 'numero_rata' => $i, 'importo' => round($importoCorrente, 2), 'data_scadenza' => $dataScadenza->copy(), 'stato' => 'da_pagare', ]); // Calcola la prossima data di scadenza switch ($piano->frequenza) { case 'mensile': $dataScadenza->addMonth(); break; case 'bimestrale': $dataScadenza->addMonths(2); break; case 'trimestrale': $dataScadenza->addMonths(3); break; case 'semestrale': $dataScadenza->addMonths(6); break; } } } /** * Genera un codice piano univoco */ private function generateCodicePiano(): string { do { $codice = 'PR' . strtoupper(Str::random(6)); } while (PianoRateizzazione::where('codice_piano', $codice)->exists()); return $codice; } }