whereHas('voceSpesa.stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }); // Filtro per stabile if ($request->filled('stabile_id')) { $query->whereHas('voceSpesa', function($q) use ($request) { $q->where('stabile_id', $request->stabile_id); }); } // Filtro per voce di spesa if ($request->filled('voce_spesa_id')) { $query->where('voce_spesa_id', $request->voce_spesa_id); } // Filtro per stato if ($request->filled('stato')) { $query->where('stato', $request->stato); } // Filtro per data if ($request->filled('data_da')) { $query->where('data_ripartizione', '>=', $request->data_da); } if ($request->filled('data_a')) { $query->where('data_ripartizione', '<=', $request->data_a); } $ripartizioni = $query->orderBy('data_ripartizione', 'desc')->paginate(15); // Dati per i filtri $stabili = Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null) ->orderBy('denominazione') ->get(); $vociSpesa = VoceSpesa::whereHas('stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->orderBy('denominazione') ->get(); return view('admin.ripartizioni-spesa.index', compact('ripartizioni', 'stabili', 'vociSpesa')); } /** * Show the form for creating a new resource. */ public function create(Request $request) { $vociSpesa = VoceSpesa::with(['stabile', 'tabellaMillesimaleDefault']) ->whereHas('stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->where('stato', 'attiva') ->orderBy('denominazione') ->get(); $voceSpesaSelezionata = null; if ($request->filled('voce_spesa_id')) { $voceSpesaSelezionata = $vociSpesa->firstWhere('id', $request->voce_spesa_id); } return view('admin.ripartizioni-spesa.create', compact('vociSpesa', 'voceSpesaSelezionata')); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $request->validate([ 'voce_spesa_id' => 'required|exists:voci_spesa,id', 'descrizione' => 'required|string|max:255', 'importo_totale' => 'required|numeric|min:0', 'data_ripartizione' => 'required|date', 'tabella_millesimale_id' => 'required|exists:tabelle_millesimali,id', 'periodo_riferimento' => 'nullable|string|max:100', 'note' => 'nullable|string', 'dettagli' => 'nullable|array', 'dettagli.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id', 'dettagli.*.importo' => 'required|numeric|min:0', 'dettagli.*.esclusa' => 'nullable|boolean', 'dettagli.*.note' => 'nullable|string|max:255', ]); // Verifica che la voce di spesa appartenga all'amministratore $voceSpesa = VoceSpesa::whereHas('stabile', function($q) { $q->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); }) ->findOrFail($request->voce_spesa_id); // Verifica che la tabella millesimale appartenga allo stabile $tabellaMillesimale = TabellaMillesimale::where('id', $request->tabella_millesimale_id) ->where('stabile_id', $voceSpesa->stabile_id) ->firstOrFail(); DB::beginTransaction(); try { // Crea la ripartizione principale $ripartizione = RipartizioneSpese::create([ 'codice_ripartizione' => $this->generateCodiceRipartizione(), 'voce_spesa_id' => $request->voce_spesa_id, 'descrizione' => $request->descrizione, 'importo_totale' => $request->importo_totale, 'data_ripartizione' => $request->data_ripartizione, 'tabella_millesimale_id' => $request->tabella_millesimale_id, 'periodo_riferimento' => $request->periodo_riferimento, 'note' => $request->note, 'stato' => 'bozza', 'created_by' => Auth::id(), ]); // Se non sono stati forniti dettagli, calcola automaticamente if (empty($request->dettagli)) { $this->calcolaRipartizioneAutomatica($ripartizione); } else { // Crea i dettagli forniti foreach ($request->dettagli as $dettaglio) { DettaglioRipartizioneSpese::create([ 'ripartizione_spese_id' => $ripartizione->id, 'unita_immobiliare_id' => $dettaglio['unita_immobiliare_id'], 'importo' => $dettaglio['importo'], 'esclusa' => $dettaglio['esclusa'] ?? false, 'note' => $dettaglio['note'] ?? null, ]); } } DB::commit(); return redirect()->route('admin.ripartizioni-spesa.show', $ripartizione) ->with('success', 'Ripartizione spesa creata con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back() ->withInput() ->with('error', 'Errore durante la creazione della ripartizione: ' . $e->getMessage()); } } /** * Display the specified resource. */ public function show(RipartizioneSpese $ripartizioneSpesa) { // Verifica autorizzazione $this->authorize('view', $ripartizioneSpesa); $ripartizioneSpesa->load([ 'voceSpesa.stabile', 'tabellaMillesimale', 'dettagli.unitaImmobiliare.anagraficaCondominiale.soggetto', 'createdBy', 'updatedBy' ]); return view('admin.ripartizioni-spesa.show', compact('ripartizioneSpesa')); } /** * Show the form for editing the specified resource. */ public function edit(RipartizioneSpese $ripartizioneSpesa) { // Verifica autorizzazione $this->authorize('update', $ripartizioneSpesa); // Solo le ripartizioni in bozza possono essere modificate if ($ripartizioneSpesa->stato !== 'bozza') { return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('error', 'Impossibile modificare una ripartizione già confermata.'); } $ripartizioneSpesa->load([ 'voceSpesa.stabile', 'tabellaMillesimale', 'dettagli.unitaImmobiliare' ]); $tabelleMillesimali = TabellaMillesimale::where('stabile_id', $ripartizioneSpesa->voceSpesa->stabile_id) ->where('stato', 'attiva') ->orderBy('denominazione') ->get(); return view('admin.ripartizioni-spesa.edit', compact('ripartizioneSpesa', 'tabelleMillesimali')); } /** * Update the specified resource in storage. */ public function update(Request $request, RipartizioneSpese $ripartizioneSpesa) { // Verifica autorizzazione $this->authorize('update', $ripartizioneSpesa); // Solo le ripartizioni in bozza possono essere modificate if ($ripartizioneSpesa->stato !== 'bozza') { return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('error', 'Impossibile modificare una ripartizione già confermata.'); } $request->validate([ 'descrizione' => 'required|string|max:255', 'importo_totale' => 'required|numeric|min:0', 'data_ripartizione' => 'required|date', 'tabella_millesimale_id' => 'required|exists:tabelle_millesimali,id', 'periodo_riferimento' => 'nullable|string|max:100', 'note' => 'nullable|string', 'dettagli' => 'nullable|array', 'dettagli.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id', 'dettagli.*.importo' => 'required|numeric|min:0', 'dettagli.*.esclusa' => 'nullable|boolean', 'dettagli.*.note' => 'nullable|string|max:255', ]); // Verifica che la tabella millesimale appartenga allo stabile $tabellaMillesimale = TabellaMillesimale::where('id', $request->tabella_millesimale_id) ->where('stabile_id', $ripartizioneSpesa->voceSpesa->stabile_id) ->firstOrFail(); DB::beginTransaction(); try { // Aggiorna la ripartizione principale $ripartizioneSpesa->update([ 'descrizione' => $request->descrizione, 'importo_totale' => $request->importo_totale, 'data_ripartizione' => $request->data_ripartizione, 'tabella_millesimale_id' => $request->tabella_millesimale_id, 'periodo_riferimento' => $request->periodo_riferimento, 'note' => $request->note, 'updated_by' => Auth::id(), ]); // Elimina i dettagli esistenti $ripartizioneSpesa->dettagli()->delete(); // Se non sono stati forniti dettagli, calcola automaticamente if (empty($request->dettagli)) { $this->calcolaRipartizioneAutomatica($ripartizioneSpesa); } else { // Crea i nuovi dettagli foreach ($request->dettagli as $dettaglio) { DettaglioRipartizioneSpese::create([ 'ripartizione_spese_id' => $ripartizioneSpesa->id, 'unita_immobiliare_id' => $dettaglio['unita_immobiliare_id'], 'importo' => $dettaglio['importo'], 'esclusa' => $dettaglio['esclusa'] ?? false, 'note' => $dettaglio['note'] ?? null, ]); } } DB::commit(); return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('success', 'Ripartizione spesa aggiornata con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back() ->withInput() ->with('error', 'Errore durante l\'aggiornamento della ripartizione: ' . $e->getMessage()); } } /** * Remove the specified resource from storage. */ public function destroy(RipartizioneSpese $ripartizioneSpesa) { // Verifica autorizzazione $this->authorize('delete', $ripartizioneSpesa); // Solo le ripartizioni in bozza possono essere eliminate if ($ripartizioneSpesa->stato !== 'bozza') { return redirect()->route('admin.ripartizioni-spesa.index') ->with('error', 'Impossibile eliminare una ripartizione già confermata.'); } DB::beginTransaction(); try { // Elimina i dettagli $ripartizioneSpesa->dettagli()->delete(); // Elimina la ripartizione $ripartizioneSpesa->delete(); DB::commit(); return redirect()->route('admin.ripartizioni-spesa.index') ->with('success', 'Ripartizione spesa eliminata con successo.'); } catch (\Exception $e) { DB::rollBack(); return redirect()->route('admin.ripartizioni-spesa.index') ->with('error', 'Errore durante l\'eliminazione della ripartizione: ' . $e->getMessage()); } } /** * Conferma una ripartizione spesa */ public function conferma(RipartizioneSpese $ripartizioneSpesa) { // Verifica autorizzazione $this->authorize('update', $ripartizioneSpesa); if ($ripartizioneSpesa->stato !== 'bozza') { return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('error', 'La ripartizione è già stata confermata.'); } // Verifica che ci siano dettagli if (!$ripartizioneSpesa->dettagli()->exists()) { return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('error', 'Impossibile confermare una ripartizione senza dettagli.'); } // Verifica che la somma dei dettagli corrisponda all'importo totale $sommaDettagli = $ripartizioneSpesa->dettagli()->sum('importo'); if (abs($sommaDettagli - $ripartizioneSpesa->importo_totale) > 0.01) { return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('error', 'La somma dei dettagli non corrisponde all\'importo totale.'); } $ripartizioneSpesa->update([ 'stato' => 'confermata', 'data_conferma' => now(), 'confermata_by' => Auth::id(), ]); return redirect()->route('admin.ripartizioni-spesa.show', $ripartizioneSpesa) ->with('success', 'Ripartizione spesa confermata con successo.'); } /** * Calcola automaticamente la ripartizione basata sui millesimi */ private function calcolaRipartizioneAutomatica(RipartizioneSpese $ripartizione) { $tabella = $ripartizione->tabellaMillesimale; $unita = UnitaImmobiliare::where('stabile_id', $ripartizione->voceSpesa->stabile_id)->get(); foreach ($unita as $unita_immobiliare) { $millesimi = $tabella->getMillesimiForUnita($unita_immobiliare->id); $importo = ($ripartizione->importo_totale * $millesimi) / 1000; DettaglioRipartizioneSpese::create([ 'ripartizione_spese_id' => $ripartizione->id, 'unita_immobiliare_id' => $unita_immobiliare->id, 'importo' => round($importo, 2), 'esclusa' => false, ]); } } /** * Genera un codice ripartizione univoco */ private function generateCodiceRipartizione(): string { do { $codice = 'RP' . strtoupper(Str::random(6)); } while (RipartizioneSpese::where('codice_ripartizione', $codice)->exists()); return $codice; } }