commit 24992624f093aad822f18aa18ec4410bb2870ed0 Author: Pikappa2 Date: Sun Jun 29 23:39:33 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/AmministratoreController.php b/AmministratoreController.php new file mode 100644 index 00000000..b91212a7 --- /dev/null +++ b/AmministratoreController.php @@ -0,0 +1,146 @@ +middleware('permission:view-amministratori', ['only' => ['index']]); // Permesso per visualizzare la lista + $this->middleware('permission:manage-amministratori', ['except' => ['index', 'show']]); // Permesso per tutte le altre azioni CRUD + } + + /** + * Display a listing of the resource. + */ + public function index() + { + // Gate::authorize('view-amministratori'); // Il middleware nel costruttore è sufficiente + $amministratori = Amministratore::with('user')->paginate(10); + return view('superadmin.amministratori.index', compact('amministratori')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $usersWithoutAdminRole = User::doesntHave('amministratore')->get(); // Utenti non ancora associati a un amministratore + return view('superadmin.amministratori.create', compact('usersWithoutAdminRole')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users,email', + 'password' => 'required|string|min:8|confirmed', + 'nome' => 'required|string|max:255', + 'cognome' => 'required|string|max:255', + 'denominazione_studio' => 'nullable|string|max:255', + 'partita_iva' => 'nullable|string|max:20|unique:amministratori,partita_iva', + 'codice_fiscale_studio' => 'nullable|string|max:20', + 'indirizzo_studio' => 'nullable|string|max:255', + 'cap_studio' => 'nullable|string|max:10', + 'citta_studio' => 'nullable|string|max:60', + 'provincia_studio' => 'nullable|string|max:2', + 'telefono_studio' => 'nullable|string|max:20', + 'email_studio' => 'nullable|string|email|max:255', + 'pec_studio' => 'nullable|string|email|max:255', + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'email_verified_at' => now(), + ]); + $user->assignRole('admin'); // Assegna il ruolo 'admin' al nuovo utente per coerenza con le rotte + + Amministratore::create([ + 'user_id' => $user->id, + 'nome' => $request->nome, + 'cognome' => $request->cognome, + 'denominazione_studio' => $request->denominazione_studio, + 'partita_iva' => $request->partita_iva, + 'codice_fiscale_studio' => $request->codice_fiscale_studio, + 'indirizzo_studio' => $request->indirizzo_studio, + 'cap_studio' => $request->cap_studio, + 'citta_studio' => $request->citta_studio, + 'provincia_studio' => $request->provincia_studio, + 'telefono_studio' => $request->telefono_studio, + 'email_studio' => $request->email_studio, + 'pec_studio' => $request->pec_studio, + ]); + + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore creato con successo.'); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Amministratore $amministratore) // Aggiunto metodo edit + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + // Recupera gli utenti che non sono ancora collegati a un record Amministratore + $usersWithoutAdminRole = User::doesntHave('amministratore')->get(); + // Includi l'utente attualmente collegato a questo amministratore nella lista + $usersWithoutAdminRole = $usersWithoutAdminRole->merge([$amministratore->user]); + return view('superadmin.amministratori.edit', compact('amministratore', 'usersWithoutAdminRole')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Amministratore $amministratore) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $request->validate([ + 'user_id' => 'required|exists:users,id|unique:amministratori,user_id,' . $amministratore->id_amministratore . ',id_amministratore', + 'nome' => 'required|string|max:255', + 'cognome' => 'required|string|max:255', + 'denominazione_studio' => 'nullable|string|max:255', + 'partita_iva' => ['nullable', 'string', 'max:20', Rule::unique('amministratori')->ignore($amministratore->id_amministratore, 'id_amministratore')], // Corretto id a id_amministratore + 'codice_fiscale_studio' => 'nullable|string|max:20', + 'indirizzo_studio' => 'nullable|string|max:255', + 'cap_studio' => 'nullable|string|max:10', + 'citta_studio' => 'nullable|string|max:255', + 'provincia_studio' => 'nullable|string|max:2', + 'telefono_studio' => 'nullable|string|max:20', + 'email_studio' => 'nullable|email|max:255', + 'pec_studio' => 'nullable|email|max:255', + ]); + + // Aggiorna i dati dell'amministratore + $amministratore->update($request->all()); + + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Amministratore $amministratore) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $amministratore->user->delete(); // Elimina anche l'utente associato + $amministratore->delete(); + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore eliminato con successo.'); + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..ba93f413 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# sb1-netgescon + +[Edit in StackBlitz next generation editor ⚡️](https://stackblitz.com/~/github.com/Pikappa2/sb1-netgescon) \ No newline at end of file diff --git a/app/Http/Controllers/Admin/AssembleaController.php b/app/Http/Controllers/Admin/AssembleaController.php new file mode 100644 index 00000000..a337cc87 --- /dev/null +++ b/app/Http/Controllers/Admin/AssembleaController.php @@ -0,0 +1,528 @@ +amministratore->id_amministratore ?? null; + + $assemblee = Assemblea::with(['stabile', 'creatoDa']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->orderBy('data_prima_convocazione', 'desc') + ->paginate(15); + + // Statistiche + $stats = [ + 'assemblee_programmate' => Assemblea::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->whereIn('stato', ['bozza', 'convocata'])->count(), + + 'assemblee_svolte' => Assemblea::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('stato', 'svolta')->count(), + + 'convocazioni_inviate' => Convocazione::whereHas('assemblea.stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('data_invio', '>=', now()->subDays(30))->count(), + + 'delibere_approvate' => OrdineGiorno::whereHas('assemblea.stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('esito_votazione', 'approvato')->count(), + ]; + + return view('admin.assemblee.index', compact('assemblee', 'stats')); + } + + /** + * Form creazione assemblea + */ + public function create() + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + $stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get(); + + return view('admin.assemblee.create', compact('stabili')); + } + + /** + * Store assemblea + */ + public function store(Request $request) + { + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'tipo' => 'required|in:ordinaria,straordinaria', + 'data_prima_convocazione' => 'required|date|after:now', + 'data_seconda_convocazione' => 'required|date|after:data_prima_convocazione', + 'luogo' => 'required|string|max:255', + 'note' => 'nullable|string', + 'ordine_giorno' => 'required|array|min:1', + 'ordine_giorno.*.titolo' => 'required|string|max:255', + 'ordine_giorno.*.descrizione' => 'required|string', + 'ordine_giorno.*.tipo_voce' => 'required|in:discussione,delibera,spesa,preventivo,altro', + 'ordine_giorno.*.importo_spesa' => 'nullable|numeric|min:0', + 'ordine_giorno.*.tabella_millesimale_id' => 'nullable|exists:tabelle_millesimali,id', + ]); + + DB::beginTransaction(); + try { + $assemblea = Assemblea::create([ + 'stabile_id' => $request->stabile_id, + 'tipo' => $request->tipo, + 'data_prima_convocazione' => $request->data_prima_convocazione, + 'data_seconda_convocazione' => $request->data_seconda_convocazione, + 'luogo' => $request->luogo, + 'note' => $request->note, + 'stato' => 'bozza', + 'creato_da_user_id' => Auth::id(), + ]); + + // Crea ordine del giorno + foreach ($request->ordine_giorno as $index => $punto) { + OrdineGiorno::create([ + 'assemblea_id' => $assemblea->id, + 'numero_punto' => $index + 1, + 'titolo' => $punto['titolo'], + 'descrizione' => $punto['descrizione'], + 'tipo_voce' => $punto['tipo_voce'], + 'importo_spesa' => $punto['importo_spesa'] ?? null, + 'tabella_millesimale_id' => $punto['tabella_millesimale_id'] ?? null, + ]); + } + + DB::commit(); + + return redirect()->route('admin.assemblee.show', $assemblea) + ->with('success', 'Assemblea creata con successo.'); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]); + } + } + + /** + * Visualizza assemblea + */ + public function show(Assemblea $assemblea) + { + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $assemblea->load([ + 'stabile', + 'ordineGiorno.preventivo', + 'ordineGiorno.tabellaMillesimale', + 'convocazioni.soggetto', + 'presenze.soggetto', + 'verbale', + 'documenti' + ]); + + // Calcola statistiche convocazioni + $statsConvocazioni = [ + 'totale_inviate' => $assemblea->convocazioni->count(), + 'consegnate' => $assemblea->convocazioni->where('esito_invio', 'consegnato')->count(), + 'lette' => $assemblea->convocazioni->where('esito_invio', 'letto')->count(), + 'conferme_presenza' => $assemblea->convocazioni->where('presenza_confermata', true)->count(), + 'deleghe' => $assemblea->convocazioni->where('delega_presente', true)->count(), + ]; + + // Calcola quorum se assemblea svolta + $quorum = null; + if ($assemblea->stato === 'svolta') { + $quorum = $assemblea->calcolaQuorum(); + } + + return view('admin.assemblee.show', compact('assemblea', 'statsConvocazioni', 'quorum')); + } + + /** + * Invia convocazioni + */ + public function inviaConvocazioni(Request $request, Assemblea $assemblea) + { + $request->validate([ + 'canali' => 'required|array', + 'canali.*' => 'in:email,pec,whatsapp,telegram,raccomandata,mano,portiere', + ]); + + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + if ($assemblea->stato !== 'bozza') { + return back()->withErrors(['error' => 'Le convocazioni possono essere inviate solo per assemblee in bozza.']); + } + + try { + $convocazioniInviate = $assemblea->inviaConvocazioni($request->canali, Auth::id()); + + return back()->with('success', "Inviate {$convocazioniInviate} convocazioni con successo."); + + } catch (\Exception $e) { + return back()->withErrors(['error' => 'Errore nell\'invio convocazioni: ' . $e->getMessage()]); + } + } + + /** + * Gestione presenze + */ + public function presenze(Assemblea $assemblea) + { + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $unitaImmobiliari = $assemblea->stabile->unitaImmobiliari() + ->with(['proprieta.soggetto']) + ->get(); + + $presenzeEsistenti = $assemblea->presenze() + ->with(['soggetto', 'unitaImmobiliare']) + ->get() + ->keyBy(function($presenza) { + return $presenza->soggetto_id . '_' . $presenza->unita_immobiliare_id; + }); + + return view('admin.assemblee.presenze', compact('assemblea', 'unitaImmobiliari', 'presenzeEsistenti')); + } + + /** + * Registra presenza + */ + public function registraPresenza(Request $request, Assemblea $assemblea) + { + $request->validate([ + 'presenze' => 'required|array', + 'presenze.*.soggetto_id' => 'required|exists:soggetti,id_soggetto', + 'presenze.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id_unita', + 'presenze.*.tipo_presenza' => 'required|in:presente,delegato,assente', + 'presenze.*.millesimi_rappresentati' => 'required|numeric|min:0', + 'presenze.*.delegante_soggetto_id' => 'nullable|exists:soggetti,id_soggetto', + ]); + + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + DB::beginTransaction(); + try { + // Elimina presenze esistenti + $assemblea->presenze()->delete(); + + // Registra nuove presenze + foreach ($request->presenze as $presenzaData) { + if ($presenzaData['tipo_presenza'] !== 'assente') { + PresenzaAssemblea::create([ + 'assemblea_id' => $assemblea->id, + 'soggetto_id' => $presenzaData['soggetto_id'], + 'unita_immobiliare_id' => $presenzaData['unita_immobiliare_id'], + 'tipo_presenza' => $presenzaData['tipo_presenza'], + 'millesimi_rappresentati' => $presenzaData['millesimi_rappresentati'], + 'delegante_soggetto_id' => $presenzaData['delegante_soggetto_id'] ?? null, + 'ora_arrivo' => now(), + ]); + } + } + + // Aggiorna stato assemblea + $assemblea->update(['stato' => 'svolta', 'data_svolgimento' => now()]); + + DB::commit(); + return back()->with('success', 'Presenze registrate con successo.'); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore nella registrazione presenze: ' . $e->getMessage()]); + } + } + + /** + * Gestione votazioni + */ + public function votazioni(Assemblea $assemblea, OrdineGiorno $ordineGiorno) + { + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + if ($assemblea->stato !== 'svolta') { + return back()->withErrors(['error' => 'Le votazioni possono essere gestite solo per assemblee svolte.']); + } + + $presenze = $assemblea->presenze()->with(['soggetto', 'unitaImmobiliare'])->get(); + $votazioniEsistenti = $ordineGiorno->votazioni() + ->get() + ->keyBy(function($voto) { + return $voto->soggetto_id . '_' . $voto->unita_immobiliare_id; + }); + + return view('admin.assemblee.votazioni', compact('assemblea', 'ordineGiorno', 'presenze', 'votazioniEsistenti')); + } + + /** + * Registra votazioni + */ + public function registraVotazioni(Request $request, Assemblea $assemblea, OrdineGiorno $ordineGiorno) + { + $request->validate([ + 'voti' => 'required|array', + 'voti.*.soggetto_id' => 'required|exists:soggetti,id_soggetto', + 'voti.*.unita_immobiliare_id' => 'required|exists:unita_immobiliari,id_unita', + 'voti.*.voto' => 'required|in:favorevole,contrario,astenuto,non_votante', + 'voti.*.millesimi_voto' => 'required|numeric|min:0', + ]); + + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + DB::beginTransaction(); + try { + // Elimina votazioni esistenti + $ordineGiorno->votazioni()->delete(); + + // Registra nuovi voti + foreach ($request->voti as $votoData) { + if ($votoData['voto'] !== 'non_votante') { + Votazione::create([ + 'ordine_giorno_id' => $ordineGiorno->id, + 'soggetto_id' => $votoData['soggetto_id'], + 'unita_immobiliare_id' => $votoData['unita_immobiliare_id'], + 'voto' => $votoData['voto'], + 'millesimi_voto' => $votoData['millesimi_voto'], + 'data_voto' => now(), + ]); + } + } + + // Calcola risultato + $risultato = $ordineGiorno->calcolaRisultato(); + + DB::commit(); + + return back()->with('success', 'Votazioni registrate. Esito: ' . $risultato['esito']); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore nella registrazione voti: ' . $e->getMessage()]); + } + } + + /** + * Gestione verbale + */ + public function verbale(Assemblea $assemblea) + { + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $assemblea->load(['ordineGiorno.delibera', 'presenze.soggetto']); + $verbale = $assemblea->verbale; + + return view('admin.assemblee.verbale', compact('assemblea', 'verbale')); + } + + /** + * Store/Update verbale + */ + public function storeVerbale(Request $request, Assemblea $assemblea) + { + $request->validate([ + 'testo_verbale' => 'required|string', + 'allegati.*' => 'nullable|file|max:10240', + ]); + + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + try { + $numeroVerbale = $this->generaNumeroVerbale($assemblea); + + // Gestione allegati + $allegati = []; + if ($request->hasFile('allegati')) { + foreach ($request->file('allegati') as $file) { + $path = $file->store('verbali/allegati', 'public'); + $allegati[] = [ + 'nome' => $file->getClientOriginalName(), + 'path' => $path, + 'size' => $file->getSize(), + ]; + } + } + + $verbale = Verbale::updateOrCreate( + ['assemblea_id' => $assemblea->id], + [ + 'numero_verbale' => $numeroVerbale, + 'testo_verbale' => $request->testo_verbale, + 'allegati' => $allegati, + 'data_redazione' => now(), + 'redatto_da_user_id' => Auth::id(), + 'stato' => 'definitivo', + ] + ); + + return back()->with('success', 'Verbale salvato con successo.'); + + } catch (\Exception $e) { + return back()->withErrors(['error' => 'Errore nel salvataggio verbale: ' . $e->getMessage()]); + } + } + + /** + * Invia verbale ai condomini + */ + public function inviaVerbale(Request $request, Assemblea $assemblea) + { + // Verifica accesso + if ($assemblea->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $verbale = $assemblea->verbale; + if (!$verbale) { + return back()->withErrors(['error' => 'Nessun verbale da inviare.']); + } + + try { + // Invia verbale a tutti i condomini + $inviiRiusciti = $this->inviaVerbaleCondomini($assemblea, $verbale); + + $verbale->update([ + 'inviato_condomini' => true, + 'data_invio_condomini' => now(), + 'stato' => 'inviato', + ]); + + return back()->with('success', "Verbale inviato a {$inviiRiusciti} condomini."); + + } catch (\Exception $e) { + return back()->withErrors(['error' => 'Errore nell\'invio verbale: ' . $e->getMessage()]); + } + } + + /** + * Registro protocollo + */ + public function registroProtocollo(Request $request) + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + + $query = RegistroProtocollo::with(['assemblea.stabile', 'soggettoDestinatario', 'creatoDa']) + ->whereHas('assemblea.stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }); + + // Filtri + if ($request->filled('tipo_comunicazione')) { + $query->where('tipo_comunicazione', $request->tipo_comunicazione); + } + + if ($request->filled('data_da')) { + $query->where('data_invio', '>=', $request->data_da); + } + + if ($request->filled('data_a')) { + $query->where('data_invio', '<=', $request->data_a); + } + + $comunicazioni = $query->orderBy('data_invio', 'desc')->paginate(20); + + return view('admin.assemblee.registro-protocollo', compact('comunicazioni')); + } + + /** + * Genera numero verbale + */ + private function generaNumeroVerbale(Assemblea $assemblea) + { + $anno = $assemblea->data_prima_convocazione->year; + $ultimoVerbale = Verbale::whereHas('assemblea', function($q) use ($anno) { + $q->whereYear('data_prima_convocazione', $anno); + })->orderBy('numero_verbale', 'desc')->first(); + + if ($ultimoVerbale) { + $numero = intval(substr($ultimoVerbale->numero_verbale, -3)) + 1; + } else { + $numero = 1; + } + + return 'VERB/' . $anno . '/' . str_pad($numero, 3, '0', STR_PAD_LEFT); + } + + /** + * Invia verbale ai condomini + */ + private function inviaVerbaleCondomini(Assemblea $assemblea, Verbale $verbale) + { + $unitaImmobiliari = $assemblea->stabile->unitaImmobiliari()->with('proprieta.soggetto')->get(); + $inviiRiusciti = 0; + + foreach ($unitaImmobiliari as $unita) { + foreach ($unita->proprieta as $proprieta) { + $soggetto = $proprieta->soggetto; + + if ($soggetto->email) { + // Simula invio email + $numeroProtocollo = RegistroProtocollo::generaNumeroProtocollo(); + + RegistroProtocollo::create([ + 'numero_protocollo' => $numeroProtocollo, + 'tipo_comunicazione' => 'verbale', + 'assemblea_id' => $assemblea->id, + 'soggetto_destinatario_id' => $soggetto->id_soggetto, + 'oggetto' => "Verbale Assemblea {$assemblea->tipo} del {$assemblea->data_prima_convocazione->format('d/m/Y')}", + 'contenuto' => $verbale->testo_verbale, + 'canale' => 'email', + 'data_invio' => now(), + 'esito' => 'inviato', + 'creato_da_user_id' => Auth::id(), + ]); + + $inviiRiusciti++; + } + } + } + + return $inviiRiusciti; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/BilancioController.php b/app/Http/Controllers/Admin/BilancioController.php new file mode 100644 index 00000000..5fe8ea59 --- /dev/null +++ b/app/Http/Controllers/Admin/BilancioController.php @@ -0,0 +1,386 @@ +amministratore->id_amministratore ?? null; + + $bilanci = Bilancio::with(['stabile', 'gestione', 'approvatoDa']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->orderBy('anno_esercizio', 'desc') + ->orderBy('created_at', 'desc') + ->paginate(15); + + // Statistiche + $stats = [ + 'bilanci_aperti' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->whereIn('stato', ['bozza', 'provvisorio'])->count(), + + 'bilanci_approvati' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('stato', 'approvato')->count(), + + 'conguagli_da_pagare' => Conguaglio::whereHas('bilancio.stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('stato', 'calcolato')->count(), + + 'totale_avanzi' => Bilancio::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('risultato_gestione', '>', 0)->sum('risultato_gestione'), + ]; + + return view('admin.bilanci.index', compact('bilanci', 'stats')); + } + + /** + * Form creazione bilancio + */ + public function create() + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + $stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get(); + + return view('admin.bilanci.create', compact('stabili')); + } + + /** + * Store bilancio + */ + public function store(Request $request) + { + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'gestione_id' => 'required|exists:gestioni,id_gestione', + 'anno_esercizio' => 'required|integer|min:2020|max:2030', + 'data_inizio_esercizio' => 'required|date', + 'data_fine_esercizio' => 'required|date|after:data_inizio_esercizio', + 'tipo_gestione' => 'required|in:ordinaria,riscaldamento,straordinaria,acqua,altro', + 'descrizione' => 'required|string|max:255', + ]); + + DB::beginTransaction(); + try { + $bilancio = Bilancio::create([ + 'stabile_id' => $request->stabile_id, + 'gestione_id' => $request->gestione_id, + 'anno_esercizio' => $request->anno_esercizio, + 'data_inizio_esercizio' => $request->data_inizio_esercizio, + 'data_fine_esercizio' => $request->data_fine_esercizio, + 'tipo_gestione' => $request->tipo_gestione, + 'descrizione' => $request->descrizione, + 'stato' => 'bozza', + 'versione' => 1, + ]); + + // Importa movimenti contabili del periodo + $this->importaMovimentiContabili($bilancio); + + DB::commit(); + + return redirect()->route('admin.bilanci.show', $bilancio) + ->with('success', 'Bilancio creato con successo.'); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore durante la creazione: ' . $e->getMessage()]); + } + } + + /** + * Visualizza bilancio + */ + public function show(Bilancio $bilancio) + { + // Verifica accesso + if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $bilancio->load([ + 'stabile', + 'gestione', + 'scritture.dettagli.conto', + 'conguagli.unitaImmobiliare', + 'quadrature', + 'rimborsiAssicurativi' + ]); + + // Calcola totali aggiornati + $bilancio->calcolaTotali(); + + return view('admin.bilanci.show', compact('bilancio')); + } + + /** + * Importa movimenti contabili nel bilancio + */ + private function importaMovimentiContabili(Bilancio $bilancio) + { + $movimenti = MovimentoContabile::where('stabile_id', $bilancio->stabile_id) + ->where('gestione_id', $bilancio->gestione_id) + ->whereBetween('data_registrazione', [ + $bilancio->data_inizio_esercizio, + $bilancio->data_fine_esercizio + ]) + ->with('dettagli') + ->get(); + + foreach ($movimenti as $movimento) { + $this->creaScritturaDaMovimento($bilancio, $movimento); + } + } + + /** + * Crea scrittura bilancio da movimento contabile + */ + private function creaScritturaDaMovimento(Bilancio $bilancio, MovimentoContabile $movimento) + { + $scrittura = ScritturaBilancio::create([ + 'bilancio_id' => $bilancio->id, + 'numero_scrittura' => $this->generaNumeroScrittura($bilancio), + 'data_scrittura' => $movimento->data_registrazione, + 'descrizione' => $movimento->descrizione, + 'tipo_scrittura' => 'gestione', + 'importo_totale' => $movimento->importo_totale, + 'movimento_contabile_id' => $movimento->id, + 'creato_da_user_id' => Auth::id(), + ]); + + // Crea dettagli in partita doppia + foreach ($movimento->dettagli as $dettaglio) { + $scrittura->dettagli()->create([ + 'conto_id' => $dettaglio->conto_id ?? $this->getContoDefault($movimento->tipo_movimento), + 'importo_dare' => $dettaglio->importo_dare, + 'importo_avere' => $dettaglio->importo_avere, + 'descrizione_dettaglio' => $dettaglio->descrizione, + ]); + } + + return $scrittura; + } + + /** + * Calcola conguagli + */ + public function calcolaConguagli(Bilancio $bilancio) + { + // Verifica accesso + if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + DB::beginTransaction(); + try { + $bilancio->calcolaConguagli(); + + DB::commit(); + return back()->with('success', 'Conguagli calcolati con successo.'); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore nel calcolo conguagli: ' . $e->getMessage()]); + } + } + + /** + * Genera rate conguaglio + */ + public function generaRateConguaglio(Request $request, Bilancio $bilancio) + { + $request->validate([ + 'conguaglio_ids' => 'required|array', + 'numero_rate' => 'required|integer|min:1|max:12', + 'data_inizio' => 'required|date', + ]); + + // Verifica accesso + if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + DB::beginTransaction(); + try { + $dataInizio = Carbon::parse($request->data_inizio); + $rateGenerate = 0; + + foreach ($request->conguaglio_ids as $conguaglioId) { + $conguaglio = Conguaglio::findOrFail($conguaglioId); + + if ($conguaglio->bilancio_id !== $bilancio->id) { + continue; + } + + $rate = $conguaglio->generaRate($request->numero_rate, $dataInizio, Auth::id()); + $rateGenerate += $rate->count(); + } + + DB::commit(); + return back()->with('success', "Generate {$rateGenerate} rate di conguaglio."); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore nella generazione rate: ' . $e->getMessage()]); + } + } + + /** + * Quadratura bilancio + */ + public function quadratura(Request $request, Bilancio $bilancio) + { + $request->validate([ + 'data_quadratura' => 'required|date', + 'saldo_banca_effettivo' => 'required|numeric', + ]); + + // Verifica accesso + if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + // Calcola saldo contabile + $saldoContabile = $this->calcolaSaldoContabile($bilancio, $request->data_quadratura); + + // Calcola totali crediti/debiti + $totaleCrediti = $bilancio->conguagli()->where('tipo_conguaglio', 'a_credito')->sum('conguaglio_dovuto'); + $totaleDebiti = $bilancio->conguagli()->where('tipo_conguaglio', 'a_debito')->sum('conguaglio_dovuto'); + + // Calcola rate + $totaleRateEmesse = $this->calcolaTotaleRateEmesse($bilancio); + $totaleRateIncassate = $this->calcolaTotaleRateIncassate($bilancio); + + $differenza = $request->saldo_banca_effettivo - $saldoContabile; + + $quadratura = Quadratura::create([ + 'bilancio_id' => $bilancio->id, + 'data_quadratura' => $request->data_quadratura, + 'saldo_banca_effettivo' => $request->saldo_banca_effettivo, + 'saldo_contabile_calcolato' => $saldoContabile, + 'differenza' => $differenza, + 'totale_crediti_condomini' => $totaleCrediti, + 'totale_debiti_condomini' => $totaleDebiti, + 'totale_rate_emesse' => $totaleRateEmesse, + 'totale_rate_incassate' => $totaleRateIncassate, + 'quadratura_ok' => abs($differenza) < 0.01, + 'verificato_da_user_id' => Auth::id(), + ]); + + return back()->with('success', 'Quadratura eseguita con successo.'); + } + + /** + * Chiusura esercizio + */ + public function chiusuraEsercizio(Request $request, Bilancio $bilancio) + { + $request->validate([ + 'motivo_chiusura' => 'required|string', + ]); + + // Verifica accesso e stato + if ($bilancio->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + if ($bilancio->stato !== 'approvato') { + return back()->withErrors(['error' => 'Il bilancio deve essere approvato prima della chiusura.']); + } + + DB::beginTransaction(); + try { + // Genera scritture di chiusura + $bilancio->generaScritture ChiusuraEsercizio(Auth::id()); + + // Aggiorna stato bilancio + $bilancio->update([ + 'stato' => 'chiuso', + 'data_chiusura' => now(), + 'chiuso_da_user_id' => Auth::id(), + ]); + + DB::commit(); + return back()->with('success', 'Esercizio chiuso con successo.'); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore nella chiusura: ' . $e->getMessage()]); + } + } + + /** + * Calcola saldo contabile alla data + */ + private function calcolaSaldoContabile(Bilancio $bilancio, $data) + { + // Implementazione calcolo saldo contabile + return $bilancio->totale_entrate - $bilancio->totale_uscite; + } + + /** + * Calcola totale rate emesse + */ + private function calcolaTotaleRateEmesse(Bilancio $bilancio) + { + // Implementazione calcolo rate emesse + return 0; // Placeholder + } + + /** + * Calcola totale rate incassate + */ + private function calcolaTotaleRateIncassate(Bilancio $bilancio) + { + // Implementazione calcolo rate incassate + return 0; // Placeholder + } + + /** + * Genera numero scrittura + */ + private function generaNumeroScrittura(Bilancio $bilancio) + { + $ultimaScrittura = ScritturaBilancio::where('bilancio_id', $bilancio->id) + ->orderBy('numero_scrittura', 'desc') + ->first(); + + if ($ultimaScrittura) { + $numero = intval(substr($ultimaScrittura->numero_scrittura, -4)) + 1; + } else { + $numero = 1; + } + + return 'SCR/' . $bilancio->anno_esercizio . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } + + /** + * Get conto default per tipo movimento + */ + private function getContoDefault($tipoMovimento) + { + // Implementazione per ottenere conto default + return 1; // Placeholder + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ContabilitaController.php b/app/Http/Controllers/Admin/ContabilitaController.php new file mode 100644 index 00000000..f4ff5ace --- /dev/null +++ b/app/Http/Controllers/Admin/ContabilitaController.php @@ -0,0 +1,273 @@ +amministratore->id_amministratore ?? null; + + // Statistiche generali + $stats = [ + 'movimenti_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->whereMonth('data_registrazione', now()->month)->count(), + + 'importo_entrate_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('tipo_movimento', 'entrata') + ->whereMonth('data_registrazione', now()->month) + ->sum('importo_totale'), + + 'importo_uscite_mese' => MovimentoContabile::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('tipo_movimento', 'uscita') + ->whereMonth('data_registrazione', now()->month) + ->sum('importo_totale'), + ]; + + // Ultimi movimenti + $ultimiMovimenti = MovimentoContabile::with(['stabile', 'gestione', 'fornitore']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->orderBy('created_at', 'desc') + ->take(10) + ->get(); + + return view('admin.contabilita.index', compact('stats', 'ultimiMovimenti')); + } + + /** + * Lista movimenti contabili + */ + public function movimenti(Request $request) + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + + $query = MovimentoContabile::with(['stabile', 'gestione', 'fornitore']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }); + + // Filtri + if ($request->filled('stabile_id')) { + $query->where('stabile_id', $request->stabile_id); + } + + if ($request->filled('gestione_id')) { + $query->where('gestione_id', $request->gestione_id); + } + + if ($request->filled('tipo_movimento')) { + $query->where('tipo_movimento', $request->tipo_movimento); + } + + if ($request->filled('data_da')) { + $query->where('data_registrazione', '>=', $request->data_da); + } + + if ($request->filled('data_a')) { + $query->where('data_registrazione', '<=', $request->data_a); + } + + $movimenti = $query->orderBy('data_registrazione', 'desc')->paginate(20); + + // Dati per i filtri + $stabili = Stabile::where('amministratore_id', $amministratore_id)->get(); + $gestioni = Gestione::whereIn('stabile_id', $stabili->pluck('id_stabile'))->get(); + + return view('admin.contabilita.movimenti', compact('movimenti', 'stabili', 'gestioni')); + } + + /** + * Form registrazione movimento + */ + public function registrazione() + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + + $stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get(); + $fornitori = Fornitore::where('amministratore_id', $amministratore_id)->get(); + + return view('admin.contabilita.registrazione', compact('stabili', 'fornitori')); + } + + /** + * Store registrazione movimento + */ + public function storeRegistrazione(Request $request) + { + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'gestione_id' => 'required|exists:gestioni,id_gestione', + 'tipo_movimento' => 'required|in:entrata,uscita', + 'data_documento' => 'required|date', + 'numero_documento' => 'required|string|max:50', + 'descrizione' => 'required|string|max:255', + 'importo_totale' => 'required|numeric|min:0', + 'fornitore_id' => 'nullable|exists:fornitori,id_fornitore', + 'ritenuta_acconto' => 'nullable|numeric|min:0', + 'dettagli' => 'required|array|min:1', + 'dettagli.*.voce_spesa_id' => 'required|exists:voci_spesa,id', + 'dettagli.*.importo' => 'required|numeric|min:0', + 'dettagli.*.tabella_millesimale_id' => 'nullable|exists:tabelle_millesimali,id', + ]); + + DB::beginTransaction(); + try { + // Genera protocollo univoco + $protocollo = $this->generaProtocollo($request->stabile_id); + + // Crea movimento principale + $movimento = MovimentoContabile::create([ + 'stabile_id' => $request->stabile_id, + 'gestione_id' => $request->gestione_id, + 'fornitore_id' => $request->fornitore_id, + 'protocollo' => $protocollo, + 'data_registrazione' => now(), + 'data_documento' => $request->data_documento, + 'numero_documento' => $request->numero_documento, + 'descrizione' => $request->descrizione, + 'tipo_movimento' => $request->tipo_movimento, + 'importo_totale' => $request->importo_totale, + 'ritenuta_acconto' => $request->ritenuta_acconto ?? 0, + 'importo_netto' => $request->importo_totale - ($request->ritenuta_acconto ?? 0), + 'stato' => 'registrato', + ]); + + // Crea dettagli movimento (partita doppia) + foreach ($request->dettagli as $dettaglio) { + $movimento->dettagli()->create([ + 'voce_spesa_id' => $dettaglio['voce_spesa_id'], + 'tabella_millesimale_id' => $dettaglio['tabella_millesimale_id'] ?? null, + 'descrizione' => $dettaglio['descrizione'] ?? '', + 'importo_dare' => $request->tipo_movimento === 'uscita' ? $dettaglio['importo'] : 0, + 'importo_avere' => $request->tipo_movimento === 'entrata' ? $dettaglio['importo'] : 0, + ]); + } + + DB::commit(); + + return redirect()->route('admin.contabilita.movimenti') + ->with('success', 'Movimento registrato con successo. Protocollo: ' . $protocollo); + + } catch (\Exception $e) { + DB::rollback(); + return back()->withErrors(['error' => 'Errore durante la registrazione: ' . $e->getMessage()]); + } + } + + /** + * Import da XML (Fattura Elettronica) + */ + public function importXml(Request $request) + { + $request->validate([ + 'xml_file' => 'required|file|mimes:xml|max:2048', + 'stabile_id' => 'required|exists:stabili,id_stabile', + ]); + + try { + $xmlContent = file_get_contents($request->file('xml_file')->path()); + $xml = simplexml_load_string($xmlContent); + + // Parsing XML fattura elettronica + $fatturaData = $this->parseXmlFattura($xml); + + // Salva documento + $documento = Documento::create([ + 'documentable_type' => Stabile::class, + 'documentable_id' => $request->stabile_id, + 'nome_file' => $request->file('xml_file')->getClientOriginalName(), + 'path_file' => $request->file('xml_file')->store('documenti/xml'), + 'tipo_documento' => 'fattura_elettronica', + 'xml_data' => $fatturaData, + 'mime_type' => 'application/xml', + 'dimensione_file' => $request->file('xml_file')->getSize(), + ]); + + return view('admin.contabilita.import-xml-review', compact('fatturaData', 'documento')); + + } catch (\Exception $e) { + return back()->withErrors(['error' => 'Errore durante l\'importazione XML: ' . $e->getMessage()]); + } + } + + /** + * Genera protocollo univoco + */ + private function generaProtocollo($stabile_id) + { + $anno = date('Y'); + $ultimoProtocollo = MovimentoContabile::where('stabile_id', $stabile_id) + ->whereYear('data_registrazione', $anno) + ->max('protocollo'); + + if ($ultimoProtocollo) { + $numero = intval(substr($ultimoProtocollo, -4)) + 1; + } else { + $numero = 1; + } + + return $stabile_id . '/' . $anno . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } + + /** + * Parse XML Fattura Elettronica + */ + private function parseXmlFattura($xml) + { + $ns = $xml->getNamespaces(true); + $xml->registerXPathNamespace('fe', $ns['']); + + // Dati generali fattura + $datiGenerali = [ + 'numero' => (string) $xml->xpath('//DatiGeneraliDocumento/Numero')[0] ?? '', + 'data' => (string) $xml->xpath('//DatiGeneraliDocumento/Data')[0] ?? '', + 'importo_totale' => (float) $xml->xpath('//DatiGeneraliDocumento/ImportoTotaleDocumento')[0] ?? 0, + ]; + + // Dati fornitore + $fornitore = [ + 'denominazione' => (string) $xml->xpath('//CedentePrestatore//Denominazione')[0] ?? '', + 'partita_iva' => (string) $xml->xpath('//CedentePrestatore//IdFiscaleIVA/IdCodice')[0] ?? '', + 'codice_fiscale' => (string) $xml->xpath('//CedentePrestatore//CodiceFiscale')[0] ?? '', + ]; + + // Righe fattura + $righe = []; + $dettaglioLinee = $xml->xpath('//DettaglioLinee'); + foreach ($dettaglioLinee as $linea) { + $righe[] = [ + 'descrizione' => (string) $linea->Descrizione ?? '', + 'quantita' => (float) $linea->Quantita ?? 1, + 'prezzo_unitario' => (float) $linea->PrezzoUnitario ?? 0, + 'importo_totale' => (float) $linea->PrezzoTotale ?? 0, + ]; + } + + return [ + 'dati_generali' => $datiGenerali, + 'fornitore' => $fornitore, + 'righe' => $righe, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 00000000..b3fd2ad8 --- /dev/null +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,73 @@ +amministratore->id_amministratore ?? null; + + // Statistiche principali + $stats = [ + 'stabili_gestiti' => Stabile::where('amministratore_id', $amministratore_id)->count(), + 'stabili_attivi' => Stabile::where('amministratore_id', $amministratore_id)->where('stato', 'attivo')->count(), + 'ticket_aperti' => Ticket::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), + 'ticket_urgenti' => Ticket::whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico'])->count(), + ]; + + // Ticket aperti da lavorare + $ticketsAperti = Ticket::with(['stabile', 'categoriaTicket']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']) + ->orderBy('priorita', 'desc') + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + + // Scadenze imminenti (prossimi 30 giorni) + $scadenzeImminenti = collect(); // Placeholder per quando implementeremo le rate + + // Ultimi documenti caricati + $ultimiDocumenti = Documento::with('documentable') + ->whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + + // Movimenti contabili recenti + $ultimiMovimenti = MovimentoContabile::with(['stabile', 'fornitore']) + ->whereHas('stabile', function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }) + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + + return view('admin.dashboard', compact( + 'stats', + 'ticketsAperti', + 'scadenzeImminenti', + 'ultimiDocumenti', + 'ultimiMovimenti' + )); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/DocumentoController.php b/app/Http/Controllers/Admin/DocumentoController.php new file mode 100644 index 00000000..bbc811ab --- /dev/null +++ b/app/Http/Controllers/Admin/DocumentoController.php @@ -0,0 +1,150 @@ +amministratore->id_amministratore ?? null; + + $query = Documento::with('documentable') + ->whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + }); + + // Filtri + if ($request->filled('tipo_documento')) { + $query->where('tipo_documento', $request->tipo_documento); + } + + if ($request->filled('search')) { + $query->where(function($q) use ($request) { + $q->where('nome_file', 'like', '%' . $request->search . '%') + ->orWhere('descrizione', 'like', '%' . $request->search . '%'); + }); + } + + $documenti = $query->orderBy('created_at', 'desc')->paginate(20); + + // Tipi documento per filtro + $tipiDocumento = Documento::whereHasMorph('documentable', [Stabile::class], function($q) use ($amministratore_id) { + $q->where('amministratore_id', $amministratore_id); + })->distinct()->pluck('tipo_documento')->filter(); + + return view('admin.documenti.index', compact('documenti', 'tipiDocumento')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + $amministratore_id = Auth::user()->amministratore->id_amministratore ?? null; + $stabili = Stabile::where('amministratore_id', $amministratore_id)->attivi()->get(); + + return view('admin.documenti.create', compact('stabili')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'documentable_type' => 'required|string', + 'documentable_id' => 'required|integer', + 'file' => 'required|file|max:10240', // 10MB max + 'tipo_documento' => 'required|string|max:100', + 'descrizione' => 'nullable|string', + ]); + + $file = $request->file('file'); + $path = $file->store('documenti', 'public'); + + Documento::create([ + 'documentable_type' => $request->documentable_type, + 'documentable_id' => $request->documentable_id, + 'nome_file' => $file->getClientOriginalName(), + 'path_file' => $path, + 'tipo_documento' => $request->tipo_documento, + 'descrizione' => $request->descrizione, + 'mime_type' => $file->getMimeType(), + 'dimensione_file' => $file->getSize(), + 'hash_file' => hash_file('sha256', $file->path()), + ]); + + return redirect()->route('admin.documenti.index') + ->with('success', 'Documento caricato con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(Documento $documento) + { + // Verifica accesso + if ($documento->documentable_type === Stabile::class) { + $stabile = $documento->documentable; + if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + } + + return view('admin.documenti.show', compact('documento')); + } + + /** + * Download del documento + */ + public function download(Documento $documento) + { + // Verifica accesso + if ($documento->documentable_type === Stabile::class) { + $stabile = $documento->documentable; + if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + } + + if (!Storage::disk('public')->exists($documento->path_file)) { + abort(404, 'File non trovato'); + } + + return Storage::disk('public')->download($documento->path_file, $documento->nome_file); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Documento $documento) + { + // Verifica accesso + if ($documento->documentable_type === Stabile::class) { + $stabile = $documento->documentable; + if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + } + + // Elimina il file fisico + if (Storage::disk('public')->exists($documento->path_file)) { + Storage::disk('public')->delete($documento->path_file); + } + + $documento->delete(); + + return redirect()->route('admin.documenti.index') + ->with('success', 'Documento eliminato con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/FornitoreController.php b/app/Http/Controllers/Admin/FornitoreController.php new file mode 100644 index 00000000..ff247cfb --- /dev/null +++ b/app/Http/Controllers/Admin/FornitoreController.php @@ -0,0 +1,140 @@ +amministratore->id_amministratore ?? null) + ->paginate(10); + + return view('admin.fornitori.index', compact('fornitori')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + return view('admin.fornitori.create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'ragione_sociale' => 'required|string|max:255', + 'partita_iva' => 'nullable|string|max:20|unique:fornitori,partita_iva', + 'codice_fiscale' => 'nullable|string|max:20|unique:fornitori,codice_fiscale', + 'indirizzo' => 'nullable|string|max:255', + 'cap' => 'nullable|string|max:10', + 'citta' => 'nullable|string|max:60', + 'provincia' => 'nullable|string|max:2', + 'email' => 'nullable|email|max:255|unique:fornitori,email', + 'pec' => 'nullable|email|max:255', + 'telefono' => 'nullable|string|max:50', + 'old_id' => 'nullable|integer|unique:fornitori,old_id', + ]); + + $fornitore = Fornitore::create([ + 'amministratore_id' => Auth::user()->amministratore->id_amministratore ?? null, + 'ragione_sociale' => $request->ragione_sociale, + 'partita_iva' => $request->partita_iva, + 'codice_fiscale' => $request->codice_fiscale, + 'indirizzo' => $request->indirizzo, + 'cap' => $request->cap, + 'citta' => $request->citta, + 'provincia' => $request->provincia, + 'email' => $request->email, + 'pec' => $request->pec, + 'telefono' => $request->telefono, + 'old_id' => $request->old_id, + ]); + + return redirect()->route('admin.fornitori.index') + ->with('success', 'Fornitore creato con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(Fornitore $fornitore) + { + // Verifica che l'utente possa accedere a questo fornitore + if ($fornitore->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + return view('admin.fornitori.show', compact('fornitore')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Fornitore $fornitore) + { + // Verifica che l'utente possa modificare questo fornitore + if ($fornitore->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + return view('admin.fornitori.edit', compact('fornitore')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Fornitore $fornitore) + { + // Verifica che l'utente possa modificare questo fornitore + if ($fornitore->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $request->validate([ + 'ragione_sociale' => 'required|string|max:255', + 'partita_iva' => 'nullable|string|max:20|unique:fornitori,partita_iva,' . $fornitore->id_fornitore . ',id_fornitore', + 'codice_fiscale' => 'nullable|string|max:20|unique:fornitori,codice_fiscale,' . $fornitore->id_fornitore . ',id_fornitore', + 'indirizzo' => 'nullable|string|max:255', + 'cap' => 'nullable|string|max:10', + 'citta' => 'nullable|string|max:60', + 'provincia' => 'nullable|string|max:2', + 'email' => 'nullable|email|max:255|unique:fornitori,email,' . $fornitore->id_fornitore . ',id_fornitore', + 'pec' => 'nullable|email|max:255', + 'telefono' => 'nullable|string|max:50', + 'old_id' => 'nullable|integer|unique:fornitori,old_id,' . $fornitore->id_fornitore . ',id_fornitore', + ]); + + $fornitore->update($request->all()); + + return redirect()->route('admin.fornitori.index') + ->with('success', 'Fornitore aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Fornitore $fornitore) + { + // Verifica che l'utente possa eliminare questo fornitore + if ($fornitore->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $fornitore->delete(); + + return redirect()->route('admin.fornitori.index') + ->with('success', 'Fornitore eliminato con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/PreventivoController.php b/app/Http/Controllers/Admin/PreventivoController.php new file mode 100644 index 00000000..779b70d6 --- /dev/null +++ b/app/Http/Controllers/Admin/PreventivoController.php @@ -0,0 +1,289 @@ +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', + ]); + + // 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(), + ]); + + 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; + } + + /** + * 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')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/StabileController.php b/app/Http/Controllers/Admin/StabileController.php new file mode 100644 index 00000000..35ea9a82 --- /dev/null +++ b/app/Http/Controllers/Admin/StabileController.php @@ -0,0 +1,137 @@ +amministratore->id_amministratore ?? null) + ->paginate(10); + + return view('admin.stabili.index', compact('stabili')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + return view('admin.stabili.create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'denominazione' => 'required|string|max:255', + 'codice_fiscale' => 'nullable|string|max:20|unique:stabili,codice_fiscale', + '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 = Stabile::create([ + 'amministratore_id' => Auth::user()->amministratore->id_amministratore ?? null, + 'denominazione' => $request->denominazione, + 'codice_fiscale' => $request->codice_fiscale, + 'cod_fisc_amministratore' => $request->cod_fisc_amministratore, + 'indirizzo' => $request->indirizzo, + 'citta' => $request->citta, + 'cap' => $request->cap, + 'provincia' => $request->provincia, + 'stato' => $request->stato, + 'note' => $request->note, + 'old_id' => $request->old_id, + ]); + + return redirect()->route('admin.stabili.index') + ->with('success', 'Stabile creato con successo.'); + } + + /** + * 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); + } + + return view('admin.stabili.show', compact('stabile')); + } + + /** + * 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_stabile . ',id_stabile', + '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_stabile . ',id_stabile', + ]); + + $stabile->update($request->all()); + + return redirect()->route('admin.stabili.index') + ->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.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/TicketController.php b/app/Http/Controllers/Admin/TicketController.php new file mode 100644 index 00000000..4e2bee5d --- /dev/null +++ b/app/Http/Controllers/Admin/TicketController.php @@ -0,0 +1,170 @@ +whereHas('stabile', function($query) { + $query->where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null); + }) + ->orderBy('created_at', 'desc') + ->paginate(10); + + return view('admin.tickets.index', compact('tickets')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + $stabili = Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null) + ->attivi() + ->get(); + + $categorieTicket = CategoriaTicket::all(); + $users = User::role(['admin', 'amministratore'])->get(); + $fornitori = Fornitore::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)->get(); + + return view('admin.tickets.create', compact('stabili', 'categorieTicket', 'users', 'fornitori')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'categoria_ticket_id' => 'nullable|exists:categorie_ticket,id', + 'titolo' => 'required|string|max:255', + 'descrizione' => 'nullable|string', + 'luogo_intervento' => 'nullable|string|max:255', + 'stato' => 'required|in:Aperto,Preso in Carico,In Lavorazione,In Attesa Approvazione,In Attesa Ricambi,Risolto,Chiuso,Annullato', + 'priorita' => 'required|in:Bassa,Media,Alta,Urgente', + 'assegnato_a_user_id' => 'nullable|exists:users,id', + 'assegnato_a_fornitore_id' => 'nullable|exists:fornitori,id_fornitore', + 'data_scadenza_prevista' => 'nullable|date', + 'data_risoluzione_effettiva' => 'nullable|date', + 'data_chiusura_effettiva' => 'nullable|date', + ]); + + $ticket = Ticket::create([ + 'stabile_id' => $request->stabile_id, + 'categoria_ticket_id' => $request->categoria_ticket_id, + 'aperto_da_user_id' => Auth::id(), + 'assegnato_a_user_id' => $request->assegnato_a_user_id, + 'assegnato_a_fornitore_id' => $request->assegnato_a_fornitore_id, + 'titolo' => $request->titolo, + 'descrizione' => $request->descrizione, + 'luogo_intervento' => $request->luogo_intervento, + 'data_apertura' => now(), + 'data_scadenza_prevista' => $request->data_scadenza_prevista, + 'data_risoluzione_effettiva' => $request->data_risoluzione_effettiva, + 'data_chiusura_effettiva' => $request->data_chiusura_effettiva, + 'stato' => $request->stato, + 'priorita' => $request->priorita, + ]); + + return redirect()->route('admin.tickets.index') + ->with('success', 'Ticket creato con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(Ticket $ticket) + { + // Verifica che l'utente possa accedere a questo ticket + if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $ticket->load(['stabile', 'categoriaTicket', 'apertoUser', 'assegnatoUser', 'assegnatoFornitore']); + + return view('admin.tickets.show', compact('ticket')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Ticket $ticket) + { + // Verifica che l'utente possa modificare questo ticket + if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $stabili = Stabile::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null) + ->attivi() + ->get(); + + $categorieTicket = CategoriaTicket::all(); + $users = User::role(['admin', 'amministratore'])->get(); + $fornitori = Fornitore::where('amministratore_id', Auth::user()->amministratore->id_amministratore ?? null)->get(); + + return view('admin.tickets.edit', compact('ticket', 'stabili', 'categorieTicket', 'users', 'fornitori')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Ticket $ticket) + { + // Verifica che l'utente possa modificare questo ticket + if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'categoria_ticket_id' => 'nullable|exists:categorie_ticket,id', + 'titolo' => 'required|string|max:255', + 'descrizione' => 'nullable|string', + 'luogo_intervento' => 'nullable|string|max:255', + 'stato' => 'required|in:Aperto,Preso in Carico,In Lavorazione,In Attesa Approvazione,In Attesa Ricambi,Risolto,Chiuso,Annullato', + 'priorita' => 'required|in:Bassa,Media,Alta,Urgente', + 'assegnato_a_user_id' => 'nullable|exists:users,id', + 'assegnato_a_fornitore_id' => 'nullable|exists:fornitori,id_fornitore', + 'data_scadenza_prevista' => 'nullable|date', + 'data_risoluzione_effettiva' => 'nullable|date', + 'data_chiusura_effettiva' => 'nullable|date', + ]); + + $ticket->update($request->all()); + + return redirect()->route('admin.tickets.index') + ->with('success', 'Ticket aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Ticket $ticket) + { + // Verifica che l'utente possa eliminare questo ticket + if ($ticket->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $ticket->delete(); + + return redirect()->route('admin.tickets.index') + ->with('success', 'Ticket eliminato con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/UnitaImmobiliareController.php b/app/Http/Controllers/Admin/UnitaImmobiliareController.php new file mode 100644 index 00000000..c6028473 --- /dev/null +++ b/app/Http/Controllers/Admin/UnitaImmobiliareController.php @@ -0,0 +1,115 @@ +amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + return view('admin.unita_immobiliari.create', compact('stabile')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request, Stabile $stabile) + { + // Verifica che l'utente possa accedere a questo stabile + if ($stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'interno' => 'nullable|string|max:255', + 'scala' => 'nullable|string|max:255', + 'piano' => 'nullable|string|max:255', + 'fabbricato' => 'nullable|string|max:255', + 'millesimi_proprieta' => 'nullable|numeric|min:0|max:9999.9999', + 'categoria_catastale' => 'nullable|string|max:255', + 'superficie' => 'nullable|numeric|min:0|max:99999999.99', + 'vani' => 'nullable|numeric|min:0|max:99.99', + 'indirizzo' => 'nullable|string|max:255', + 'note' => 'nullable|string', + ]); + + $unitaImmobiliare = UnitaImmobiliare::create($request->all()); + + return redirect()->route('admin.stabili.show', $stabile) + ->with('success', 'Unità immobiliare creata con successo.'); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(UnitaImmobiliare $unitaImmobiliare) + { + // Verifica che l'utente possa modificare questa unità + if ($unitaImmobiliare->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + return view('admin.unita_immobiliari.edit', compact('unitaImmobiliare')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, UnitaImmobiliare $unitaImmobiliare) + { + // Verifica che l'utente possa modificare questa unità + if ($unitaImmobiliare->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $request->validate([ + 'stabile_id' => 'required|exists:stabili,id_stabile', + 'interno' => 'nullable|string|max:255', + 'scala' => 'nullable|string|max:255', + 'piano' => 'nullable|string|max:255', + 'fabbricato' => 'nullable|string|max:255', + 'millesimi_proprieta' => 'nullable|numeric|min:0|max:9999.9999', + 'categoria_catastale' => 'nullable|string|max:255', + 'superficie' => 'nullable|numeric|min:0|max:99999999.99', + 'vani' => 'nullable|numeric|min:0|max:99.99', + 'indirizzo' => 'nullable|string|max:255', + 'note' => 'nullable|string', + ]); + + $unitaImmobiliare->update($request->all()); + + return redirect()->route('admin.stabili.show', $unitaImmobiliare->stabile) + ->with('success', 'Unità immobiliare aggiornata con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(UnitaImmobiliare $unitaImmobiliare) + { + // Verifica che l'utente possa eliminare questa unità + if ($unitaImmobiliare->stabile->amministratore_id !== Auth::user()->amministratore->id_amministratore ?? null) { + abort(403); + } + + $stabile = $unitaImmobiliare->stabile; + $unitaImmobiliare->delete(); + + return redirect()->route('admin.stabili.show', $stabile) + ->with('success', 'Unità immobiliare eliminata con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Condomino/DashboardController.php b/app/Http/Controllers/Condomino/DashboardController.php new file mode 100644 index 00000000..015ac77a --- /dev/null +++ b/app/Http/Controllers/Condomino/DashboardController.php @@ -0,0 +1,60 @@ +where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->with(['stabile', 'proprieta.soggetto'])->get(); + + // Statistiche principali + $stats = [ + 'unita_possedute' => $unitaImmobiliari->count(), + 'ticket_aperti' => Ticket::where('soggetto_richiedente_id', $user->soggetto->id_soggetto ?? null) + ->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), + 'rate_scadute' => 0, // Implementeremo quando avremo le rate + 'documenti_disponibili' => Documento::whereHasMorph('documentable', ['App\Models\Stabile'], function($q) use ($unitaImmobiliari) { + $q->whereIn('id_stabile', $unitaImmobiliari->pluck('stabile_id')); + })->count(), + ]; + + // Ticket recenti + $ticketRecenti = Ticket::where('soggetto_richiedente_id', $user->soggetto->id_soggetto ?? null) + ->with(['stabile', 'categoriaTicket']) + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + + // Rate in scadenza (placeholder) + $rateInScadenza = collect(); + + // Ultimi documenti + $ultimiDocumenti = Documento::whereHasMorph('documentable', ['App\Models\Stabile'], function($q) use ($unitaImmobiliari) { + $q->whereIn('id_stabile', $unitaImmobiliari->pluck('stabile_id')); + })->orderBy('created_at', 'desc')->take(5)->get(); + + return view('condomino.dashboard', compact( + 'stats', + 'unitaImmobiliari', + 'ticketRecenti', + 'rateInScadenza', + 'ultimiDocumenti' + )); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Condomino/DocumentoController.php b/app/Http/Controllers/Condomino/DocumentoController.php new file mode 100644 index 00000000..c4e9b653 --- /dev/null +++ b/app/Http/Controllers/Condomino/DocumentoController.php @@ -0,0 +1,70 @@ +where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->pluck('stabile_id')->unique(); + + $query = Documento::whereHasMorph('documentable', ['App\Models\Stabile'], function($q) use ($stabiliIds) { + $q->whereIn('id_stabile', $stabiliIds); + })->with('documentable'); + + // Filtri + if ($request->filled('tipo_documento')) { + $query->where('tipo_documento', $request->tipo_documento); + } + + if ($request->filled('search')) { + $query->where(function($q) use ($request) { + $q->where('nome_file', 'like', '%' . $request->search . '%') + ->orWhere('descrizione', 'like', '%' . $request->search . '%'); + }); + } + + $documenti = $query->orderBy('created_at', 'desc')->paginate(20); + + // Tipi documento per filtro + $tipiDocumento = Documento::whereHasMorph('documentable', ['App\Models\Stabile'], function($q) use ($stabiliIds) { + $q->whereIn('id_stabile', $stabiliIds); + })->distinct()->pluck('tipo_documento')->filter(); + + return view('condomino.documenti.index', compact('documenti', 'tipiDocumento')); + } + + public function download(Documento $documento) + { + $user = Auth::user(); + + // Verifica accesso + $stabiliIds = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) { + $q->where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->pluck('stabile_id')->unique(); + + if ($documento->documentable_type === 'App\Models\Stabile') { + if (!$stabiliIds->contains($documento->documentable_id)) { + abort(403); + } + } + + if (!Storage::disk('public')->exists($documento->path_file)) { + abort(404, 'File non trovato'); + } + + return Storage::disk('public')->download($documento->path_file, $documento->nome_file); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Condomino/TicketController.php b/app/Http/Controllers/Condomino/TicketController.php new file mode 100644 index 00000000..3994f4a4 --- /dev/null +++ b/app/Http/Controllers/Condomino/TicketController.php @@ -0,0 +1,105 @@ +soggetto->id_soggetto ?? null) + ->with(['stabile', 'categoriaTicket', 'unitaImmobiliare']) + ->orderBy('created_at', 'desc') + ->paginate(10); + + return view('condomino.tickets.index', compact('tickets')); + } + + public function create() + { + $user = Auth::user(); + + // Unità immobiliari dell'utente + $unitaImmobiliari = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) { + $q->where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->with('stabile')->get(); + + $categorieTicket = CategoriaTicket::all(); + + return view('condomino.tickets.create', compact('unitaImmobiliari', 'categorieTicket')); + } + + public function store(Request $request) + { + $user = Auth::user(); + + $request->validate([ + 'unita_immobiliare_id' => 'required|exists:unita_immobiliari,id_unita', + 'categoria_ticket_id' => 'nullable|exists:categorie_ticket,id', + 'titolo' => 'required|string|max:255', + 'descrizione' => 'required|string', + 'luogo_intervento' => 'nullable|string|max:255', + 'priorita' => 'required|in:Bassa,Media,Alta,Urgente', + 'allegati.*' => 'nullable|file|max:10240', // 10MB per file + ]); + + // Verifica che l'unità appartenga all'utente + $unitaImmobiliare = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) { + $q->where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->findOrFail($request->unita_immobiliare_id); + + $ticket = Ticket::create([ + 'stabile_id' => $unitaImmobiliare->stabile_id, + 'unita_immobiliare_id' => $request->unita_immobiliare_id, + 'soggetto_richiedente_id' => $user->soggetto->id_soggetto, + 'categoria_ticket_id' => $request->categoria_ticket_id, + 'aperto_da_user_id' => $user->id, + 'titolo' => $request->titolo, + 'descrizione' => $request->descrizione, + 'luogo_intervento' => $request->luogo_intervento, + 'data_apertura' => now(), + 'stato' => 'Aperto', + 'priorita' => $request->priorita, + ]); + + // Gestione allegati + if ($request->hasFile('allegati')) { + foreach ($request->file('allegati') as $file) { + $path = $file->store('ticket-allegati', 'public'); + + $ticket->documenti()->create([ + 'nome_file' => $file->getClientOriginalName(), + 'path_file' => $path, + 'tipo_documento' => 'Allegato Ticket', + 'mime_type' => $file->getMimeType(), + 'dimensione_file' => $file->getSize(), + ]); + } + } + + return redirect()->route('condomino.tickets.index') + ->with('success', 'Ticket creato con successo.'); + } + + public function show(Ticket $ticket) + { + $user = Auth::user(); + + // Verifica che il ticket appartenga all'utente + if ($ticket->soggetto_richiedente_id !== $user->soggetto->id_soggetto ?? null) { + abort(403); + } + + $ticket->load(['stabile', 'categoriaTicket', 'unitaImmobiliare', 'documenti']); + + return view('condomino.tickets.show', compact('ticket')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Condomino/UnitaController.php b/app/Http/Controllers/Condomino/UnitaController.php new file mode 100644 index 00000000..e390dedd --- /dev/null +++ b/app/Http/Controllers/Condomino/UnitaController.php @@ -0,0 +1,73 @@ +where('soggetto_id', $user->soggetto->id_soggetto ?? null); + })->with(['stabile', 'proprieta.soggetto'])->get(); + + return view('condomino.unita.index', compact('unitaImmobiliari')); + } + + public function show(UnitaImmobiliare $unitaImmobiliare) + { + $user = Auth::user(); + + // Verifica accesso + $hasAccess = $unitaImmobiliare->proprieta() + ->where('soggetto_id', $user->soggetto->id_soggetto ?? null) + ->exists(); + + if (!$hasAccess) { + abort(403); + } + + $unitaImmobiliare->load(['stabile', 'proprieta.soggetto']); + + return view('condomino.unita.show', compact('unitaImmobiliare')); + } + + public function richiestaModifica(Request $request, UnitaImmobiliare $unitaImmobiliare) + { + $user = Auth::user(); + + // Verifica accesso + $hasAccess = $unitaImmobiliare->proprieta() + ->where('soggetto_id', $user->soggetto->id_soggetto ?? null) + ->exists(); + + if (!$hasAccess) { + abort(403); + } + + $request->validate([ + 'tipo_modifica' => 'required|in:anagrafica,catastale,proprieta', + 'descrizione' => 'required|string', + 'dati_proposti' => 'required|array', + ]); + + RichiestaModifica::create([ + 'unita_immobiliare_id' => $unitaImmobiliare->id_unita, + 'soggetto_richiedente_id' => $user->soggetto->id_soggetto, + 'tipo_modifica' => $request->tipo_modifica, + 'descrizione' => $request->descrizione, + 'dati_attuali' => $unitaImmobiliare->toArray(), + 'dati_proposti' => $request->dati_proposti, + 'stato' => 'in_attesa', + ]); + + return back()->with('success', 'Richiesta di modifica inviata con successo.'); + } +} \ No newline at end of file diff --git a/app/Models/Assemblea.php b/app/Models/Assemblea.php new file mode 100644 index 00000000..c11d8cf3 --- /dev/null +++ b/app/Models/Assemblea.php @@ -0,0 +1,266 @@ + 'datetime', + 'data_seconda_convocazione' => 'datetime', + 'data_convocazione' => 'date', + 'data_svolgimento' => 'date', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Ordine del Giorno + */ + public function ordineGiorno() + { + return $this->hasMany(OrdineGiorno::class, 'assemblea_id')->orderBy('numero_punto'); + } + + /** + * Relazione con Convocazioni + */ + public function convocazioni() + { + return $this->hasMany(Convocazione::class, 'assemblea_id'); + } + + /** + * Relazione con Presenze + */ + public function presenze() + { + return $this->hasMany(PresenzaAssemblea::class, 'assemblea_id'); + } + + /** + * Relazione con Verbale + */ + public function verbale() + { + return $this->hasOne(Verbale::class, 'assemblea_id'); + } + + /** + * Relazione con Documenti + */ + public function documenti() + { + return $this->hasMany(DocumentoAssemblea::class, 'assemblea_id'); + } + + /** + * Relazione con User creatore + */ + public function creatoDa() + { + return $this->belongsTo(User::class, 'creato_da_user_id'); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } + + /** + * Scope per tipo + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo', $tipo); + } + + /** + * Invia convocazioni massive + */ + public function inviaConvocazioni($canali = ['email'], $userId) + { + $unitaImmobiliari = $this->stabile->unitaImmobiliari()->with('proprieta.soggetto')->get(); + $convocazioniInviate = 0; + + foreach ($unitaImmobiliari as $unita) { + foreach ($unita->proprieta as $proprieta) { + $soggetto = $proprieta->soggetto; + + foreach ($canali as $canale) { + if ($this->verificaCanaleDisponibile($soggetto, $canale)) { + $convocazione = $this->creaConvocazione($soggetto, $unita, $canale); + + if ($this->inviaConvocazione($convocazione)) { + $convocazioniInviate++; + + // Registra nel protocollo + $this->registraProtocollo($convocazione, $userId); + } + } + } + } + } + + // Aggiorna stato assemblea + $this->update([ + 'stato' => 'convocata', + 'data_convocazione' => now(), + ]); + + return $convocazioniInviate; + } + + /** + * Verifica se il canale è disponibile per il soggetto + */ + private function verificaCanaleDisponibile($soggetto, $canale) + { + switch ($canale) { + case 'email': + return !empty($soggetto->email); + case 'pec': + return !empty($soggetto->pec); + case 'whatsapp': + case 'telegram': + return !empty($soggetto->telefono); + default: + return true; + } + } + + /** + * Crea record convocazione + */ + private function creaConvocazione($soggetto, $unita, $canale) + { + return Convocazione::create([ + 'assemblea_id' => $this->id, + 'soggetto_id' => $soggetto->id_soggetto, + 'unita_immobiliare_id' => $unita->id_unita, + 'canale_invio' => $canale, + 'data_invio' => now(), + 'esito_invio' => 'inviato', + ]); + } + + /** + * Invia singola convocazione + */ + private function inviaConvocazione($convocazione) + { + // Implementazione invio basata sul canale + // Qui si integrerebbe con servizi email, SMS, etc. + + // Simula invio riuscito + $convocazione->update([ + 'esito_invio' => 'consegnato', + 'riferimento_invio' => 'REF-' . time(), + ]); + + return true; + } + + /** + * Registra comunicazione nel protocollo + */ + private function registraProtocollo($convocazione, $userId) + { + $numeroProtocollo = $this->generaNumeroProtocollo(); + + RegistroProtocollo::create([ + 'numero_protocollo' => $numeroProtocollo, + 'tipo_comunicazione' => 'convocazione', + 'assemblea_id' => $this->id, + 'soggetto_destinatario_id' => $convocazione->soggetto_id, + 'oggetto' => "Convocazione Assemblea {$this->tipo} - {$this->data_prima_convocazione->format('d/m/Y')}", + 'canale' => $convocazione->canale_invio, + 'data_invio' => $convocazione->data_invio, + 'esito' => $convocazione->esito_invio, + 'riferimento_esterno' => $convocazione->riferimento_invio, + 'creato_da_user_id' => $userId, + ]); + } + + /** + * Genera numero protocollo progressivo + */ + private function generaNumeroProtocollo() + { + $anno = date('Y'); + $ultimoProtocollo = RegistroProtocollo::whereYear('created_at', $anno) + ->orderBy('numero_protocollo', 'desc') + ->first(); + + if ($ultimoProtocollo) { + $numero = intval(substr($ultimoProtocollo->numero_protocollo, -4)) + 1; + } else { + $numero = 1; + } + + return 'PROT/' . $anno . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } + + /** + * Calcola quorum assemblea + */ + public function calcolaQuorum() + { + $totaleMillesimi = $this->stabile->unitaImmobiliari()->sum('millesimi_proprieta'); + $millesimiPresenti = $this->presenze()->sum('millesimi_rappresentati'); + + return [ + 'totale_millesimi' => $totaleMillesimi, + 'millesimi_presenti' => $millesimiPresenti, + 'percentuale_presenza' => $totaleMillesimi > 0 ? ($millesimiPresenti / $totaleMillesimi) * 100 : 0, + 'quorum_raggiunto' => $millesimiPresenti >= ($totaleMillesimi / 2), + ]; + } + + /** + * Genera report presenze + */ + public function generaReportPresenze() + { + $presenze = $this->presenze()->with(['soggetto', 'unitaImmobiliare'])->get(); + $quorum = $this->calcolaQuorum(); + + return [ + 'quorum' => $quorum, + 'presenze' => $presenze, + 'totale_presenti' => $presenze->where('tipo_presenza', 'presente')->count(), + 'totale_delegati' => $presenze->where('tipo_presenza', 'delegato')->count(), + 'totale_assenti' => $this->stabile->unitaImmobiliari()->count() - $presenze->count(), + ]; + } +} \ No newline at end of file diff --git a/app/Models/Banca.php b/app/Models/Banca.php new file mode 100644 index 00000000..5696d8c4 --- /dev/null +++ b/app/Models/Banca.php @@ -0,0 +1,70 @@ + 'decimal:2', + 'data_apertura' => 'date', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Movimenti Bancari + */ + public function movimentiBancari() + { + return $this->hasMany(MovimentoBancario::class, 'banca_id'); + } + + /** + * Scope per conti attivi + */ + public function scopeAttivi($query) + { + return $query->where('stato', 'attivo'); + } + + /** + * Calcola il saldo attuale + */ + public function getSaldoAttualeAttribute() + { + $movimenti = $this->movimentiBancari() + ->selectRaw('SUM(CASE WHEN tipo_movimento = "entrata" THEN importo ELSE -importo END) as saldo') + ->first(); + + return $this->saldo_iniziale + ($movimenti->saldo ?? 0); + } +} \ No newline at end of file diff --git a/app/Models/Bilancio.php b/app/Models/Bilancio.php new file mode 100644 index 00000000..3282e8aa --- /dev/null +++ b/app/Models/Bilancio.php @@ -0,0 +1,279 @@ + 'integer', + 'data_inizio_esercizio' => 'date', + 'data_fine_esercizio' => 'date', + 'totale_entrate' => 'decimal:2', + 'totale_uscite' => 'decimal:2', + 'risultato_gestione' => 'decimal:2', + 'data_approvazione' => 'date', + 'data_chiusura' => 'date', + 'versione' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Gestione + */ + public function gestione() + { + return $this->belongsTo(Gestione::class, 'gestione_id', 'id_gestione'); + } + + /** + * Relazione con Scritture + */ + public function scritture() + { + return $this->hasMany(ScritturaBilancio::class, 'bilancio_id'); + } + + /** + * Relazione con Conguagli + */ + public function conguagli() + { + return $this->hasMany(Conguaglio::class, 'bilancio_id'); + } + + /** + * Relazione con Quadrature + */ + public function quadrature() + { + return $this->hasMany(Quadratura::class, 'bilancio_id'); + } + + /** + * Relazione con Rimborsi Assicurativi + */ + public function rimborsiAssicurativi() + { + return $this->hasMany(RimborsoAssicurativo::class, 'bilancio_id'); + } + + /** + * Relazione con User che ha approvato + */ + public function approvatoDa() + { + return $this->belongsTo(User::class, 'approvato_da_user_id'); + } + + /** + * Relazione con User che ha chiuso + */ + public function chiusoDa() + { + return $this->belongsTo(User::class, 'chiuso_da_user_id'); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } + + /** + * Calcola totali da scritture + */ + public function calcolaTotali() + { + $entrate = $this->scritture() + ->whereHas('dettagli', function($q) { + $q->whereHas('conto', function($c) { + $c->where('tipo_conto', 'ricavo'); + }); + }) + ->sum('importo_totale'); + + $uscite = $this->scritture() + ->whereHas('dettagli', function($q) { + $q->whereHas('conto', function($c) { + $c->where('tipo_conto', 'costo'); + }); + }) + ->sum('importo_totale'); + + $this->update([ + 'totale_entrate' => $entrate, + 'totale_uscite' => $uscite, + 'risultato_gestione' => $entrate - $uscite, + ]); + + return $this; + } + + /** + * Calcola conguagli per tutte le unità + */ + public function calcolaConguagli() + { + $unitaImmobiliari = $this->stabile->unitaImmobiliari; + + foreach ($unitaImmobiliari as $unita) { + $this->calcolaConguaglioUnita($unita); + } + + return $this; + } + + /** + * Calcola conguaglio per singola unità + */ + public function calcolaConguaglioUnita($unitaImmobiliare) + { + // Calcola totale rate pagate + $totaleRatePagate = $this->calcolaTotaleRatePagate($unitaImmobiliare); + + // Calcola totale spese effettive ripartite + $totaleSpese = $this->calcolaTotaleSpese($unitaImmobiliare); + + // Calcola conguaglio + $conguaglioDovuto = $totaleRatePagate - $totaleSpese; + + $tipoConguaglio = $conguaglioDovuto > 0 ? 'a_credito' : + ($conguaglioDovuto < 0 ? 'a_debito' : 'pareggio'); + + // Trova il soggetto principale dell'unità + $soggetto = $unitaImmobiliare->proprieta() + ->where('tipo_diritto', 'proprietario') + ->first()?->soggetto; + + if (!$soggetto) { + return null; + } + + return Conguaglio::updateOrCreate( + [ + 'bilancio_id' => $this->id, + 'unita_immobiliare_id' => $unitaImmobiliare->id_unita, + 'soggetto_id' => $soggetto->id_soggetto, + ], + [ + 'totale_rate_pagate' => $totaleRatePagate, + 'totale_spese_effettive' => $totaleSpese, + 'conguaglio_dovuto' => abs($conguaglioDovuto), + 'tipo_conguaglio' => $tipoConguaglio, + 'data_calcolo' => now(), + ] + ); + } + + /** + * Calcola totale rate pagate per unità + */ + private function calcolaTotaleRatePagate($unitaImmobiliare) + { + // Implementazione del calcolo rate pagate + // Collegamento con tabella rate e incassi + return 0; // Placeholder + } + + /** + * Calcola totale spese per unità + */ + private function calcolaTotaleSpese($unitaImmobiliare) + { + return $this->scritture() + ->whereHas('ripartizioni', function($q) use ($unitaImmobiliare) { + $q->where('unita_immobiliare_id', $unitaImmobiliare->id_unita); + }) + ->sum('quota_finale'); + } + + /** + * Genera scritture di chiusura + */ + public function generaScritture ChiusuraEsercizio($userId) + { + // Scrittura di chiusura costi + $totaleCosti = $this->scritture() + ->whereHas('dettagli.conto', function($q) { + $q->where('tipo_conto', 'costo'); + }) + ->sum('importo_totale'); + + // Scrittura di chiusura ricavi + $totaleRicavi = $this->scritture() + ->whereHas('dettagli.conto', function($q) { + $q->where('tipo_conto', 'ricavo'); + }) + ->sum('importo_totale'); + + // Crea scrittura di chiusura + $scritturaChiusura = ScritturaBilancio::create([ + 'bilancio_id' => $this->id, + 'numero_scrittura' => $this->generaNumeroScrittura('CHIUS'), + 'data_scrittura' => $this->data_fine_esercizio, + 'descrizione' => 'Chiusura esercizio ' . $this->anno_esercizio, + 'tipo_scrittura' => 'chiusura', + 'importo_totale' => abs($totaleRicavi - $totaleCosti), + 'creato_da_user_id' => $userId, + ]); + + return $scritturaChiusura; + } + + /** + * Genera numero scrittura progressivo + */ + private function generaNumeroScrittura($prefisso = 'SCR') + { + $ultimaScrittura = $this->scritture() + ->where('numero_scrittura', 'like', $prefisso . '%') + ->orderBy('numero_scrittura', 'desc') + ->first(); + + if ($ultimaScrittura) { + $numero = intval(substr($ultimaScrittura->numero_scrittura, -4)) + 1; + } else { + $numero = 1; + } + + return $prefisso . '/' . $this->anno_esercizio . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } +} \ No newline at end of file diff --git a/app/Models/Conguaglio.php b/app/Models/Conguaglio.php new file mode 100644 index 00000000..cb7b2753 --- /dev/null +++ b/app/Models/Conguaglio.php @@ -0,0 +1,136 @@ + 'decimal:2', + 'totale_spese_effettive' => 'decimal:2', + 'conguaglio_dovuto' => 'decimal:2', + 'data_calcolo' => 'date', + 'data_pagamento' => 'date', + 'importo_pagato' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Bilancio + */ + public function bilancio() + { + return $this->belongsTo(Bilancio::class, 'bilancio_id'); + } + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con Soggetto + */ + public function soggetto() + { + return $this->belongsTo(Soggetto::class, 'soggetto_id', 'id_soggetto'); + } + + /** + * Relazione con Rate Conguaglio + */ + public function rateConguaglio() + { + return $this->hasMany(RataConguaglio::class, 'conguaglio_id'); + } + + /** + * Scope per tipo conguaglio + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_conguaglio', $tipo); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } + + /** + * Genera rate per conguaglio + */ + public function generaRate($numeroRate, $dataInizio, $userId) + { + if ($this->tipo_conguaglio === 'pareggio') { + return collect(); + } + + $importoPerRata = $this->conguaglio_dovuto / $numeroRate; + $rate = collect(); + + for ($i = 1; $i <= $numeroRate; $i++) { + $dataScadenza = $dataInizio->copy()->addMonths($i - 1); + $numeroRata = $this->generaNumeroRata($i); + + $rata = RataConguaglio::create([ + 'conguaglio_id' => $this->id, + 'numero_rata' => $numeroRata, + 'descrizione' => "Conguaglio rata {$i} di {$numeroRate} - " . $this->unitaImmobiliare->identificazione_completa, + 'data_scadenza' => $dataScadenza, + 'importo_rata' => $importoPerRata, + 'rateizzato' => $numeroRate > 1, + 'numero_rate_totali' => $numeroRate, + 'numero_rata_corrente' => $i, + ]); + + $rate->push($rata); + } + + return $rate; + } + + /** + * Genera numero rata univoco + */ + private function generaNumeroRata($numeroRata) + { + $prefisso = $this->tipo_conguaglio === 'a_credito' ? 'RIMB' : 'CONG'; + return $prefisso . '/' . $this->bilancio->anno_esercizio . '/' . + $this->unita_immobiliare_id . '/' . str_pad($numeroRata, 2, '0', STR_PAD_LEFT); + } + + /** + * Calcola importo residuo + */ + public function getImportoResiduoAttribute() + { + return $this->conguaglio_dovuto - $this->importo_pagato; + } +} \ No newline at end of file diff --git a/app/Models/Convocazione.php b/app/Models/Convocazione.php new file mode 100644 index 00000000..d793bbe8 --- /dev/null +++ b/app/Models/Convocazione.php @@ -0,0 +1,128 @@ + 'datetime', + 'data_lettura' => 'datetime', + 'delega_presente' => 'boolean', + 'presenza_confermata' => 'boolean', + 'data_conferma_presenza' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Assemblea + */ + public function assemblea() + { + return $this->belongsTo(Assemblea::class, 'assemblea_id'); + } + + /** + * Relazione con Soggetto destinatario + */ + public function soggetto() + { + return $this->belongsTo(Soggetto::class, 'soggetto_id', 'id_soggetto'); + } + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con Soggetto delegato + */ + public function delegato() + { + return $this->belongsTo(Soggetto::class, 'delegato_soggetto_id', 'id_soggetto'); + } + + /** + * Scope per esito + */ + public function scopeEsito($query, $esito) + { + return $query->where('esito_invio', $esito); + } + + /** + * Scope per canale + */ + public function scopeCanale($query, $canale) + { + return $query->where('canale_invio', $canale); + } + + /** + * Conferma lettura + */ + public function confermaLettura() + { + $this->update([ + 'esito_invio' => 'letto', + 'data_lettura' => now(), + ]); + + return $this; + } + + /** + * Conferma presenza + */ + public function confermaPresenza() + { + $this->update([ + 'presenza_confermata' => true, + 'data_conferma_presenza' => now(), + ]); + + return $this; + } + + /** + * Carica delega + */ + public function caricaDelega($delegatoId, $documentoPath) + { + $this->update([ + 'delega_presente' => true, + 'delegato_soggetto_id' => $delegatoId, + 'documento_delega' => $documentoPath, + ]); + + return $this; + } +} \ No newline at end of file diff --git a/app/Models/DettaglioMovimento.php b/app/Models/DettaglioMovimento.php new file mode 100644 index 00000000..9226e839 --- /dev/null +++ b/app/Models/DettaglioMovimento.php @@ -0,0 +1,79 @@ + 'decimal:2', + 'importo_avere' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Movimento Contabile + */ + public function movimento() + { + return $this->belongsTo(MovimentoContabile::class, 'movimento_id'); + } + + /** + * Relazione con Piano Conti + */ + public function conto() + { + return $this->belongsTo(PianoConti::class, 'conto_id'); + } + + /** + * Relazione con Voce Spesa + */ + public function voceSpesa() + { + return $this->belongsTo(VoceSpesa::class, 'voce_spesa_id'); + } + + /** + * Relazione con Tabella Millesimale + */ + public function tabellaMillesimale() + { + return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_id'); + } + + /** + * Scope per dare + */ + public function scopeDare($query) + { + return $query->where('importo_dare', '>', 0); + } + + /** + * Scope per avere + */ + public function scopeAvere($query) + { + return $query->where('importo_avere', '>', 0); + } +} \ No newline at end of file diff --git a/app/Models/DettaglioTabellaMillesimale.php b/app/Models/DettaglioTabellaMillesimale.php new file mode 100644 index 00000000..d5f5fbad --- /dev/null +++ b/app/Models/DettaglioTabellaMillesimale.php @@ -0,0 +1,42 @@ + 'decimal:4', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Tabella Millesimale + */ + public function tabellaMillesimale() + { + return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_id'); + } + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } +} \ No newline at end of file diff --git a/app/Models/Documento.php b/app/Models/Documento.php new file mode 100644 index 00000000..e3dbf020 --- /dev/null +++ b/app/Models/Documento.php @@ -0,0 +1,73 @@ + 'array', + 'dimensione_file' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione polimorfica + */ + public function documentable(): MorphTo + { + return $this->morphTo(); + } + + /** + * Scope per tipo documento + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_documento', $tipo); + } + + /** + * Accessor per URL download + */ + public function getUrlDownloadAttribute() + { + return route('admin.documenti.download', $this->id); + } + + /** + * Accessor per dimensione leggibile + */ + public function getDimensioneLeggibileAttribute() + { + $bytes = $this->dimensione_file; + $units = ['B', 'KB', 'MB', 'GB']; + + for ($i = 0; $bytes > 1024; $i++) { + $bytes /= 1024; + } + + return round($bytes, 2) . ' ' . $units[$i]; + } +} \ No newline at end of file diff --git a/app/Models/Fornitore.php b/app/Models/Fornitore.php new file mode 100644 index 00000000..508d3761 --- /dev/null +++ b/app/Models/Fornitore.php @@ -0,0 +1,81 @@ + 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Amministratore + */ + public function amministratore() + { + return $this->belongsTo(Amministratore::class, 'amministratore_id', 'id_amministratore'); + } + + /** + * Relazione con Tickets assegnati + */ + public function ticketsAssegnati() + { + return $this->hasMany(Ticket::class, 'assegnato_a_fornitore_id', 'id_fornitore'); + } + + /** + * Accessor per l'indirizzo completo + */ + public function getIndirizzoCompletoAttribute() + { + $parts = []; + + if ($this->indirizzo) $parts[] = $this->indirizzo; + if ($this->cap && $this->citta) { + $parts[] = $this->cap . ' ' . $this->citta; + } elseif ($this->citta) { + $parts[] = $this->citta; + } + if ($this->provincia) $parts[] = '(' . $this->provincia . ')'; + + return implode(', ', $parts) ?: '-'; + } + + /** + * Scope per ricerca + */ + public function scopeSearch($query, $search) + { + return $query->where(function($q) use ($search) { + $q->where('ragione_sociale', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('telefono', 'like', "%{$search}%") + ->orWhere('partita_iva', 'like', "%{$search}%"); + }); + } +} \ No newline at end of file diff --git a/app/Models/Gestione.php b/app/Models/Gestione.php new file mode 100644 index 00000000..c5c6eeba --- /dev/null +++ b/app/Models/Gestione.php @@ -0,0 +1,95 @@ + 'date', + 'data_fine' => 'date', + 'data_approvazione' => 'date', + 'preventivo_approvato' => 'boolean', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Preventivi + */ + public function preventivi() + { + return $this->hasMany(Preventivo::class, 'gestione_id', 'id_gestione'); + } + + /** + * Relazione con Movimenti Contabili + */ + public function movimentiContabili() + { + return $this->hasMany(MovimentoContabile::class, 'gestione_id', 'id_gestione'); + } + + /** + * Relazione con Rate + */ + public function rate() + { + return $this->hasMany(Rata::class, 'gestione_id', 'id_gestione'); + } + + /** + * Scope per gestioni attive + */ + public function scopeAttive($query) + { + return $query->where('stato', 'attiva'); + } + + /** + * Scope per tipo gestione + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_gestione', $tipo); + } + + /** + * Accessor per il nome completo della gestione + */ + public function getNomeCompletoAttribute() + { + return $this->anno_gestione . ' - ' . ucfirst($this->tipo_gestione) . + ($this->descrizione ? ' (' . $this->descrizione . ')' : ''); + } +} \ No newline at end of file diff --git a/app/Models/LogModificaPreventivo.php b/app/Models/LogModificaPreventivo.php new file mode 100644 index 00000000..b597ec8b --- /dev/null +++ b/app/Models/LogModificaPreventivo.php @@ -0,0 +1,83 @@ + 'integer', + 'versione_nuova' => 'integer', + 'dati_precedenti' => 'array', + 'dati_nuovi' => 'array', + 'diff' => 'array', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con User + */ + public function utente() + { + return $this->belongsTo(User::class, 'utente_id'); + } + + /** + * Relazione polimorfica con entità + */ + public function entita() + { + return $this->morphTo(); + } + + /** + * Scope per entità + */ + public function scopePerEntita($query, $entita, $entitaId) + { + return $query->where('entita', $entita)->where('entita_id', $entitaId); + } + + /** + * Genera diff stile GIT + */ + public static function generaDiff($datiPrecedenti, $datiNuovi) + { + $diff = []; + + foreach ($datiNuovi as $campo => $valoreNuovo) { + $valorePrecedente = $datiPrecedenti[$campo] ?? null; + + if ($valorePrecedente != $valoreNuovo) { + $diff[] = [ + 'campo' => $campo, + 'da' => $valorePrecedente, + 'a' => $valoreNuovo, + 'tipo' => $valorePrecedente === null ? 'aggiunto' : 'modificato', + ]; + } + } + + return $diff; + } +} \ No newline at end of file diff --git a/app/Models/MovimentoBancario.php b/app/Models/MovimentoBancario.php new file mode 100644 index 00000000..e1853a9a --- /dev/null +++ b/app/Models/MovimentoBancario.php @@ -0,0 +1,68 @@ + 'date', + 'data_contabile' => 'date', + 'importo' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Banca + */ + public function banca() + { + return $this->belongsTo(Banca::class, 'banca_id'); + } + + /** + * Relazione con Movimento Contabile + */ + public function movimentoContabile() + { + return $this->belongsTo(MovimentoContabile::class, 'movimento_contabile_id'); + } + + /** + * Scope per tipo movimento + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_movimento', $tipo); + } + + /** + * Scope per stato riconciliazione + */ + public function scopeRiconciliazione($query, $stato) + { + return $query->where('stato_riconciliazione', $stato); + } +} \ No newline at end of file diff --git a/app/Models/MovimentoContabile.php b/app/Models/MovimentoContabile.php new file mode 100644 index 00000000..16c48f3c --- /dev/null +++ b/app/Models/MovimentoContabile.php @@ -0,0 +1,99 @@ + 'date', + 'data_documento' => 'date', + 'importo_totale' => 'decimal:2', + 'ritenuta_acconto' => 'decimal:2', + 'importo_netto' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Gestione + */ + public function gestione() + { + return $this->belongsTo(Gestione::class, 'gestione_id', 'id_gestione'); + } + + /** + * Relazione con Fornitore + */ + public function fornitore() + { + return $this->belongsTo(Fornitore::class, 'fornitore_id', 'id_fornitore'); + } + + /** + * Relazione con Documento + */ + public function documento() + { + return $this->belongsTo(Documento::class, 'documento_id'); + } + + /** + * Relazione con Dettagli Movimento (partita doppia) + */ + public function dettagli() + { + return $this->hasMany(DettaglioMovimento::class, 'movimento_id'); + } + + /** + * Scope per tipo movimento + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_movimento', $tipo); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } +} \ No newline at end of file diff --git a/app/Models/OrdineGiorno.php b/app/Models/OrdineGiorno.php new file mode 100644 index 00000000..d57de0ff --- /dev/null +++ b/app/Models/OrdineGiorno.php @@ -0,0 +1,244 @@ + 'integer', + 'importo_spesa' => 'decimal:2', + 'voti_favorevoli' => 'integer', + 'voti_contrari' => 'integer', + 'astenuti' => 'integer', + 'millesimi_favorevoli' => 'decimal:4', + 'millesimi_contrari' => 'decimal:4', + 'millesimi_astenuti' => 'decimal:4', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Assemblea + */ + public function assemblea() + { + return $this->belongsTo(Assemblea::class, 'assemblea_id'); + } + + /** + * Relazione con Preventivo collegato + */ + public function preventivo() + { + return $this->belongsTo(Preventivo::class, 'collegamento_preventivo_id'); + } + + /** + * Relazione con Tabella Millesimale + */ + public function tabellaMillesimale() + { + return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_id'); + } + + /** + * Relazione con Votazioni + */ + public function votazioni() + { + return $this->hasMany(Votazione::class, 'ordine_giorno_id'); + } + + /** + * Relazione con Delibera + */ + public function delibera() + { + return $this->hasOne(Delibera::class, 'ordine_giorno_id'); + } + + /** + * Registra votazione + */ + public function registraVotazione($soggetto, $unitaImmobiliare, $voto, $millesimi, $motivazione = null) + { + return Votazione::updateOrCreate( + [ + 'ordine_giorno_id' => $this->id, + 'soggetto_id' => $soggetto->id_soggetto, + 'unita_immobiliare_id' => $unitaImmobiliare->id_unita, + ], + [ + 'voto' => $voto, + 'millesimi_voto' => $millesimi, + 'data_voto' => now(), + 'motivazione' => $motivazione, + ] + ); + } + + /** + * Calcola risultato votazione + */ + public function calcolaRisultato() + { + $votazioni = $this->votazioni; + + $favorevoli = $votazioni->where('voto', 'favorevole'); + $contrari = $votazioni->where('voto', 'contrario'); + $astenuti = $votazioni->where('voto', 'astenuto'); + + $totaleMillesimiFavorevoli = $favorevoli->sum('millesimi_voto'); + $totaleMillesimiContrari = $contrari->sum('millesimi_voto'); + $totaleMillesimiAstenuti = $astenuti->sum('millesimi_voto'); + + $totaleMillesimiVotanti = $totaleMillesimiFavorevoli + $totaleMillesimiContrari + $totaleMillesimiAstenuti; + + // Calcola maggioranza (50% + 1 dei millesimi votanti) + $maggioranzaRichiesta = ($totaleMillesimiVotanti / 2) + 0.0001; + $maggioranzaRaggiunta = $totaleMillesimiFavorevoli >= $maggioranzaRichiesta; + + $esito = $maggioranzaRaggiunta ? 'approvato' : 'respinto'; + + // Aggiorna il punto dell'ordine del giorno + $this->update([ + 'esito_votazione' => $esito, + 'voti_favorevoli' => $favorevoli->count(), + 'voti_contrari' => $contrari->count(), + 'astenuti' => $astenuti->count(), + 'millesimi_favorevoli' => $totaleMillesimiFavorevoli, + 'millesimi_contrari' => $totaleMillesimiContrari, + 'millesimi_astenuti' => $totaleMillesimiAstenuti, + ]); + + // Crea delibera + if ($esito === 'approvato') { + $this->creaDelibera($totaleMillesimiFavorevoli, $totaleMillesimiContrari, $totaleMillesimiAstenuti); + } + + return [ + 'esito' => $esito, + 'maggioranza_raggiunta' => $maggioranzaRaggiunta, + 'percentuale_favorevoli' => $totaleMillesimiVotanti > 0 ? ($totaleMillesimiFavorevoli / $totaleMillesimiVotanti) * 100 : 0, + 'dettagli' => [ + 'favorevoli' => ['voti' => $favorevoli->count(), 'millesimi' => $totaleMillesimiFavorevoli], + 'contrari' => ['voti' => $contrari->count(), 'millesimi' => $totaleMillesimiContrari], + 'astenuti' => ['voti' => $astenuti->count(), 'millesimi' => $totaleMillesimiAstenuti], + ] + ]; + } + + /** + * Crea delibera se approvata + */ + private function creaDelibera($millFav, $millContr, $millAst) + { + $numeroDelibera = $this->generaNumeroDelibera(); + + $delibera = Delibera::create([ + 'ordine_giorno_id' => $this->id, + 'numero_delibera' => $numeroDelibera, + 'esito' => 'approvata', + 'testo_delibera' => $this->generaTestoDelibera(), + 'totale_voti_favorevoli' => $this->voti_favorevoli, + 'totale_voti_contrari' => $this->voti_contrari, + 'totale_astenuti' => $this->astenuti, + 'totale_millesimi_favorevoli' => $millFav, + 'totale_millesimi_contrari' => $millContr, + 'totale_millesimi_astenuti' => $millAst, + 'percentuale_approvazione' => ($millFav / ($millFav + $millContr + $millAst)) * 100, + 'maggioranza_raggiunta' => true, + 'data_delibera' => now(), + ]); + + // Se è una spesa approvata, avvia automazione + if ($this->tipo_voce === 'spesa' && $this->importo_spesa > 0) { + $this->avviaAutomazioneSpesa($delibera); + } + + return $delibera; + } + + /** + * Genera numero delibera + */ + private function generaNumeroDelibera() + { + $anno = $this->assemblea->data_prima_convocazione->year; + $ultimaDelibera = Delibera::whereHas('ordineGiorno.assemblea', function($q) use ($anno) { + $q->whereYear('data_prima_convocazione', $anno); + })->orderBy('numero_delibera', 'desc')->first(); + + if ($ultimaDelibera) { + $numero = intval(substr($ultimaDelibera->numero_delibera, -3)) + 1; + } else { + $numero = 1; + } + + return 'DEL/' . $anno . '/' . str_pad($numero, 3, '0', STR_PAD_LEFT); + } + + /** + * Genera testo delibera + */ + private function generaTestoDelibera() + { + $testo = "DELIBERA N. {$this->generaNumeroDelibera()}\n\n"; + $testo .= "OGGETTO: {$this->titolo}\n\n"; + $testo .= "DESCRIZIONE:\n{$this->descrizione}\n\n"; + + if ($this->tipo_voce === 'spesa' && $this->importo_spesa > 0) { + $testo .= "IMPORTO APPROVATO: € " . number_format($this->importo_spesa, 2, ',', '.') . "\n"; + if ($this->tabellaMillesimale) { + $testo .= "RIPARTIZIONE: {$this->tabellaMillesimale->nome}\n"; + } + } + + $testo .= "\nRISULTATO VOTAZIONE:\n"; + $testo .= "- Voti favorevoli: {$this->voti_favorevoli} (millesimi: {$this->millesimi_favorevoli})\n"; + $testo .= "- Voti contrari: {$this->voti_contrari} (millesimi: {$this->millesimi_contrari})\n"; + $testo .= "- Astenuti: {$this->astenuti} (millesimi: {$this->millesimi_astenuti})\n"; + + return $testo; + } + + /** + * Avvia automazione per spese approvate + */ + private function avviaAutomazioneSpesa($delibera) + { + AutomazioneSpesaApprovata::create([ + 'delibera_id' => $delibera->id, + 'stato_automazione' => 'in_attesa', + ]); + + // Qui si potrebbe implementare una coda per processare l'automazione + // Ad esempio: dispatch(new ProcessaSpesaApprovata($delibera)); + } +} \ No newline at end of file diff --git a/app/Models/Preventivo.php b/app/Models/Preventivo.php new file mode 100644 index 00000000..eaaa4449 --- /dev/null +++ b/app/Models/Preventivo.php @@ -0,0 +1,156 @@ + 'date', + 'data_approvazione' => 'date', + 'importo_totale' => 'decimal:2', + 'versione' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Voci Preventivo + */ + public function voci() + { + return $this->hasMany(VocePreventivo::class, 'preventivo_id'); + } + + /** + * Relazione con Rate + */ + public function rate() + { + return $this->hasMany(Rata::class, 'preventivo_id'); + } + + /** + * Relazione con User che ha approvato + */ + public function approvatoDa() + { + return $this->belongsTo(User::class, 'approvato_da_user_id'); + } + + /** + * Relazione con Log Modifiche + */ + public function logModifiche() + { + return $this->morphMany(LogModificaPreventivo::class, 'entita'); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } + + /** + * Scope per tipo gestione + */ + public function scopeTipoGestione($query, $tipo) + { + return $query->where('tipo_gestione', $tipo); + } + + /** + * Calcola importo totale dalle voci + */ + public function calcolaImportoTotale() + { + $this->importo_totale = $this->voci()->sum('importo_preventivato'); + $this->save(); + return $this->importo_totale; + } + + /** + * Crea nuova versione del preventivo + */ + public function creaVersione($motivo, $userId) + { + $nuovaVersione = $this->versione + 1; + + // Log della modifica + LogModificaPreventivo::create([ + 'entita' => 'preventivo', + 'entita_id' => $this->id, + 'versione_precedente' => $this->versione, + 'versione_nuova' => $nuovaVersione, + 'utente_id' => $userId, + 'tipo_operazione' => 'update', + 'motivo' => $motivo, + 'dati_precedenti' => $this->getOriginal(), + 'dati_nuovi' => $this->getAttributes(), + ]); + + $this->versione = $nuovaVersione; + $this->save(); + + return $this; + } + + /** + * Genera rate dal preventivo + */ + public function generaRate($numeroRate, $dataInizio, $userId) + { + $importoPerRata = $this->importo_totale / $numeroRate; + $rate = []; + + for ($i = 1; $i <= $numeroRate; $i++) { + $dataScadenza = $dataInizio->copy()->addMonths($i - 1); + $numeroRata = $this->stabile_id . '/' . $this->anno_gestione . '/' . str_pad($i, 3, '0', STR_PAD_LEFT); + + $rata = Rata::create([ + 'preventivo_id' => $this->id, + 'numero_rata' => $numeroRata, + 'descrizione' => "Rata {$i} di {$numeroRate} - {$this->descrizione}", + 'data_scadenza' => $dataScadenza, + 'stato' => 'emessa', + 'importo_totale' => $importoPerRata, + 'creato_da_user_id' => $userId, + ]); + + $rate[] = $rata; + } + + return $rate; + } +} \ No newline at end of file diff --git a/app/Models/Rata.php b/app/Models/Rata.php new file mode 100644 index 00000000..06cb6b5f --- /dev/null +++ b/app/Models/Rata.php @@ -0,0 +1,96 @@ + 'decimal:2', + 'importo_pagato' => 'decimal:2', + 'data_scadenza' => 'date', + 'data_pagamento' => 'date', + 'numero_rata' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Gestione + */ + public function gestione() + { + return $this->belongsTo(Gestione::class, 'gestione_id', 'id_gestione'); + } + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con Soggetto + */ + public function soggetto() + { + return $this->belongsTo(Soggetto::class, 'soggetto_id', 'id_soggetto'); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } + + /** + * Scope per scadute + */ + public function scopeScadute($query) + { + return $query->where('data_scadenza', '<', now()) + ->where('stato', '!=', 'pagata'); + } + + /** + * Scope per in scadenza + */ + public function scopeInScadenza($query, $giorni = 30) + { + return $query->whereBetween('data_scadenza', [now(), now()->addDays($giorni)]) + ->where('stato', '!=', 'pagata'); + } + + /** + * Accessor per importo residuo + */ + public function getImportoResiduoAttribute() + { + return $this->importo - $this->importo_pagato; + } +} \ No newline at end of file diff --git a/app/Models/RegistroProtocollo.php b/app/Models/RegistroProtocollo.php new file mode 100644 index 00000000..14982379 --- /dev/null +++ b/app/Models/RegistroProtocollo.php @@ -0,0 +1,108 @@ + 'datetime', + 'data_consegna' => 'datetime', + 'data_lettura' => 'datetime', + 'allegati' => 'array', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Assemblea + */ + public function assemblea() + { + return $this->belongsTo(Assemblea::class, 'assemblea_id'); + } + + /** + * Relazione con Soggetto destinatario + */ + public function soggettoDestinatario() + { + return $this->belongsTo(Soggetto::class, 'soggetto_destinatario_id', 'id_soggetto'); + } + + /** + * Relazione con Soggetto mittente + */ + public function soggettoMittente() + { + return $this->belongsTo(Soggetto::class, 'soggetto_mittente_id', 'id_soggetto'); + } + + /** + * Relazione con User creatore + */ + public function creatoDa() + { + return $this->belongsTo(User::class, 'creato_da_user_id'); + } + + /** + * Scope per tipo comunicazione + */ + public function scopeTipo($query, $tipo) + { + return $query->where('tipo_comunicazione', $tipo); + } + + /** + * Scope per periodo + */ + public function scopePeriodo($query, $dataInizio, $dataFine) + { + return $query->whereBetween('data_invio', [$dataInizio, $dataFine]); + } + + /** + * Genera numero protocollo automatico + */ + public static function generaNumeroProtocollo() + { + $anno = date('Y'); + $ultimoProtocollo = self::whereYear('created_at', $anno) + ->orderBy('numero_protocollo', 'desc') + ->first(); + + if ($ultimoProtocollo) { + $numero = intval(substr($ultimoProtocollo->numero_protocollo, -4)) + 1; + } else { + $numero = 1; + } + + return 'PROT/' . $anno . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } +} \ No newline at end of file diff --git a/app/Models/RichiestaModifica.php b/app/Models/RichiestaModifica.php new file mode 100644 index 00000000..409030e8 --- /dev/null +++ b/app/Models/RichiestaModifica.php @@ -0,0 +1,66 @@ + 'array', + 'dati_proposti' => 'array', + 'data_approvazione' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con Soggetto richiedente + */ + public function soggettoRichiedente() + { + return $this->belongsTo(Soggetto::class, 'soggetto_richiedente_id', 'id_soggetto'); + } + + /** + * Relazione con User che ha approvato + */ + public function approvatoDa() + { + return $this->belongsTo(User::class, 'approvato_da_user_id'); + } + + /** + * Scope per stato + */ + public function scopeStato($query, $stato) + { + return $query->where('stato', $stato); + } +} \ No newline at end of file diff --git a/app/Models/RipartizionePreventivo.php b/app/Models/RipartizionePreventivo.php new file mode 100644 index 00000000..c29eda7f --- /dev/null +++ b/app/Models/RipartizionePreventivo.php @@ -0,0 +1,98 @@ + 'decimal:2', + 'quota_modificata' => 'decimal:2', + 'quota_finale' => 'decimal:2', + 'versione' => 'integer', + 'data_modifica' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Voce Preventivo + */ + public function vocePreventivo() + { + return $this->belongsTo(VocePreventivo::class, 'voce_preventivo_id'); + } + + /** + * Relazione con Unità Immobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con User che ha modificato + */ + public function modificatoDa() + { + return $this->belongsTo(User::class, 'modificato_da_user_id'); + } + + /** + * Modifica quota con versionamento + */ + public function modificaQuota($nuovaQuota, $motivo, $userId) + { + $versionePrecedente = $this->versione; + $quotaPrecedente = $this->quota_finale; + + // Log della modifica + LogModificaPreventivo::create([ + 'entita' => 'ripartizione', + 'entita_id' => $this->id, + 'versione_precedente' => $versionePrecedente, + 'versione_nuova' => $versionePrecedente + 1, + 'utente_id' => $userId, + 'tipo_operazione' => 'update', + 'motivo' => $motivo, + 'dati_precedenti' => ['quota_finale' => $quotaPrecedente], + 'dati_nuovi' => ['quota_finale' => $nuovaQuota], + 'diff' => [ + 'campo' => 'quota_finale', + 'da' => $quotaPrecedente, + 'a' => $nuovaQuota, + 'differenza' => $nuovaQuota - $quotaPrecedente, + ], + ]); + + $this->update([ + 'quota_modificata' => $nuovaQuota, + 'quota_finale' => $nuovaQuota, + 'versione' => $versionePrecedente + 1, + 'modificato_da_user_id' => $userId, + 'motivo_modifica' => $motivo, + 'data_modifica' => now(), + ]); + + return $this; + } +} \ No newline at end of file diff --git a/app/Models/ScritturaBilancio.php b/app/Models/ScritturaBilancio.php new file mode 100644 index 00000000..cb174158 --- /dev/null +++ b/app/Models/ScritturaBilancio.php @@ -0,0 +1,141 @@ + 'date', + 'importo_totale' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Bilancio + */ + public function bilancio() + { + return $this->belongsTo(Bilancio::class, 'bilancio_id'); + } + + /** + * Relazione con Dettagli (Dare/Avere) + */ + public function dettagli() + { + return $this->hasMany(DettaglioScritturaBilancio::class, 'scrittura_bilancio_id'); + } + + /** + * Relazione con Ripartizioni + */ + public function ripartizioni() + { + return $this->hasMany(RipartizioneBilancio::class, 'scrittura_bilancio_id'); + } + + /** + * Relazione con Movimento Contabile + */ + public function movimentoContabile() + { + return $this->belongsTo(MovimentoContabile::class, 'movimento_contabile_id'); + } + + /** + * Relazione con User creatore + */ + public function creatoDa() + { + return $this->belongsTo(User::class, 'creato_da_user_id'); + } + + /** + * Verifica quadratura dare/avere + */ + public function verificaQuadratura() + { + $totaleDare = $this->dettagli()->sum('importo_dare'); + $totaleAvere = $this->dettagli()->sum('importo_avere'); + + return abs($totaleDare - $totaleAvere) < 0.01; // Tolleranza centesimi + } + + /** + * Crea scrittura in partita doppia + */ + public static function creaScrittura($bilancioId, $data, $descrizione, $dettagli, $userId) + { + $importoTotale = collect($dettagli)->sum(function($dettaglio) { + return max($dettaglio['dare'] ?? 0, $dettaglio['avere'] ?? 0); + }); + + $scrittura = self::create([ + 'bilancio_id' => $bilancioId, + 'numero_scrittura' => self::generaNumeroScrittura($bilancioId), + 'data_scrittura' => $data, + 'descrizione' => $descrizione, + 'tipo_scrittura' => 'gestione', + 'importo_totale' => $importoTotale, + 'creato_da_user_id' => $userId, + ]); + + // Crea dettagli dare/avere + foreach ($dettagli as $dettaglio) { + DettaglioScritturaBilancio::create([ + 'scrittura_bilancio_id' => $scrittura->id, + 'conto_id' => $dettaglio['conto_id'], + 'importo_dare' => $dettaglio['dare'] ?? 0, + 'importo_avere' => $dettaglio['avere'] ?? 0, + 'descrizione_dettaglio' => $dettaglio['descrizione'] ?? null, + ]); + } + + // Verifica quadratura + if (!$scrittura->verificaQuadratura()) { + throw new \Exception('Scrittura non quadra: totale dare diverso da totale avere'); + } + + return $scrittura; + } + + /** + * Genera numero scrittura progressivo + */ + private static function generaNumeroScrittura($bilancioId) + { + $bilancio = Bilancio::find($bilancioId); + $ultimaScrittura = self::where('bilancio_id', $bilancioId) + ->orderBy('numero_scrittura', 'desc') + ->first(); + + if ($ultimaScrittura) { + $numero = intval(substr($ultimaScrittura->numero_scrittura, -4)) + 1; + } else { + $numero = 1; + } + + return 'SCR/' . $bilancio->anno_esercizio . '/' . str_pad($numero, 4, '0', STR_PAD_LEFT); + } +} \ No newline at end of file diff --git a/app/Models/Stabile.php b/app/Models/Stabile.php new file mode 100644 index 00000000..eb942c2c --- /dev/null +++ b/app/Models/Stabile.php @@ -0,0 +1,74 @@ + 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Amministratore + */ + public function amministratore() + { + return $this->belongsTo(Amministratore::class, 'amministratore_id', 'id_amministratore'); + } + + /** + * Relazione con UnitaImmobiliari + */ + public function unitaImmobiliari() + { + return $this->hasMany(UnitaImmobiliare::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Tickets + */ + public function tickets() + { + return $this->hasMany(Ticket::class, 'stabile_id', 'id_stabile'); + } + + /** + * Scope per stabili attivi + */ + public function scopeAttivi($query) + { + return $query->where('stato', 'attivo'); + } + + /** + * Accessor per il nome completo dell'indirizzo + */ + public function getIndirizzoCompletoAttribute() + { + return $this->indirizzo . ', ' . $this->cap . ' ' . $this->citta . + ($this->provincia ? ' (' . $this->provincia . ')' : ''); + } +} \ No newline at end of file diff --git a/app/Models/TabellaMillesimale.php b/app/Models/TabellaMillesimale.php new file mode 100644 index 00000000..871a7f82 --- /dev/null +++ b/app/Models/TabellaMillesimale.php @@ -0,0 +1,71 @@ + 'boolean', + 'data_approvazione' => 'date', + 'ordinamento' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Dettagli Millesimi + */ + public function dettagli() + { + return $this->hasMany(DettaglioTabellaMillesimale::class, 'tabella_millesimale_id'); + } + + /** + * Scope per tabelle attive + */ + public function scopeAttive($query) + { + return $query->where('attiva', true); + } + + /** + * Scope ordinato + */ + public function scopeOrdinato($query) + { + return $query->orderBy('ordinamento')->orderBy('nome'); + } + + /** + * Calcola il totale millesimi + */ + public function getTotaleMillesimiAttribute() + { + return $this->dettagli()->sum('millesimi'); + } +} \ No newline at end of file diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php new file mode 100644 index 00000000..22ddefb2 --- /dev/null +++ b/app/Models/Ticket.php @@ -0,0 +1,119 @@ + 'datetime', + 'data_scadenza_prevista' => 'date', + 'data_risoluzione_effettiva' => 'date', + 'data_chiusura_effettiva' => 'date', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con UnitaImmobiliare + */ + public function unitaImmobiliare() + { + return $this->belongsTo(UnitaImmobiliare::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Relazione con Soggetto richiedente + */ + public function soggettoRichiedente() + { + return $this->belongsTo(Soggetto::class, 'soggetto_richiedente_id', 'id_soggetto'); + } + + /** + * Relazione con CategoriaTicket + */ + public function categoriaTicket() + { + return $this->belongsTo(CategoriaTicket::class, 'categoria_ticket_id'); + } + + /** + * Relazione con User che ha aperto il ticket + */ + public function apertoUser() + { + return $this->belongsTo(User::class, 'aperto_da_user_id'); + } + + /** + * Relazione con User assegnato + */ + public function assegnatoUser() + { + return $this->belongsTo(User::class, 'assegnato_a_user_id'); + } + + /** + * Relazione con Fornitore assegnato + */ + public function assegnatoFornitore() + { + return $this->belongsTo(Fornitore::class, 'assegnato_a_fornitore_id', 'id_fornitore'); + } + + /** + * Scope per ticket aperti + */ + public function scopeAperti($query) + { + return $query->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']); + } + + /** + * Scope per ticket chiusi + */ + public function scopeChiusi($query) + { + return $query->whereIn('stato', ['Risolto', 'Chiuso']); + } + + /** + * Scope per priorità + */ + public function scopePriorita($query, $priorita) + { + return $query->where('priorita', $priorita); + } +} \ No newline at end of file diff --git a/app/Models/UnitaImmobiliare.php b/app/Models/UnitaImmobiliare.php new file mode 100644 index 00000000..f98d3c1a --- /dev/null +++ b/app/Models/UnitaImmobiliare.php @@ -0,0 +1,75 @@ + 'decimal:4', + 'superficie' => 'decimal:2', + 'vani' => 'decimal:2', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Tickets + */ + public function tickets() + { + return $this->hasMany(Ticket::class, 'unita_immobiliare_id', 'id_unita'); + } + + /** + * Accessor per identificazione completa dell'unità + */ + public function getIdentificazioneCompiletaAttribute() + { + $parts = []; + + if ($this->fabbricato) $parts[] = 'Fabb. ' . $this->fabbricato; + if ($this->scala) $parts[] = 'Scala ' . $this->scala; + if ($this->piano) $parts[] = 'Piano ' . $this->piano; + if ($this->interno) $parts[] = 'Int. ' . $this->interno; + + return implode(', ', $parts) ?: 'N/A'; + } + + /** + * Accessor per l'indirizzo completo + */ + public function getIndirizzoCompletoAttribute() + { + return $this->indirizzo ?: $this->stabile->indirizzo_completo; + } +} \ No newline at end of file diff --git a/app/Models/VocePreventivo.php b/app/Models/VocePreventivo.php new file mode 100644 index 00000000..6fbd67e2 --- /dev/null +++ b/app/Models/VocePreventivo.php @@ -0,0 +1,101 @@ + 'decimal:2', + 'importo_effettivo' => 'decimal:2', + 'ricorrente' => 'boolean', + 'data_scadenza_prevista' => 'date', + 'ordinamento' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Preventivo + */ + public function preventivo() + { + return $this->belongsTo(Preventivo::class, 'preventivo_id'); + } + + /** + * Relazione con Tabella Millesimale + */ + public function tabellaMillesimale() + { + return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_id'); + } + + /** + * Relazione con Voce Spesa + */ + public function voceSpesa() + { + return $this->belongsTo(VoceSpesa::class, 'voce_spesa_id'); + } + + /** + * Relazione con Ripartizioni + */ + public function ripartizioni() + { + return $this->hasMany(RipartizionePreventivo::class, 'voce_preventivo_id'); + } + + /** + * Calcola ripartizione automatica + */ + public function calcolaRipartizione() + { + if (!$this->tabella_millesimale_id) { + return false; + } + + $dettagliMillesimi = $this->tabellaMillesimale->dettagli; + $totaleMillesimi = $dettagliMillesimi->sum('millesimi'); + + foreach ($dettagliMillesimi as $dettaglio) { + $quota = ($this->importo_preventivato * $dettaglio->millesimi) / $totaleMillesimi; + + RipartizionePreventivo::updateOrCreate( + [ + 'voce_preventivo_id' => $this->id, + 'unita_immobiliare_id' => $dettaglio->unita_immobiliare_id, + ], + [ + 'quota_calcolata' => $quota, + 'quota_finale' => $quota, + 'versione' => 1, + ] + ); + } + + return true; + } +} \ No newline at end of file diff --git a/app/Models/VoceSpesa.php b/app/Models/VoceSpesa.php new file mode 100644 index 00000000..0acfc92a --- /dev/null +++ b/app/Models/VoceSpesa.php @@ -0,0 +1,73 @@ + 'decimal:2', + 'attiva' => 'boolean', + 'ordinamento' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Relazione con Stabile + */ + public function stabile() + { + return $this->belongsTo(Stabile::class, 'stabile_id', 'id_stabile'); + } + + /** + * Relazione con Tabella Millesimale Default + */ + public function tabellaMillesimaleDefault() + { + return $this->belongsTo(TabellaMillesimale::class, 'tabella_millesimale_default_id'); + } + + /** + * Scope per voci attive + */ + public function scopeAttive($query) + { + return $query->where('attiva', true); + } + + /** + * Scope per tipo gestione + */ + public function scopeTipoGestione($query, $tipo) + { + return $query->where('tipo_gestione', $tipo); + } + + /** + * Scope ordinato + */ + public function scopeOrdinato($query) + { + return $query->orderBy('ordinamento')->orderBy('descrizione'); + } +} \ No newline at end of file diff --git a/database/migrations/2024_01_01_000001_rename_condomini_to_stabili.php b/database/migrations/2024_01_01_000001_rename_condomini_to_stabili.php new file mode 100644 index 00000000..771afdf5 --- /dev/null +++ b/database/migrations/2024_01_01_000001_rename_condomini_to_stabili.php @@ -0,0 +1,72 @@ +renameColumn('nome', 'denominazione'); + $table->renameColumn('id_condominio', 'id_stabile'); + }); + + // Aggiorna le foreign key nelle tabelle correlate + if (Schema::hasTable('unita_immobiliari')) { + Schema::table('unita_immobiliari', function (Blueprint $table) { + $table->dropForeign(['condominio_id']); + $table->renameColumn('condominio_id', 'stabile_id'); + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + }); + } + + if (Schema::hasTable('tickets')) { + Schema::table('tickets', function (Blueprint $table) { + $table->dropForeign(['condominio_id']); + $table->renameColumn('condominio_id', 'stabile_id'); + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Ripristina le foreign key nelle tabelle correlate + if (Schema::hasTable('tickets')) { + Schema::table('tickets', function (Blueprint $table) { + $table->dropForeign(['stabile_id']); + $table->renameColumn('stabile_id', 'condominio_id'); + $table->foreign('condominio_id')->references('id_condominio')->on('condomini')->onDelete('cascade'); + }); + } + + if (Schema::hasTable('unita_immobiliari')) { + Schema::table('unita_immobiliari', function (Blueprint $table) { + $table->dropForeign(['stabile_id']); + $table->renameColumn('stabile_id', 'condominio_id'); + $table->foreign('condominio_id')->references('id_condominio')->on('condomini')->onDelete('cascade'); + }); + } + + // Ripristina le colonne originali + Schema::table('stabili', function (Blueprint $table) { + $table->renameColumn('denominazione', 'nome'); + $table->renameColumn('id_stabile', 'id_condominio'); + }); + + // Rinomina la tabella stabili in condomini + Schema::rename('stabili', 'condomini'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_02_000001_create_contabilita_tables.php b/database/migrations/2024_01_02_000001_create_contabilita_tables.php new file mode 100644 index 00000000..98cde836 --- /dev/null +++ b/database/migrations/2024_01_02_000001_create_contabilita_tables.php @@ -0,0 +1,227 @@ +id('id_gestione'); + $table->unsignedBigInteger('stabile_id'); + $table->year('anno_gestione'); + $table->enum('tipo_gestione', ['ordinaria', 'riscaldamento', 'straordinaria', 'acqua', 'altro']); + $table->date('data_inizio'); + $table->date('data_fine'); + $table->string('descrizione')->nullable(); + $table->enum('stato', ['bozza', 'attiva', 'chiusa'])->default('bozza'); + $table->boolean('preventivo_approvato')->default(false); + $table->date('data_approvazione')->nullable(); + $table->text('note')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->index(['stabile_id', 'anno_gestione', 'tipo_gestione']); + }); + + // Tabella voci_spesa + Schema::create('voci_spesa', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->string('codice', 20); + $table->string('descrizione'); + $table->enum('tipo_gestione', ['ordinaria', 'riscaldamento', 'straordinaria', 'acqua', 'altro']); + $table->string('categoria', 100)->nullable(); + $table->unsignedBigInteger('tabella_millesimale_default_id')->nullable(); + $table->decimal('ritenuta_acconto_default', 5, 2)->default(0); + $table->boolean('attiva')->default(true); + $table->integer('ordinamento')->default(0); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->unique(['stabile_id', 'codice']); + }); + + // Tabella tabelle_millesimali + Schema::create('tabelle_millesimali', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->string('nome'); + $table->text('descrizione')->nullable(); + $table->enum('tipo', ['proprieta', 'riscaldamento', 'ascensore', 'scale', 'altro']); + $table->boolean('attiva')->default(true); + $table->date('data_approvazione')->nullable(); + $table->integer('ordinamento')->default(0); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + }); + + // Tabella dettagli_tabelle_millesimali + Schema::create('dettagli_tabelle_millesimali', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('tabella_millesimale_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->decimal('millesimi', 10, 4); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->unique(['tabella_millesimale_id', 'unita_immobiliare_id'], 'unique_tabella_unita'); + }); + + // Tabella documenti + Schema::create('documenti', function (Blueprint $table) { + $table->id(); + $table->morphs('documentable'); + $table->string('nome_file'); + $table->string('path_file'); + $table->string('tipo_documento', 100); + $table->unsignedBigInteger('dimensione_file')->nullable(); + $table->string('mime_type', 100)->nullable(); + $table->text('descrizione')->nullable(); + $table->json('xml_data')->nullable(); + $table->string('hash_file', 64)->nullable(); + $table->timestamps(); + + $table->index(['documentable_type', 'documentable_id']); + $table->index('tipo_documento'); + }); + + // Tabella movimenti_contabili + Schema::create('movimenti_contabili', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->unsignedBigInteger('gestione_id'); + $table->unsignedBigInteger('fornitore_id')->nullable(); + $table->unsignedBigInteger('documento_id')->nullable(); + $table->string('protocollo', 50)->unique(); + $table->date('data_registrazione'); + $table->date('data_documento'); + $table->string('numero_documento', 50); + $table->text('descrizione'); + $table->enum('tipo_movimento', ['entrata', 'uscita']); + $table->decimal('importo_totale', 10, 2); + $table->decimal('ritenuta_acconto', 10, 2)->default(0); + $table->decimal('importo_netto', 10, 2); + $table->enum('stato', ['bozza', 'registrato', 'contabilizzato', 'annullato'])->default('bozza'); + $table->text('note')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('gestione_id')->references('id_gestione')->on('gestioni')->onDelete('cascade'); + $table->foreign('fornitore_id')->references('id_fornitore')->on('fornitori')->onDelete('set null'); + $table->foreign('documento_id')->references('id')->on('documenti')->onDelete('set null'); + + $table->index(['stabile_id', 'data_registrazione']); + $table->index(['gestione_id', 'tipo_movimento']); + }); + + // Tabella dettagli_movimenti (partita doppia) + Schema::create('dettagli_movimenti', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('movimento_id'); + $table->unsignedBigInteger('conto_id')->nullable(); + $table->unsignedBigInteger('voce_spesa_id')->nullable(); + $table->unsignedBigInteger('tabella_millesimale_id')->nullable(); + $table->text('descrizione')->nullable(); + $table->decimal('importo_dare', 10, 2)->default(0); + $table->decimal('importo_avere', 10, 2)->default(0); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('movimento_id')->references('id')->on('movimenti_contabili')->onDelete('cascade'); + $table->foreign('voce_spesa_id')->references('id')->on('voci_spesa')->onDelete('set null'); + $table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('set null'); + }); + + // Tabella banche + Schema::create('banche', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->string('denominazione'); + $table->string('iban', 34); + $table->string('bic_swift', 11)->nullable(); + $table->string('agenzia')->nullable(); + $table->string('indirizzo_agenzia')->nullable(); + $table->enum('tipo_conto', ['corrente', 'deposito', 'altro'])->default('corrente'); + $table->decimal('saldo_iniziale', 10, 2)->default(0); + $table->date('data_apertura')->nullable(); + $table->enum('stato', ['attivo', 'chiuso'])->default('attivo'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->unique(['stabile_id', 'iban']); + }); + + // Tabella movimenti_bancari + Schema::create('movimenti_bancari', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('banca_id'); + $table->unsignedBigInteger('movimento_contabile_id')->nullable(); + $table->date('data_valuta'); + $table->date('data_contabile'); + $table->enum('tipo_movimento', ['entrata', 'uscita']); + $table->decimal('importo', 10, 2); + $table->text('causale'); + $table->string('beneficiario')->nullable(); + $table->string('ordinante')->nullable(); + $table->string('cro_tro', 50)->nullable(); + $table->enum('stato_riconciliazione', ['da_riconciliare', 'riconciliato', 'sospeso'])->default('da_riconciliare'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('banca_id')->references('id')->on('banche')->onDelete('cascade'); + $table->foreign('movimento_contabile_id')->references('id')->on('movimenti_contabili')->onDelete('set null'); + + $table->index(['banca_id', 'data_valuta']); + $table->index('stato_riconciliazione'); + }); + + // Tabella rate + Schema::create('rate', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('gestione_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->integer('numero_rata'); + $table->string('descrizione'); + $table->decimal('importo', 10, 2); + $table->date('data_scadenza'); + $table->date('data_pagamento')->nullable(); + $table->decimal('importo_pagato', 10, 2)->default(0); + $table->enum('stato', ['da_pagare', 'pagata', 'parziale', 'insoluta'])->default('da_pagare'); + $table->enum('tipo_rata', ['ordinaria', 'riscaldamento', 'straordinaria', 'conguaglio'])->default('ordinaria'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('gestione_id')->references('id_gestione')->on('gestioni')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + + $table->index(['gestione_id', 'data_scadenza']); + $table->index(['unita_immobiliare_id', 'stato']); + }); + } + + public function down(): void + { + Schema::dropIfExists('rate'); + Schema::dropIfExists('movimenti_bancari'); + Schema::dropIfExists('banche'); + Schema::dropIfExists('dettagli_movimenti'); + Schema::dropIfExists('movimenti_contabili'); + Schema::dropIfExists('documenti'); + Schema::dropIfExists('dettagli_tabelle_millesimali'); + Schema::dropIfExists('tabelle_millesimali'); + Schema::dropIfExists('voci_spesa'); + Schema::dropIfExists('gestioni'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_03_000001_create_richieste_modifiche_table.php b/database/migrations/2024_01_03_000001_create_richieste_modifiche_table.php new file mode 100644 index 00000000..b5863028 --- /dev/null +++ b/database/migrations/2024_01_03_000001_create_richieste_modifiche_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->unsignedBigInteger('soggetto_richiedente_id'); + $table->enum('tipo_modifica', ['anagrafica', 'catastale', 'proprieta']); + $table->text('descrizione'); + $table->json('dati_attuali'); + $table->json('dati_proposti'); + $table->enum('stato', ['in_attesa', 'approvata', 'rifiutata'])->default('in_attesa'); + $table->text('note_amministratore')->nullable(); + $table->datetime('data_approvazione')->nullable(); + $table->unsignedBigInteger('approvato_da_user_id')->nullable(); + $table->timestamps(); + + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('soggetto_richiedente_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->foreign('approvato_da_user_id')->references('id')->on('users')->onDelete('set null'); + + $table->index(['stato', 'created_at']); + }); + } + + public function down(): void + { + Schema::dropIfExists('richieste_modifiche'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_04_000001_create_preventivi_tables.php b/database/migrations/2024_01_04_000001_create_preventivi_tables.php new file mode 100644 index 00000000..f07a60c3 --- /dev/null +++ b/database/migrations/2024_01_04_000001_create_preventivi_tables.php @@ -0,0 +1,204 @@ +id(); + $table->unsignedBigInteger('stabile_id'); + $table->year('anno_gestione'); + $table->enum('tipo_gestione', ['ordinaria', 'riscaldamento', 'straordinaria', 'acqua', 'altro']); + $table->string('descrizione'); + $table->enum('stato', ['bozza', 'provvisorio', 'definitivo', 'approvato', 'archiviato'])->default('bozza'); + $table->decimal('importo_totale', 12, 2)->default(0); + $table->date('data_creazione'); + $table->date('data_approvazione')->nullable(); + $table->unsignedBigInteger('approvato_da_user_id')->nullable(); + $table->text('note')->nullable(); + $table->integer('versione')->default(1); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('approvato_da_user_id')->references('id')->on('users')->onDelete('set null'); + $table->index(['stabile_id', 'anno_gestione', 'tipo_gestione']); + }); + + // Tabella voci preventivo + Schema::create('voci_preventivo', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('preventivo_id'); + $table->string('codice', 20); + $table->string('descrizione'); + $table->decimal('importo_preventivato', 10, 2); + $table->decimal('importo_effettivo', 10, 2)->default(0); + $table->unsignedBigInteger('tabella_millesimale_id')->nullable(); + $table->unsignedBigInteger('voce_spesa_id')->nullable(); + $table->boolean('ricorrente')->default(false); + $table->enum('frequenza', ['mensile', 'trimestrale', 'semestrale', 'annuale'])->nullable(); + $table->date('data_scadenza_prevista')->nullable(); + $table->integer('ordinamento')->default(0); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('preventivo_id')->references('id')->on('preventivi')->onDelete('cascade'); + $table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('set null'); + $table->foreign('voce_spesa_id')->references('id')->on('voci_spesa')->onDelete('set null'); + }); + + // Tabella ripartizioni preventivo + Schema::create('ripartizioni_preventivo', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('voce_preventivo_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->decimal('quota_calcolata', 10, 2); + $table->decimal('quota_modificata', 10, 2)->nullable(); + $table->decimal('quota_finale', 10, 2); + $table->integer('versione')->default(1); + $table->unsignedBigInteger('modificato_da_user_id')->nullable(); + $table->string('motivo_modifica')->nullable(); + $table->timestamp('data_modifica')->nullable(); + $table->timestamps(); + + $table->foreign('voce_preventivo_id')->references('id')->on('voci_preventivo')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('modificato_da_user_id')->references('id')->on('users')->onDelete('set null'); + $table->unique(['voce_preventivo_id', 'unita_immobiliare_id'], 'unique_voce_unita'); + }); + + // Tabella rate + Schema::create('rate', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('preventivo_id'); + $table->string('numero_rata', 50)->unique(); + $table->string('descrizione'); + $table->date('data_scadenza'); + $table->enum('stato', ['bozza', 'emessa', 'modificata', 'annullata'])->default('bozza'); + $table->decimal('importo_totale', 12, 2); + $table->integer('versione')->default(1); + $table->unsignedBigInteger('creato_da_user_id'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('preventivo_id')->references('id')->on('preventivi')->onDelete('cascade'); + $table->foreign('creato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + // Tabella rate unità (dettaglio rate per ogni unità) + Schema::create('rate_unita', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('rata_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->decimal('importo_originale', 10, 2); + $table->decimal('importo_modificato', 10, 2)->nullable(); + $table->decimal('importo_finale', 10, 2); + $table->decimal('importo_pagato', 10, 2)->default(0); + $table->enum('stato_pagamento', ['da_pagare', 'parziale', 'pagata', 'insoluta'])->default('da_pagare'); + $table->date('data_pagamento')->nullable(); + $table->integer('versione')->default(1); + $table->unsignedBigInteger('modificato_da_user_id')->nullable(); + $table->string('motivo_modifica')->nullable(); + $table->timestamp('data_modifica')->nullable(); + $table->timestamps(); + + $table->foreign('rata_id')->references('id')->on('rate')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->foreign('modificato_da_user_id')->references('id')->on('users')->onDelete('set null'); + $table->unique(['rata_id', 'unita_immobiliare_id'], 'unique_rata_unita'); + }); + + // Tabella incassi + Schema::create('incassi', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('rata_unita_id'); + $table->date('data_incasso'); + $table->decimal('importo', 10, 2); + $table->enum('metodo_pagamento', ['bonifico', 'contanti', 'assegno', 'pos', 'altro']); + $table->string('riferimento_bancario')->nullable(); + $table->string('numero_documento')->nullable(); + $table->unsignedBigInteger('movimento_bancario_id')->nullable(); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('rata_unita_id')->references('id')->on('rate_unita')->onDelete('cascade'); + $table->index(['data_incasso', 'metodo_pagamento']); + }); + + // Tabella log modifiche (audit trail stile GIT) + Schema::create('log_modifiche_preventivo', function (Blueprint $table) { + $table->id(); + $table->string('entita'); // 'preventivo', 'voce', 'ripartizione', 'rata' + $table->unsignedBigInteger('entita_id'); + $table->integer('versione_precedente'); + $table->integer('versione_nuova'); + $table->unsignedBigInteger('utente_id'); + $table->string('tipo_operazione'); // 'create', 'update', 'delete' + $table->text('motivo'); + $table->json('dati_precedenti')->nullable(); + $table->json('dati_nuovi'); + $table->json('diff')->nullable(); // Differenze stile GIT + $table->timestamps(); + + $table->foreign('utente_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['entita', 'entita_id', 'versione_nuova']); + }); + + // Tabella pianificazione spese + Schema::create('pianificazione_spese', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->unsignedBigInteger('preventivo_id')->nullable(); + $table->string('descrizione'); + $table->decimal('importo_previsto', 10, 2); + $table->date('data_scadenza_prevista'); + $table->enum('tipo', ['ricorrente', 'straordinaria', 'manutenzione']); + $table->enum('stato', ['pianificata', 'confermata', 'pagata', 'annullata'])->default('pianificata'); + $table->boolean('notifica_inviata')->default(false); + $table->integer('giorni_preavviso')->default(30); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('preventivo_id')->references('id')->on('preventivi')->onDelete('set null'); + $table->index(['data_scadenza_prevista', 'stato']); + }); + + // Tabella configurazione banche per automazione + Schema::create('configurazioni_banche', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->string('nome_banca'); + $table->string('iban', 34); + $table->string('api_endpoint')->nullable(); + $table->text('credenziali_api')->nullable(); // Encrypted + $table->enum('tipo_importazione', ['api', 'csv', 'cbi', 'manuale']); + $table->json('mapping_campi')->nullable(); + $table->boolean('attiva')->default(true); + $table->timestamp('ultima_sincronizzazione')->nullable(); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::dropIfExists('configurazioni_banche'); + Schema::dropIfExists('pianificazione_spese'); + Schema::dropIfExists('log_modifiche_preventivo'); + Schema::dropIfExists('incassi'); + Schema::dropIfExists('rate_unita'); + Schema::dropIfExists('rate'); + Schema::dropIfExists('ripartizioni_preventivo'); + Schema::dropIfExists('voci_preventivo'); + Schema::dropIfExists('preventivi'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_05_000001_create_bilanci_tables.php b/database/migrations/2024_01_05_000001_create_bilanci_tables.php new file mode 100644 index 00000000..aea82ee8 --- /dev/null +++ b/database/migrations/2024_01_05_000001_create_bilanci_tables.php @@ -0,0 +1,273 @@ +id(); + $table->unsignedBigInteger('stabile_id'); + $table->unsignedBigInteger('gestione_id'); + $table->year('anno_esercizio'); + $table->date('data_inizio_esercizio'); + $table->date('data_fine_esercizio'); + $table->enum('tipo_gestione', ['ordinaria', 'riscaldamento', 'straordinaria', 'acqua', 'altro']); + $table->string('descrizione'); + $table->enum('stato', ['bozza', 'provvisorio', 'definitivo', 'approvato', 'chiuso'])->default('bozza'); + $table->decimal('totale_entrate', 12, 2)->default(0); + $table->decimal('totale_uscite', 12, 2)->default(0); + $table->decimal('risultato_gestione', 12, 2)->default(0); // Avanzo/Disavanzo + $table->date('data_approvazione')->nullable(); + $table->unsignedBigInteger('approvato_da_user_id')->nullable(); + $table->date('data_chiusura')->nullable(); + $table->unsignedBigInteger('chiuso_da_user_id')->nullable(); + $table->text('note')->nullable(); + $table->integer('versione')->default(1); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('gestione_id')->references('id_gestione')->on('gestioni')->onDelete('cascade'); + $table->foreign('approvato_da_user_id')->references('id')->on('users')->onDelete('set null'); + $table->foreign('chiuso_da_user_id')->references('id')->on('users')->onDelete('set null'); + $table->index(['stabile_id', 'anno_esercizio', 'tipo_gestione']); + }); + + // Tabella piano dei conti + Schema::create('piano_conti', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('stabile_id'); + $table->string('codice', 20); + $table->string('descrizione'); + $table->enum('tipo_conto', ['attivo', 'passivo', 'costo', 'ricavo']); + $table->enum('categoria', ['patrimoniale', 'economico']); + $table->unsignedBigInteger('conto_padre_id')->nullable(); + $table->integer('livello')->default(1); + $table->boolean('attivo')->default(true); + $table->integer('ordinamento')->default(0); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('conto_padre_id')->references('id')->on('piano_conti')->onDelete('set null'); + $table->unique(['stabile_id', 'codice']); + }); + + // Tabella scritture contabili (partita doppia) + Schema::create('scritture_bilancio', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->string('numero_scrittura', 50); + $table->date('data_scrittura'); + $table->string('descrizione'); + $table->enum('tipo_scrittura', ['apertura', 'gestione', 'chiusura', 'rettifica']); + $table->decimal('importo_totale', 12, 2); + $table->string('riferimento_documento')->nullable(); + $table->unsignedBigInteger('movimento_contabile_id')->nullable(); + $table->unsignedBigInteger('creato_da_user_id'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + $table->foreign('movimento_contabile_id')->references('id')->on('movimenti_contabili')->onDelete('set null'); + $table->foreign('creato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['bilancio_id', 'data_scrittura']); + }); + + // Tabella dettagli scritture (dare/avere) + Schema::create('dettagli_scritture_bilancio', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('scrittura_bilancio_id'); + $table->unsignedBigInteger('conto_id'); + $table->decimal('importo_dare', 12, 2)->default(0); + $table->decimal('importo_avere', 12, 2)->default(0); + $table->string('descrizione_dettaglio')->nullable(); + $table->timestamps(); + + $table->foreign('scrittura_bilancio_id')->references('id')->on('scritture_bilancio')->onDelete('cascade'); + $table->foreign('conto_id')->references('id')->on('piano_conti')->onDelete('cascade'); + }); + + // Tabella ripartizioni bilancio + Schema::create('ripartizioni_bilancio', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('scrittura_bilancio_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->unsignedBigInteger('tabella_millesimale_id'); + $table->decimal('quota_calcolata', 10, 2); + $table->decimal('quota_modificata', 10, 2)->nullable(); + $table->decimal('quota_finale', 10, 2); + $table->integer('versione')->default(1); + $table->unsignedBigInteger('modificato_da_user_id')->nullable(); + $table->string('motivo_modifica')->nullable(); + $table->timestamp('data_modifica')->nullable(); + $table->timestamps(); + + $table->foreign('scrittura_bilancio_id')->references('id')->on('scritture_bilancio')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('cascade'); + $table->foreign('modificato_da_user_id')->references('id')->on('users')->onDelete('set null'); + }); + + // Tabella conguagli + Schema::create('conguagli', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->decimal('totale_rate_pagate', 10, 2)->default(0); + $table->decimal('totale_spese_effettive', 10, 2)->default(0); + $table->decimal('conguaglio_dovuto', 10, 2)->default(0); // Positivo = a credito, Negativo = a debito + $table->enum('tipo_conguaglio', ['a_credito', 'a_debito', 'pareggio']); + $table->enum('stato', ['calcolato', 'confermato', 'pagato', 'rimborsato'])->default('calcolato'); + $table->date('data_calcolo'); + $table->date('data_pagamento')->nullable(); + $table->decimal('importo_pagato', 10, 2)->default(0); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->index(['bilancio_id', 'tipo_conguaglio']); + }); + + // Tabella rate conguaglio + Schema::create('rate_conguaglio', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('conguaglio_id'); + $table->string('numero_rata', 50)->unique(); + $table->string('descrizione'); + $table->date('data_scadenza'); + $table->decimal('importo_rata', 10, 2); + $table->enum('stato_pagamento', ['da_pagare', 'parziale', 'pagata', 'insoluta'])->default('da_pagare'); + $table->decimal('importo_pagato', 10, 2)->default(0); + $table->date('data_pagamento')->nullable(); + $table->boolean('rateizzato')->default(false); + $table->integer('numero_rate_totali')->default(1); + $table->integer('numero_rata_corrente')->default(1); + $table->integer('versione')->default(1); + $table->timestamps(); + + $table->foreign('conguaglio_id')->references('id')->on('conguagli')->onDelete('cascade'); + }); + + // Tabella quadrature + Schema::create('quadrature', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->date('data_quadratura'); + $table->decimal('saldo_banca_effettivo', 12, 2); + $table->decimal('saldo_contabile_calcolato', 12, 2); + $table->decimal('differenza', 12, 2); + $table->decimal('totale_crediti_condomini', 12, 2); + $table->decimal('totale_debiti_condomini', 12, 2); + $table->decimal('totale_rate_emesse', 12, 2); + $table->decimal('totale_rate_incassate', 12, 2); + $table->boolean('quadratura_ok')->default(false); + $table->text('note_differenze')->nullable(); + $table->unsignedBigInteger('verificato_da_user_id'); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + $table->foreign('verificato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + // Tabella rimborsi assicurativi + Schema::create('rimborsi_assicurativi', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->string('numero_sinistro'); + $table->string('compagnia_assicurativa'); + $table->date('data_sinistro'); + $table->date('data_denuncia'); + $table->decimal('importo_richiesto', 10, 2); + $table->decimal('importo_liquidato', 10, 2)->default(0); + $table->enum('stato', ['denunciato', 'in_valutazione', 'liquidato', 'rifiutato', 'chiuso']); + $table->enum('tipo_accredito', ['rate_condomini', 'pagamento_diretto', 'fondo_comune']); + $table->date('data_liquidazione')->nullable(); + $table->text('descrizione_sinistro'); + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + }); + + // Tabella reminder e ticket automatici + Schema::create('reminder_bilancio', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->enum('tipo_reminder', ['scadenza_spesa', 'rinnovo_contratto', 'verifica_quadratura', 'chiusura_esercizio']); + $table->string('descrizione'); + $table->date('data_scadenza'); + $table->boolean('ricorrente')->default(false); + $table->enum('frequenza', ['mensile', 'trimestrale', 'semestrale', 'annuale'])->nullable(); + $table->integer('giorni_preavviso')->default(30); + $table->enum('stato', ['attivo', 'eseguito', 'annullato'])->default('attivo'); + $table->boolean('notifica_inviata')->default(false); + $table->timestamp('data_notifica')->nullable(); + $table->unsignedBigInteger('ticket_generato_id')->nullable(); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + $table->foreign('ticket_generato_id')->references('id')->on('tickets')->onDelete('set null'); + }); + + // Tabella log modifiche bilancio (audit trail) + Schema::create('log_modifiche_bilancio', function (Blueprint $table) { + $table->id(); + $table->string('entita'); // 'bilancio', 'scrittura', 'ripartizione', 'conguaglio' + $table->unsignedBigInteger('entita_id'); + $table->integer('versione_precedente'); + $table->integer('versione_nuova'); + $table->unsignedBigInteger('utente_id'); + $table->string('tipo_operazione'); // 'create', 'update', 'delete', 'approve', 'close' + $table->text('motivo'); + $table->json('dati_precedenti')->nullable(); + $table->json('dati_nuovi'); + $table->json('diff')->nullable(); // Differenze stile GIT + $table->timestamps(); + + $table->foreign('utente_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['entita', 'entita_id', 'versione_nuova']); + }); + + // Tabella automazioni fine anno + Schema::create('automazioni_fine_anno', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('bilancio_id'); + $table->enum('tipo_automazione', ['chiusura_conti', 'riporto_saldi', 'calcolo_conguagli', 'generazione_rate']); + $table->string('descrizione'); + $table->enum('stato', ['programmata', 'in_esecuzione', 'completata', 'errore']); + $table->date('data_programmata'); + $table->timestamp('data_esecuzione')->nullable(); + $table->json('parametri')->nullable(); + $table->json('risultato')->nullable(); + $table->text('log_esecuzione')->nullable(); + $table->text('errori')->nullable(); + $table->timestamps(); + + $table->foreign('bilancio_id')->references('id')->on('bilanci')->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::dropIfExists('automazioni_fine_anno'); + Schema::dropIfExists('log_modifiche_bilancio'); + Schema::dropIfExists('reminder_bilancio'); + Schema::dropIfExists('rimborsi_assicurativi'); + Schema::dropIfExists('quadrature'); + Schema::dropIfExists('rate_conguaglio'); + Schema::dropIfExists('conguagli'); + Schema::dropIfExists('ripartizioni_bilancio'); + Schema::dropIfExists('dettagli_scritture_bilancio'); + Schema::dropIfExists('scritture_bilancio'); + Schema::dropIfExists('piano_conti'); + Schema::dropIfExists('bilanci'); + } +}; \ No newline at end of file diff --git a/database/migrations/2024_01_06_000001_create_assemblee_tables.php b/database/migrations/2024_01_06_000001_create_assemblee_tables.php new file mode 100644 index 00000000..1d7256f8 --- /dev/null +++ b/database/migrations/2024_01_06_000001_create_assemblee_tables.php @@ -0,0 +1,242 @@ +id(); + $table->unsignedBigInteger('stabile_id'); + $table->enum('tipo', ['ordinaria', 'straordinaria']); + $table->datetime('data_prima_convocazione'); + $table->datetime('data_seconda_convocazione'); + $table->string('luogo'); + $table->text('note')->nullable(); + $table->enum('stato', ['bozza', 'convocata', 'svolta', 'chiusa', 'archiviata'])->default('bozza'); + $table->date('data_convocazione')->nullable(); + $table->date('data_svolgimento')->nullable(); + $table->unsignedBigInteger('creato_da_user_id'); + $table->timestamps(); + + $table->foreign('stabile_id')->references('id_stabile')->on('stabili')->onDelete('cascade'); + $table->foreign('creato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['stabile_id', 'data_prima_convocazione']); + }); + + // Tabella ordine del giorno + Schema::create('ordine_giorno', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('assemblea_id'); + $table->integer('numero_punto'); + $table->string('titolo'); + $table->text('descrizione'); + $table->enum('tipo_voce', ['discussione', 'delibera', 'spesa', 'preventivo', 'altro']); + $table->unsignedBigInteger('collegamento_preventivo_id')->nullable(); + $table->decimal('importo_spesa', 12, 2)->nullable(); + $table->unsignedBigInteger('tabella_millesimale_id')->nullable(); + $table->enum('esito_votazione', ['non_votato', 'approvato', 'respinto', 'rinviato'])->default('non_votato'); + $table->integer('voti_favorevoli')->default(0); + $table->integer('voti_contrari')->default(0); + $table->integer('astenuti')->default(0); + $table->decimal('millesimi_favorevoli', 10, 4)->default(0); + $table->decimal('millesimi_contrari', 10, 4)->default(0); + $table->decimal('millesimi_astenuti', 10, 4)->default(0); + $table->text('note_delibera')->nullable(); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('cascade'); + $table->foreign('collegamento_preventivo_id')->references('id')->on('preventivi')->onDelete('set null'); + $table->foreign('tabella_millesimale_id')->references('id')->on('tabelle_millesimali')->onDelete('set null'); + }); + + // Tabella convocazioni (tracciamento invii) + Schema::create('convocazioni', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('assemblea_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->enum('canale_invio', ['email', 'pec', 'whatsapp', 'telegram', 'raccomandata', 'mano', 'portiere', 'postale']); + $table->datetime('data_invio'); + $table->enum('esito_invio', ['inviato', 'consegnato', 'letto', 'errore', 'rifiutato']); + $table->datetime('data_lettura')->nullable(); + $table->string('riferimento_invio')->nullable(); // ID email, numero raccomandata, etc. + $table->text('note_invio')->nullable(); + $table->boolean('delega_presente')->default(false); + $table->unsignedBigInteger('delegato_soggetto_id')->nullable(); + $table->string('documento_delega')->nullable(); + $table->boolean('presenza_confermata')->default(false); + $table->datetime('data_conferma_presenza')->nullable(); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('delegato_soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('set null'); + $table->index(['assemblea_id', 'soggetto_id']); + }); + + // Tabella presenze assemblea + Schema::create('presenze_assemblea', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('assemblea_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->enum('tipo_presenza', ['presente', 'delegato', 'assente']); + $table->datetime('ora_arrivo')->nullable(); + $table->datetime('ora_uscita')->nullable(); + $table->string('firma_digitale')->nullable(); + $table->string('qr_code')->nullable(); + $table->boolean('firma_fisica')->default(false); + $table->decimal('millesimi_rappresentati', 10, 4); + $table->unsignedBigInteger('delegante_soggetto_id')->nullable(); // Se è un delegato + $table->text('note')->nullable(); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->foreign('delegante_soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('set null'); + }); + + // Tabella votazioni + Schema::create('votazioni', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('ordine_giorno_id'); + $table->unsignedBigInteger('soggetto_id'); + $table->unsignedBigInteger('unita_immobiliare_id'); + $table->enum('voto', ['favorevole', 'contrario', 'astenuto', 'non_votante']); + $table->decimal('millesimi_voto', 10, 4); + $table->datetime('data_voto'); + $table->text('motivazione')->nullable(); + $table->timestamps(); + + $table->foreign('ordine_giorno_id')->references('id')->on('ordine_giorno')->onDelete('cascade'); + $table->foreign('soggetto_id')->references('id_soggetto')->on('soggetti')->onDelete('cascade'); + $table->foreign('unita_immobiliare_id')->references('id_unita')->on('unita_immobiliari')->onDelete('cascade'); + $table->unique(['ordine_giorno_id', 'soggetto_id', 'unita_immobiliare_id'], 'unique_voto'); + }); + + // Tabella delibere (risultati votazioni) + Schema::create('delibere', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('ordine_giorno_id'); + $table->string('numero_delibera'); + $table->enum('esito', ['approvata', 'respinta', 'rinviata']); + $table->text('testo_delibera'); + $table->integer('totale_voti_favorevoli'); + $table->integer('totale_voti_contrari'); + $table->integer('totale_astenuti'); + $table->decimal('totale_millesimi_favorevoli', 10, 4); + $table->decimal('totale_millesimi_contrari', 10, 4); + $table->decimal('totale_millesimi_astenuti', 10, 4); + $table->decimal('percentuale_approvazione', 5, 2); + $table->boolean('maggioranza_raggiunta'); + $table->date('data_delibera'); + $table->json('allegati')->nullable(); + $table->timestamps(); + + $table->foreign('ordine_giorno_id')->references('id')->on('ordine_giorno')->onDelete('cascade'); + $table->unique('numero_delibera'); + }); + + // Tabella verbali + Schema::create('verbali', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('assemblea_id'); + $table->string('numero_verbale'); + $table->text('testo_verbale'); + $table->json('allegati')->nullable(); + $table->date('data_redazione'); + $table->unsignedBigInteger('redatto_da_user_id'); + $table->string('firma_digitale')->nullable(); + $table->boolean('inviato_condomini')->default(false); + $table->datetime('data_invio_condomini')->nullable(); + $table->enum('stato', ['bozza', 'definitivo', 'inviato', 'archiviato'])->default('bozza'); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('cascade'); + $table->foreign('redatto_da_user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + // Tabella registro protocollo (per tutte le comunicazioni) + Schema::create('registro_protocollo', function (Blueprint $table) { + $table->id(); + $table->string('numero_protocollo')->unique(); + $table->enum('tipo_comunicazione', ['convocazione', 'verbale', 'delibera', 'comunicazione', 'delega', 'altro']); + $table->unsignedBigInteger('assemblea_id')->nullable(); + $table->unsignedBigInteger('soggetto_destinatario_id')->nullable(); + $table->unsignedBigInteger('soggetto_mittente_id')->nullable(); + $table->string('oggetto'); + $table->text('contenuto')->nullable(); + $table->enum('canale', ['email', 'pec', 'whatsapp', 'telegram', 'raccomandata', 'mano', 'portiere', 'postale']); + $table->datetime('data_invio'); + $table->enum('esito', ['inviato', 'consegnato', 'letto', 'errore', 'rifiutato']); + $table->datetime('data_consegna')->nullable(); + $table->datetime('data_lettura')->nullable(); + $table->string('riferimento_esterno')->nullable(); + $table->json('allegati')->nullable(); + $table->text('note')->nullable(); + $table->unsignedBigInteger('creato_da_user_id'); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('set null'); + $table->foreign('soggetto_destinatario_id')->references('id_soggetto')->on('soggetti')->onDelete('set null'); + $table->foreign('soggetto_mittente_id')->references('id_soggetto')->on('soggetti')->onDelete('set null'); + $table->foreign('creato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['data_invio', 'tipo_comunicazione']); + }); + + // Tabella documenti assemblea + Schema::create('documenti_assemblea', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('assemblea_id'); + $table->string('nome_documento'); + $table->string('tipo_documento'); // convocazione, verbale, allegato, delega, etc. + $table->string('path_file'); + $table->string('mime_type'); + $table->unsignedBigInteger('dimensione_file'); + $table->string('hash_file'); + $table->text('descrizione')->nullable(); + $table->unsignedBigInteger('caricato_da_user_id'); + $table->timestamps(); + + $table->foreign('assemblea_id')->references('id')->on('assemblee')->onDelete('cascade'); + $table->foreign('caricato_da_user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + // Tabella automazioni spese approvate + Schema::create('automazioni_spese_approvate', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('delibera_id'); + $table->unsignedBigInteger('preventivo_generato_id')->nullable(); + $table->unsignedBigInteger('ripartizione_generata_id')->nullable(); + $table->json('rate_generate')->nullable(); + $table->enum('stato_automazione', ['in_attesa', 'in_corso', 'completata', 'errore']); + $table->text('log_automazione')->nullable(); + $table->datetime('data_esecuzione')->nullable(); + $table->timestamps(); + + $table->foreign('delibera_id')->references('id')->on('delibere')->onDelete('cascade'); + $table->foreign('preventivo_generato_id')->references('id')->on('preventivi')->onDelete('set null'); + }); + } + + public function down(): void + { + Schema::dropIfExists('automazioni_spese_approvate'); + Schema::dropIfExists('documenti_assemblea'); + Schema::dropIfExists('registro_protocollo'); + Schema::dropIfExists('verbali'); + Schema::dropIfExists('delibere'); + Schema::dropIfExists('votazioni'); + Schema::dropIfExists('presenze_assemblea'); + Schema::dropIfExists('convocazioni'); + Schema::dropIfExists('ordine_giorno'); + Schema::dropIfExists('assemblee'); + } +}; \ No newline at end of file diff --git a/resources/views/admin/api_tokens/index.blade.php b/resources/views/admin/api_tokens/index.blade.php new file mode 100644 index 00000000..9bb9dff4 --- /dev/null +++ b/resources/views/admin/api_tokens/index.blade.php @@ -0,0 +1,154 @@ + + +

