amministratore->id_amministratore ?? null; $preventivi = Preventivo::with(['stabile', 'approvatoDa']) ->whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); }) ->orderBy('anno_gestione', 'desc') ->orderBy('created_at', 'desc') ->paginate(15); // Statistiche $stats = [ 'preventivi_bozza' => Preventivo::whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->where('stato', 'bozza')->count(), 'preventivi_approvati' => Preventivo::whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->where('stato', 'approvato')->count(), 'importo_totale_anno' => Preventivo::whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->where('anno_gestione', date('Y'))->sum('importo_totale'), ]; return view('admin.preventivi.index', compact('preventivi', 'stats')); } /** * Form creazione preventivo */ public function create() { $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; $stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get(); return view('admin.preventivi.create', compact('stabili')); } /** * Store preventivo */ public function store(Request $request) { $request->validate([ 'stabile_id' => 'required|exists:stabili,id_stabile', 'anno_gestione' => 'required|integer|min:2020|max:2030', 'tipo_gestione' => 'required|in:ordinaria,riscaldamento,straordinaria,acqua,altro', 'descrizione' => 'required|string|max:255', 'voci' => 'required|array|min:1', 'voci.*.codice' => 'required|string|max:20', 'voci.*.descrizione' => 'required|string|max:255', 'voci.*.importo' => 'required|numeric|min:0', 'voci.*.tabella_millesimale_id' => 'nullable|exists:tabelle_millesimali,id', ]); DB::beginTransaction(); try { $preventivo = Preventivo::create([ 'stabile_id' => $request->stabile_id, 'anno_gestione' => $request->anno_gestione, 'tipo_gestione' => $request->tipo_gestione, 'descrizione' => $request->descrizione, 'stato' => 'bozza', 'data_creazione' => now(), 'versione' => 1, ]); $importoTotale = 0; foreach ($request->voci as $index => $voceData) { $voce = VocePreventivo::create([ 'preventivo_id' => $preventivo->id, 'codice' => $voceData['codice'], 'descrizione' => $voceData['descrizione'], 'importo_preventivato' => $voceData['importo'], 'tabella_millesimale_id' => $voceData['tabella_millesimale_id'] ?? null, 'ordinamento' => $index + 1, ]); // Calcola ripartizione se specificata tabella millesimale if ($voce->tabella_millesimale_id) { $voce->calcolaRipartizione(); } $importoTotale += $voceData['importo']; } $preventivo->update(['importo_totale' => $importoTotale]); // Log creazione LogModificaPreventivo::create([ 'entita' => 'preventivo', 'entita_id' => $preventivo->id, 'versione_precedente' => 0, 'versione_nuova' => 1, 'utente_id' => Auth::id(), 'tipo_operazione' => 'create', 'motivo' => 'Creazione preventivo', 'dati_nuovi' => $preventivo->toArray(), ]); DB::commit(); return redirect()->route('admin.preventivi.show', $preventivo) ->with('success', 'Preventivo creato con successo.'); } catch (\Exception $e) { DB::rollback(); return back()->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]); } } /** * Visualizza preventivo */ public function show(Preventivo $preventivo) { // Verifica accesso if ($preventivo->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } $preventivo->load([ 'stabile', 'voci.tabellaMillesimale', 'voci.ripartizioni.unitaImmobiliare', 'rate', 'logModifiche.utente', ]); return view('admin.preventivi.show', compact('preventivo')); } /** * Approva preventivo */ public function approva(Request $request, Preventivo $preventivo) { $request->validate([ 'motivo' => 'required|string', 'auto_genera_rate' => 'nullable|boolean', 'piano_rate' => 'nullable|in:mensile,bimestrale,trimestrale,semestrale,annuale,custom', 'numero_rate' => 'nullable|integer|min:1|max:24', 'data_inizio_rate' => 'nullable|date', ]); // Verifica accesso if ($preventivo->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } $preventivo->creaVersione($request->motivo, Auth::id()); $preventivo->update([ 'stato' => 'approvato', 'data_approvazione' => now(), 'approvato_da_user_id' => Auth::id(), ]); // Automazione addebiti: genera automaticamente le rate in base al piano richiesto. if ((bool) $request->boolean('auto_genera_rate')) { $pianoRate = (string) ($request->input('piano_rate') ?: $this->defaultPianoRateByTipoGestione((string) $preventivo->tipo_gestione)); $numeroRate = $this->resolveNumeroRate( $pianoRate, $request->input('numero_rate'), (string) $preventivo->tipo_gestione ); $dataInizio = Carbon::parse((string) ($request->input('data_inizio_rate') ?: now()->startOfMonth()->toDateString())); $this->generaRateConFrequenza($preventivo, $numeroRate, $dataInizio, $pianoRate, (int) Auth::id()); } return back()->with('success', 'Preventivo approvato con successo.'); } /** * Genera rate dal preventivo */ public function generaRate(Request $request, Preventivo $preventivo) { $request->validate([ 'numero_rate' => 'required|integer|min:1|max:12', 'data_inizio' => 'required|date', ]); // Verifica accesso e stato if ($preventivo->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } if ($preventivo->stato !== 'approvato') { return back()->withErrors(['error' => 'Il preventivo deve essere approvato prima di generare le rate.']); } $dataInizio = Carbon::parse($request->data_inizio); $rate = $preventivo->generaRate($request->numero_rate, $dataInizio, Auth::id()); return back()->with('success', 'Generate ' . count($rate) . ' rate con successo.'); } /** * Dashboard pianificazione */ public function pianificazione() { $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; // Spese in scadenza $speseInScadenza = PianificazioneSpesa::with('stabile') ->whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); }) ->where('data_scadenza_prevista', '<=', now()->addDays(30)) ->where('stato', 'pianificata') ->orderBy('data_scadenza_prevista') ->get(); // Cashflow previsto (prossimi 6 mesi) $cashflow = $this->calcolaCashflow($amministratore_id); return view('admin.preventivi.pianificazione', compact('speseInScadenza', 'cashflow')); } /** * Calcola cashflow previsto */ private function calcolaCashflow($amministratore_id) { $mesi = []; for ($i = 0; $i < 6; $i++) { $dataInizio = now()->startOfMonth()->addMonths($i); $dataFine = $dataInizio->copy()->endOfMonth(); // Entrate previste (rate) $entrate = DB::table('rate') ->join('preventivi', 'rate.preventivo_id', '=', 'preventivi.id') ->join('stabili', 'preventivi.stabile_id', '=', 'stabili.id_stabile') ->where('stabili.amministratore_id', $amministratore_id) ->whereBetween('rate.data_scadenza', [$dataInizio, $dataFine]) ->sum('rate.importo_totale'); // Uscite previste (spese pianificate) $uscite = PianificazioneSpesa::whereHas('stabile', function ($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); }) ->whereBetween('data_scadenza_prevista', [$dataInizio, $dataFine]) ->sum('importo_previsto'); $mesi[] = [ 'mese' => $dataInizio->format('M Y'), 'entrate' => $entrate, 'uscite' => $uscite, 'saldo' => $entrate - $uscite, ]; } return $mesi; } private function defaultPianoRateByTipoGestione(string $tipoGestione): string { return match ($tipoGestione) { 'riscaldamento' => 'trimestrale', 'straordinaria' => 'custom', 'acqua' => 'bimestrale', default => 'mensile', }; } private function resolveNumeroRate(string $pianoRate, mixed $requestedNumeroRate, string $tipoGestione): int { if (is_numeric($requestedNumeroRate) && (int) $requestedNumeroRate > 0) { return min(24, max(1, (int) $requestedNumeroRate)); } if ($pianoRate === 'custom') { return $tipoGestione === 'straordinaria' ? 6 : 4; } return match ($pianoRate) { 'mensile' => 12, 'bimestrale' => 6, 'trimestrale' => 4, 'semestrale' => 2, 'annuale' => 1, default => 12, }; } private function generaRateConFrequenza(Preventivo $preventivo, int $numeroRate, Carbon $dataInizio, string $pianoRate, int $userId): void { // Evita duplicazioni: se il preventivo ha gia rate emesse, non rigeneriamo. if ($preventivo->rate()->exists()) { return; } $stepMonths = match ($pianoRate) { 'mensile' => 1, 'bimestrale' => 2, 'trimestrale' => 3, 'semestrale' => 6, 'annuale' => 12, default => 1, }; $importoPerRata = round(((float) $preventivo->importo_totale) / $numeroRate, 2); for ($i = 1; $i <= $numeroRate; $i++) { $dataScadenza = $dataInizio->copy()->addMonths(($i - 1) * $stepMonths); $numeroRata = $preventivo->stabile_id . '/' . $preventivo->anno_gestione . '/' . str_pad((string) $i, 3, '0', STR_PAD_LEFT); $importo = $i === $numeroRate ? round((float) $preventivo->importo_totale - ($importoPerRata * ($numeroRate - 1)), 2) : $importoPerRata; \App\Models\Rata::create([ 'preventivo_id' => $preventivo->id, 'numero_rata' => $numeroRata, 'descrizione' => "Rata {$i} di {$numeroRate} - {$preventivo->descrizione}", 'data_scadenza' => $dataScadenza, 'stato' => 'emessa', 'importo_totale' => $importo, 'creato_da_user_id' => $userId, ]); } } /** * Storico modifiche (stile GIT) */ public function storicoModifiche(Preventivo $preventivo) { // Verifica accesso if ($preventivo->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } $modifiche = LogModificaPreventivo::with('utente') ->where(function ($q) use ($preventivo) { $q->where('entita', 'preventivo')->where('entita_id', $preventivo->id) ->orWhereIn('entita_id', $preventivo->voci->pluck('id')) ->where('entita', 'voce'); }) ->orderBy('created_at', 'desc') ->paginate(20); return view('admin.preventivi.storico', compact('preventivo', 'modifiche')); } }