amministratore->id_amministratore ?? null; // Statistiche $stats = [ 'tabelle_attive' => TabellaMillesimale::whereHas('stabile', function($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->where('attiva', true)->count(), 'totale_unita' => QuotaMillesimale::whereHas('tabellaMillesimale.stabile', function($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->distinct('condomino_id')->count(), 'regole_automatiche' => RegolaRipartizione::whereHas('stabile', function($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->where('attiva', true)->count(), ]; // Stabili con tabelle millesimali $stabili = Stabile::where('amministratore_id', $amministratore_id) ->with(['tabelleMillesimali' => function($q) { $q->where('attiva', true)->with('quote'); }]) ->get(); // Ultime modifiche $ultimaModifica = TabellaMillesimale::whereHas('stabile', function($q) use ($amministratore_id) { $q->where('amministratore_id', $amministratore_id); })->orderBy('updated_at', 'desc')->first(); return view('admin.millesimi.index', compact('stats', 'stabili', 'ultimaModifica')); } /** * Lista tabelle millesimali per uno stabile */ public function tabelle(Stabile $stabile) { // Verifica autorizzazioni $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; if ($stabile->amministratore_id !== $amministratore_id) { abort(403); } $tabelle = TabellaMillesimale::where('stabile_id', $stabile->id_stabile) ->with(['quote.condomino', 'vociSpesaAssociate']) ->orderBy('nome') ->get(); return view('admin.millesimi.tabelle', compact('stabile', 'tabelle')); } /** * Form creazione nuova tabella millesimale */ public function createTabella(Stabile $stabile) { // Verifica autorizzazioni $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; if ($stabile->amministratore_id !== $amministratore_id) { abort(403); } $condomini = Condomino::where('stabile_id', $stabile->id_stabile) ->where('attivo', true) ->orderBy('interno') ->get(); $vociSpesa = VoceSpesa::orderBy('codice')->get(); // Template predefiniti $template = [ 'generale' => [ 'nome' => 'Tabella Generale', 'descrizione' => 'Ripartizione in base ai millesimi generali', 'tipo' => 'generale' ], 'riscaldamento' => [ 'nome' => 'Tabella Riscaldamento', 'descrizione' => 'Ripartizione spese riscaldamento', 'tipo' => 'riscaldamento' ], 'ascensore' => [ 'nome' => 'Tabella Ascensore', 'descrizione' => 'Ripartizione spese ascensore per piano', 'tipo' => 'ascensore' ], 'scale' => [ 'nome' => 'Tabella Scale', 'descrizione' => 'Ripartizione spese scale comuni', 'tipo' => 'scale' ] ]; return view('admin.millesimi.create-tabella', compact( 'stabile', 'condomini', 'vociSpesa', 'template' )); } /** * Salva nuova tabella millesimale */ public function storeTabella(Request $request, Stabile $stabile) { $request->validate([ 'nome' => 'required|string|max:100', 'descrizione' => 'nullable|string', 'tipo_tabella' => 'required|string', 'data_validita_da' => 'required|date', 'data_validita_a' => 'nullable|date|after:data_validita_da', 'quote' => 'required|array|min:1', 'quote.*.condomino_id' => 'required|exists:condomini,id_condomino', 'quote.*.quota_millesimi' => 'required|numeric|min:0|max:1000', 'voci_spesa_associate' => 'nullable|array', 'voci_spesa_associate.*' => 'exists:voci_spesa,id' ]); // Verifica che i millesimi totali siano 1000 $totaleMillesimi = array_sum(array_column($request->quote, 'quota_millesimi')); if (abs($totaleMillesimi - 1000) > 0.001) { return back() ->withInput() ->withErrors(['quote' => "Il totale dei millesimi deve essere 1000. Attuale: {$totaleMillesimi}"]); } DB::beginTransaction(); try { // Se questa tabella è impostata come principale, disattiva le altre principali $attiva = $request->boolean('tabella_principale', false); if ($attiva) { TabellaMillesimale::where('stabile_id', $stabile->id_stabile) ->where('tipo_tabella', $request->tipo_tabella) ->update(['attiva' => false]); } // Crea tabella millesimale $tabella = TabellaMillesimale::create([ 'stabile_id' => $stabile->id_stabile, 'nome' => $request->nome, 'descrizione' => $request->descrizione, 'tipo_tabella' => $request->tipo_tabella, 'data_validita_da' => $request->data_validita_da, 'data_validita_a' => $request->data_validita_a, 'attiva' => $attiva, 'totale_millesimi' => $totaleMillesimi, 'numero_unita' => count($request->quote), 'utente_creazione_id' => Auth::id() ]); // Crea quote millesimali foreach ($request->quote as $quota) { QuotaMillesimale::create([ 'tabella_millesimale_id' => $tabella->id, 'condomino_id' => $quota['condomino_id'], 'quota_millesimi' => $quota['quota_millesimi'], 'note' => $quota['note'] ?? null ]); } // Associa voci di spesa se specificate if ($request->has('voci_spesa_associate')) { $tabella->vociSpesaAssociate()->sync($request->voci_spesa_associate); } // Crea regole di ripartizione automatica se richiesto if ($request->boolean('crea_regole_automatiche')) { $this->creaRegoleAutomatiche($tabella, $request->voci_spesa_associate ?? []); } DB::commit(); return redirect() ->route('admin.millesimi.show-tabella', [$stabile, $tabella]) ->with('success', 'Tabella millesimale creata con successo'); } catch (\Exception $e) { DB::rollback(); return back() ->withInput() ->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]); } } /** * Mostra dettagli tabella millesimale */ public function showTabella(Stabile $stabile, TabellaMillesimale $tabella) { $tabella->load([ 'quote.condomino', 'vociSpesaAssociate', 'regoleRipartizione', 'utenteCreazione' ]); // Verifica che i millesimi siano quadrati $totaleMillesimi = $tabella->quote->sum('quota_millesimi'); $isQuadrata = abs($totaleMillesimi - 1000) < 0.001; // Statistiche utilizzo $utilizzo = [ 'movimenti_ripartiti' => 0, // Da implementare con query su movimenti 'ultimo_utilizzo' => null, // Da implementare 'voci_associate' => $tabella->vociSpesaAssociate->count() ]; return view('admin.millesimi.show-tabella', compact( 'stabile', 'tabella', 'isQuadrata', 'utilizzo' )); } /** * API: Calcola ripartizione per importo */ public function calcolaRipartizione(Request $request) { $request->validate([ 'tabella_id' => 'required|exists:tabelle_millesimali,id', 'importo' => 'required|numeric|min:0' ]); $tabella = TabellaMillesimale::with('quote.condomino')->find($request->tabella_id); $importo = $request->importo; $ripartizioni = []; $totaleRipartito = 0; foreach ($tabella->quote as $quota) { $importoQuota = round(($importo * $quota->quota_millesimi) / 1000, 2); $totaleRipartito += $importoQuota; $ripartizioni[] = [ 'condomino_id' => $quota->condomino_id, 'condomino' => [ 'interno' => $quota->condomino->interno, 'ragione_sociale' => $quota->condomino->ragione_sociale, 'piano' => $quota->condomino->piano ], 'quota_millesimi' => $quota->quota_millesimi, 'importo' => $importoQuota, 'percentuale' => round(($quota->quota_millesimi / 1000) * 100, 3) ]; } // Gestione arrotondamenti $differenza = $importo - $totaleRipartito; if (abs($differenza) > 0.01) { // Distribuisci la differenza sulla quota più alta $quotaMaggiore = collect($ripartizioni)->sortByDesc('quota_millesimi')->first(); $index = array_search($quotaMaggiore, $ripartizioni); $ripartizioni[$index]['importo'] += $differenza; $ripartizioni[$index]['note'] = 'Adeguato per arrotondamento: €' . number_format($differenza, 2); } return response()->json([ 'tabella' => [ 'nome' => $tabella->nome, 'tipo' => $tabella->tipo_tabella, 'totale_millesimi' => $tabella->totale_millesimi ], 'ripartizioni' => $ripartizioni, 'riepilogo' => [ 'importo_originale' => $importo, 'totale_ripartito' => array_sum(array_column($ripartizioni, 'importo')), 'numero_quote' => count($ripartizioni) ] ]); } /** * API: Ottieni template per tipo tabella */ public function getTemplateTabella($tipo) { $templates = [ 'generale' => [ 'nome' => 'Tabella Generale', 'descrizione' => 'Ripartizione in base ai millesimi generali dell\'edificio', 'suggerimenti' => 'I millesimi generali si basano su superficie e valore delle unità immobiliari' ], 'riscaldamento' => [ 'nome' => 'Tabella Riscaldamento', 'descrizione' => 'Ripartizione spese riscaldamento centralizzato', 'suggerimenti' => 'Considerare: superficie riscaldata, esposizione, piano, presenza termovalvole' ], 'ascensore' => [ 'nome' => 'Tabella Ascensore', 'descrizione' => 'Ripartizione spese ascensore', 'suggerimenti' => 'Quote maggiori per piani alti, piano terra spesso escluso o quota ridotta' ], 'pulizie' => [ 'nome' => 'Tabella Pulizie Scale', 'descrizione' => 'Ripartizione spese pulizie parti comuni', 'suggerimenti' => 'Può essere in base al numero di componenti famiglia o millesimi generali' ] ]; return response()->json($templates[$tipo] ?? []); } /** * Importa tabella da file Excel/CSV */ public function importTabella(Request $request, Stabile $stabile) { $request->validate([ 'file' => 'required|file|mimes:xlsx,xls,csv', 'nome_tabella' => 'required|string', 'tipo_tabella' => 'required|string' ]); // Implementazione import da Excel/CSV // Da sviluppare con phpspreadsheet return back()->with('info', 'Funzione import in sviluppo'); } /** * Esporta tabella in Excel */ public function exportTabella(Stabile $stabile, TabellaMillesimale $tabella) { // Implementazione export Excel // Da sviluppare con phpspreadsheet return back()->with('info', 'Funzione export in sviluppo'); } /** * Crea regole di ripartizione automatica */ private function creaRegoleAutomatiche(TabellaMillesimale $tabella, array $vociSpesa) { foreach ($vociSpesa as $voceId) { RegolaRipartizione::create([ 'stabile_id' => $tabella->stabile_id, 'tabella_millesimale_id' => $tabella->id, 'voce_spesa_id' => $voceId, 'nome_regola' => "Auto: {$tabella->nome}", 'condizioni' => json_encode([ 'voce_spesa_id' => $voceId, 'tabella_millesimale_id' => $tabella->id ]), 'attiva' => true, 'priorita' => 1, 'utente_creazione_id' => Auth::id() ]); } } /** * Verifica coerenza tabelle millesimali */ public function verificaCoerenza(Stabile $stabile) { $tabelle = TabellaMillesimale::where('stabile_id', $stabile->id_stabile) ->with('quote') ->get(); $report = []; foreach ($tabelle as $tabella) { $totaleMillesimi = $tabella->quote->sum('quota_millesimi'); $numeroQuote = $tabella->quote->count(); $problemi = []; if (abs($totaleMillesimi - 1000) > 0.001) { $problemi[] = "Totale millesimi non è 1000 (attuale: {$totaleMillesimi})"; } if ($numeroQuote === 0) { $problemi[] = "Nessuna quota definita"; } $report[] = [ 'tabella' => $tabella->nome, 'totale_millesimi' => $totaleMillesimi, 'numero_quote' => $numeroQuote, 'problemi' => $problemi, 'stato' => empty($problemi) ? 'ok' : 'errore' ]; } return response()->json(['report' => $report]); } }