+ {{ __('API Tokens') }} +

+
+ +
+
+
+
+ +
+

Gestione API Tokens

+

Crea e gestisci i token di accesso per le API

+
+ + +
+

Crea Nuovo Token

+
+ @csrf +
+ +
+ + + +
+ + +
+ + + +

Tieni premuto Ctrl/Cmd per selezionare più opzioni

+
+
+ +
+ + {{ __('Crea Token') }} + +
+
+
+ + +
+

Token Esistenti

+ + @if(isset($tokens) && $tokens->count() > 0) +
+ + + + + + + + + + + + @foreach($tokens as $token) + + + + + + + + @endforeach + +
+ Nome + + Abilità + + Ultimo Utilizzo + + Creato il + + Azioni +
+ {{ $token->name }} + + @if($token->abilities) + @foreach(json_decode($token->abilities, true) as $ability) + + {{ ucfirst($ability) }} + + @endforeach + @else + Tutte + @endif + + {{ $token->last_used_at ? $token->last_used_at->format('d/m/Y H:i') : 'Mai utilizzato' }} + + {{ $token->created_at->format('d/m/Y H:i') }} + +
+ @csrf + @method('DELETE') + +
+
+
+ @else +
+

Nessun token API creato

+

Crea il tuo primo token utilizzando il form sopra

