amministratore->id_amministratore ?? null) ->paginate(10); // Se è una richiesta AJAX, restituisce solo la vista parziale if ($request->ajax()) { return view('admin.stabili.index-ajax', compact('stabili')); } return view('admin.stabili.index', compact('stabili')); } /** * Show the form for creating a new resource. */ public function create() { // Passa le variabili necessarie per la sidebar $userRole = 'admin'; $userPermissions = [ 'dashboard' => true, 'stabili' => true, 'condomini' => true, 'tickets' => true, 'super_admin' => false ]; return view('admin.stabili.create', compact('userRole', 'userPermissions')); } /** * Restituisce solo il form di creazione per richieste AJAX */ public function createForm() { if (request()->ajax()) { return view('admin.stabili._form-bootstrap', ['stabile' => null]); } // Se non è AJAX, passa le variabili per la sidebar $userRole = 'admin'; $userPermissions = [ 'dashboard' => true, 'stabili' => true, 'condomini' => true, 'tickets' => true, 'super_admin' => false ]; return redirect()->route('admin.stabili.create')->with(compact('userRole', 'userPermissions')); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $validated = $request->validate([ 'denominazione' => 'required|string|max:255', 'codice_stabile' => 'nullable|string|max:50', 'codice_fiscale' => 'nullable|string|max:20', 'partita_iva' => 'nullable|string|max:20', 'indirizzo' => 'required|string|max:255', 'citta' => 'required|string|max:255', 'cap' => 'required|string|max:10', 'provincia' => 'nullable|string|max:2', 'note' => 'nullable|string', // Campi bancari 'iban_principale' => 'nullable|string|max:34', 'banca_principale' => 'nullable|string|max:255', 'filiale' => 'nullable|string|max:255', 'iban_secondario' => 'nullable|string|max:34', 'banca_secondaria' => 'nullable|string|max:255', 'filiale_secondaria' => 'nullable|string|max:255', // Amministratore 'amministratore_nome' => 'nullable|string|max:255', 'amministratore_email' => 'nullable|email|max:255', 'data_nomina' => 'nullable|date', 'scadenza_mandato' => 'nullable|date', // Dati catastali 'foglio' => 'nullable|string|max:20', 'mappale' => 'nullable|string|max:20', 'subalterno' => 'nullable|string|max:20', 'categoria' => 'nullable|string|max:20', 'rendita_catastale' => 'nullable|numeric', 'superficie_catastale' => 'nullable|numeric', // Palazzine e Locali 'palazzine' => 'nullable|array', 'palazzine.*.numero' => 'nullable|string|max:10', 'palazzine.*.indirizzo' => 'nullable|string|max:255', 'palazzine.*.scala' => 'nullable|string|max:10', 'palazzine.*.interni' => 'nullable|integer|min:0', 'palazzine.*.piani' => 'nullable|integer|min:0', 'locali' => 'nullable|array', 'locali.*.tipo' => 'nullable|string|max:50', 'locali.*.descrizione' => 'nullable|string|max:255', 'locali.*.ubicazione' => 'nullable|string|max:100', ]); // Auto-genera codice stabile se non fornito if (empty($validated['codice_stabile'])) { $validated['codice_stabile'] = $this->generateCodiceStabile(); } // Prepara dati JSON per palazzine if (isset($validated['palazzine'])) { $validated['palazzine_data'] = json_encode($validated['palazzine']); unset($validated['palazzine']); } // Prepara dati JSON per locali di servizio if (isset($validated['locali'])) { $validated['locali_servizio'] = json_encode($validated['locali']); unset($validated['locali']); } $stabile = Stabile::create($validated); if ($request->wantsJson()) { return response()->json([ 'success' => true, 'message' => 'Stabile creato con successo!', 'stabile' => $stabile, 'redirect' => route('admin.stabili.show', $stabile) ]); } return redirect()->route('admin.stabili.show', $stabile) ->with('success', 'Stabile creato con successo!'); } /** * Generate a unique codice stabile */ private function generateCodiceStabile() { $prefix = 'STB'; $lastStabile = Stabile::where('codice_stabile', 'LIKE', $prefix . '%') ->orderBy('codice_stabile', 'desc') ->first(); if ($lastStabile) { $lastNumber = (int) substr($lastStabile->codice_stabile, 3); $newNumber = $lastNumber + 1; } else { $newNumber = 1; } return $prefix . str_pad($newNumber, 3, '0', STR_PAD_LEFT); } /** * Display the specified resource. */ public function show(Stabile $stabile) { // Verifica che l'utente possa accedere a questo stabile if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } // Carica relazioni per dashboard $stabile->load([ 'chiavi' => function($query) { $query->with('movimenti')->latest(); }, 'fondiCondominiali' => function($query) { $query->orderBy('tipo'); }, 'tabelleMillesimali' => function($query) { $query->with('dettagliMillesimi'); }, 'contatori' => function($query) { $query->with('lettureContatore'); }, 'unitaImmobiliari' ]); // Calcoli KPI dashboard $kpi = [ 'totale_unita' => $stabile->unitaImmobiliari->count(), 'totale_chiavi' => $stabile->chiavi->count(), 'chiavi_disponibili' => $stabile->chiavi->where('stato', 'disponibile')->count(), 'chiavi_in_uso' => $stabile->chiavi->where('stato', 'in_uso')->count(), 'totale_fondi' => $stabile->fondiCondominiali->count(), 'saldo_totale' => $stabile->fondiCondominiali->sum('saldo_attuale'), 'totale_contatori' => $stabile->contatori->count(), 'totale_tabelle_millesimali' => $stabile->tabelleMillesimali->count(), ]; return view('admin.stabili.show', compact('stabile', 'kpi')); } /** * Show the form for editing the specified resource. */ public function edit(Stabile $stabile) { // Verifica che l'utente possa modificare questo stabile if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } return view('admin.stabili.edit', compact('stabile')); } /** * Update the specified resource in storage. */ public function update(Request $request, Stabile $stabile) { // Verifica che l'utente possa modificare questo stabile if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } $request->validate([ 'denominazione' => 'required|string|max:255', 'codice_fiscale' => 'nullable|string|max:20|unique:stabili,codice_fiscale,' . $stabile->id . ',id', 'cod_fisc_amministratore' => 'nullable|string|max:20', 'indirizzo' => 'required|string|max:255', 'citta' => 'required|string|max:255', 'cap' => 'required|string|max:10', 'provincia' => 'nullable|string|max:2', 'stato' => 'required|in:attivo,inattivo', 'note' => 'nullable|string', 'old_id' => 'nullable|integer|unique:stabili,old_id,' . $stabile->id . ',id', // Campi avanzati struttura fisica 'numero_palazzine' => 'nullable|integer|min:1|max:20', 'numero_scale_per_palazzina' => 'nullable|integer|min:1|max:10', 'numero_piani' => 'nullable|integer|min:1|max:50', 'piano_seminterrato' => 'boolean', 'piano_sottotetto' => 'boolean', 'presenza_ascensore' => 'boolean', 'cortile_giardino' => 'boolean', 'superficie_cortile' => 'nullable|numeric|min:0', // Servizi 'riscaldamento_centralizzato' => 'boolean', 'acqua_centralizzata' => 'boolean', 'gas_centralizzato' => 'boolean', 'servizio_portineria' => 'boolean', 'orari_portineria' => 'nullable|string|max:255', 'videocitofono' => 'boolean', 'antenna_tv_centralizzata' => 'boolean', 'internet_condominiale' => 'boolean', // Dati economici 'fondo_riserva_minimo' => 'nullable|numeric|min:0', 'importo_rata_standard' => 'nullable|numeric|min:0', 'frequenza_rate' => 'nullable|in:mensile,bimestrale,trimestrale,quadrimestrale,semestrale,annuale', 'giorno_scadenza_rate' => 'nullable|integer|min:1|max:31', 'iban_condominio' => 'nullable|string|max:34', // Configurazioni millesimi 'millesimi_generali_calcolati' => 'boolean', 'millesimi_riscaldamento_separati' => 'boolean', 'millesimi_acqua_separati' => 'boolean', 'millesimi_ascensore_separati' => 'boolean', ]); $dati = $request->all(); // Gestione configurazione avanzata $configurazioneAvanzata = [ 'auto_generazione_unita' => $request->has('auto_generazione_unita'), 'calcolo_automatico_millesimi' => $request->has('calcolo_automatico_millesimi'), 'notifiche_scadenze' => $request->has('notifiche_scadenze'), 'backup_automatico' => $request->has('backup_automatico'), ]; $dati['configurazione_avanzata'] = $configurazioneAvanzata; $stabile->update($dati); return redirect()->route('admin.stabili.show', $stabile) ->with('success', 'Stabile aggiornato con successo.'); } /** * Remove the specified resource from storage. */ public function destroy(Stabile $stabile) { // Verifica che l'utente possa eliminare questo stabile if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403); } $stabile->delete(); return redirect()->route('admin.stabili.index') ->with('success', 'Stabile eliminato con successo.'); } // ================================ // MODULO STABILI AVANZATO // ================================ /** * Dashboard gestione chiavi */ public function chiavi(Stabile $stabile) { $this->authorizeAccess($stabile); $chiavi = $stabile->chiavi() ->with(['movimenti' => function($query) { $query->latest()->limit(3); }]) ->paginate(15); return view('admin.stabili.chiavi.index', compact('stabile', 'chiavi')); } /** * Movimento chiave (consegna/restituzione) */ public function movimentoChiave(Request $request, Stabile $stabile, ChiaveStabile $chiave) { $this->authorizeAccess($stabile); $request->validate([ 'tipo_movimento' => 'required|in:consegna,restituzione,smarrimento,duplicazione', 'soggetto_riferimento' => 'required|string|max:255', 'note' => 'nullable|string|max:500', ]); DB::transaction(function() use ($request, $chiave) { // Crea movimento $movimento = $chiave->movimenti()->create([ 'tipo_movimento' => $request->tipo_movimento, 'soggetto_riferimento' => $request->soggetto_riferimento, 'data_movimento' => now(), 'note' => $request->note, 'utente_id' => Auth::id(), ]); // Aggiorna stato chiave $nuovoStato = match($request->tipo_movimento) { 'consegna' => 'in_uso', 'restituzione' => 'disponibile', 'smarrimento' => 'smarrita', 'duplicazione' => 'disponibile', default => $chiave->stato }; $chiave->update(['stato' => $nuovoStato]); }); return back()->with('success', 'Movimento registrato con successo'); } /** * Gestione fondi condominiali */ public function fondi(Stabile $stabile) { $this->authorizeAccess($stabile); $fondi = $stabile->fondiCondominiali() ->orderBy('tipo') ->paginate(10); return view('admin.stabili.fondi.index', compact('stabile', 'fondi')); } /** * Struttura fisica dettaglio */ public function strutturaFisica(Stabile $stabile) { $this->authorizeAccess($stabile); $strutture = $stabile->strutturaFisica() ->orderBy('tipo') ->orderBy('codice') ->get() ->groupBy('tipo'); return view('admin.stabili.struttura.index', compact('stabile', 'strutture')); } /** * Auto-generazione struttura fisica */ public function autoGeneraStruttura(Request $request, Stabile $stabile) { $this->authorizeAccess($stabile); $request->validate([ 'num_palazzine' => 'required|integer|min:1|max:20', 'num_scale_per_palazzina' => 'required|integer|min:1|max:10', 'num_piani_per_scala' => 'required|integer|min:1|max:50', 'num_unita_per_piano' => 'required|integer|min:1|max:20', ]); DB::transaction(function() use ($request, $stabile) { // Elimina struttura esistente se richiesto if ($request->pulisci_esistente) { $stabile->strutturaFisica()->delete(); } $strutture = []; // Genera palazzine for ($p = 1; $p <= $request->num_palazzine; $p++) { $strutture[] = [ 'stabile_id' => $stabile->id, 'tipo' => 'palazzina', 'codice' => sprintf('PAL-%02d', $p), 'nome' => "Palazzina {$p}", 'parent_id' => null, 'created_at' => now(), 'updated_at' => now(), ]; } StrutturaFisicaDettaglio::insert($strutture); // Recupera palazzine create per le scale $palazzine = $stabile->strutturaFisica()->where('tipo', 'palazzina')->get(); foreach ($palazzine as $palazzina) { $strutture_scale = []; // Genera scale per questa palazzina for ($s = 1; $s <= $request->num_scale_per_palazzina; $s++) { $strutture_scale[] = [ 'stabile_id' => $stabile->id, 'tipo' => 'scala', 'codice' => sprintf('%s-SC%02d', $palazzina->codice, $s), 'nome' => "Scala {$s}", 'parent_id' => $palazzina->id, 'created_at' => now(), 'updated_at' => now(), ]; } StrutturaFisicaDettaglio::insert($strutture_scale); // Genera piani per ogni scala $scale = $stabile->strutturaFisica()->where('parent_id', $palazzina->id)->get(); foreach ($scale as $scala) { $strutture_piani = []; for ($piano = 0; $piano <= $request->num_piani_per_scala; $piano++) { $nome_piano = $piano == 0 ? 'Piano Terra' : "Piano {$piano}"; $strutture_piani[] = [ 'stabile_id' => $stabile->id, 'tipo' => 'piano', 'codice' => sprintf('%s-P%02d', $scala->codice, $piano), 'nome' => $nome_piano, 'parent_id' => $scala->id, 'created_at' => now(), 'updated_at' => now(), ]; } StrutturaFisicaDettaglio::insert($strutture_piani); } } }); return back()->with('success', 'Struttura fisica generata automaticamente'); } /** * Auto-generazione unità immobiliari da struttura */ public function autoGeneraUnita(Request $request, Stabile $stabile) { $this->authorizeAccess($stabile); $request->validate([ 'template_nome' => 'required|string', 'millesimi_default' => 'required|numeric|min:0|max:1000', ]); $unita_create = 0; DB::transaction(function() use ($request, $stabile, &$unita_create) { $piani = $stabile->strutturaFisica()->where('tipo', 'piano')->get(); foreach ($piani as $piano) { for ($u = 1; $u <= $request->num_unita_per_piano; $u++) { // Genera codice unità $codice_unita = sprintf('%s-U%02d', $piano->codice, $u); // Template nome (es: "Appartamento {piano} - {numero}") $nome_unita = str_replace( ['{piano}', '{numero}', '{codice}'], [$piano->nome, $u, $codice_unita], $request->template_nome ); // Crea unità immobiliare (assumendo esistenza model UnitaImmobiliare) // Sarà implementato nel prossimo modulo $unita_create++; } } }); return back()->with('success', "Programmate {$unita_create} unità immobiliari per generazione"); } /** * API per gestione tabelle millesimali */ public function storeTabellaMillesimale(Request $request, Stabile $stabile) { $request->validate([ 'nome' => 'required|string|max:255', 'descrizione' => 'nullable|string|max:1000', ]); try { $tabella = $stabile->tabelleMillesimali()->create([ 'nome' => $request->nome, 'descrizione' => $request->descrizione, 'stato' => 'attiva', 'created_by' => Auth::id(), ]); return response()->json(['success' => true, 'tabella' => $tabella]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function updateTabellaMillesimale(Request $request, Stabile $stabile, $tabellaId) { $request->validate([ 'nome' => 'required|string|max:255', 'descrizione' => 'nullable|string|max:1000', ]); try { $tabella = $stabile->tabelleMillesimali()->findOrFail($tabellaId); $tabella->update([ 'nome' => $request->nome, 'descrizione' => $request->descrizione, 'updated_by' => Auth::id(), ]); return response()->json(['success' => true, 'tabella' => $tabella]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function destroyTabellaMillesimale(Stabile $stabile, $tabellaId) { try { $tabella = $stabile->tabelleMillesimali()->findOrFail($tabellaId); $tabella->delete(); return response()->json(['success' => true]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } /** * API per gestione contatori */ public function storeContatore(Request $request, Stabile $stabile) { $request->validate([ 'nome' => 'required|string|max:255', 'tipo' => 'required|in:gas,elettricita,acqua,riscaldamento,altro', 'ubicazione' => 'required|string|max:255', 'unita_misura' => 'required|string|max:50', 'numero_serie' => 'nullable|string|max:100', ]); try { $contatore = $stabile->contatori()->create([ 'nome' => $request->nome, 'tipo' => $request->tipo, 'ubicazione' => $request->ubicazione, 'unita_misura' => $request->unita_misura, 'numero_serie' => $request->numero_serie, 'stato' => 'attivo', 'created_by' => Auth::id(), ]); return response()->json(['success' => true, 'contatore' => $contatore]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function storeLetturaContatore(Request $request, Stabile $stabile, $contatoreId) { $request->validate([ 'valore' => 'required|numeric|min:0', 'data_lettura' => 'required|date', ]); try { $contatore = $stabile->contatori()->findOrFail($contatoreId); $lettura = $contatore->lettureContatore()->create([ 'valore' => $request->valore, 'data_lettura' => $request->data_lettura, 'rilevata_da' => Auth::id(), ]); return response()->json(['success' => true, 'lettura' => $lettura]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } /** * API per gestione chiavi */ public function storeChiave(Request $request, Stabile $stabile) { $request->validate([ 'codice' => 'required|string|max:50', 'descrizione' => 'required|string|max:255', 'tipo' => 'required|in:portone,ascensore,cantina,terrazza,garage,altro', 'zona' => 'nullable|string|max:100', 'numero_copie' => 'required|integer|min:1', 'note' => 'nullable|string|max:1000', ]); try { $chiave = $stabile->chiavi()->create([ 'codice' => $request->codice, 'descrizione' => $request->descrizione, 'tipo' => $request->tipo, 'zona' => $request->zona, 'numero_copie' => $request->numero_copie, 'stato' => 'disponibile', 'note' => $request->note, 'created_by' => Auth::id(), ]); return response()->json(['success' => true, 'chiave' => $chiave]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function assegnaChiave(Request $request, Stabile $stabile, $chiaveId) { $request->validate([ 'assegnatario' => 'required|string|max:255', ]); try { $chiave = $stabile->chiavi()->findOrFail($chiaveId); if ($chiave->stato !== 'disponibile') { return response()->json(['success' => false, 'message' => 'Chiave non disponibile']); } DB::transaction(function () use ($chiave, $request) { // Crea movimento MovimentoChiave::create([ 'chiave_id' => $chiave->id, 'tipo_movimento' => 'assegnazione', 'assegnatario' => $request->assegnatario, 'data_movimento' => now(), 'created_by' => Auth::id(), ]); // Aggiorna stato chiave $chiave->update([ 'stato' => 'in_uso', 'assegnatario_attuale' => $request->assegnatario, ]); }); return response()->json(['success' => true, 'chiave' => $chiave]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function restituisciChiave(Request $request, Stabile $stabile, $chiaveId) { try { $chiave = $stabile->chiavi()->findOrFail($chiaveId); if ($chiave->stato !== 'in_uso') { return response()->json(['success' => false, 'message' => 'Chiave non in uso']); } DB::transaction(function () use ($chiave) { // Crea movimento MovimentoChiave::create([ 'chiave_id' => $chiave->id, 'tipo_movimento' => 'restituzione', 'assegnatario' => $chiave->assegnatario_attuale, 'data_movimento' => now(), 'created_by' => Auth::id(), ]); // Aggiorna stato chiave $chiave->update([ 'stato' => 'disponibile', 'assegnatario_attuale' => null, ]); }); return response()->json(['success' => true, 'chiave' => $chiave]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } /** * API per gestione fondi */ public function storeFondo(Request $request, Stabile $stabile) { $request->validate([ 'nome' => 'required|string|max:255', 'tipo' => 'required|in:ordinario,straordinario,riserva,lavori,manutenzione,altro', 'saldo_iniziale' => 'required|numeric', 'obiettivo_saldo' => 'nullable|numeric', 'descrizione' => 'nullable|string|max:1000', 'attivo' => 'boolean', ]); try { $fondo = $stabile->fondiCondominiali()->create([ 'nome' => $request->nome, 'tipo' => $request->tipo, 'saldo_iniziale' => $request->saldo_iniziale, 'saldo_attuale' => $request->saldo_iniziale, 'obiettivo_saldo' => $request->obiettivo_saldo, 'descrizione' => $request->descrizione, 'stato' => $request->attivo ? 'attivo' : 'inattivo', 'created_by' => Auth::id(), ]); return response()->json(['success' => true, 'fondo' => $fondo]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } /** * API per import GESCON */ public function importStabile(Request $request, Stabile $stabile) { try { // Simulazione import da GESCON // TODO: Implementare connessione reale al database GESCON // Per ora simuliamo un import di successo sleep(2); // Simula processamento // Aggiorna alcuni campi con dati "importati" $stabile->update([ 'anno_costruzione' => 1985, 'superficie_totale' => 1200, 'numero_ascensori' => 1, 'numero_garage' => 15, 'note' => 'Dati importati da GESCON il ' . now()->format('d/m/Y H:i'), 'updated_by' => Auth::id(), ]); return response()->json([ 'success' => true, 'message' => 'Importazione completata con successo', 'stabile' => $stabile ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function importMillesimi(Request $request, Stabile $stabile) { try { // Simulazione import tabelle millesimali da GESCON sleep(1); // Crea tabelle di esempio $tabelle = [ ['nome' => 'Proprietà Generale', 'descrizione' => 'Tabella millesimale principale'], ['nome' => 'Scale', 'descrizione' => 'Spese scale e ascensore'], ['nome' => 'Riscaldamento', 'descrizione' => 'Ripartizione spese riscaldamento'], ]; foreach ($tabelle as $tabella) { $stabile->tabelleMillesimali()->firstOrCreate( ['nome' => $tabella['nome']], [ 'descrizione' => $tabella['descrizione'], 'stato' => 'attiva', 'created_by' => Auth::id(), ] ); } return response()->json([ 'success' => true, 'message' => 'Tabelle millesimali importate con successo' ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function importUnita(Request $request, Stabile $stabile) { try { // Simulazione import unità immobiliari sleep(2); return response()->json([ 'success' => true, 'message' => 'Import unità immobiliari completato (funzionalità da implementare)' ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } public function importFinanziari(Request $request, Stabile $stabile) { try { // Simulazione import dati finanziari sleep(3); return response()->json([ 'success' => true, 'message' => 'Import dati finanziari completato (funzionalità da implementare)' ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()]); } } // ================================ // UTILITY METHODS // ================================ /** * Autorizzazione accesso stabile */ private function authorizeAccess(Stabile $stabile) { if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { abort(403, 'Accesso non autorizzato a questo stabile'); } } /** * Genera QR Code per chiave */ private function generateQrCode(Stabile $stabile, string $nomeChiave): string { return "NETGESCON-KEY-{$stabile->id}-" . strtoupper(Str::slug($nomeChiave)) . "-" . time(); } }