+
+ @endif +
+ + +
+
⚠️ Informazioni di Sicurezza
+
    +
  • • I token API forniscono accesso completo al tuo account
  • +
  • • Non condividere mai i tuoi token con terze parti non autorizzate
  • +
  • • Elimina immediatamente i token che non utilizzi più
  • +
  • • Monitora regolarmente l'utilizzo dei tuoi token
  • +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/assemblee/index.blade.php b/resources/views/admin/assemblee/index.blade.php new file mode 100644 index 00000000..e69de29b diff --git a/resources/views/admin/bilanci/index.blade.php b/resources/views/admin/bilanci/index.blade.php new file mode 100644 index 00000000..75f3be0d --- /dev/null +++ b/resources/views/admin/bilanci/index.blade.php @@ -0,0 +1,235 @@ + + +

+ {{ __('Gestione Bilanci e Consuntivi') }} +

+
+ +
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
Bilanci Aperti
+
{{ $stats['bilanci_aperti'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Bilanci Approvati
+
{{ $stats['bilanci_approvati'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Conguagli da Pagare
+
{{ $stats['conguagli_da_pagare'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+
Totale Avanzi
+
€ {{ number_format($stats['totale_avanzi'], 2, ',', '.') }}
+
+
+
+
+
+
+ + + + + +
+
+
+

Bilanci e Consuntivi

+
+ +
+ + + + + + + + + + + + + + @forelse($bilanci as $bilancio) + + + + + + + + + + @empty + + + + @endforelse + +
+ Anno/Tipo + + Stabile + + Periodo + + Risultato + + Stato + + Versione + + Azioni +
+
+
{{ $bilancio->anno_esercizio }}
+
{{ ucfirst($bilancio->tipo_gestione) }}
+
+
+ {{ $bilancio->stabile->denominazione }} + +
+
{{ $bilancio->data_inizio_esercizio->format('d/m/Y') }}
+
{{ $bilancio->data_fine_esercizio->format('d/m/Y') }}
+
+
+
+
+ {{ $bilancio->risultato_gestione >= 0 ? 'Avanzo' : 'Disavanzo' }} +
+
€ {{ number_format(abs($bilancio->risultato_gestione), 2, ',', '.') }}
+
+
+ + {{ ucfirst($bilancio->stato) }} + + + v{{ $bilancio->versione }} + + + Visualizza + + @if(in_array($bilancio->stato, ['bozza', 'provvisorio'])) + + Modifica + + @endif + + Storico + +
+ Nessun bilancio trovato +
+
+ + +
+ {{ $bilanci->links() }} +
+
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/contabilita/index.blade.php b/resources/views/admin/contabilita/index.blade.php new file mode 100644 index 00000000..2edc834a --- /dev/null +++ b/resources/views/admin/contabilita/index.blade.php @@ -0,0 +1,161 @@ + + +

+ {{ __('Dashboard Contabilità') }} +

+
+ +
+
+ +
+
+
+
+
+
+ + + +
+
+
+
+
Movimenti Mese
+
{{ $stats['movimenti_mese'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Entrate Mese
+
€ {{ number_format($stats['importo_entrate_mese'], 2, ',', '.') }}
+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+
Uscite Mese
+
€ {{ number_format($stats['importo_uscite_mese'], 2, ',', '.') }}
+
+
+
+
+
+
+ + + + + +
+
+

Ultimi Movimenti

+ +
+ + + + + + + + + + + + @forelse($ultimiMovimenti as $movimento) + + + + + + + + @empty + + + + @endforelse + +
+ Data + + Protocollo + + Descrizione + + Tipo + + Importo +
+ {{ $movimento->data_registrazione->format('d/m/Y') }} + + {{ $movimento->protocollo }} + + {{ $movimento->descrizione }} + + + {{ ucfirst($movimento->tipo_movimento) }} + + + € {{ number_format($movimento->importo_totale, 2, ',', '.') }} +
+ Nessun movimento registrato +
+
+
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/contabilita/registrazione.blade.php b/resources/views/admin/contabilita/registrazione.blade.php new file mode 100644 index 00000000..823e5257 --- /dev/null +++ b/resources/views/admin/contabilita/registrazione.blade.php @@ -0,0 +1,210 @@ + + +

+ {{ __('Registrazione Movimento Contabile') }} +

+
+ +
+
+
+
+ +
+

Nuova Registrazione Contabile

+ + Torna alla Dashboard + +
+ +
+ @csrf + + +
+

Dati Generali Documento

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+
+

Dettaglio Spese

+ +
+ +
+ +
+
+ + +
+ + {{ __('Annulla') }} + + + {{ __('Registra Movimento') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+ + +
\ No newline at end of file diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php new file mode 100644 index 00000000..bd78f354 --- /dev/null +++ b/resources/views/admin/dashboard.blade.php @@ -0,0 +1,269 @@ + + +

+ {{ __('Dashboard Amministratore') }} +

+
+ +
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
Stabili Gestiti
+
{{ $stats['stabili_gestiti'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Stabili Attivi
+
{{ $stats['stabili_attivi'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Ticket Aperti
+
{{ $stats['ticket_aperti'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Ticket Urgenti
+
{{ $stats['ticket_urgenti'] }}
+
+
+
+
+
+
+ + + + + +
+ + +
+
+
+

Ticket da Lavorare

+ + Vedi tutti + +
+ +
+ @forelse($ticketsAperti as $ticket) +
+
+
+

+ + {{ $ticket->titolo }} + +

+

+ {{ $ticket->stabile->denominazione }} +

+
+
+ + {{ $ticket->priorita }} + + + {{ $ticket->created_at->diffForHumans() }} + +
+
+
+ @empty +

Nessun ticket aperto

+ @endforelse +
+
+
+ + +
+
+
+

Ultimi Documenti

+ + Vedi tutti + +
+ +
+ @forelse($ultimiDocumenti as $documento) +
+
+
+

+ {{ $documento->nome_file }} +

+

+ {{ $documento->tipo_documento }} - {{ $documento->documentable->denominazione ?? 'N/A' }} +

+
+
+ + {{ $documento->created_at->diffForHumans() }} + + + Download + +
+
+
+ @empty +

Nessun documento caricato

+ @endforelse +
+
+
+
+ + +
+
+
+

Ultimi Movimenti Contabili

+ + Vedi tutti + +
+ +
+ + + + + + + + + + + @forelse($ultimiMovimenti as $movimento) + + + + + + + @empty + + + + @endforelse + +
DataDescrizioneFornitoreImporto
+ {{ $movimento->data_registrazione->format('d/m/Y') }} + + {{ $movimento->descrizione }} + + {{ $movimento->fornitore->ragione_sociale ?? '-' }} + + + {{ $movimento->tipo_movimento === 'entrata' ? '+' : '-' }}€ {{ number_format($movimento->importo_totale, 2, ',', '.') }} + +
+ Nessun movimento registrato +
+
+
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/documenti/create.blade.php b/resources/views/admin/documenti/create.blade.php new file mode 100644 index 00000000..a9cb6f98 --- /dev/null +++ b/resources/views/admin/documenti/create.blade.php @@ -0,0 +1,117 @@ + + +

+ {{ __('Carica Nuovo Documento') }} +

+
+ +
+
+
+
+ +
+

Carica Nuovo Documento

+ + Torna all'Archivio + +
+ +
+ @csrf + +
+ +
+

File da Caricare

+
+ + + +

+ Formati supportati: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG, XML. Dimensione massima: 10MB +

+
+
+ + +
+

Classificazione Documento

+
+ +
+ + + + +
+ + +
+ + + +
+
+
+ + +
+

Descrizione

+
+ + + +
+
+
+ +
+ + {{ __('Annulla') }} + + + {{ __('Carica Documento') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/documenti/index.blade.php b/resources/views/admin/documenti/index.blade.php new file mode 100644 index 00000000..f96ba0f4 --- /dev/null +++ b/resources/views/admin/documenti/index.blade.php @@ -0,0 +1,146 @@ + + +

+ {{ __('Gestione Documenti') }} +

+
+ +
+
+
+
+ +
+

Archivio Documenti

+ + Carica Documento + +
+ + +
+
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + {{ __('Filtra') }} + + + {{ __('Reset') }} + +
+
+
+
+ + +
+ + + + + + + + + + + + + @forelse($documenti as $documento) + + + + + + + + + @empty + + + + @endforelse + +
+ Nome File + + Tipo + + Collegato a + + Dimensione + + Data Caricamento + + Azioni +
+ {{ $documento->nome_file }} + @if($documento->descrizione) +

{{ $documento->descrizione }}

+ @endif +
+ + {{ $documento->tipo_documento }} + + + {{ $documento->documentable->denominazione ?? 'N/A' }} + + {{ $documento->dimensione_leggibile }} + + {{ $documento->created_at->format('d/m/Y H:i') }} + + + Download + + + Visualizza + +
+ @csrf + @method('DELETE') + +
+
+ Nessun documento trovato +
+
+ + +
+ {{ $documenti->appends(request()->query())->links() }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/fornitori/_form.blade.php b/resources/views/admin/fornitori/_form.blade.php new file mode 100644 index 00000000..133683cd --- /dev/null +++ b/resources/views/admin/fornitori/_form.blade.php @@ -0,0 +1,99 @@ +
+ +
+

Informazioni Generali

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Contatti

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Indirizzo

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/fornitori/create.blade.php b/resources/views/admin/fornitori/create.blade.php new file mode 100644 index 00000000..ca9c7d49 --- /dev/null +++ b/resources/views/admin/fornitori/create.blade.php @@ -0,0 +1,49 @@ + + +

+ {{ __('Nuovo Fornitore') }} +

+
+ +
+
+
+
+ +
+

Crea Nuovo Fornitore

+ + Torna alla Lista + +
+ +
+ @csrf + @include('admin.fornitori._form', ['fornitore' => null]) + +
+ + {{ __('Annulla') }} + + + {{ __('Crea Fornitore') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/fornitori/edit.blade.php b/resources/views/admin/fornitori/edit.blade.php new file mode 100644 index 00000000..b1eb91ba --- /dev/null +++ b/resources/views/admin/fornitori/edit.blade.php @@ -0,0 +1,50 @@ + + +

+ {{ __('Modifica Fornitore') }} +

+
+ +
+
+
+
+ +
+

Modifica: {{ $fornitore->ragione_sociale }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + @include('admin.fornitori._form', ['fornitore' => $fornitore]) + +
+ + {{ __('Annulla') }} + + + {{ __('Aggiorna Fornitore') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/fornitori/index.blade.php b/resources/views/admin/fornitori/index.blade.php new file mode 100644 index 00000000..7a9e6d4f --- /dev/null +++ b/resources/views/admin/fornitori/index.blade.php @@ -0,0 +1,110 @@ + + +

+ {{ __('Gestione Fornitori') }} +

+
+ +
+
+
+
+ +
+

Lista Fornitori

+ + Nuovo Fornitore + +
+ + +
+ + + + + + + + + + + + + + @forelse($fornitori as $fornitore) + + + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Ragione Sociale + + Partita IVA + + Email + + Telefono + + Città + + Azioni +
+ {{ $fornitore->id_fornitore }} + + {{ $fornitore->ragione_sociale }} + + {{ $fornitore->partita_iva ?? '-' }} + + {{ $fornitore->email ?? '-' }} + + {{ $fornitore->telefono ?? '-' }} + + {{ $fornitore->citta ?? '-' }} + + + Visualizza + + + Modifica + +
+ @csrf + @method('DELETE') + +
+
+ Nessun fornitore trovato +
+
+ + +
+ {{ $fornitori->links() }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/fornitori/show.blade.php b/resources/views/admin/fornitori/show.blade.php new file mode 100644 index 00000000..2491d61d --- /dev/null +++ b/resources/views/admin/fornitori/show.blade.php @@ -0,0 +1,118 @@ + + +

+ {{ __('Dettagli Fornitore') }} +

+
+ +
+
+
+
+ +
+

{{ $fornitore->ragione_sociale }}

+ +
+ + +
+
+

Informazioni Generali

+
+
+
ID Fornitore:
+
{{ $fornitore->id_fornitore }}
+
+
+
Ragione Sociale:
+
{{ $fornitore->ragione_sociale }}
+
+
+
Partita IVA:
+
{{ $fornitore->partita_iva ?? '-' }}
+
+
+
Codice Fiscale:
+
{{ $fornitore->codice_fiscale ?? '-' }}
+
+
+
+ +
+

Contatti

+
+
+
Email:
+
{{ $fornitore->email ?? '-' }}
+
+
+
PEC:
+
{{ $fornitore->pec ?? '-' }}
+
+
+
Telefono:
+
{{ $fornitore->telefono ?? '-' }}
+
+
+
+
+ + +
+

Indirizzo

+
+
+
Indirizzo:
+
{{ $fornitore->indirizzo ?? '-' }}
+
+
+
Città:
+
{{ $fornitore->citta ?? '-' }}
+
+
+
CAP:
+
{{ $fornitore->cap ?? '-' }}
+
+
+
Provincia:
+
{{ $fornitore->provincia ?? '-' }}
+
+
+
+ + +
+

Informazioni Sistema

+
+
+
Creato il:
+
{{ $fornitore->created_at->format('d/m/Y H:i') }}
+
+
+
Ultimo aggiornamento:
+
{{ $fornitore->updated_at->format('d/m/Y H:i') }}
+
+ @if($fornitore->old_id) +
+
Old ID:
+
{{ $fornitore->old_id }}
+
+ @endif +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/impostazioni/index.blade.php b/resources/views/admin/impostazioni/index.blade.php new file mode 100644 index 00000000..efaacca2 --- /dev/null +++ b/resources/views/admin/impostazioni/index.blade.php @@ -0,0 +1,144 @@ + + +

+ {{ __('Impostazioni') }} +

+
+ +
+
+
+
+ +
+

Configurazione Sistema

+

Gestisci le impostazioni generali dell'applicazione

+
+ +
+ @csrf + + +
+

Impostazioni Applicazione

+
+ +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Branding

+
+ +
+ + + +

Formati supportati: JPG, PNG, SVG (max 2MB)

+
+ + +
+ + + +
+ + +
+ + + +

Formato ICO o PNG 32x32px

+
+
+
+ + +
+

Configurazione Email

+
+ +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Configurazione Pagamenti

+
+ +
+ + + +
+ + +
+ + + +
+
+
+ + +
+ + {{ __('Ripristina') }} + + + {{ __('Salva Impostazioni') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/preventivi/index.blade.php b/resources/views/admin/preventivi/index.blade.php new file mode 100644 index 00000000..b0086237 --- /dev/null +++ b/resources/views/admin/preventivi/index.blade.php @@ -0,0 +1,203 @@ + + +

+ {{ __('Gestione Preventivi') }} +

+
+ +
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
Preventivi Bozza
+
{{ $stats['preventivi_bozza'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Preventivi Approvati
+
{{ $stats['preventivi_approvati'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+
Importo Anno Corrente
+
€ {{ number_format($stats['importo_totale_anno'], 2, ',', '.') }}
+
+
+
+
+
+
+ + + + + +
+
+
+

Preventivi

+
+ +
+ + + + + + + + + + + + + + @forelse($preventivi as $preventivo) + + + + + + + + + + @empty + + + + @endforelse + +
+ Anno/Tipo + + Stabile + + Descrizione + + Importo + + Stato + + Versione + + Azioni +
+
+
{{ $preventivo->anno_gestione }}
+
{{ ucfirst($preventivo->tipo_gestione) }}
+
+
+ {{ $preventivo->stabile->denominazione }} + + {{ $preventivo->descrizione }} + + € {{ number_format($preventivo->importo_totale, 2, ',', '.') }} + + + {{ ucfirst($preventivo->stato) }} + + + v{{ $preventivo->versione }} + + + Visualizza + + @if($preventivo->stato === 'bozza') + + Modifica + + @endif + + Storico + +
+ Nessun preventivo trovato +
+
+ + +
+ {{ $preventivi->links() }} +
+
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/preventivi/pianificazione.blade.php b/resources/views/admin/preventivi/pianificazione.blade.php new file mode 100644 index 00000000..c5eb8cc6 --- /dev/null +++ b/resources/views/admin/preventivi/pianificazione.blade.php @@ -0,0 +1,184 @@ + + +

+ {{ __('Pianificazione Spese e Entrate') }} +

+
+ +
+
+ + +
+
+

Previsione Cashflow (Prossimi 6 Mesi)

+ +
+ + + + + + + + + + + @foreach($cashflow as $mese) + + + + + + + @endforeach + +
+ Mese + + Entrate Previste + + Uscite Previste + + Saldo Previsto +
+ {{ $mese['mese'] }} + + +€ {{ number_format($mese['entrate'], 2, ',', '.') }} + + -€ {{ number_format($mese['uscite'], 2, ',', '.') }} + + {{ $mese['saldo'] >= 0 ? '+' : '' }}€ {{ number_format($mese['saldo'], 2, ',', '.') }} +
+
+
+
+ + +
+
+
+

Spese in Scadenza (Prossimi 30 giorni)

+ +
+ +
+ @forelse($speseInScadenza as $spesa) +
+
+
+

+ {{ $spesa->descrizione }} +

+

+ {{ $spesa->stabile->denominazione }} +

+
+ + € {{ number_format($spesa->importo_previsto, 2, ',', '.') }} + + + {{ ucfirst($spesa->tipo) }} + +
+
+
+ + Scadenza: {{ $spesa->data_scadenza_prevista->format('d/m/Y') }} + +
+ + + +
+
+
+
+ @empty +
+

Nessuna spesa in scadenza nei prossimi 30 giorni

+
+ @endforelse +
+
+
+ + +
+
+

Andamento Cashflow

+
+ +
+
+
+
+
+ + + + +
\ No newline at end of file diff --git a/resources/views/admin/rubrica/index.blade.php b/resources/views/admin/rubrica/index.blade.php new file mode 100644 index 00000000..03feee5e --- /dev/null +++ b/resources/views/admin/rubrica/index.blade.php @@ -0,0 +1,171 @@ + + +

+ {{ __('Rubrica') }} +

+
+ +
+
+
+
+ +
+

Rubrica Contatti

+ + Nuovo Contatto + +
+ + +
+
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + {{ __('Filtra') }} + + + {{ __('Reset') }} + +
+
+
+
+ + +
+ + + + + + + + + + + + + @forelse($soggetti as $soggetto) + + + + + + + + + @empty + + + + @endforelse + +
+ Nome/Ragione Sociale + + Tipo + + Email + + Telefono + + Città + + Azioni +
+ @if($soggetto->tipo_soggetto === 'Persona Fisica') + {{ $soggetto->nome }} {{ $soggetto->cognome }} + @else + {{ $soggetto->ragione_sociale }} + @endif + + + {{ $soggetto->tipo_soggetto }} + + + {{ $soggetto->email ?? '-' }} + + {{ $soggetto->telefono ?? '-' }} + + {{ $soggetto->citta ?? '-' }} + + + Visualizza + + + Modifica + + @if($soggetto->email) + + Email + + @endif +
+ Nessun contatto trovato +
+
+ + +
+ {{ $soggetti->appends(request()->query())->links() }} +
+ + +
+
+

Persone Fisiche

+

+ {{ $soggetti->where('tipo_soggetto', 'Persona Fisica')->count() }} +

+
+
+

Persone Giuridiche

+

+ {{ $soggetti->where('tipo_soggetto', 'Persona Giuridica')->count() }} +

+
+
+

Totale Contatti

+

+ {{ $soggetti->count() }} +

+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/stabili/_form.blade.php b/resources/views/admin/stabili/_form.blade.php new file mode 100644 index 00000000..3d9eb8f2 --- /dev/null +++ b/resources/views/admin/stabili/_form.blade.php @@ -0,0 +1,90 @@ +
+ +
+

Informazioni Generali

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Indirizzo

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Note

+
+ + + +
+
+
\ No newline at end of file diff --git a/resources/views/admin/stabili/create.blade.php b/resources/views/admin/stabili/create.blade.php new file mode 100644 index 00000000..79fe23d5 --- /dev/null +++ b/resources/views/admin/stabili/create.blade.php @@ -0,0 +1,49 @@ + + +

+ {{ __('Nuovo Stabile') }} +

+
+ +
+
+
+
+ +
+

Crea Nuovo Stabile

+ + Torna alla Lista + +
+ +
+ @csrf + @include('admin.stabili._form', ['stabile' => null]) + +
+ + {{ __('Annulla') }} + + + {{ __('Crea Stabile') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/stabili/edit.blade.php b/resources/views/admin/stabili/edit.blade.php new file mode 100644 index 00000000..a536d563 --- /dev/null +++ b/resources/views/admin/stabili/edit.blade.php @@ -0,0 +1,50 @@ + + +

+ {{ __('Modifica Stabile') }} +

+
+ +
+
+
+
+ +
+

Modifica: {{ $stabile->denominazione }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + @include('admin.stabili._form', ['stabile' => $stabile]) + +
+ + {{ __('Annulla') }} + + + {{ __('Aggiorna Stabile') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/stabili/index.blade.php b/resources/views/admin/stabili/index.blade.php new file mode 100644 index 00000000..e309258e --- /dev/null +++ b/resources/views/admin/stabili/index.blade.php @@ -0,0 +1,113 @@ + + +

+ {{ __('Gestione Stabili') }} +

+
+ +
+
+
+
+ +
+

Lista Stabili

+ + Nuovo Stabile + +
+ + +
+ + + + + + + + + + + + + + @forelse($stabili as $stabile) + + + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Denominazione + + Codice Fiscale + + Indirizzo + + Città + + Stato + + Azioni +
+ {{ $stabile->id_stabile }} + + {{ $stabile->denominazione }} + + {{ $stabile->codice_fiscale ?? '-' }} + + {{ $stabile->indirizzo }} + + {{ $stabile->citta }} ({{ $stabile->provincia }}) + + + {{ ucfirst($stabile->stato) }} + + + + Visualizza + + + Modifica + +
+ @csrf + @method('DELETE') + +
+
+ Nessuno stabile trovato +
+
+ + +
+ {{ $stabili->links() }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/stabili/show.blade.php b/resources/views/admin/stabili/show.blade.php new file mode 100644 index 00000000..72c4fb5e --- /dev/null +++ b/resources/views/admin/stabili/show.blade.php @@ -0,0 +1,116 @@ + + +

+ {{ __('Dettagli Stabile') }} +

+
+ +
+
+
+
+ +
+

{{ $stabile->denominazione }}

+ +
+ + +
+
+

Informazioni Generali

+
+
+
ID Stabile:
+
{{ $stabile->id_stabile }}
+
+
+
Denominazione:
+
{{ $stabile->denominazione }}
+
+
+
Codice Fiscale:
+
{{ $stabile->codice_fiscale ?? '-' }}
+
+
+
CF Amministratore:
+
{{ $stabile->cod_fisc_amministratore ?? '-' }}
+
+
+
Stato:
+
+ + {{ ucfirst($stabile->stato) }} + +
+
+
+
+ +
+

Indirizzo

+
+
+
Indirizzo:
+
{{ $stabile->indirizzo }}
+
+
+
Città:
+
{{ $stabile->citta }}
+
+
+
CAP:
+
{{ $stabile->cap }}
+
+
+
Provincia:
+
{{ $stabile->provincia ?? '-' }}
+
+
+
+
+ + + @if($stabile->note) +
+

Note

+

{{ $stabile->note }}

+
+ @endif + + +
+

Informazioni Sistema

+
+
+
Creato il:
+
{{ $stabile->created_at->format('d/m/Y H:i') }}
+
+
+
Ultimo aggiornamento:
+
{{ $stabile->updated_at->format('d/m/Y H:i') }}
+
+ @if($stabile->old_id) +
+
Old ID:
+
{{ $stabile->old_id }}
+
+ @endif +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/tickets/_form.blade.php b/resources/views/admin/tickets/_form.blade.php new file mode 100644 index 00000000..d5696634 --- /dev/null +++ b/resources/views/admin/tickets/_form.blade.php @@ -0,0 +1,153 @@ +
+ +
+

Informazioni Principali

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Descrizione

+
+ + + +
+ +
+ + + +
+
+ + +
+

Assegnazione

+
+ +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Date

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/tickets/create.blade.php b/resources/views/admin/tickets/create.blade.php new file mode 100644 index 00000000..7c15160b --- /dev/null +++ b/resources/views/admin/tickets/create.blade.php @@ -0,0 +1,49 @@ + + +

+ {{ __('Nuovo Ticket') }} +

+
+ +
+
+
+
+ +
+

Crea Nuovo Ticket

+ + Torna alla Lista + +
+ +
+ @csrf + @include('admin.tickets._form', ['ticket' => null]) + +
+ + {{ __('Annulla') }} + + + {{ __('Crea Ticket') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/tickets/edit.blade.php b/resources/views/admin/tickets/edit.blade.php new file mode 100644 index 00000000..d22ed995 --- /dev/null +++ b/resources/views/admin/tickets/edit.blade.php @@ -0,0 +1,50 @@ + + +

+ {{ __('Modifica Ticket') }} +

+
+ +
+
+
+
+ +
+

Modifica: {{ $ticket->titolo }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + @include('admin.tickets._form', ['ticket' => $ticket]) + +
+ + {{ __('Annulla') }} + + + {{ __('Aggiorna Ticket') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/tickets/index.blade.php b/resources/views/admin/tickets/index.blade.php new file mode 100644 index 00000000..8b1dc966 --- /dev/null +++ b/resources/views/admin/tickets/index.blade.php @@ -0,0 +1,149 @@ + + +

+ {{ __('Gestione Tickets') }} +

+
+ +
+
+
+
+ +
+

Lista Tickets

+ + Nuovo Ticket + +
+ + +
+ + + + + + + + + + + + + + @forelse($tickets as $ticket) + + + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Titolo + + Stabile + + Stato + + Priorità + + Data Apertura + + Azioni +
+ {{ $ticket->id }} + + {{ $ticket->titolo }} + + {{ $ticket->stabile->denominazione ?? '-' }} + + + {{ $ticket->stato }} + + + + {{ $ticket->priorita }} + + + {{ $ticket->data_apertura->format('d/m/Y H:i') }} + + + Visualizza + + + Modifica + +
+ @csrf + @method('DELETE') + +
+
+ Nessun ticket trovato +
+
+ + +
+ {{ $tickets->links() }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/tickets/show.blade.php b/resources/views/admin/tickets/show.blade.php new file mode 100644 index 00000000..068ed2b9 --- /dev/null +++ b/resources/views/admin/tickets/show.blade.php @@ -0,0 +1,173 @@ + + +

+ {{ __('Dettagli Ticket') }} +

+
+ +
+
+
+
+ +
+

{{ $ticket->titolo }}

+ +
+ + +
+
+

Informazioni Generali

+
+
+
ID Ticket:
+
{{ $ticket->id }}
+
+
+
Titolo:
+
{{ $ticket->titolo }}
+
+
+
Stabile:
+
{{ $ticket->stabile->denominazione ?? '-' }}
+
+
+
Categoria:
+
{{ $ticket->categoriaTicket->nome ?? '-' }}
+
+
+
+ +
+

Stato e Priorità

+
+
+
Stato:
+
+ + {{ $ticket->stato }} + +
+
+
+
Priorità:
+
+ + {{ $ticket->priorita }} + +
+
+
+
Aperto da:
+
{{ $ticket->apertoUser->name ?? '-' }}
+
+
+
Assegnato a:
+
{{ $ticket->assegnatoUser->name ?? $ticket->assegnatoFornitore->ragione_sociale ?? '-' }}
+
+
+
+
+ + + @if($ticket->descrizione) +
+

Descrizione

+

{{ $ticket->descrizione }}

+
+ @endif + + +
+

Date e Scadenze

+
+
+
Data Apertura:
+
{{ $ticket->data_apertura->format('d/m/Y H:i') }}
+
+ @if($ticket->data_scadenza_prevista) +
+
Scadenza Prevista:
+
{{ $ticket->data_scadenza_prevista->format('d/m/Y') }}
+
+ @endif + @if($ticket->data_risoluzione_effettiva) +
+
Data Risoluzione:
+
{{ $ticket->data_risoluzione_effettiva->format('d/m/Y') }}
+
+ @endif + @if($ticket->data_chiusura_effettiva) +
+
Data Chiusura:
+
{{ $ticket->data_chiusura_effettiva->format('d/m/Y') }}
+
+ @endif +
+
+ + +
+

Informazioni Sistema

+
+
+
Creato il:
+
{{ $ticket->created_at->format('d/m/Y H:i') }}
+
+
+
Ultimo aggiornamento:
+
{{ $ticket->updated_at->format('d/m/Y H:i') }}
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/unita_immobiliari/_form.blade.php b/resources/views/admin/unita_immobiliari/_form.blade.php new file mode 100644 index 00000000..2d7af83a --- /dev/null +++ b/resources/views/admin/unita_immobiliari/_form.blade.php @@ -0,0 +1,105 @@ +
+ + + + +
+

Identificazione Unità

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Dati Catastali e Tecnici

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Indirizzo Specifico

+
+ + + +

+ Lascia vuoto se l'indirizzo è lo stesso dello stabile: {{ $stabile->indirizzo }}, {{ $stabile->citta }} +

+
+
+ + +
+

Note

+
+ + + +
+
+
\ No newline at end of file diff --git a/resources/views/admin/unita_immobiliari/create.blade.php b/resources/views/admin/unita_immobiliari/create.blade.php new file mode 100644 index 00000000..b8604af0 --- /dev/null +++ b/resources/views/admin/unita_immobiliari/create.blade.php @@ -0,0 +1,49 @@ + + +

+ {{ __('Nuova Unità Immobiliare') }} +

+
+ +
+
+
+
+ +
+

Crea Nuova Unità Immobiliare

+ + Torna allo Stabile + +
+ +
+ @csrf + @include('admin.unita_immobiliari._form', ['unitaImmobiliare' => null, 'stabile' => $stabile]) + +
+ + {{ __('Annulla') }} + + + {{ __('Crea Unità Immobiliare') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/admin/unita_immobiliari/edit.blade.php b/resources/views/admin/unita_immobiliari/edit.blade.php new file mode 100644 index 00000000..d882a3a6 --- /dev/null +++ b/resources/views/admin/unita_immobiliari/edit.blade.php @@ -0,0 +1,50 @@ + + +

+ {{ __('Modifica Unità Immobiliare') }} +

+
+ +
+
+
+
+ +
+

Modifica Unità: {{ $unitaImmobiliare->interno ?? 'N/A' }}

+ + Torna allo Stabile + +
+ +
+ @csrf + @method('PUT') + @include('admin.unita_immobiliari._form', ['unitaImmobiliare' => $unitaImmobiliare, 'stabile' => $unitaImmobiliare->stabile]) + +
+ + {{ __('Annulla') }} + + + {{ __('Aggiorna Unità Immobiliare') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/condomino/dashboard.blade.php b/resources/views/condomino/dashboard.blade.php new file mode 100644 index 00000000..dcef89d4 --- /dev/null +++ b/resources/views/condomino/dashboard.blade.php @@ -0,0 +1,262 @@ + + +

+ {{ __('Dashboard Condomino') }} +

+
+ +
+
+ + +
+
+

Benvenuto, {{ Auth::user()->name }}!

+

Gestisci le tue proprietà e rimani aggiornato su tutto quello che riguarda il tuo condominio.

+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
Le Mie Unità
+
{{ $stats['unita_possedute'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Ticket Aperti
+
{{ $stats['ticket_aperti'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Rate Scadute
+
{{ $stats['rate_scadute'] }}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Documenti
+
{{ $stats['documenti_disponibili'] }}
+
+
+
+
+
+
+ + + + + +
+ + +
+
+
+

I Miei Ticket

+ + Vedi tutti + +
+ +
+ @forelse($ticketRecenti as $ticket) +
+
+
+

+ + {{ $ticket->titolo }} + +

+

+ {{ $ticket->stabile->denominazione }} +

+
+
+ + {{ $ticket->stato }} + + + {{ $ticket->created_at->diffForHumans() }} + +
+
+
+ @empty +

Nessun ticket aperto

+ @endforelse +
+
+
+ + +
+
+
+

Ultimi Documenti

+ + Vedi tutti + +
+ +
+ @forelse($ultimiDocumenti as $documento) +
+
+
+

+ {{ $documento->nome_file }} +

+

+ {{ $documento->tipo_documento }} - {{ $documento->documentable->denominazione ?? 'N/A' }} +

+
+
+ + {{ $documento->created_at->diffForHumans() }} + + + Download + +
+
+
+ @empty +

Nessun documento disponibile

+ @endforelse +
+
+
+
+ + +
+
+
+

Le Mie Unità Immobiliari

+ + Gestisci + +
+ +
+ @forelse($unitaImmobiliari as $unita) +
+

+ {{ $unita->stabile->denominazione }} +

+

+ {{ $unita->identificazione_completa }} +

+
+ + Millesimi: {{ $unita->millesimi_proprieta ?? 'N/A' }} + + + Dettagli + +
+
+ @empty +
+

Nessuna unità immobiliare associata

+
+ @endforelse +
+
+
+
+
+ + + + +
\ No newline at end of file diff --git a/resources/views/condomino/tickets/create.blade.php b/resources/views/condomino/tickets/create.blade.php new file mode 100644 index 00000000..e11a7327 --- /dev/null +++ b/resources/views/condomino/tickets/create.blade.php @@ -0,0 +1,141 @@ + + +

+ {{ __('Nuovo Ticket') }} +

+
+ +
+
+
+
+ +
+

Crea Nuovo Ticket

+ + Torna ai Ticket + +
+ +
+ @csrf + +
+ +
+

Informazioni Principali

+
+ +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+
+ + +
+

Descrizione Dettagliata

+
+ + + +
+
+ + +
+

Allegati (Opzionale)

+
+ + + +

+ Puoi caricare foto, documenti PDF, Word. Massimo 10MB per file. +

+
+
+
+ +
+ + {{ __('Annulla') }} + + + {{ __('Crea Ticket') }} + +
+
+ + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/condomino/tickets/index.blade.php b/resources/views/condomino/tickets/index.blade.php new file mode 100644 index 00000000..9bf670c8 --- /dev/null +++ b/resources/views/condomino/tickets/index.blade.php @@ -0,0 +1,110 @@ + + +

+ {{ __('I Miei Ticket') }} +

+
+ +
+
+
+
+ +
+

I Miei Ticket

+ + Nuovo Ticket + +
+ + +
+ + + + + + + + + + + + + @forelse($tickets as $ticket) + + + + + + + + + @empty + + + + @endforelse + +
+ Titolo + + Stabile + + Stato + + Priorità + + Data Apertura + + Azioni +
+ {{ $ticket->titolo }} + + {{ $ticket->stabile->denominazione ?? '-' }} + + + {{ $ticket->stato }} + + + + {{ $ticket->priorita }} + + + {{ $ticket->data_apertura->format('d/m/Y H:i') }} + + + Visualizza + +
+ Nessun ticket trovato +
+
+ + +
+ {{ $tickets->links() }} +
+ +
+
+
+
+
\ No newline at end of file diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php new file mode 100644 index 00000000..e576aae5 --- /dev/null +++ b/resources/views/layouts/navigation.blade.php @@ -0,0 +1,303 @@ + \ No newline at end of file diff --git a/resources/views/superadmin/amministratori/create.blade.php b/resources/views/superadmin/amministratori/create.blade.php new file mode 100644 index 00000000..5a4da19d --- /dev/null +++ b/resources/views/superadmin/amministratori/create.blade.php @@ -0,0 +1,206 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Crea Nuovo Amministratore

+ + Torna alla Lista + +
+ +
+ @csrf + + +
+

Dati Utente (Account di accesso)

+
+ +
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('email') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('password') +

{{ $message }}

+ @enderror +
+ + +
+ + +
+
+
+ + +
+

Dati Personali Amministratore

+
+ +
+ + + @error('nome') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('cognome') +

{{ $message }}

+ @enderror +
+
+
+ + +
+

Dati Studio Professionale

+
+ +
+ + + @error('denominazione_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('partita_iva') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('codice_fiscale_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('indirizzo_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('cap_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('citta_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('provincia_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('telefono_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('email_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('pec_studio') +

{{ $message }}

+ @enderror +
+
+
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/amministratori/edit.blade.php b/resources/views/superadmin/amministratori/edit.blade.php new file mode 100644 index 00000000..de88feb4 --- /dev/null +++ b/resources/views/superadmin/amministratori/edit.blade.php @@ -0,0 +1,195 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Modifica Amministratore: {{ $amministratore->nome }} {{ $amministratore->cognome }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + + +
+

Utente Associato

+
+ +
+ + + @error('user_id') +

{{ $message }}

+ @enderror +

Attualmente associato a: {{ $amministratore->user->name }} ({{ $amministratore->user->email }})

+
+
+
+ + +
+

Dati Personali Amministratore

+
+ +
+ + + @error('nome') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('cognome') +

{{ $message }}

+ @enderror +
+
+
+ + +
+

Dati Studio Professionale

+
+ +
+ + + @error('denominazione_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('partita_iva') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('codice_fiscale_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('indirizzo_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('cap_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('citta_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('provincia_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('telefono_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('email_studio') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('pec_studio') +

{{ $message }}

+ @enderror +
+
+
+ + +
+

Informazioni Record

+

Creato il: {{ $amministratore->created_at->format('d/m/Y H:i') }}

+

Ultimo aggiornamento: {{ $amministratore->updated_at->format('d/m/Y H:i') }}

+
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/amministratori/index.blade.php b/resources/views/superadmin/amministratori/index.blade.php new file mode 100644 index 00000000..8df9351e --- /dev/null +++ b/resources/views/superadmin/amministratori/index.blade.php @@ -0,0 +1,95 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Gestione Amministratori

+ + Nuovo Amministratore + +
+ + +
+ + + + + + + + + + + + + + @forelse($amministratori as $amministratore) + + + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Nome e Cognome + + Denominazione Studio + + Utente Associato + + Partita IVA + + Email Studio + + Azioni +
+ {{ $amministratore->id_amministratore }} + + {{ $amministratore->nome }} {{ $amministratore->cognome }} + + {{ $amministratore->denominazione_studio ?? '-' }} + +
+
{{ $amministratore->user->name }}
+
{{ $amministratore->user->email }}
+
+
+ {{ $amministratore->partita_iva ?? '-' }} + + {{ $amministratore->email_studio ?? '-' }} + + Modifica + +
+ @csrf + @method('DELETE') + +
+
+ Nessun amministratore trovato +
+
+ + +
+ {{ $amministratori->links() }} +
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/categorie_ticket/create.blade.php b/resources/views/superadmin/categorie_ticket/create.blade.php new file mode 100644 index 00000000..25c77bf7 --- /dev/null +++ b/resources/views/superadmin/categorie_ticket/create.blade.php @@ -0,0 +1,82 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Crea Nuova Categoria Ticket

+ + Torna alla Lista + +
+ +
+ @csrf + + +
+

Informazioni Categoria

+
+ +
+ + + @error('nome') +

{{ $message }}

+ @enderror +

Il nome deve essere unico nel sistema

+
+ + +
+ + + @error('descrizione') +

{{ $message }}

+ @enderror +

Fornisci una descrizione per aiutare gli utenti a comprendere quando utilizzare questa categoria

+
+
+
+ + +
+

Informazioni

+
    +
  • • I campi contrassegnati con * sono obbligatori
  • +
  • • Il nome della categoria deve essere unico nel sistema
  • +
  • • La descrizione aiuta gli utenti a scegliere la categoria corretta per i loro ticket
  • +
+
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/categorie_ticket/edit.blade.php b/resources/views/superadmin/categorie_ticket/edit.blade.php new file mode 100644 index 00000000..1caae559 --- /dev/null +++ b/resources/views/superadmin/categorie_ticket/edit.blade.php @@ -0,0 +1,107 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Modifica Categoria: {{ $categoriaTicket->nome }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + + +
+

Informazioni Categoria

+
+ +
+ + + @error('nome') +

{{ $message }}

+ @enderror +

Il nome deve essere unico nel sistema

+
+ + +
+ + + @error('descrizione') +

{{ $message }}

+ @enderror +

Fornisci una descrizione per aiutare gli utenti a comprendere quando utilizzare questa categoria

+
+
+
+ + +
+

Informazioni Record

+
+
+ ID: {{ $categoriaTicket->id }} +
+
+ Creato il: {{ $categoriaTicket->created_at->format('d/m/Y H:i') }} +
+
+ Ultimo aggiornamento: {{ $categoriaTicket->updated_at->format('d/m/Y H:i') }} +
+ @if($categoriaTicket->tickets_count ?? 0 > 0) +
+ Ticket associati: + + {{ $categoriaTicket->tickets_count }} + +
+ @endif +
+
+ + +
+

⚠️ Attenzione

+
    +
  • • I campi contrassegnati con * sono obbligatori
  • +
  • • Il nome della categoria deve essere unico nel sistema
  • +
  • • Le modifiche si applicheranno a tutti i ticket esistenti di questa categoria
  • +
+
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/categorie_ticket/index.blade.php b/resources/views/superadmin/categorie_ticket/index.blade.php new file mode 100644 index 00000000..1dd3aee5 --- /dev/null +++ b/resources/views/superadmin/categorie_ticket/index.blade.php @@ -0,0 +1,87 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Gestione Categorie Ticket

+ + Nuova Categoria + +
+ + +
+ + + + + + + + + + + + @forelse($categorieTicket as $categoria) + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Nome + + Descrizione + + Data Creazione + + Azioni +
+ {{ $categoria->id }} + + {{ $categoria->nome }} + +
+ {{ $categoria->descrizione ?? '-' }} +
+
+ {{ $categoria->created_at->format('d/m/Y H:i') }} + + + Modifica + + +
+ @csrf + @method('DELETE') + +
+
+ Nessuna categoria ticket trovata +
+
+ + +
+ {{ $categorieTicket->links() }} +
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/dashboard.blade.php b/resources/views/superadmin/dashboard.blade.php new file mode 100644 index 00000000..2ee530ff --- /dev/null +++ b/resources/views/superadmin/dashboard.blade.php @@ -0,0 +1,139 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+ +
+
+

Dashboard Super Admin

+

Benvenuto nel pannello di amministrazione del sistema

+
+
+ + +
+ +
+
+
+
+
+ + + +
+
+
+
+
Totale Utenti
+
{{ \App\Models\User::count() }}
+
+
+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
Amministratori
+
{{ \App\Models\Amministratore::count() }}
+
+
+
+
+
+ + +
+
+
+
+
+ + + +
+
+
+
+
Super Admin
+
{{ \App\Models\User::role('super-admin')->count() }}
+
+
+
+
+
+
+ + + + + +
+
+

Ultimi Utenti Creati

+
+ + + + + + + + + + + @foreach(\App\Models\User::latest()->take(5)->get() as $user) + + + + + + + @endforeach + +
NomeEmailRuoloData Creazione
{{ $user->name }}{{ $user->email }} + @foreach($user->roles as $role) + + {{ $role->name }} + + @endforeach + {{ $user->created_at->format('d/m/Y H:i') }}
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/layouts/app.blade.php b/resources/views/superadmin/layouts/app.blade.php new file mode 100644 index 00000000..465262e4 --- /dev/null +++ b/resources/views/superadmin/layouts/app.blade.php @@ -0,0 +1,93 @@ + + + + + + + + {{ config('app.name', 'Laravel') }} - Super Admin + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + +
+ + + + +
+
+ + @if (session('success')) +
+ {{ session('success') }} +
+ @endif + + @if (session('error')) +
+ {{ session('error') }} +
+ @endif + + @if (session('status')) +
+ {{ session('status') }} +
+ @endif + + @yield('content') +
+
+
+ + \ No newline at end of file diff --git a/resources/views/superadmin/users/create.blade.php b/resources/views/superadmin/users/create.blade.php new file mode 100644 index 00000000..16e6fee0 --- /dev/null +++ b/resources/views/superadmin/users/create.blade.php @@ -0,0 +1,85 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Crea Nuovo Utente

+ + Torna alla Lista + +
+ +
+ @csrf + + +
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('email') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('password') +

{{ $message }}

+ @enderror +
+ + +
+ + +
+ + +
+ + + @error('role') +

{{ $message }}

+ @enderror +
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/users/edit.blade.php b/resources/views/superadmin/users/edit.blade.php new file mode 100644 index 00000000..c9e01f05 --- /dev/null +++ b/resources/views/superadmin/users/edit.blade.php @@ -0,0 +1,82 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Modifica Utente: {{ $user->name }}

+ + Torna alla Lista + +
+ +
+ @csrf + @method('PUT') + + +
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('email') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('role') +

{{ $message }}

+ @enderror +
+ + +
+

Informazioni Account

+

Creato il: {{ $user->created_at->format('d/m/Y H:i') }}

+

Ultimo aggiornamento: {{ $user->updated_at->format('d/m/Y H:i') }}

+ @if($user->email_verified_at) +

Email verificata il: {{ $user->email_verified_at->format('d/m/Y H:i') }}

+ @else +

Email non verificata

+ @endif +
+ + +
+ + Annulla + + +
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/superadmin/users/index.blade.php b/resources/views/superadmin/users/index.blade.php new file mode 100644 index 00000000..e0446b39 --- /dev/null +++ b/resources/views/superadmin/users/index.blade.php @@ -0,0 +1,109 @@ +@extends('superadmin.layouts.app') + +@section('content') +
+
+
+

Gestione Utenti

+ + Nuovo Utente + +
+ + +
+ + + + + + + + + + + + + @forelse($users as $user) + + + + + + + + + @empty + + + + @endforelse + +
+ ID + + Nome + + Email + + Ruoli + + Data Creazione + + Azioni +
+ {{ $user->id }} + + {{ $user->name }} + + {{ $user->email }} + + @foreach($user->roles as $role) + + {{ $role->name }} + + @endforeach + + {{ $user->created_at->format('d/m/Y H:i') }} + + Modifica + + +
+ @csrf + @method('PATCH') + +
+ + @if($user->id !== auth()->id()) + Impersona + +
+ @csrf + @method('DELETE') + +
+ @endif +
+ Nessun utente trovato +
+
+ + +
+ {{ $users->links() }} +
+
+
+@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 00000000..8c0472a8 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,181 @@ +group(function () { + + // Generic Dashboard (redirects to the correct panel based on role) + Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard'); + + // Profile Routes + Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); + Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); + Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); + + // --- SUPER-ADMIN PANEL --- + Route::middleware(['role:super-admin'])->prefix('superadmin')->name('superadmin.')->group(function () { + Route::get('/', function() { + return view('superadmin.dashboard'); + })->name('dashboard'); + + // Gestione utenti + Route::resource('users', SuperAdminUserController::class)->except(['show']); + Route::patch('users/{user}/update-role', [SuperAdminUserController::class, 'updateRole'])->name('users.updateRole'); + Route::get('users/{user}/impersonate', [SuperAdminUserController::class, 'impersonate'])->name('users.impersonate'); + + // Gestione Amministratori + Route::resource('amministratori', SuperAdminAmministratoreController::class) + ->except(['show']) + ->parameters(['amministratori' => 'amministratore']); + + // Gestione Categorie Ticket + Route::resource('categorie-ticket', CategoriaTicketController::class)->except(['show']); + + // Diagnostica + Route::get('/diagnostica', function() { return view('superadmin.diagnostica'); })->name('diagnostica'); + }); + + // --- ADMIN / AMMINISTRATORE PANEL --- + Route::middleware(['role:admin|amministratore'])->prefix('admin')->name('admin.')->group(function () { + // Dashboard dell'amministratore + Route::get('/', [DashboardController::class, 'index'])->name('dashboard'); + + // Rotte CRUD principali + Route::resource('stabili', StabileController::class); + Route::resource('stabili.unitaImmobiliari', UnitaImmobiliareController::class)->shallow(); + Route::resource('unitaImmobiliari', UnitaImmobiliareController::class)->only(['edit', 'update', 'destroy']); + Route::resource('soggetti', SoggettoController::class); + Route::resource('fornitori', FornitoreController::class); + Route::resource('tickets', TicketController::class); + + // Gestione Documenti + Route::resource('documenti', DocumentoController::class)->except(['edit', 'update']); + Route::get('documenti/{documento}/download', [DocumentoController::class, 'download'])->name('documenti.download'); + + // Gestione Preventivi + Route::prefix('preventivi')->name('preventivi.')->group(function () { + Route::get('/', [PreventivoController::class, 'index'])->name('index'); + Route::get('/create', [PreventivoController::class, 'create'])->name('create'); + Route::post('/', [PreventivoController::class, 'store'])->name('store'); + Route::get('/{preventivo}', [PreventivoController::class, 'show'])->name('show'); + Route::get('/{preventivo}/edit', [PreventivoController::class, 'edit'])->name('edit'); + Route::put('/{preventivo}', [PreventivoController::class, 'update'])->name('update'); + Route::post('/{preventivo}/approva', [PreventivoController::class, 'approva'])->name('approva'); + Route::post('/{preventivo}/genera-rate', [PreventivoController::class, 'generaRate'])->name('genera-rate'); + Route::get('/{preventivo}/storico', [PreventivoController::class, 'storicoModifiche'])->name('storico'); + Route::get('/pianificazione/dashboard', [PreventivoController::class, 'pianificazione'])->name('pianificazione'); + }); + + // Gestione Bilanci e Consuntivi + Route::prefix('bilanci')->name('bilanci.')->group(function () { + Route::get('/', [BilancioController::class, 'index'])->name('index'); + Route::get('/create', [BilancioController::class, 'create'])->name('create'); + Route::post('/', [BilancioController::class, 'store'])->name('store'); + Route::get('/{bilancio}', [BilancioController::class, 'show'])->name('show'); + Route::get('/{bilancio}/edit', [BilancioController::class, 'edit'])->name('edit'); + Route::put('/{bilancio}', [BilancioController::class, 'update'])->name('update'); + Route::post('/{bilancio}/calcola-conguagli', [BilancioController::class, 'calcolaConguagli'])->name('calcola-conguagli'); + Route::post('/{bilancio}/genera-rate-conguaglio', [BilancioController::class, 'generaRateConguaglio'])->name('genera-rate-conguaglio'); + Route::post('/{bilancio}/quadratura', [BilancioController::class, 'quadratura'])->name('quadratura'); + Route::post('/{bilancio}/chiusura-esercizio', [BilancioController::class, 'chiusuraEsercizio'])->name('chiusura-esercizio'); + Route::get('/{bilancio}/storico', [BilancioController::class, 'storicoModifiche'])->name('storico'); + Route::get('/quadrature/dashboard', [BilancioController::class, 'quadratureDashboard'])->name('quadrature'); + Route::get('/conguagli/dashboard', [BilancioController::class, 'conguagliDashboard'])->name('conguagli'); + Route::get('/rimborsi/dashboard', [BilancioController::class, 'rimborsiDashboard'])->name('rimborsi'); + Route::get('/automazioni/dashboard', [BilancioController::class, 'automazioniDashboard'])->name('automazioni'); + }); + + // Contabilità + Route::prefix('contabilita')->name('contabilita.')->group(function () { + Route::get('/', [ContabilitaController::class, 'index'])->name('index'); + Route::get('/movimenti', [ContabilitaController::class, 'movimenti'])->name('movimenti'); + Route::get('/registrazione', [ContabilitaController::class, 'registrazione'])->name('registrazione'); + Route::post('/registrazione', [ContabilitaController::class, 'storeRegistrazione'])->name('store-registrazione'); + Route::get('/import-xml', [ContabilitaController::class, 'importXml'])->name('import-xml'); + Route::post('/import-xml', [ContabilitaController::class, 'importXml'])->name('import-xml.store'); + }); + + // Impostazioni e API Tokens + Route::get('impostazioni', [ImpostazioniController::class, 'index'])->name('impostazioni.index'); + Route::post('impostazioni', [ImpostazioniController::class, 'store'])->name('impostazioni.store'); + Route::get('api-tokens', [ApiTokenController::class, 'index'])->name('api-tokens.index'); + Route::post('api-tokens', [ApiTokenController::class, 'store'])->name('api-tokens.store'); + Route::delete('api-tokens/{token_id}', [ApiTokenController::class, 'destroy'])->name('api-tokens.destroy'); + + // Rubrica + Route::get('rubrica', [RubricaController::class, 'index'])->name('rubrica.index'); + }); + + // --- CONDOMINO PANEL --- + Route::middleware(['role:condomino'])->prefix('condomino')->name('condomino.')->group(function () { + // Dashboard + Route::get('/', [CondominoDashboardController::class, 'index'])->name('dashboard'); + + // Tickets + Route::resource('tickets', CondominoTicketController::class)->only(['index', 'create', 'store', 'show']); + + // Documenti + Route::get('/documenti', [CondominoDocumentoController::class, 'index'])->name('documenti.index'); + Route::get('/documenti/{documento}/download', [CondominoDocumentoController::class, 'download'])->name('documenti.download'); + + // Unità Immobiliari + Route::get('/unita', [CondominoUnitaController::class, 'index'])->name('unita.index'); + Route::get('/unita/{unitaImmobiliare}', [CondominoUnitaController::class, 'show'])->name('unita.show'); + Route::post('/unita/{unitaImmobiliare}/richiesta-modifica', [CondominoUnitaController::class, 'richiestaModifica'])->name('unita.richiesta-modifica'); + + // Pagamenti (placeholder) + Route::view('/pagamenti', 'condomino.pagamenti.index')->name('pagamenti.index'); + + // Altre viste placeholder + Route::view('/scadenze', 'condomino.scadenze')->name('scadenze'); + Route::view('/comunicazioni', 'condomino.comunicazioni')->name('comunicazioni'); + Route::view('/avvisi', 'condomino.avvisi')->name('avvisi'); + Route::view('/guasti', 'condomino.guasti')->name('guasti'); + Route::view('/contabilita', 'condomino.contabilita')->name('contabilita'); + Route::view('/fornitori', 'condomino.fornitori')->name('fornitori'); + Route::view('/bacheca', 'condomino.bacheca')->name('bacheca'); + Route::view('/sondaggi', 'condomino.sondaggi')->name('sondaggi'); + }); + + // --- DEBUG ROUTE FOR PERMISSIONS --- + Route::get('/test-permissions', function() { + $user = Auth::user(); + echo "

Diagnostica Permessi per: " . $user->name . "

"; + echo "

Ruoli Assegnati:

"; + echo ""; + }); +}); + +// --- PUBLIC ROUTE TO LEAVE IMPERSONATION --- +Route::get('impersonate/leave', [\Lab404\Impersonate\Controllers\ImpersonateController::class, 'leave'])->name('impersonate.leave'); + +// --- AUTHENTICATION ROUTES --- +require __DIR__.'/auth.php'; \ No newline at end of file