Initial commit

This commit is contained in:
Pikappa2 2025-06-29 23:39:33 +02:00
commit 24992624f0
95 changed files with 12588 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\Amministratore;
use App\Models\User;
use Spatie\Permission\Models\Role; // Aggiunto per Role
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth; // Aggiunto per Auth
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Gate; // Aggiunto per Gate
class AmministratoreController extends Controller
{
public function __construct()
{
// Proteggi le rotte con i permessi di Spatie
$this->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.');
}
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# sb1-netgescon
[Edit in StackBlitz next generation editor ⚡️](https://stackblitz.com/~/github.com/Pikappa2/sb1-netgescon)

View File

@ -0,0 +1,528 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Assemblea;
use App\Models\OrdineGiorno;
use App\Models\Convocazione;
use App\Models\PresenzaAssemblea;
use App\Models\Votazione;
use App\Models\Verbale;
use App\Models\RegistroProtocollo;
use App\Models\Stabile;
use App\Models\Preventivo;
use App\Models\TabellaMillesimale;
use App\Models\Soggetto;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
class AssembleaController extends Controller
{
/**
* Dashboard assemblee
*/
public function index()
{
$amministratore_id = Auth::user()->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;
}
}

View File

@ -0,0 +1,386 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Bilancio;
use App\Models\ScritturaBilancio;
use App\Models\Conguaglio;
use App\Models\Quadratura;
use App\Models\Stabile;
use App\Models\Gestione;
use App\Models\PianoConto;
use App\Models\MovimentoContabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class BilancioController extends Controller
{
/**
* Dashboard bilanci
*/
public function index()
{
$amministratore_id = Auth::user()->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
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\MovimentoContabile;
use App\Models\Gestione;
use App\Models\Stabile;
use App\Models\Fornitore;
use App\Models\VoceSpesa;
use App\Models\TabellaMillesimale;
use App\Models\Documento;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class ContabilitaController extends Controller
{
/**
* Dashboard contabilità
*/
public function index()
{
$amministratore_id = Auth::user()->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,
];
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Stabile;
use App\Models\Ticket;
use App\Models\Rata;
use App\Models\Documento;
use App\Models\MovimentoContabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class DashboardController extends Controller
{
public function index()
{
$amministratore_id = Auth::user()->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'
));
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Documento;
use App\Models\Stabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class DocumentoController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$amministratore_id = Auth::user()->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.');
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Fornitore;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class FornitoreController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$fornitori = Fornitore::where('amministratore_id', Auth::user()->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.');
}
}

View File

@ -0,0 +1,289 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Preventivo;
use App\Models\VocePreventivo;
use App\Models\Stabile;
use App\Models\TabellaMillesimale;
use App\Models\VoceSpesa;
use App\Models\LogModificaPreventivo;
use App\Models\PianificazioneSpesa;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class PreventivoController extends Controller
{
/**
* Dashboard preventivi
*/
public function index()
{
$amministratore_id = Auth::user()->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'));
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Stabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class StabileController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$stabili = Stabile::where('amministratore_id', Auth::user()->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.');
}
}

View File

@ -0,0 +1,170 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\Stabile;
use App\Models\CategoriaTicket;
use App\Models\User;
use App\Models\Fornitore;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class TicketController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$tickets = Ticket::with(['stabile', 'categoriaTicket', 'apertoUser', 'assegnatoUser', 'assegnatoFornitore'])
->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.');
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\UnitaImmobiliare;
use App\Models\Stabile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UnitaImmobiliareController extends Controller
{
/**
* Show the form for creating a new resource.
*/
public function create(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.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.');
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Condomino;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\Rata;
use App\Models\Documento;
use App\Models\UnitaImmobiliare;
use App\Models\Proprieta;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class DashboardController extends Controller
{
public function index()
{
$user = Auth::user();
// Trova le unità immobiliari associate all'utente
$unitaImmobiliari = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) {
$q->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'
));
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Condomino;
use App\Http\Controllers\Controller;
use App\Models\Documento;
use App\Models\UnitaImmobiliare;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class DocumentoController extends Controller
{
public function index(Request $request)
{
$user = Auth::user();
// Trova gli stabili delle unità dell'utente
$stabiliIds = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) {
$q->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);
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers\Condomino;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\CategoriaTicket;
use App\Models\UnitaImmobiliare;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class TicketController extends Controller
{
public function index()
{
$user = Auth::user();
$tickets = Ticket::where('soggetto_richiedente_id', $user->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'));
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Condomino;
use App\Http\Controllers\Controller;
use App\Models\UnitaImmobiliare;
use App\Models\RichiestaModifica;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UnitaController extends Controller
{
public function index()
{
$user = Auth::user();
$unitaImmobiliari = UnitaImmobiliare::whereHas('proprieta', function($q) use ($user) {
$q->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.');
}
}

266
app/Models/Assemblea.php Normal file
View File

@ -0,0 +1,266 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Assemblea extends Model
{
use HasFactory;
protected $table = 'assemblee';
protected $fillable = [
'stabile_id',
'tipo',
'data_prima_convocazione',
'data_seconda_convocazione',
'luogo',
'note',
'stato',
'data_convocazione',
'data_svolgimento',
'creato_da_user_id',
];
protected $casts = [
'data_prima_convocazione' => '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(),
];
}
}

70
app/Models/Banca.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Banca extends Model
{
use HasFactory;
protected $table = 'banche';
protected $fillable = [
'stabile_id',
'denominazione',
'iban',
'bic_swift',
'agenzia',
'indirizzo_agenzia',
'tipo_conto',
'saldo_iniziale',
'data_apertura',
'stato',
'note',
];
protected $casts = [
'saldo_iniziale' => '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);
}
}

279
app/Models/Bilancio.php Normal file
View File

@ -0,0 +1,279 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Bilancio extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'stabile_id',
'gestione_id',
'anno_esercizio',
'data_inizio_esercizio',
'data_fine_esercizio',
'tipo_gestione',
'descrizione',
'stato',
'totale_entrate',
'totale_uscite',
'risultato_gestione',
'data_approvazione',
'approvato_da_user_id',
'data_chiusura',
'chiuso_da_user_id',
'note',
'versione',
];
protected $casts = [
'anno_esercizio' => '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);
}
}

136
app/Models/Conguaglio.php Normal file
View File

@ -0,0 +1,136 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Conguaglio extends Model
{
use HasFactory;
protected $fillable = [
'bilancio_id',
'unita_immobiliare_id',
'soggetto_id',
'totale_rate_pagate',
'totale_spese_effettive',
'conguaglio_dovuto',
'tipo_conguaglio',
'stato',
'data_calcolo',
'data_pagamento',
'importo_pagato',
'note',
];
protected $casts = [
'totale_rate_pagate' => '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;
}
}

128
app/Models/Convocazione.php Normal file
View File

@ -0,0 +1,128 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Convocazione extends Model
{
use HasFactory;
protected $table = 'convocazioni';
protected $fillable = [
'assemblea_id',
'soggetto_id',
'unita_immobiliare_id',
'canale_invio',
'data_invio',
'esito_invio',
'data_lettura',
'riferimento_invio',
'note_invio',
'delega_presente',
'delegato_soggetto_id',
'documento_delega',
'presenza_confermata',
'data_conferma_presenza',
];
protected $casts = [
'data_invio' => '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;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DettaglioMovimento extends Model
{
use HasFactory;
protected $table = 'dettagli_movimenti';
protected $fillable = [
'movimento_id',
'conto_id',
'voce_spesa_id',
'tabella_millesimale_id',
'descrizione',
'importo_dare',
'importo_avere',
'note',
];
protected $casts = [
'importo_dare' => '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);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DettaglioTabellaMillesimale extends Model
{
use HasFactory;
protected $table = 'dettagli_tabelle_millesimali';
protected $fillable = [
'tabella_millesimale_id',
'unita_immobiliare_id',
'millesimi',
'note',
];
protected $casts = [
'millesimi' => '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');
}
}

73
app/Models/Documento.php Normal file
View File

@ -0,0 +1,73 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Documento extends Model
{
use HasFactory;
protected $table = 'documenti';
protected $fillable = [
'documentable_id',
'documentable_type',
'nome_file',
'path_file',
'tipo_documento',
'dimensione_file',
'mime_type',
'descrizione',
'xml_data',
'hash_file',
];
protected $casts = [
'xml_data' => '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];
}
}

81
app/Models/Fornitore.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Fornitore extends Model
{
use HasFactory;
protected $table = 'fornitori';
protected $primaryKey = 'id_fornitore';
protected $fillable = [
'amministratore_id',
'ragione_sociale',
'partita_iva',
'codice_fiscale',
'indirizzo',
'cap',
'citta',
'provincia',
'email',
'pec',
'telefono',
'old_id',
];
protected $casts = [
'created_at' => '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}%");
});
}
}

95
app/Models/Gestione.php Normal file
View File

@ -0,0 +1,95 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Gestione extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'gestioni';
protected $primaryKey = 'id_gestione';
protected $fillable = [
'stabile_id',
'anno_gestione',
'tipo_gestione',
'data_inizio',
'data_fine',
'descrizione',
'stato',
'preventivo_approvato',
'data_approvazione',
'note',
];
protected $casts = [
'data_inizio' => '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 . ')' : '');
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LogModificaPreventivo extends Model
{
use HasFactory;
protected $table = 'log_modifiche_preventivo';
protected $fillable = [
'entita',
'entita_id',
'versione_precedente',
'versione_nuova',
'utente_id',
'tipo_operazione',
'motivo',
'dati_precedenti',
'dati_nuovi',
'diff',
];
protected $casts = [
'versione_precedente' => '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;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MovimentoBancario extends Model
{
use HasFactory;
protected $table = 'movimenti_bancari';
protected $fillable = [
'banca_id',
'movimento_contabile_id',
'data_valuta',
'data_contabile',
'tipo_movimento',
'importo',
'causale',
'beneficiario',
'ordinante',
'cro_tro',
'stato_riconciliazione',
'note',
];
protected $casts = [
'data_valuta' => '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);
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class MovimentoContabile extends Model
{
use HasFactory, SoftDeletes;
protected $table = 'movimenti_contabili';
protected $fillable = [
'stabile_id',
'gestione_id',
'fornitore_id',
'documento_id',
'protocollo',
'data_registrazione',
'data_documento',
'numero_documento',
'descrizione',
'tipo_movimento',
'importo_totale',
'ritenuta_acconto',
'importo_netto',
'stato',
'note',
];
protected $casts = [
'data_registrazione' => '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);
}
}

244
app/Models/OrdineGiorno.php Normal file
View File

@ -0,0 +1,244 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrdineGiorno extends Model
{
use HasFactory;
protected $table = 'ordine_giorno';
protected $fillable = [
'assemblea_id',
'numero_punto',
'titolo',
'descrizione',
'tipo_voce',
'collegamento_preventivo_id',
'importo_spesa',
'tabella_millesimale_id',
'esito_votazione',
'voti_favorevoli',
'voti_contrari',
'astenuti',
'millesimi_favorevoli',
'millesimi_contrari',
'millesimi_astenuti',
'note_delibera',
];
protected $casts = [
'numero_punto' => '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));
}
}

156
app/Models/Preventivo.php Normal file
View File

@ -0,0 +1,156 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Preventivo extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'stabile_id',
'anno_gestione',
'tipo_gestione',
'descrizione',
'stato',
'importo_totale',
'data_creazione',
'data_approvazione',
'approvato_da_user_id',
'note',
'versione',
];
protected $casts = [
'data_creazione' => '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;
}
}

96
app/Models/Rata.php Normal file
View File

@ -0,0 +1,96 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Rata extends Model
{
use HasFactory;
protected $table = 'rate';
protected $fillable = [
'gestione_id',
'unita_immobiliare_id',
'soggetto_id',
'numero_rata',
'descrizione',
'importo',
'data_scadenza',
'data_pagamento',
'importo_pagato',
'stato',
'tipo_rata',
'note',
];
protected $casts = [
'importo' => '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;
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class RegistroProtocollo extends Model
{
use HasFactory;
protected $table = 'registro_protocollo';
protected $fillable = [
'numero_protocollo',
'tipo_comunicazione',
'assemblea_id',
'soggetto_destinatario_id',
'soggetto_mittente_id',
'oggetto',
'contenuto',
'canale',
'data_invio',
'esito',
'data_consegna',
'data_lettura',
'riferimento_esterno',
'allegati',
'note',
'creato_da_user_id',
];
protected $casts = [
'data_invio' => '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);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class RichiestaModifica extends Model
{
use HasFactory;
protected $table = 'richieste_modifiche';
protected $fillable = [
'unita_immobiliare_id',
'soggetto_richiedente_id',
'tipo_modifica',
'descrizione',
'dati_attuali',
'dati_proposti',
'stato',
'note_amministratore',
'data_approvazione',
'approvato_da_user_id',
];
protected $casts = [
'dati_attuali' => '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);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class RipartizionePreventivo extends Model
{
use HasFactory;
protected $table = 'ripartizioni_preventivo';
protected $fillable = [
'voce_preventivo_id',
'unita_immobiliare_id',
'quota_calcolata',
'quota_modificata',
'quota_finale',
'versione',
'modificato_da_user_id',
'motivo_modifica',
'data_modifica',
];
protected $casts = [
'quota_calcolata' => '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;
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ScritturaBilancio extends Model
{
use HasFactory;
protected $table = 'scritture_bilancio';
protected $fillable = [
'bilancio_id',
'numero_scrittura',
'data_scrittura',
'descrizione',
'tipo_scrittura',
'importo_totale',
'riferimento_documento',
'movimento_contabile_id',
'creato_da_user_id',
'note',
];
protected $casts = [
'data_scrittura' => '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);
}
}

74
app/Models/Stabile.php Normal file
View File

@ -0,0 +1,74 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Stabile extends Model
{
use HasFactory;
protected $table = 'stabili';
protected $primaryKey = 'id_stabile';
protected $fillable = [
'amministratore_id',
'denominazione',
'codice_fiscale',
'cod_fisc_amministratore',
'indirizzo',
'citta',
'cap',
'provincia',
'note',
'old_id',
'stato',
];
protected $casts = [
'created_at' => '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 . ')' : '');
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TabellaMillesimale extends Model
{
use HasFactory;
protected $table = 'tabelle_millesimali';
protected $fillable = [
'stabile_id',
'nome',
'descrizione',
'tipo',
'attiva',
'data_approvazione',
'ordinamento',
];
protected $casts = [
'attiva' => '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');
}
}

119
app/Models/Ticket.php Normal file
View File

@ -0,0 +1,119 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
use HasFactory;
protected $fillable = [
'stabile_id',
'unita_immobiliare_id',
'soggetto_richiedente_id',
'categoria_ticket_id',
'aperto_da_user_id',
'assegnato_a_user_id',
'assegnato_a_fornitore_id',
'titolo',
'descrizione',
'luogo_intervento',
'data_apertura',
'data_scadenza_prevista',
'data_risoluzione_effettiva',
'data_chiusura_effettiva',
'stato',
'priorita',
];
protected $casts = [
'data_apertura' => '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);
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UnitaImmobiliare extends Model
{
use HasFactory;
protected $table = 'unita_immobiliari';
protected $primaryKey = 'id_unita';
protected $fillable = [
'stabile_id',
'interno',
'scala',
'piano',
'fabbricato',
'millesimi_proprieta',
'categoria_catastale',
'superficie',
'vani',
'indirizzo',
'note',
];
protected $casts = [
'millesimi_proprieta' => '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;
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VocePreventivo extends Model
{
use HasFactory;
protected $table = 'voci_preventivo';
protected $fillable = [
'preventivo_id',
'codice',
'descrizione',
'importo_preventivato',
'importo_effettivo',
'tabella_millesimale_id',
'voce_spesa_id',
'ricorrente',
'frequenza',
'data_scadenza_prevista',
'ordinamento',
'note',
];
protected $casts = [
'importo_preventivato' => '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;
}
}

73
app/Models/VoceSpesa.php Normal file
View File

@ -0,0 +1,73 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VoceSpesa extends Model
{
use HasFactory;
protected $table = 'voci_spesa';
protected $fillable = [
'stabile_id',
'codice',
'descrizione',
'tipo_gestione',
'categoria',
'tabella_millesimale_default_id',
'ritenuta_acconto_default',
'attiva',
'ordinamento',
];
protected $casts = [
'ritenuta_acconto_default' => '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');
}
}

View File

@ -0,0 +1,72 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Rinomina la tabella condomini in stabili
Schema::rename('condomini', 'stabili');
// Rinomina la colonna nome in denominazione
Schema::table('stabili', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,227 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Tabella gestioni
Schema::create('gestioni', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('richieste_modifiche', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,204 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Tabella preventivi
Schema::create('preventivi', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,273 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Tabella bilanci
Schema::create('bilanci', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,242 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Tabella assemblee
Schema::create('assemblee', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,154 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('API Tokens') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Gestione API Tokens</h3>
<p class="text-gray-600 dark:text-gray-400 mt-2">Crea e gestisci i token di accesso per le API</p>
</div>
<!-- Form Creazione Token -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg mb-8">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Crea Nuovo Token</h4>
<form method="POST" action="{{ route('admin.api-tokens.store') }}">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome Token -->
<div>
<x-input-label for="token_name" :value="__('Nome Token')" />
<x-text-input id="token_name" name="token_name" type="text" class="mt-1 block w-full"
:value="old('token_name')" required
placeholder="Es. API Mobile App, Integrazione CRM..." />
<x-input-error class="mt-2" :messages="$errors->get('token_name')" />
</div>
<!-- Abilità -->
<div>
<x-input-label for="abilities" :value="__('Abilità')" />
<select id="abilities" name="abilities[]" multiple
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="read">Lettura</option>
<option value="write">Scrittura</option>
<option value="delete">Eliminazione</option>
<option value="admin">Amministrazione</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('abilities')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Tieni premuto Ctrl/Cmd per selezionare più opzioni</p>
</div>
</div>
<div class="mt-4">
<x-primary-button>
{{ __('Crea Token') }}
</x-primary-button>
</div>
</form>
</div>
<!-- Lista Token Esistenti -->
<div class="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Token Esistenti</h4>
@if(isset($tokens) && $tokens->count() > 0)
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Nome
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Abilità
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Ultimo Utilizzo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Creato il
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@foreach($tokens as $token)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $token->name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
@if($token->abilities)
@foreach(json_decode($token->abilities, true) as $ability)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 mr-1">
{{ ucfirst($ability) }}
</span>
@endforeach
@else
<span class="text-gray-500 dark:text-gray-400">Tutte</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $token->last_used_at ? $token->last_used_at->format('d/m/Y H:i') : 'Mai utilizzato' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $token->created_at->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<form method="POST" action="{{ route('admin.api-tokens.destroy', $token->id) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo token? Questa azione non può essere annullata.')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
Elimina
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="text-center py-8">
<p class="text-gray-500 dark:text-gray-400">Nessun token API creato</p>
<p class="text-sm text-gray-400 dark:text-gray-500 mt-2">Crea il tuo primo token utilizzando il form sopra</p>
</div>
@endif
</div>
<!-- Informazioni di Sicurezza -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg mt-6">
<h5 class="text-sm font-medium text-yellow-800 dark:text-yellow-200 mb-2">⚠️ Informazioni di Sicurezza</h5>
<ul class="text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
<li> I token API forniscono accesso completo al tuo account</li>
<li> Non condividere mai i tuoi token con terze parti non autorizzate</li>
<li> Elimina immediatamente i token che non utilizzi più</li>
<li> Monitora regolarmente l'utilizzo dei tuoi token</li>
</ul>
</div>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,235 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Bilanci e Consuntivi') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Bilanci Aperti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['bilanci_aperti'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Bilanci Approvati</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['bilanci_approvati'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Conguagli da Pagare</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['conguagli_da_pagare'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z"></path>
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Totale Avanzi</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100"> {{ number_format($stats['totale_avanzi'], 2, ',', '.') }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
<a href="{{ route('admin.bilanci.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Bilancio
</a>
<a href="{{ route('admin.bilanci.quadrature') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Quadrature
</a>
<a href="{{ route('admin.bilanci.conguagli') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Conguagli
</a>
<a href="{{ route('admin.bilanci.rimborsi') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Rimborsi
</a>
<a href="{{ route('admin.bilanci.automazioni') }}"
class="bg-yellow-500 hover:bg-yellow-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Automazioni
</a>
</div>
</div>
</div>
<!-- Lista Bilanci -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Bilanci e Consuntivi</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Anno/Tipo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stabile
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Periodo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Risultato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Versione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($bilanci as $bilancio)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div class="font-medium">{{ $bilancio->anno_esercizio }}</div>
<div class="text-gray-500 dark:text-gray-400">{{ ucfirst($bilancio->tipo_gestione) }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $bilancio->stabile->denominazione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div>{{ $bilancio->data_inizio_esercizio->format('d/m/Y') }}</div>
<div class="text-gray-500 dark:text-gray-400">{{ $bilancio->data_fine_esercizio->format('d/m/Y') }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div class="font-medium {{ $bilancio->risultato_gestione >= 0 ? 'text-green-600' : 'text-red-600' }}">
{{ $bilancio->risultato_gestione >= 0 ? 'Avanzo' : 'Disavanzo' }}
</div>
<div class="text-sm"> {{ number_format(abs($bilancio->risultato_gestione), 2, ',', '.') }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($bilancio->stato)
@case('bozza') bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100 @break
@case('provvisorio') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@case('definitivo') bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 @break
@case('approvato') bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100 @break
@case('chiuso') bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-100 @break
@endswitch">
{{ ucfirst($bilancio->stato) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
v{{ $bilancio->versione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.bilanci.show', $bilancio) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
@if(in_array($bilancio->stato, ['bozza', 'provvisorio']))
<a href="{{ route('admin.bilanci.edit', $bilancio) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
@endif
<a href="{{ route('admin.bilanci.storico', $bilancio) }}"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300">
Storico
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun bilancio trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $bilanci->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,161 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard Contabilità') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Movimenti Mese</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['movimenti_mese'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M12 2l3.09 6.26L22 9l-5 4.87L18.18 22 12 18.27 5.82 22 7 13.87 2 9l6.91-.74L12 2z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Entrate Mese</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100"> {{ number_format($stats['importo_entrate_mese'], 2, ',', '.') }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path>
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Uscite Mese</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100"> {{ number_format($stats['importo_uscite_mese'], 2, ',', '.') }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg mb-8">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<a href="{{ route('admin.contabilita.registrazione') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuova Registrazione
</a>
<a href="{{ route('admin.contabilita.import-xml') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Import XML
</a>
<a href="{{ route('admin.contabilita.movimenti') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Tutti i Movimenti
</a>
<a href="{{ route('admin.contabilita.riconciliazione') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Riconciliazione
</a>
</div>
</div>
</div>
<!-- Ultimi Movimenti -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Ultimi Movimenti</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Data
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Protocollo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Descrizione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Tipo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Importo
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($ultimiMovimenti as $movimento)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $movimento->data_registrazione->format('d/m/Y') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $movimento->protocollo }}
</td>
<td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $movimento->descrizione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ $movimento->tipo_movimento === 'entrata' ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' }}">
{{ ucfirst($movimento->tipo_movimento) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ number_format($movimento->importo_totale, 2, ',', '.') }}
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun movimento registrato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,210 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Registrazione Movimento Contabile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Nuova Registrazione Contabile</h3>
<a href="{{ route('admin.contabilita.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Dashboard
</a>
</div>
<form method="POST" action="{{ route('admin.contabilita.store-registrazione') }}" id="registrazione-form">
@csrf
<!-- Sezione Dati Generali -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Dati Generali Documento</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Stabile -->
<div>
<x-input-label for="stabile_id" :value="__('Stabile')" />
<select id="stabile_id" name="stabile_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="">Seleziona stabile</option>
@foreach($stabili as $stabile)
<option value="{{ $stabile->id_stabile }}" {{ old('stabile_id') == $stabile->id_stabile ? 'selected' : '' }}>
{{ $stabile->denominazione }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('stabile_id')" />
</div>
<!-- Gestione -->
<div>
<x-input-label for="gestione_id" :value="__('Gestione')" />
<select id="gestione_id" name="gestione_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="">Seleziona gestione</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('gestione_id')" />
</div>
<!-- Tipo Movimento -->
<div>
<x-input-label for="tipo_movimento" :value="__('Tipo Movimento')" />
<select id="tipo_movimento" name="tipo_movimento" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="">Seleziona tipo</option>
<option value="entrata" {{ old('tipo_movimento') == 'entrata' ? 'selected' : '' }}>Entrata</option>
<option value="uscita" {{ old('tipo_movimento') == 'uscita' ? 'selected' : '' }}>Uscita</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('tipo_movimento')" />
</div>
<!-- Data Documento -->
<div>
<x-input-label for="data_documento" :value="__('Data Documento')" />
<x-text-input id="data_documento" name="data_documento" type="date" class="mt-1 block w-full"
:value="old('data_documento', date('Y-m-d'))" required />
<x-input-error class="mt-2" :messages="$errors->get('data_documento')" />
</div>
<!-- Numero Documento -->
<div>
<x-input-label for="numero_documento" :value="__('Numero Documento')" />
<x-text-input id="numero_documento" name="numero_documento" type="text" class="mt-1 block w-full"
:value="old('numero_documento')" required />
<x-input-error class="mt-2" :messages="$errors->get('numero_documento')" />
</div>
<!-- Fornitore -->
<div>
<x-input-label for="fornitore_id" :value="__('Fornitore')" />
<select id="fornitore_id" name="fornitore_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona fornitore</option>
@foreach($fornitori as $fornitore)
<option value="{{ $fornitore->id_fornitore }}" {{ old('fornitore_id') == $fornitore->id_fornitore ? 'selected' : '' }}>
{{ $fornitore->ragione_sociale }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('fornitore_id')" />
</div>
<!-- Descrizione -->
<div class="md:col-span-2">
<x-input-label for="descrizione" :value="__('Descrizione')" />
<x-text-input id="descrizione" name="descrizione" type="text" class="mt-1 block w-full"
:value="old('descrizione')" required />
<x-input-error class="mt-2" :messages="$errors->get('descrizione')" />
</div>
<!-- Importo Totale -->
<div>
<x-input-label for="importo_totale" :value="__('Importo Totale')" />
<x-text-input id="importo_totale" name="importo_totale" type="number" step="0.01" class="mt-1 block w-full"
:value="old('importo_totale')" required />
<x-input-error class="mt-2" :messages="$errors->get('importo_totale')" />
</div>
<!-- Ritenuta d'Acconto -->
<div>
<x-input-label for="ritenuta_acconto" :value="__('Ritenuta d\'Acconto')" />
<x-text-input id="ritenuta_acconto" name="ritenuta_acconto" type="number" step="0.01" class="mt-1 block w-full"
:value="old('ritenuta_acconto', '0')" />
<x-input-error class="mt-2" :messages="$errors->get('ritenuta_acconto')" />
</div>
</div>
</div>
<!-- Sezione Dettaglio Spese -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg mb-6">
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100">Dettaglio Spese</h4>
<button type="button" id="aggiungi-dettaglio" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Aggiungi Voce
</button>
</div>
<div id="dettagli-container">
<!-- I dettagli verranno aggiunti dinamicamente qui -->
</div>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.contabilita.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Registra Movimento') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
<script>
let dettaglioIndex = 0;
document.getElementById('aggiungi-dettaglio').addEventListener('click', function() {
aggiungiDettaglio();
});
function aggiungiDettaglio() {
const container = document.getElementById('dettagli-container');
const dettaglioHtml = `
<div class="dettaglio-item border border-gray-300 dark:border-gray-600 p-4 rounded-lg mb-4" data-index="${dettaglioIndex}">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Voce Spesa</label>
<select name="dettagli[${dettaglioIndex}][voce_spesa_id]" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="">Seleziona voce</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Importo</label>
<input type="number" step="0.01" name="dettagli[${dettaglioIndex}][importo]" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tabella Millesimale</label>
<select name="dettagli[${dettaglioIndex}][tabella_millesimale_id]" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona tabella</option>
</select>
</div>
<div class="flex items-end">
<button type="button" onclick="rimuoviDettaglio(${dettaglioIndex})" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
Rimuovi
</button>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', dettaglioHtml);
dettaglioIndex++;
}
function rimuoviDettaglio(index) {
const item = document.querySelector(`[data-index="${index}"]`);
if (item) {
item.remove();
}
}
// Aggiungi il primo dettaglio automaticamente
document.addEventListener('DOMContentLoaded', function() {
aggiungiDettaglio();
});
</script>
</x-app-layout>

View File

@ -0,0 +1,269 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard Amministratore') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Statistiche Principali -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Stabili Gestiti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['stabili_gestiti'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Stabili Attivi</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['stabili_attivi'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Ticket Aperti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['ticket_aperti'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Ticket Urgenti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['ticket_urgenti'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
<a href="{{ route('admin.stabili.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Stabile
</a>
<a href="{{ route('admin.tickets.create') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Ticket
</a>
<a href="{{ route('admin.fornitori.create') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Fornitore
</a>
<a href="{{ route('admin.soggetti.create') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Soggetto
</a>
<a href="{{ route('admin.contabilita.registrazione') }}"
class="bg-yellow-500 hover:bg-yellow-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Registrazione
</a>
<a href="{{ route('admin.documenti.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Documenti
</a>
</div>
</div>
</div>
<!-- Contenuto principale in due colonne -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Ticket da Lavorare -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Ticket da Lavorare</h3>
<a href="{{ route('admin.tickets.index') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Vedi tutti
</a>
</div>
<div class="space-y-3">
@forelse($ticketsAperti as $ticket)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100">
<a href="{{ route('admin.tickets.show', $ticket) }}" class="hover:text-blue-600">
{{ $ticket->titolo }}
</a>
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ $ticket->stabile->denominazione }}
</p>
</div>
<div class="flex flex-col items-end space-y-1">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
@switch($ticket->priorita)
@case('Urgente') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@case('Alta') bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100 @break
@case('Media') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@default bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@endswitch">
{{ $ticket->priorita }}
</span>
<span class="text-xs text-gray-500 dark:text-gray-400">
{{ $ticket->created_at->diffForHumans() }}
</span>
</div>
</div>
</div>
@empty
<p class="text-gray-500 dark:text-gray-400 text-center py-4">Nessun ticket aperto</p>
@endforelse
</div>
</div>
</div>
<!-- Ultimi Documenti -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Ultimi Documenti</h3>
<a href="{{ route('admin.documenti.index') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Vedi tutti
</a>
</div>
<div class="space-y-3">
@forelse($ultimiDocumenti as $documento)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $documento->nome_file }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ $documento->tipo_documento }} - {{ $documento->documentable->denominazione ?? 'N/A' }}
</p>
</div>
<div class="flex flex-col items-end space-y-1">
<span class="text-xs text-gray-500 dark:text-gray-400">
{{ $documento->created_at->diffForHumans() }}
</span>
<a href="{{ $documento->url_download }}" class="text-blue-600 hover:text-blue-900 text-xs">
Download
</a>
</div>
</div>
</div>
@empty
<p class="text-gray-500 dark:text-gray-400 text-center py-4">Nessun documento caricato</p>
@endforelse
</div>
</div>
</div>
</div>
<!-- Ultimi Movimenti Contabili -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Ultimi Movimenti Contabili</h3>
<a href="{{ route('admin.contabilita.movimenti') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Vedi tutti
</a>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Data</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Descrizione</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Fornitore</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Importo</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($ultimiMovimenti as $movimento)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $movimento->data_registrazione->format('d/m/Y') }}
</td>
<td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $movimento->descrizione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $movimento->fornitore->ragione_sociale ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="font-medium {{ $movimento->tipo_movimento === 'entrata' ? 'text-green-600' : 'text-red-600' }}">
{{ $movimento->tipo_movimento === 'entrata' ? '+' : '-' }} {{ number_format($movimento->importo_totale, 2, ',', '.') }}
</span>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun movimento registrato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,117 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Carica Nuovo Documento') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Carica Nuovo Documento</h3>
<a href="{{ route('admin.documenti.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna all'Archivio
</a>
</div>
<form method="POST" action="{{ route('admin.documenti.store') }}" enctype="multipart/form-data">
@csrf
<div class="space-y-6">
<!-- Sezione File -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">File da Caricare</h4>
<div>
<x-input-label for="file" :value="__('Seleziona File')" />
<input type="file" id="file" name="file" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('file')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Formati supportati: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG, XML. Dimensione massima: 10MB
</p>
</div>
</div>
<!-- Sezione Classificazione -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Classificazione Documento</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Collega a Stabile -->
<div>
<x-input-label for="documentable_id" :value="__('Collega a Stabile')" />
<select id="documentable_id" name="documentable_id" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona uno stabile</option>
@foreach($stabili as $stabile)
<option value="{{ $stabile->id_stabile }}" {{ old('documentable_id') == $stabile->id_stabile ? 'selected' : '' }}>
{{ $stabile->denominazione }}
</option>
@endforeach
</select>
<input type="hidden" name="documentable_type" value="App\Models\Stabile">
<x-input-error class="mt-2" :messages="$errors->get('documentable_id')" />
</div>
<!-- Tipo Documento -->
<div>
<x-input-label for="tipo_documento" :value="__('Tipo Documento')" />
<select id="tipo_documento" name="tipo_documento" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona tipo</option>
<option value="Fattura" {{ old('tipo_documento') === 'Fattura' ? 'selected' : '' }}>Fattura</option>
<option value="Verbale Assemblea" {{ old('tipo_documento') === 'Verbale Assemblea' ? 'selected' : '' }}>Verbale Assemblea</option>
<option value="Bilancio" {{ old('tipo_documento') === 'Bilancio' ? 'selected' : '' }}>Bilancio</option>
<option value="Contratto" {{ old('tipo_documento') === 'Contratto' ? 'selected' : '' }}>Contratto</option>
<option value="Comunicazione" {{ old('tipo_documento') === 'Comunicazione' ? 'selected' : '' }}>Comunicazione</option>
<option value="Certificato" {{ old('tipo_documento') === 'Certificato' ? 'selected' : '' }}>Certificato</option>
<option value="Planimetria" {{ old('tipo_documento') === 'Planimetria' ? 'selected' : '' }}>Planimetria</option>
<option value="Foto" {{ old('tipo_documento') === 'Foto' ? 'selected' : '' }}>Foto</option>
<option value="Altro" {{ old('tipo_documento') === 'Altro' ? 'selected' : '' }}>Altro</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('tipo_documento')" />
</div>
</div>
</div>
<!-- Sezione Descrizione -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Descrizione</h4>
<div>
<x-input-label for="descrizione" :value="__('Descrizione (opzionale)')" />
<textarea id="descrizione" name="descrizione" rows="3"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm"
placeholder="Aggiungi una descrizione per identificare meglio il documento...">{{ old('descrizione') }}</textarea>
<x-input-error class="mt-2" :messages="$errors->get('descrizione')" />
</div>
</div>
</div>
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.documenti.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Carica Documento') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,146 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Documenti') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Archivio Documenti</h3>
<a href="{{ route('admin.documenti.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Carica Documento
</a>
</div>
<!-- Filtri -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<form method="GET" action="{{ route('admin.documenti.index') }}">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Ricerca -->
<div>
<x-input-label for="search" :value="__('Cerca')" />
<x-text-input id="search" name="search" type="text" class="mt-1 block w-full"
:value="request('search')" placeholder="Nome file o descrizione..." />
</div>
<!-- Tipo Documento -->
<div>
<x-input-label for="tipo_documento" :value="__('Tipo Documento')" />
<select id="tipo_documento" name="tipo_documento" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Tutti i tipi</option>
@foreach($tipiDocumento as $tipo)
<option value="{{ $tipo }}" {{ request('tipo_documento') === $tipo ? 'selected' : '' }}>
{{ $tipo }}
</option>
@endforeach
</select>
</div>
<!-- Pulsanti -->
<div class="flex items-end space-x-2">
<x-primary-button type="submit">
{{ __('Filtra') }}
</x-primary-button>
<x-secondary-button type="button" onclick="window.location='{{ route('admin.documenti.index') }}'">
{{ __('Reset') }}
</x-secondary-button>
</div>
</div>
</form>
</div>
<!-- Tabella Documenti -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Nome File
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Tipo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Collegato a
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Dimensione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Data Caricamento
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($documenti as $documento)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $documento->nome_file }}
@if($documento->descrizione)
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ $documento->descrizione }}</p>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100">
{{ $documento->tipo_documento }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $documento->documentable->denominazione ?? 'N/A' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $documento->dimensione_leggibile }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $documento->created_at->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.documenti.download', $documento) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Download
</a>
<a href="{{ route('admin.documenti.show', $documento) }}"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300">
Visualizza
</a>
<form method="POST" action="{{ route('admin.documenti.destroy', $documento) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo documento?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
Elimina
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun documento trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $documenti->appends(request()->query())->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,99 @@
<div class="space-y-6">
<!-- Sezione Informazioni Generali -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Informazioni Generali</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Ragione Sociale -->
<div class="md:col-span-2">
<x-input-label for="ragione_sociale" :value="__('Ragione Sociale')" />
<x-text-input id="ragione_sociale" name="ragione_sociale" type="text" class="mt-1 block w-full"
:value="old('ragione_sociale', $fornitore->ragione_sociale ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('ragione_sociale')" />
</div>
<!-- Partita IVA -->
<div>
<x-input-label for="partita_iva" :value="__('Partita IVA')" />
<x-text-input id="partita_iva" name="partita_iva" type="text" class="mt-1 block w-full"
:value="old('partita_iva', $fornitore->partita_iva ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('partita_iva')" />
</div>
<!-- Codice Fiscale -->
<div>
<x-input-label for="codice_fiscale" :value="__('Codice Fiscale')" />
<x-text-input id="codice_fiscale" name="codice_fiscale" type="text" class="mt-1 block w-full"
:value="old('codice_fiscale', $fornitore->codice_fiscale ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('codice_fiscale')" />
</div>
</div>
</div>
<!-- Sezione Contatti -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Contatti</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Email -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full"
:value="old('email', $fornitore->email ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('email')" />
</div>
<!-- PEC -->
<div>
<x-input-label for="pec" :value="__('PEC')" />
<x-text-input id="pec" name="pec" type="email" class="mt-1 block w-full"
:value="old('pec', $fornitore->pec ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('pec')" />
</div>
<!-- Telefono -->
<div>
<x-input-label for="telefono" :value="__('Telefono')" />
<x-text-input id="telefono" name="telefono" type="text" class="mt-1 block w-full"
:value="old('telefono', $fornitore->telefono ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('telefono')" />
</div>
</div>
</div>
<!-- Sezione Indirizzo -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Indirizzo</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Indirizzo -->
<div class="md:col-span-2">
<x-input-label for="indirizzo" :value="__('Indirizzo')" />
<x-text-input id="indirizzo" name="indirizzo" type="text" class="mt-1 block w-full"
:value="old('indirizzo', $fornitore->indirizzo ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('indirizzo')" />
</div>
<!-- Città -->
<div>
<x-input-label for="citta" :value="__('Città')" />
<x-text-input id="citta" name="citta" type="text" class="mt-1 block w-full"
:value="old('citta', $fornitore->citta ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('citta')" />
</div>
<!-- CAP -->
<div>
<x-input-label for="cap" :value="__('CAP')" />
<x-text-input id="cap" name="cap" type="text" class="mt-1 block w-full"
:value="old('cap', $fornitore->cap ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('cap')" />
</div>
<!-- Provincia -->
<div>
<x-input-label for="provincia" :value="__('Provincia')" />
<x-text-input id="provincia" name="provincia" type="text" class="mt-1 block w-full" maxlength="2"
:value="old('provincia', $fornitore->provincia ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('provincia')" />
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Nuovo Fornitore') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Crea Nuovo Fornitore</h3>
<a href="{{ route('admin.fornitori.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.fornitori.store') }}">
@csrf
@include('admin.fornitori._form', ['fornitore' => null])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.fornitori.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Crea Fornitore') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,50 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Modifica Fornitore') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Modifica: {{ $fornitore->ragione_sociale }}</h3>
<a href="{{ route('admin.fornitori.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.fornitori.update', $fornitore) }}">
@csrf
@method('PUT')
@include('admin.fornitori._form', ['fornitore' => $fornitore])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.fornitori.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Aggiorna Fornitore') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,110 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Fornitori') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Lista Fornitori</h3>
<a href="{{ route('admin.fornitori.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Fornitore
</a>
</div>
<!-- Tabella Fornitori -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Ragione Sociale
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Partita IVA
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Email
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Telefono
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Città
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($fornitori as $fornitore)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $fornitore->id_fornitore }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $fornitore->ragione_sociale }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $fornitore->partita_iva ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $fornitore->email ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $fornitore->telefono ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $fornitore->citta ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.fornitori.show', $fornitore) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
<a href="{{ route('admin.fornitori.edit', $fornitore) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
<form method="POST" action="{{ route('admin.fornitori.destroy', $fornitore) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo fornitore?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
Elimina
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun fornitore trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $fornitori->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,118 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dettagli Fornitore') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">{{ $fornitore->ragione_sociale }}</h3>
<div class="space-x-2">
<a href="{{ route('admin.fornitori.edit', $fornitore) }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Modifica
</a>
<a href="{{ route('admin.fornitori.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
</div>
<!-- Informazioni Principali -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Generali</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">ID Fornitore:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->id_fornitore }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Ragione Sociale:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->ragione_sociale }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Partita IVA:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->partita_iva ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Codice Fiscale:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->codice_fiscale ?? '-' }}</dd>
</div>
</dl>
</div>
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Contatti</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Email:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->email ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">PEC:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->pec ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Telefono:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->telefono ?? '-' }}</dd>
</div>
</dl>
</div>
</div>
<!-- Indirizzo -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Indirizzo</h4>
<dl class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Indirizzo:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->indirizzo ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Città:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->citta ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">CAP:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->cap ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Provincia:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->provincia ?? '-' }}</dd>
</div>
</dl>
</div>
<!-- Informazioni Sistema -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Sistema</h4>
<dl class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Creato il:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->created_at->format('d/m/Y H:i') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Ultimo aggiornamento:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->updated_at->format('d/m/Y H:i') }}</dd>
</div>
@if($fornitore->old_id)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Old ID:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $fornitore->old_id }}</dd>
</div>
@endif
</dl>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,144 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Impostazioni') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Configurazione Sistema</h3>
<p class="text-gray-600 dark:text-gray-400 mt-2">Gestisci le impostazioni generali dell'applicazione</p>
</div>
<form method="POST" action="{{ route('admin.impostazioni.store') }}" enctype="multipart/form-data">
@csrf
<!-- Sezione Applicazione -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Impostazioni Applicazione</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome Applicazione -->
<div>
<x-input-label for="app_name" :value="__('Nome Applicazione')" />
<x-text-input id="app_name" name="app_name" type="text" class="mt-1 block w-full"
:value="old('app_name', config('app.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_name')" />
</div>
<!-- URL Applicazione -->
<div>
<x-input-label for="app_url" :value="__('URL Applicazione')" />
<x-text-input id="app_url" name="app_url" type="url" class="mt-1 block w-full"
:value="old('app_url', config('app.url'))" />
<x-input-error class="mt-2" :messages="$errors->get('app_url')" />
</div>
</div>
</div>
<!-- Sezione Branding -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Branding</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Logo Applicazione -->
<div>
<x-input-label for="app_logo" :value="__('Logo Applicazione')" />
<input type="file" id="app_logo" name="app_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('app_logo')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formati supportati: JPG, PNG, SVG (max 2MB)</p>
</div>
<!-- Logo Dashboard -->
<div>
<x-input-label for="dashboard_logo" :value="__('Logo Dashboard')" />
<input type="file" id="dashboard_logo" name="dashboard_logo" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('dashboard_logo')" />
</div>
<!-- Favicon -->
<div>
<x-input-label for="favicon" :value="__('Favicon')" />
<input type="file" id="favicon" name="favicon" accept="image/*"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('favicon')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Formato ICO o PNG 32x32px</p>
</div>
</div>
</div>
<!-- Sezione Email -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Email</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Email Mittente -->
<div>
<x-input-label for="mail_from_address" :value="__('Email Mittente')" />
<x-text-input id="mail_from_address" name="mail_from_address" type="email" class="mt-1 block w-full"
:value="old('mail_from_address', config('mail.from.address'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_address')" />
</div>
<!-- Nome Mittente -->
<div>
<x-input-label for="mail_from_name" :value="__('Nome Mittente')" />
<x-text-input id="mail_from_name" name="mail_from_name" type="text" class="mt-1 block w-full"
:value="old('mail_from_name', config('mail.from.name'))" />
<x-input-error class="mt-2" :messages="$errors->get('mail_from_name')" />
</div>
</div>
</div>
<!-- Sezione Pagamenti -->
<div class="bg-purple-50 dark:bg-purple-900/20 p-6 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Configurazione Pagamenti</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Stripe Key -->
<div>
<x-input-label for="stripe_key" :value="__('Stripe Publishable Key')" />
<x-text-input id="stripe_key" name="stripe_key" type="text" class="mt-1 block w-full"
:value="old('stripe_key', config('services.stripe.key'))" />
<x-input-error class="mt-2" :messages="$errors->get('stripe_key')" />
</div>
<!-- PayPal Client ID -->
<div>
<x-input-label for="paypal_client_id" :value="__('PayPal Client ID')" />
<x-text-input id="paypal_client_id" name="paypal_client_id" type="text" class="mt-1 block w-full"
:value="old('paypal_client_id', config('services.paypal.client_id'))" />
<x-input-error class="mt-2" :messages="$errors->get('paypal_client_id')" />
</div>
</div>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<x-secondary-button type="button" onclick="window.location.reload()">
{{ __('Ripristina') }}
</x-secondary-button>
<x-primary-button>
{{ __('Salva Impostazioni') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,203 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Preventivi') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Preventivi Bozza</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['preventivi_bozza'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Preventivi Approvati</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['preventivi_approvati'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z"></path>
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Importo Anno Corrente</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100"> {{ number_format($stats['importo_totale_anno'], 2, ',', '.') }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<a href="{{ route('admin.preventivi.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Preventivo
</a>
<a href="{{ route('admin.preventivi.pianificazione') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Pianificazione
</a>
<a href="{{ route('admin.preventivi.template') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Template
</a>
<a href="{{ route('admin.preventivi.report') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Report
</a>
</div>
</div>
</div>
<!-- Lista Preventivi -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Preventivi</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Anno/Tipo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stabile
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Descrizione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Importo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Versione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($preventivi as $preventivo)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div class="font-medium">{{ $preventivo->anno_gestione }}</div>
<div class="text-gray-500 dark:text-gray-400">{{ ucfirst($preventivo->tipo_gestione) }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $preventivo->stabile->denominazione }}
</td>
<td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100">
{{ $preventivo->descrizione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="font-medium"> {{ number_format($preventivo->importo_totale, 2, ',', '.') }}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($preventivo->stato)
@case('bozza') bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100 @break
@case('provvisorio') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@case('definitivo') bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 @break
@case('approvato') bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100 @break
@case('archiviato') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@endswitch">
{{ ucfirst($preventivo->stato) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
v{{ $preventivo->versione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.preventivi.show', $preventivo) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
@if($preventivo->stato === 'bozza')
<a href="{{ route('admin.preventivi.edit', $preventivo) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
@endif
<a href="{{ route('admin.preventivi.storico', $preventivo) }}"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300">
Storico
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun preventivo trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $preventivi->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,184 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Pianificazione Spese e Entrate') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Dashboard Cashflow -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Previsione Cashflow (Prossimi 6 Mesi)</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Mese
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Entrate Previste
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Uscite Previste
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Saldo Previsto
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@foreach($cashflow as $mese)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $mese['mese'] }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-green-600 dark:text-green-400">
+ {{ number_format($mese['entrate'], 2, ',', '.') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-red-600 dark:text-red-400">
- {{ number_format($mese['uscite'], 2, ',', '.') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium {{ $mese['saldo'] >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
{{ $mese['saldo'] >= 0 ? '+' : '' }} {{ number_format($mese['saldo'], 2, ',', '.') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<!-- Spese in Scadenza -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Spese in Scadenza (Prossimi 30 giorni)</h3>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuova Spesa Pianificata
</button>
</div>
<div class="space-y-4">
@forelse($speseInScadenza as $spesa)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div class="flex justify-between items-start">
<div class="flex-1">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $spesa->descrizione }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ $spesa->stabile->denominazione }}
</p>
<div class="mt-2 flex items-center space-x-4">
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ number_format($spesa->importo_previsto, 2, ',', '.') }}
</span>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
@switch($spesa->tipo)
@case('ricorrente') bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 @break
@case('straordinaria') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@case('manutenzione') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@endswitch">
{{ ucfirst($spesa->tipo) }}
</span>
</div>
</div>
<div class="flex flex-col items-end space-y-2">
<span class="text-sm text-gray-500 dark:text-gray-400">
Scadenza: {{ $spesa->data_scadenza_prevista->format('d/m/Y') }}
</span>
<div class="flex space-x-2">
<button class="text-green-600 hover:text-green-900 text-sm">Conferma</button>
<button class="text-blue-600 hover:text-blue-900 text-sm">Modifica</button>
<button class="text-red-600 hover:text-red-900 text-sm">Annulla</button>
</div>
</div>
</div>
</div>
@empty
<div class="text-center py-8">
<p class="text-gray-500 dark:text-gray-400">Nessuna spesa in scadenza nei prossimi 30 giorni</p>
</div>
@endforelse
</div>
</div>
</div>
<!-- Grafici Cashflow -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Andamento Cashflow</h3>
<div class="h-64">
<canvas id="cashflowChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Script per grafici -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('cashflowChart').getContext('2d');
const cashflowData = @json($cashflow);
new Chart(ctx, {
type: 'line',
data: {
labels: cashflowData.map(item => item.mese),
datasets: [
{
label: 'Entrate',
data: cashflowData.map(item => item.entrate),
borderColor: 'rgb(34, 197, 94)',
backgroundColor: 'rgba(34, 197, 94, 0.1)',
tension: 0.1
},
{
label: 'Uscite',
data: cashflowData.map(item => item.uscite),
borderColor: 'rgb(239, 68, 68)',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
tension: 0.1
},
{
label: 'Saldo',
data: cashflowData.map(item => item.saldo),
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '€ ' + value.toLocaleString();
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': € ' + context.parsed.y.toLocaleString();
}
}
}
}
}
});
</script>
</x-app-layout>

View File

@ -0,0 +1,171 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Rubrica') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Rubrica Contatti</h3>
<a href="{{ route('admin.soggetti.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Contatto
</a>
</div>
<!-- Filtri di Ricerca -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<form method="GET" action="{{ route('admin.rubrica.index') }}">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<!-- Ricerca per Nome -->
<div>
<x-input-label for="search" :value="__('Cerca per Nome/Cognome')" />
<x-text-input id="search" name="search" type="text" class="mt-1 block w-full"
:value="request('search')" placeholder="Nome o cognome..." />
</div>
<!-- Filtro per Tipo -->
<div>
<x-input-label for="tipo_soggetto" :value="__('Tipo Soggetto')" />
<select id="tipo_soggetto" name="tipo_soggetto" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Tutti i tipi</option>
<option value="Persona Fisica" {{ request('tipo_soggetto') === 'Persona Fisica' ? 'selected' : '' }}>Persona Fisica</option>
<option value="Persona Giuridica" {{ request('tipo_soggetto') === 'Persona Giuridica' ? 'selected' : '' }}>Persona Giuridica</option>
</select>
</div>
<!-- Filtro per Email -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full"
:value="request('email')" placeholder="email@esempio.com" />
</div>
<!-- Pulsanti -->
<div class="flex items-end space-x-2">
<x-primary-button type="submit">
{{ __('Filtra') }}
</x-primary-button>
<x-secondary-button type="button" onclick="window.location='{{ route('admin.rubrica.index') }}'">
{{ __('Reset') }}
</x-secondary-button>
</div>
</div>
</form>
</div>
<!-- Tabella Contatti -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Nome/Ragione Sociale
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Tipo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Email
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Telefono
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Città
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($soggetti as $soggetto)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
@if($soggetto->tipo_soggetto === 'Persona Fisica')
{{ $soggetto->nome }} {{ $soggetto->cognome }}
@else
{{ $soggetto->ragione_sociale }}
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ $soggetto->tipo_soggetto === 'Persona Fisica' ? 'bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100' : 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' }}">
{{ $soggetto->tipo_soggetto }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $soggetto->email ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $soggetto->telefono ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $soggetto->citta ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.soggetti.show', $soggetto) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
<a href="{{ route('admin.soggetti.edit', $soggetto) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
@if($soggetto->email)
<a href="mailto:{{ $soggetto->email }}"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300">
Email
</a>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun contatto trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $soggetti->appends(request()->query())->links() }}
</div>
<!-- Statistiche -->
<div class="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
<h4 class="text-sm font-medium text-blue-800 dark:text-blue-200">Persone Fisiche</h4>
<p class="text-2xl font-bold text-blue-900 dark:text-blue-100">
{{ $soggetti->where('tipo_soggetto', 'Persona Fisica')->count() }}
</p>
</div>
<div class="bg-green-50 dark:bg-green-900/20 p-4 rounded-lg">
<h4 class="text-sm font-medium text-green-800 dark:text-green-200">Persone Giuridiche</h4>
<p class="text-2xl font-bold text-green-900 dark:text-green-100">
{{ $soggetti->where('tipo_soggetto', 'Persona Giuridica')->count() }}
</p>
</div>
<div class="bg-purple-50 dark:bg-purple-900/20 p-4 rounded-lg">
<h4 class="text-sm font-medium text-purple-800 dark:text-purple-200">Totale Contatti</h4>
<p class="text-2xl font-bold text-purple-900 dark:text-purple-100">
{{ $soggetti->count() }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,90 @@
<div class="space-y-6">
<!-- Sezione Informazioni Generali -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Informazioni Generali</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Denominazione -->
<div>
<x-input-label for="denominazione" :value="__('Denominazione Stabile')" />
<x-text-input id="denominazione" name="denominazione" type="text" class="mt-1 block w-full"
:value="old('denominazione', $stabile->denominazione ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('denominazione')" />
</div>
<!-- Codice Fiscale -->
<div>
<x-input-label for="codice_fiscale" :value="__('Codice Fiscale')" />
<x-text-input id="codice_fiscale" name="codice_fiscale" type="text" class="mt-1 block w-full"
:value="old('codice_fiscale', $stabile->codice_fiscale ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('codice_fiscale')" />
</div>
<!-- CF Amministratore -->
<div>
<x-input-label for="cod_fisc_amministratore" :value="__('CF Amministratore')" />
<x-text-input id="cod_fisc_amministratore" name="cod_fisc_amministratore" type="text" class="mt-1 block w-full"
:value="old('cod_fisc_amministratore', $stabile->cod_fisc_amministratore ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('cod_fisc_amministratore')" />
</div>
<!-- Stato -->
<div>
<x-input-label for="stato" :value="__('Stato')" />
<select id="stato" name="stato" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="attivo" {{ old('stato', $stabile->stato ?? 'attivo') === 'attivo' ? 'selected' : '' }}>Attivo</option>
<option value="inattivo" {{ old('stato', $stabile->stato ?? '') === 'inattivo' ? 'selected' : '' }}>Inattivo</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('stato')" />
</div>
</div>
</div>
<!-- Sezione Indirizzo -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Indirizzo</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Indirizzo -->
<div class="md:col-span-2">
<x-input-label for="indirizzo" :value="__('Indirizzo')" />
<x-text-input id="indirizzo" name="indirizzo" type="text" class="mt-1 block w-full"
:value="old('indirizzo', $stabile->indirizzo ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('indirizzo')" />
</div>
<!-- Città -->
<div>
<x-input-label for="citta" :value="__('Città')" />
<x-text-input id="citta" name="citta" type="text" class="mt-1 block w-full"
:value="old('citta', $stabile->citta ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('citta')" />
</div>
<!-- CAP -->
<div>
<x-input-label for="cap" :value="__('CAP')" />
<x-text-input id="cap" name="cap" type="text" class="mt-1 block w-full"
:value="old('cap', $stabile->cap ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('cap')" />
</div>
<!-- Provincia -->
<div>
<x-input-label for="provincia" :value="__('Provincia')" />
<x-text-input id="provincia" name="provincia" type="text" class="mt-1 block w-full" maxlength="2"
:value="old('provincia', $stabile->provincia ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('provincia')" />
</div>
</div>
</div>
<!-- Sezione Note -->
<div class="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Note</h4>
<div>
<x-input-label for="note" :value="__('Note')" />
<textarea id="note" name="note" rows="4"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">{{ old('note', $stabile->note ?? '') }}</textarea>
<x-input-error class="mt-2" :messages="$errors->get('note')" />
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Nuovo Stabile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Crea Nuovo Stabile</h3>
<a href="{{ route('admin.stabili.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.stabili.store') }}">
@csrf
@include('admin.stabili._form', ['stabile' => null])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.stabili.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Crea Stabile') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,50 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Modifica Stabile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Modifica: {{ $stabile->denominazione }}</h3>
<a href="{{ route('admin.stabili.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.stabili.update', $stabile) }}">
@csrf
@method('PUT')
@include('admin.stabili._form', ['stabile' => $stabile])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.stabili.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Aggiorna Stabile') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,113 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Stabili') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Lista Stabili</h3>
<a href="{{ route('admin.stabili.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Stabile
</a>
</div>
<!-- Tabella Stabili -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Denominazione
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Codice Fiscale
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Indirizzo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Città
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($stabili as $stabile)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $stabile->id_stabile }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $stabile->denominazione }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $stabile->codice_fiscale ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $stabile->indirizzo }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $stabile->citta }} ({{ $stabile->provincia }})
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ $stabile->stato === 'attivo' ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' }}">
{{ ucfirst($stabile->stato) }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.stabili.show', $stabile) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
<a href="{{ route('admin.stabili.edit', $stabile) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
<form method="POST" action="{{ route('admin.stabili.destroy', $stabile) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo stabile?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
Elimina
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessuno stabile trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $stabili->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,116 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dettagli Stabile') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">{{ $stabile->denominazione }}</h3>
<div class="space-x-2">
<a href="{{ route('admin.stabili.edit', $stabile) }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Modifica
</a>
<a href="{{ route('admin.stabili.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
</div>
<!-- Informazioni Principali -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Generali</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">ID Stabile:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->id_stabile }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Denominazione:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->denominazione }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Codice Fiscale:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->codice_fiscale ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">CF Amministratore:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->cod_fisc_amministratore ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Stato:</dt>
<dd class="text-sm">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ $stabile->stato === 'attivo' ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' }}">
{{ ucfirst($stabile->stato) }}
</span>
</dd>
</div>
</dl>
</div>
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Indirizzo</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Indirizzo:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->indirizzo }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Città:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->citta }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">CAP:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->cap }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Provincia:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->provincia ?? '-' }}</dd>
</div>
</dl>
</div>
</div>
<!-- Note -->
@if($stabile->note)
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Note</h4>
<p class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->note }}</p>
</div>
@endif
<!-- Informazioni Sistema -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Sistema</h4>
<dl class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Creato il:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->created_at->format('d/m/Y H:i') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Ultimo aggiornamento:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->updated_at->format('d/m/Y H:i') }}</dd>
</div>
@if($stabile->old_id)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Old ID:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $stabile->old_id }}</dd>
</div>
@endif
</dl>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,153 @@
<div class="space-y-6">
<!-- Sezione Informazioni Principali -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Informazioni Principali</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Titolo -->
<div class="md:col-span-2">
<x-input-label for="titolo" :value="__('Titolo')" />
<x-text-input id="titolo" name="titolo" type="text" class="mt-1 block w-full"
:value="old('titolo', $ticket->titolo ?? '')" required />
<x-input-error class="mt-2" :messages="$errors->get('titolo')" />
</div>
<!-- Stabile -->
<div>
<x-input-label for="stabile_id" :value="__('Stabile')" />
<select id="stabile_id" name="stabile_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="">Seleziona uno stabile</option>
@foreach($stabili ?? [] as $stabile)
<option value="{{ $stabile->id_stabile }}" {{ old('stabile_id', $ticket->stabile_id ?? '') == $stabile->id_stabile ? 'selected' : '' }}>
{{ $stabile->denominazione }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('stabile_id')" />
</div>
<!-- Categoria -->
<div>
<x-input-label for="categoria_ticket_id" :value="__('Categoria')" />
<select id="categoria_ticket_id" name="categoria_ticket_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona una categoria</option>
@foreach($categorieTicket ?? [] as $categoria)
<option value="{{ $categoria->id }}" {{ old('categoria_ticket_id', $ticket->categoria_ticket_id ?? '') == $categoria->id ? 'selected' : '' }}>
{{ $categoria->nome }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('categoria_ticket_id')" />
</div>
<!-- Stato -->
<div>
<x-input-label for="stato" :value="__('Stato')" />
<select id="stato" name="stato" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="Aperto" {{ old('stato', $ticket->stato ?? 'Aperto') === 'Aperto' ? 'selected' : '' }}>Aperto</option>
<option value="Preso in Carico" {{ old('stato', $ticket->stato ?? '') === 'Preso in Carico' ? 'selected' : '' }}>Preso in Carico</option>
<option value="In Lavorazione" {{ old('stato', $ticket->stato ?? '') === 'In Lavorazione' ? 'selected' : '' }}>In Lavorazione</option>
<option value="In Attesa Approvazione" {{ old('stato', $ticket->stato ?? '') === 'In Attesa Approvazione' ? 'selected' : '' }}>In Attesa Approvazione</option>
<option value="In Attesa Ricambi" {{ old('stato', $ticket->stato ?? '') === 'In Attesa Ricambi' ? 'selected' : '' }}>In Attesa Ricambi</option>
<option value="Risolto" {{ old('stato', $ticket->stato ?? '') === 'Risolto' ? 'selected' : '' }}>Risolto</option>
<option value="Chiuso" {{ old('stato', $ticket->stato ?? '') === 'Chiuso' ? 'selected' : '' }}>Chiuso</option>
<option value="Annullato" {{ old('stato', $ticket->stato ?? '') === 'Annullato' ? 'selected' : '' }}>Annullato</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('stato')" />
</div>
<!-- Priorità -->
<div>
<x-input-label for="priorita" :value="__('Priorità')" />
<select id="priorita" name="priorita" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="Bassa" {{ old('priorita', $ticket->priorita ?? 'Media') === 'Bassa' ? 'selected' : '' }}>Bassa</option>
<option value="Media" {{ old('priorita', $ticket->priorita ?? 'Media') === 'Media' ? 'selected' : '' }}>Media</option>
<option value="Alta" {{ old('priorita', $ticket->priorita ?? '') === 'Alta' ? 'selected' : '' }}>Alta</option>
<option value="Urgente" {{ old('priorita', $ticket->priorita ?? '') === 'Urgente' ? 'selected' : '' }}>Urgente</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('priorita')" />
</div>
</div>
</div>
<!-- Sezione Descrizione -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Descrizione</h4>
<div>
<x-input-label for="descrizione" :value="__('Descrizione')" />
<textarea id="descrizione" name="descrizione" rows="4"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">{{ old('descrizione', $ticket->descrizione ?? '') }}</textarea>
<x-input-error class="mt-2" :messages="$errors->get('descrizione')" />
</div>
<div class="mt-4">
<x-input-label for="luogo_intervento" :value="__('Luogo Intervento')" />
<x-text-input id="luogo_intervento" name="luogo_intervento" type="text" class="mt-1 block w-full"
:value="old('luogo_intervento', $ticket->luogo_intervento ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('luogo_intervento')" />
</div>
</div>
<!-- Sezione Assegnazione -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Assegnazione</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Assegnato a Utente -->
<div>
<x-input-label for="assegnato_a_user_id" :value="__('Assegnato a Utente')" />
<select id="assegnato_a_user_id" name="assegnato_a_user_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Nessun utente</option>
@foreach($users ?? [] as $user)
<option value="{{ $user->id }}" {{ old('assegnato_a_user_id', $ticket->assegnato_a_user_id ?? '') == $user->id ? 'selected' : '' }}>
{{ $user->name }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('assegnato_a_user_id')" />
</div>
<!-- Assegnato a Fornitore -->
<div>
<x-input-label for="assegnato_a_fornitore_id" :value="__('Assegnato a Fornitore')" />
<select id="assegnato_a_fornitore_id" name="assegnato_a_fornitore_id" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Nessun fornitore</option>
@foreach($fornitori ?? [] as $fornitore)
<option value="{{ $fornitore->id_fornitore }}" {{ old('assegnato_a_fornitore_id', $ticket->assegnato_a_fornitore_id ?? '') == $fornitore->id_fornitore ? 'selected' : '' }}>
{{ $fornitore->ragione_sociale }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('assegnato_a_fornitore_id')" />
</div>
</div>
</div>
<!-- Sezione Date -->
<div class="bg-purple-50 dark:bg-purple-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Date</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Data Scadenza Prevista -->
<div>
<x-input-label for="data_scadenza_prevista" :value="__('Data Scadenza Prevista')" />
<x-text-input id="data_scadenza_prevista" name="data_scadenza_prevista" type="date" class="mt-1 block w-full"
:value="old('data_scadenza_prevista', $ticket->data_scadenza_prevista?->format('Y-m-d') ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('data_scadenza_prevista')" />
</div>
<!-- Data Risoluzione Effettiva -->
<div>
<x-input-label for="data_risoluzione_effettiva" :value="__('Data Risoluzione Effettiva')" />
<x-text-input id="data_risoluzione_effettiva" name="data_risoluzione_effettiva" type="date" class="mt-1 block w-full"
:value="old('data_risoluzione_effettiva', $ticket->data_risoluzione_effettiva?->format('Y-m-d') ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('data_risoluzione_effettiva')" />
</div>
<!-- Data Chiusura Effettiva -->
<div>
<x-input-label for="data_chiusura_effettiva" :value="__('Data Chiusura Effettiva')" />
<x-text-input id="data_chiusura_effettiva" name="data_chiusura_effettiva" type="date" class="mt-1 block w-full"
:value="old('data_chiusura_effettiva', $ticket->data_chiusura_effettiva?->format('Y-m-d') ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('data_chiusura_effettiva')" />
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Nuovo Ticket') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Crea Nuovo Ticket</h3>
<a href="{{ route('admin.tickets.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.tickets.store') }}">
@csrf
@include('admin.tickets._form', ['ticket' => null])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.tickets.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Crea Ticket') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,50 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Modifica Ticket') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Modifica: {{ $ticket->titolo }}</h3>
<a href="{{ route('admin.tickets.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('admin.tickets.update', $ticket) }}">
@csrf
@method('PUT')
@include('admin.tickets._form', ['ticket' => $ticket])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.tickets.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Aggiorna Ticket') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,149 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Gestione Tickets') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Lista Tickets</h3>
<a href="{{ route('admin.tickets.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Ticket
</a>
</div>
<!-- Tabella Tickets -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Titolo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stabile
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Priorità
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Data Apertura
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($tickets as $ticket)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $ticket->id }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $ticket->titolo }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $ticket->stabile->denominazione ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->stato)
@case('Aperto')
bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100
@break
@case('Preso in Carico')
bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100
@break
@case('In Lavorazione')
bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100
@break
@case('Risolto')
bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@break
@case('Chiuso')
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@break
@default
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->stato }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->priorita)
@case('Urgente')
bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100
@break
@case('Alta')
bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100
@break
@case('Media')
bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100
@break
@case('Bassa')
bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@break
@default
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->priorita }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $ticket->data_apertura->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('admin.tickets.show', $ticket) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
<a href="{{ route('admin.tickets.edit', $ticket) }}"
class="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300">
Modifica
</a>
<form method="POST" action="{{ route('admin.tickets.destroy', $ticket) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo ticket?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
Elimina
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun ticket trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $tickets->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,173 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dettagli Ticket') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">{{ $ticket->titolo }}</h3>
<div class="space-x-2">
<a href="{{ route('admin.tickets.edit', $ticket) }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Modifica
</a>
<a href="{{ route('admin.tickets.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
</div>
<!-- Informazioni Principali -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Generali</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">ID Ticket:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->id }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Titolo:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->titolo }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Stabile:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->stabile->denominazione ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Categoria:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->categoriaTicket->nome ?? '-' }}</dd>
</div>
</dl>
</div>
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Stato e Priorità</h4>
<dl class="space-y-2">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Stato:</dt>
<dd class="text-sm">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->stato)
@case('Aperto')
bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100
@break
@case('Preso in Carico')
bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100
@break
@case('In Lavorazione')
bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100
@break
@case('Risolto')
bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@break
@case('Chiuso')
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@break
@default
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->stato }}
</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Priorità:</dt>
<dd class="text-sm">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->priorita)
@case('Urgente')
bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100
@break
@case('Alta')
bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100
@break
@case('Media')
bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100
@break
@case('Bassa')
bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@break
@default
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->priorita }}
</span>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Aperto da:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->apertoUser->name ?? '-' }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Assegnato a:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->assegnatoUser->name ?? $ticket->assegnatoFornitore->ragione_sociale ?? '-' }}</dd>
</div>
</dl>
</div>
</div>
<!-- Descrizione -->
@if($ticket->descrizione)
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Descrizione</h4>
<p class="text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap">{{ $ticket->descrizione }}</p>
</div>
@endif
<!-- Date e Scadenze -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg mb-6">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Date e Scadenze</h4>
<dl class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Data Apertura:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->data_apertura->format('d/m/Y H:i') }}</dd>
</div>
@if($ticket->data_scadenza_prevista)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Scadenza Prevista:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->data_scadenza_prevista->format('d/m/Y') }}</dd>
</div>
@endif
@if($ticket->data_risoluzione_effettiva)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Data Risoluzione:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->data_risoluzione_effettiva->format('d/m/Y') }}</dd>
</div>
@endif
@if($ticket->data_chiusura_effettiva)
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Data Chiusura:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->data_chiusura_effettiva->format('d/m/Y') }}</dd>
</div>
@endif
</dl>
</div>
<!-- Informazioni Sistema -->
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Informazioni Sistema</h4>
<dl class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Creato il:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->created_at->format('d/m/Y H:i') }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Ultimo aggiornamento:</dt>
<dd class="text-sm text-gray-900 dark:text-gray-100">{{ $ticket->updated_at->format('d/m/Y H:i') }}</dd>
</div>
</dl>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,105 @@
<div class="space-y-6">
<!-- Campo nascosto per stabile_id -->
<input type="hidden" name="stabile_id" value="{{ $stabile->id_stabile }}">
<!-- Sezione Identificazione -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Identificazione Unità</h4>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Interno -->
<div>
<x-input-label for="interno" :value="__('Interno')" />
<x-text-input id="interno" name="interno" type="text" class="mt-1 block w-full"
:value="old('interno', $unitaImmobiliare->interno ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('interno')" />
</div>
<!-- Scala -->
<div>
<x-input-label for="scala" :value="__('Scala')" />
<x-text-input id="scala" name="scala" type="text" class="mt-1 block w-full"
:value="old('scala', $unitaImmobiliare->scala ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('scala')" />
</div>
<!-- Piano -->
<div>
<x-input-label for="piano" :value="__('Piano')" />
<x-text-input id="piano" name="piano" type="text" class="mt-1 block w-full"
:value="old('piano', $unitaImmobiliare->piano ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('piano')" />
</div>
<!-- Fabbricato/Palazzina -->
<div>
<x-input-label for="fabbricato" :value="__('Fabbricato/Palazzina')" />
<x-text-input id="fabbricato" name="fabbricato" type="text" class="mt-1 block w-full"
:value="old('fabbricato', $unitaImmobiliare->fabbricato ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('fabbricato')" />
</div>
</div>
</div>
<!-- Sezione Dati Catastali -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Dati Catastali e Tecnici</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Categoria Catastale -->
<div>
<x-input-label for="categoria_catastale" :value="__('Categoria Catastale')" />
<x-text-input id="categoria_catastale" name="categoria_catastale" type="text" class="mt-1 block w-full"
:value="old('categoria_catastale', $unitaImmobiliare->categoria_catastale ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('categoria_catastale')" />
</div>
<!-- Millesimi Proprietà -->
<div>
<x-input-label for="millesimi_proprieta" :value="__('Millesimi Proprietà')" />
<x-text-input id="millesimi_proprieta" name="millesimi_proprieta" type="number" step="0.0001" class="mt-1 block w-full"
:value="old('millesimi_proprieta', $unitaImmobiliare->millesimi_proprieta ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('millesimi_proprieta')" />
</div>
<!-- Superficie -->
<div>
<x-input-label for="superficie" :value="__('Superficie (mq)')" />
<x-text-input id="superficie" name="superficie" type="number" step="0.01" class="mt-1 block w-full"
:value="old('superficie', $unitaImmobiliare->superficie ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('superficie')" />
</div>
<!-- Vani -->
<div>
<x-input-label for="vani" :value="__('Vani')" />
<x-text-input id="vani" name="vani" type="number" step="0.01" class="mt-1 block w-full"
:value="old('vani', $unitaImmobiliare->vani ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('vani')" />
</div>
</div>
</div>
<!-- Sezione Indirizzo -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Indirizzo Specifico</h4>
<div>
<x-input-label for="indirizzo" :value="__('Indirizzo (se diverso dallo stabile)')" />
<x-text-input id="indirizzo" name="indirizzo" type="text" class="mt-1 block w-full"
:value="old('indirizzo', $unitaImmobiliare->indirizzo ?? '')" />
<x-input-error class="mt-2" :messages="$errors->get('indirizzo')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Lascia vuoto se l'indirizzo è lo stesso dello stabile: {{ $stabile->indirizzo }}, {{ $stabile->citta }}
</p>
</div>
</div>
<!-- Sezione Note -->
<div class="bg-gray-50 dark:bg-gray-700 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Note</h4>
<div>
<x-input-label for="note" :value="__('Note')" />
<textarea id="note" name="note" rows="3"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">{{ old('note', $unitaImmobiliare->note ?? '') }}</textarea>
<x-input-error class="mt-2" :messages="$errors->get('note')" />
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Nuova Unità Immobiliare') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Crea Nuova Unità Immobiliare</h3>
<a href="{{ route('admin.stabili.show', $stabile) }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna allo Stabile
</a>
</div>
<form method="POST" action="{{ route('admin.stabili.unitaImmobiliari.store', $stabile) }}">
@csrf
@include('admin.unita_immobiliari._form', ['unitaImmobiliare' => null, 'stabile' => $stabile])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.stabili.show', $stabile) }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Crea Unità Immobiliare') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,50 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Modifica Unità Immobiliare') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Modifica Unità: {{ $unitaImmobiliare->interno ?? 'N/A' }}</h3>
<a href="{{ route('admin.stabili.show', $unitaImmobiliare->stabile) }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna allo Stabile
</a>
</div>
<form method="POST" action="{{ route('admin.unitaImmobiliari.update', $unitaImmobiliare) }}">
@csrf
@method('PUT')
@include('admin.unita_immobiliari._form', ['unitaImmobiliare' => $unitaImmobiliare, 'stabile' => $unitaImmobiliare->stabile])
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('admin.stabili.show', $unitaImmobiliare->stabile) }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Aggiorna Unità Immobiliare') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,262 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Dashboard Condomino') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Benvenuto -->
<div class="bg-gradient-to-r from-blue-500 to-purple-600 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-white">
<h3 class="text-2xl font-bold">Benvenuto, {{ Auth::user()->name }}!</h3>
<p class="mt-2">Gestisci le tue proprietà e rimani aggiornato su tutto quello che riguarda il tuo condominio.</p>
</div>
</div>
<!-- Statistiche Principali -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Le Mie Unità</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['unita_possedute'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Ticket Aperti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['ticket_aperti'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Rate Scadute</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['rate_scadute'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-6a1 1 0 00-1-1H9a1 1 0 00-1 1v6a1 1 0 01-1 1H4a1 1 0 110-2V4zm3 1h2v2H7V5zm2 4H7v2h2V9zm2-4h2v2h-2V5zm2 4h-2v2h2V9z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Documenti</dt>
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">{{ $stats['documenti_disponibili'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<a href="{{ route('condomino.tickets.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Ticket
</a>
<a href="{{ route('condomino.documenti.index') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Documenti
</a>
<a href="{{ route('condomino.unita.index') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Le Mie Unità
</a>
<a href="{{ route('condomino.pagamenti.index') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Pagamenti
</a>
</div>
</div>
</div>
<!-- Contenuto principale in due colonne -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- I Miei Ticket -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">I Miei Ticket</h3>
<a href="{{ route('condomino.tickets.index') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Vedi tutti
</a>
</div>
<div class="space-y-3">
@forelse($ticketRecenti as $ticket)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100">
<a href="{{ route('condomino.tickets.show', $ticket) }}" class="hover:text-blue-600">
{{ $ticket->titolo }}
</a>
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ $ticket->stabile->denominazione }}
</p>
</div>
<div class="flex flex-col items-end space-y-1">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
@switch($ticket->stato)
@case('Aperto') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@case('Preso in Carico') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@case('In Lavorazione') bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 @break
@default bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100
@endswitch">
{{ $ticket->stato }}
</span>
<span class="text-xs text-gray-500 dark:text-gray-400">
{{ $ticket->created_at->diffForHumans() }}
</span>
</div>
</div>
</div>
@empty
<p class="text-gray-500 dark:text-gray-400 text-center py-4">Nessun ticket aperto</p>
@endforelse
</div>
</div>
</div>
<!-- Ultimi Documenti -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Ultimi Documenti</h3>
<a href="{{ route('condomino.documenti.index') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Vedi tutti
</a>
</div>
<div class="space-y-3">
@forelse($ultimiDocumenti as $documento)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start">
<div class="flex-1">
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $documento->nome_file }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ $documento->tipo_documento }} - {{ $documento->documentable->denominazione ?? 'N/A' }}
</p>
</div>
<div class="flex flex-col items-end space-y-1">
<span class="text-xs text-gray-500 dark:text-gray-400">
{{ $documento->created_at->diffForHumans() }}
</span>
<a href="{{ route('condomino.documenti.download', $documento) }}" class="text-blue-600 hover:text-blue-900 text-xs">
Download
</a>
</div>
</div>
</div>
@empty
<p class="text-gray-500 dark:text-gray-400 text-center py-4">Nessun documento disponibile</p>
@endforelse
</div>
</div>
</div>
</div>
<!-- Le Mie Unità -->
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">Le Mie Unità Immobiliari</h3>
<a href="{{ route('condomino.unita.index') }}" class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Gestisci
</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@forelse($unitaImmobiliari as $unita)
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<h4 class="font-medium text-gray-900 dark:text-gray-100">
{{ $unita->stabile->denominazione }}
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
{{ $unita->identificazione_completa }}
</p>
<div class="mt-3 flex justify-between items-center">
<span class="text-xs text-gray-500 dark:text-gray-400">
Millesimi: {{ $unita->millesimi_proprieta ?? 'N/A' }}
</span>
<a href="{{ route('condomino.unita.show', $unita) }}"
class="text-blue-600 hover:text-blue-900 text-sm">
Dettagli
</a>
</div>
</div>
@empty
<div class="col-span-full text-center py-8">
<p class="text-gray-500 dark:text-gray-400">Nessuna unità immobiliare associata</p>
</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
<!-- Script per grafici (Chart.js) -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Implementeremo i grafici quando avremo i dati delle rate e pagamenti
</script>
</x-app-layout>

View File

@ -0,0 +1,141 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Nuovo Ticket') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Crea Nuovo Ticket</h3>
<a href="{{ route('condomino.tickets.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna ai Ticket
</a>
</div>
<form method="POST" action="{{ route('condomino.tickets.store') }}" enctype="multipart/form-data">
@csrf
<div class="space-y-6">
<!-- Sezione Informazioni Principali -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Informazioni Principali</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Titolo -->
<div class="md:col-span-2">
<x-input-label for="titolo" :value="__('Titolo')" />
<x-text-input id="titolo" name="titolo" type="text" class="mt-1 block w-full"
:value="old('titolo')" required placeholder="Descrivi brevemente il problema..." />
<x-input-error class="mt-2" :messages="$errors->get('titolo')" />
</div>
<!-- Unità Immobiliare -->
<div>
<x-input-label for="unita_immobiliare_id" :value="__('Unità Immobiliare')" />
<select id="unita_immobiliare_id" name="unita_immobiliare_id" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona unità</option>
@foreach($unitaImmobiliari as $unita)
<option value="{{ $unita->id_unita }}" {{ old('unita_immobiliare_id') == $unita->id_unita ? 'selected' : '' }}>
{{ $unita->stabile->denominazione }} - {{ $unita->identificazione_completa }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('unita_immobiliare_id')" />
</div>
<!-- Categoria -->
<div>
<x-input-label for="categoria_ticket_id" :value="__('Categoria')" />
<select id="categoria_ticket_id" name="categoria_ticket_id"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="">Seleziona categoria</option>
@foreach($categorieTicket as $categoria)
<option value="{{ $categoria->id }}" {{ old('categoria_ticket_id') == $categoria->id ? 'selected' : '' }}>
{{ $categoria->nome }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('categoria_ticket_id')" />
</div>
<!-- Priorità -->
<div>
<x-input-label for="priorita" :value="__('Priorità')" />
<select id="priorita" name="priorita" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
<option value="Bassa" {{ old('priorita', 'Media') === 'Bassa' ? 'selected' : '' }}>Bassa</option>
<option value="Media" {{ old('priorita', 'Media') === 'Media' ? 'selected' : '' }}>Media</option>
<option value="Alta" {{ old('priorita') === 'Alta' ? 'selected' : '' }}>Alta</option>
<option value="Urgente" {{ old('priorita') === 'Urgente' ? 'selected' : '' }}>Urgente</option>
</select>
<x-input-error class="mt-2" :messages="$errors->get('priorita')" />
</div>
<!-- Luogo Intervento -->
<div>
<x-input-label for="luogo_intervento" :value="__('Luogo Intervento')" />
<x-text-input id="luogo_intervento" name="luogo_intervento" type="text" class="mt-1 block w-full"
:value="old('luogo_intervento')" placeholder="Es. Bagno, Cucina, Parti comuni..." />
<x-input-error class="mt-2" :messages="$errors->get('luogo_intervento')" />
</div>
</div>
</div>
<!-- Sezione Descrizione -->
<div class="bg-green-50 dark:bg-green-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Descrizione Dettagliata</h4>
<div>
<x-input-label for="descrizione" :value="__('Descrizione')" />
<textarea id="descrizione" name="descrizione" rows="6" required
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm"
placeholder="Descrivi dettagliatamente il problema, quando si è verificato, eventuali danni...">{{ old('descrizione') }}</textarea>
<x-input-error class="mt-2" :messages="$errors->get('descrizione')" />
</div>
</div>
<!-- Sezione Allegati -->
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-6 rounded-lg">
<h4 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Allegati (Opzionale)</h4>
<div>
<x-input-label for="allegati" :value="__('Carica File')" />
<input type="file" id="allegati" name="allegati[]" multiple accept="image/*,.pdf,.doc,.docx"
class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" />
<x-input-error class="mt-2" :messages="$errors->get('allegati.*')" />
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Puoi caricare foto, documenti PDF, Word. Massimo 10MB per file.
</p>
</div>
</div>
</div>
<div class="flex items-center justify-end space-x-4 mt-6">
<x-secondary-button type="button" onclick="window.location='{{ route('condomino.tickets.index') }}'">
{{ __('Annulla') }}
</x-secondary-button>
<x-primary-button>
{{ __('Crea Ticket') }}
</x-primary-button>
</div>
</form>
@if ($errors->any())
<div class="mt-4 text-red-600 dark:text-red-400">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,110 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('I Miei Ticket') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 dark:text-gray-100">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-gray-800 dark:text-gray-200">I Miei Ticket</h3>
<a href="{{ route('condomino.tickets.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Ticket
</a>
</div>
<!-- Tabella Tickets -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Titolo
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stabile
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Stato
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Priorità
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Data Apertura
</th>
<th class="px-6 py-3 border-b border-gray-200 dark:border-gray-600 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@forelse($tickets as $ticket)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-gray-100">
{{ $ticket->titolo }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $ticket->stabile->denominazione ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->stato)
@case('Aperto') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@case('Preso in Carico') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@case('In Lavorazione') bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100 @break
@case('Risolto') bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100 @break
@case('Chiuso') bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100 @break
@default bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->stato }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($ticket->priorita)
@case('Urgente') bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 @break
@case('Alta') bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100 @break
@case('Media') bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100 @break
@case('Bassa') bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100 @break
@default bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-100
@endswitch">
{{ $ticket->priorita }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{{ $ticket->data_apertura->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="{{ route('condomino.tickets.show', $ticket) }}"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300">
Visualizza
</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
Nessun ticket trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $tickets->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,303 @@
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
@role('super-admin')
<!-- Super Admin Menu -->
<x-nav-link :href="route('superadmin.dashboard')" :active="request()->routeIs('superadmin.dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('superadmin.users.index')" :active="request()->routeIs('superadmin.users.*')">
{{ __('Utenti') }}
</x-nav-link>
<x-nav-link :href="route('superadmin.amministratori.index')" :active="request()->routeIs('superadmin.amministratori.*')">
{{ __('Amministratori') }}
</x-nav-link>
<x-nav-link :href="route('superadmin.categorie-ticket.index')" :active="request()->routeIs('superadmin.categorie-ticket.*')">
{{ __('Categorie Ticket') }}
</x-nav-link>
@endrole
@role('admin|amministratore')
<!-- Admin Menu -->
<x-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<!-- Dropdown Stabili -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="left" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ __('Stabili') }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('admin.stabili.index')">
{{ __('Elenco Stabili') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.stabili.create')">
{{ __('Nuovo Stabile') }}
</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<x-nav-link :href="route('admin.soggetti.index')" :active="request()->routeIs('admin.soggetti.*')">
{{ __('Soggetti') }}
</x-nav-link>
<!-- Dropdown Gestione -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="left" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ __('Gestione') }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('admin.tickets.index')">
{{ __('Ticket') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.fornitori.index')">
{{ __('Fornitori') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.documenti.index')">
{{ __('Documenti') }}
</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<!-- Dropdown Contabilità -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="left" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ __('Contabilità') }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('admin.contabilita.index')">
{{ __('Dashboard Contabilità') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.contabilita.registrazione')">
{{ __('Nuova Registrazione') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.contabilita.movimenti')">
{{ __('Movimenti') }}
</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<!-- Dropdown Impostazioni -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="left" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ __('Strumenti') }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('admin.rubrica.index')">
{{ __('Rubrica') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.impostazioni.index')">
{{ __('Impostazioni') }}
</x-dropdown-link>
<x-dropdown-link :href="route('admin.api-tokens.index')">
{{ __('API Tokens') }}
</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
@endrole
@role('condomino')
<!-- Condomino Menu -->
<x-nav-link :href="route('condomino.dashboard')" :active="request()->routeIs('condomino.dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
<x-nav-link :href="route('condomino.scadenze')" :active="request()->routeIs('condomino.scadenze')">
{{ __('Scadenze') }}
</x-nav-link>
<x-nav-link :href="route('condomino.documenti')" :active="request()->routeIs('condomino.documenti')">
{{ __('Documenti') }}
</x-nav-link>
<x-nav-link :href="route('condomino.guasti')" :active="request()->routeIs('condomino.guasti')">
{{ __('Segnalazioni') }}
</x-nav-link>
@endrole
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
@impersonating
<x-dropdown-link :href="route('impersonate.leave')">
{{ __('Torna al tuo account') }}
</x-dropdown-link>
@endImpersonating
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
</div>
<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
@role('super-admin')
<x-responsive-nav-link :href="route('superadmin.dashboard')" :active="request()->routeIs('superadmin.dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('superadmin.users.index')" :active="request()->routeIs('superadmin.users.*')">
{{ __('Utenti') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('superadmin.amministratori.index')" :active="request()->routeIs('superadmin.amministratori.*')">
{{ __('Amministratori') }}
</x-responsive-nav-link>
@endrole
@role('admin|amministratore')
<x-responsive-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.stabili.index')" :active="request()->routeIs('admin.stabili.*')">
{{ __('Stabili') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.soggetti.index')" :active="request()->routeIs('admin.soggetti.*')">
{{ __('Soggetti') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.tickets.index')" :active="request()->routeIs('admin.tickets.*')">
{{ __('Ticket') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.fornitori.index')" :active="request()->routeIs('admin.fornitori.*')">
{{ __('Fornitori') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.documenti.index')" :active="request()->routeIs('admin.documenti.*')">
{{ __('Documenti') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('admin.contabilita.index')" :active="request()->routeIs('admin.contabilita.*')">
{{ __('Contabilità') }}
</x-responsive-nav-link>
@endrole
@role('condomino')
<x-responsive-nav-link :href="route('condomino.dashboard')" :active="request()->routeIs('condomino.dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('condomino.scadenze')" :active="request()->routeIs('condomino.scadenze')">
{{ __('Scadenze') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('condomino.documenti')" :active="request()->routeIs('condomino.documenti')">
{{ __('Documenti') }}
</x-responsive-nav-link>
<x-responsive-nav-link :href="route('condomino.guasti')" :active="request()->routeIs('condomino.guasti')">
{{ __('Segnalazioni') }}
</x-responsive-nav-link>
@endrole
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
<div class="px-4">
<div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-responsive-nav-link>
@impersonating
<x-responsive-nav-link :href="route('impersonate.leave')">
{{ __('Torna al tuo account') }}
</x-responsive-nav-link>
@endImpersonating
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
</div>
</div>
</div>
</nav>

View File

@ -0,0 +1,206 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Crea Nuovo Amministratore</h2>
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.amministratori.store') }}" class="space-y-8">
@csrf
<!-- Sezione Dati Utente -->
<div class="bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Dati Utente (Account di accesso)</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome Utente -->
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Nome Utente</label>
<input type="text" name="name" id="name" value="{{ old('name') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('name') border-red-500 @enderror">
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Email Utente -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email Utente</label>
<input type="email" name="email" id="email" value="{{ old('email') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('email') border-red-500 @enderror">
@error('email')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<input type="password" name="password" id="password" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('password') border-red-500 @enderror">
@error('password')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Conferma Password -->
<div>
<label for="password_confirmation" class="block text-sm font-medium text-gray-700">Conferma Password</label>
<input type="password" name="password_confirmation" id="password_confirmation" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
</div>
<!-- Sezione Dati Amministratore -->
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Dati Personali Amministratore</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome -->
<div>
<label for="nome" class="block text-sm font-medium text-gray-700">Nome</label>
<input type="text" name="nome" id="nome" value="{{ old('nome') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('nome') border-red-500 @enderror">
@error('nome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Cognome -->
<div>
<label for="cognome" class="block text-sm font-medium text-gray-700">Cognome</label>
<input type="text" name="cognome" id="cognome" value="{{ old('cognome') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('cognome') border-red-500 @enderror">
@error('cognome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Sezione Dati Studio -->
<div class="bg-green-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Dati Studio Professionale</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Denominazione Studio -->
<div class="md:col-span-2">
<label for="denominazione_studio" class="block text-sm font-medium text-gray-700">Denominazione Studio</label>
<input type="text" name="denominazione_studio" id="denominazione_studio" value="{{ old('denominazione_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('denominazione_studio') border-red-500 @enderror">
@error('denominazione_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Partita IVA -->
<div>
<label for="partita_iva" class="block text-sm font-medium text-gray-700">Partita IVA</label>
<input type="text" name="partita_iva" id="partita_iva" value="{{ old('partita_iva') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('partita_iva') border-red-500 @enderror">
@error('partita_iva')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Codice Fiscale Studio -->
<div>
<label for="codice_fiscale_studio" class="block text-sm font-medium text-gray-700">Codice Fiscale Studio</label>
<input type="text" name="codice_fiscale_studio" id="codice_fiscale_studio" value="{{ old('codice_fiscale_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('codice_fiscale_studio') border-red-500 @enderror">
@error('codice_fiscale_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Indirizzo Studio -->
<div class="md:col-span-2">
<label for="indirizzo_studio" class="block text-sm font-medium text-gray-700">Indirizzo Studio</label>
<input type="text" name="indirizzo_studio" id="indirizzo_studio" value="{{ old('indirizzo_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('indirizzo_studio') border-red-500 @enderror">
@error('indirizzo_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- CAP Studio -->
<div>
<label for="cap_studio" class="block text-sm font-medium text-gray-700">CAP Studio</label>
<input type="text" name="cap_studio" id="cap_studio" value="{{ old('cap_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('cap_studio') border-red-500 @enderror">
@error('cap_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Città Studio -->
<div>
<label for="citta_studio" class="block text-sm font-medium text-gray-700">Città Studio</label>
<input type="text" name="citta_studio" id="citta_studio" value="{{ old('citta_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('citta_studio') border-red-500 @enderror">
@error('citta_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Provincia Studio -->
<div>
<label for="provincia_studio" class="block text-sm font-medium text-gray-700">Provincia Studio</label>
<input type="text" name="provincia_studio" id="provincia_studio" value="{{ old('provincia_studio') }}" maxlength="2"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('provincia_studio') border-red-500 @enderror">
@error('provincia_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Telefono Studio -->
<div>
<label for="telefono_studio" class="block text-sm font-medium text-gray-700">Telefono Studio</label>
<input type="text" name="telefono_studio" id="telefono_studio" value="{{ old('telefono_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('telefono_studio') border-red-500 @enderror">
@error('telefono_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Email Studio -->
<div>
<label for="email_studio" class="block text-sm font-medium text-gray-700">Email Studio</label>
<input type="email" name="email_studio" id="email_studio" value="{{ old('email_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('email_studio') border-red-500 @enderror">
@error('email_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- PEC Studio -->
<div>
<label for="pec_studio" class="block text-sm font-medium text-gray-700">PEC Studio</label>
<input type="email" name="pec_studio" id="pec_studio" value="{{ old('pec_studio') }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('pec_studio') border-red-500 @enderror">
@error('pec_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Crea Amministratore
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,195 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Modifica Amministratore: {{ $amministratore->nome }} {{ $amministratore->cognome }}</h2>
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.amministratori.update', $amministratore) }}" class="space-y-8">
@csrf
@method('PUT')
<!-- Sezione Utente Associato -->
<div class="bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Utente Associato</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Selezione Utente -->
<div class="md:col-span-2">
<label for="user_id" class="block text-sm font-medium text-gray-700">Utente Associato</label>
<select name="user_id" id="user_id" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('user_id') border-red-500 @enderror">
@foreach($usersWithoutAdminRole as $user)
<option value="{{ $user->id }}"
{{ old('user_id', $amministratore->user_id) == $user->id ? 'selected' : '' }}>
{{ $user->name }} ({{ $user->email }})
</option>
@endforeach
</select>
@error('user_id')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">Attualmente associato a: {{ $amministratore->user->name }} ({{ $amministratore->user->email }})</p>
</div>
</div>
</div>
<!-- Sezione Dati Amministratore -->
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Dati Personali Amministratore</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Nome -->
<div>
<label for="nome" class="block text-sm font-medium text-gray-700">Nome</label>
<input type="text" name="nome" id="nome" value="{{ old('nome', $amministratore->nome) }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('nome') border-red-500 @enderror">
@error('nome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Cognome -->
<div>
<label for="cognome" class="block text-sm font-medium text-gray-700">Cognome</label>
<input type="text" name="cognome" id="cognome" value="{{ old('cognome', $amministratore->cognome) }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('cognome') border-red-500 @enderror">
@error('cognome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Sezione Dati Studio -->
<div class="bg-green-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Dati Studio Professionale</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Denominazione Studio -->
<div class="md:col-span-2">
<label for="denominazione_studio" class="block text-sm font-medium text-gray-700">Denominazione Studio</label>
<input type="text" name="denominazione_studio" id="denominazione_studio" value="{{ old('denominazione_studio', $amministratore->denominazione_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('denominazione_studio') border-red-500 @enderror">
@error('denominazione_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Partita IVA -->
<div>
<label for="partita_iva" class="block text-sm font-medium text-gray-700">Partita IVA</label>
<input type="text" name="partita_iva" id="partita_iva" value="{{ old('partita_iva', $amministratore->partita_iva) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('partita_iva') border-red-500 @enderror">
@error('partita_iva')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Codice Fiscale Studio -->
<div>
<label for="codice_fiscale_studio" class="block text-sm font-medium text-gray-700">Codice Fiscale Studio</label>
<input type="text" name="codice_fiscale_studio" id="codice_fiscale_studio" value="{{ old('codice_fiscale_studio', $amministratore->codice_fiscale_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('codice_fiscale_studio') border-red-500 @enderror">
@error('codice_fiscale_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Indirizzo Studio -->
<div class="md:col-span-2">
<label for="indirizzo_studio" class="block text-sm font-medium text-gray-700">Indirizzo Studio</label>
<input type="text" name="indirizzo_studio" id="indirizzo_studio" value="{{ old('indirizzo_studio', $amministratore->indirizzo_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('indirizzo_studio') border-red-500 @enderror">
@error('indirizzo_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- CAP Studio -->
<div>
<label for="cap_studio" class="block text-sm font-medium text-gray-700">CAP Studio</label>
<input type="text" name="cap_studio" id="cap_studio" value="{{ old('cap_studio', $amministratore->cap_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('cap_studio') border-red-500 @enderror">
@error('cap_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Città Studio -->
<div>
<label for="citta_studio" class="block text-sm font-medium text-gray-700">Città Studio</label>
<input type="text" name="citta_studio" id="citta_studio" value="{{ old('citta_studio', $amministratore->citta_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('citta_studio') border-red-500 @enderror">
@error('citta_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Provincia Studio -->
<div>
<label for="provincia_studio" class="block text-sm font-medium text-gray-700">Provincia Studio</label>
<input type="text" name="provincia_studio" id="provincia_studio" value="{{ old('provincia_studio', $amministratore->provincia_studio) }}" maxlength="2"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('provincia_studio') border-red-500 @enderror">
@error('provincia_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Telefono Studio -->
<div>
<label for="telefono_studio" class="block text-sm font-medium text-gray-700">Telefono Studio</label>
<input type="text" name="telefono_studio" id="telefono_studio" value="{{ old('telefono_studio', $amministratore->telefono_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('telefono_studio') border-red-500 @enderror">
@error('telefono_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Email Studio -->
<div>
<label for="email_studio" class="block text-sm font-medium text-gray-700">Email Studio</label>
<input type="email" name="email_studio" id="email_studio" value="{{ old('email_studio', $amministratore->email_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('email_studio') border-red-500 @enderror">
@error('email_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- PEC Studio -->
<div>
<label for="pec_studio" class="block text-sm font-medium text-gray-700">PEC Studio</label>
<input type="email" name="pec_studio" id="pec_studio" value="{{ old('pec_studio', $amministratore->pec_studio) }}"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('pec_studio') border-red-500 @enderror">
@error('pec_studio')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Info aggiuntive -->
<div class="bg-gray-50 p-4 rounded-md">
<h3 class="text-sm font-medium text-gray-700 mb-2">Informazioni Record</h3>
<p class="text-sm text-gray-600">Creato il: {{ $amministratore->created_at->format('d/m/Y H:i') }}</p>
<p class="text-sm text-gray-600">Ultimo aggiornamento: {{ $amministratore->updated_at->format('d/m/Y H:i') }}</p>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Aggiorna Amministratore
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,95 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Gestione Amministratori</h2>
<a href="{{ route('superadmin.amministratori.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Amministratore
</a>
</div>
<!-- Tabella Amministratori -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Nome e Cognome
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Denominazione Studio
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Utente Associato
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Partita IVA
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email Studio
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($amministratori as $amministratore)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $amministratore->id_amministratore }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{{ $amministratore->nome }} {{ $amministratore->cognome }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $amministratore->denominazione_studio ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<div>
<div class="font-medium">{{ $amministratore->user->name }}</div>
<div class="text-gray-500">{{ $amministratore->user->email }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $amministratore->partita_iva ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $amministratore->email_studio ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('superadmin.amministratori.edit', $amministratore) }}"
class="text-indigo-600 hover:text-indigo-900">Modifica</a>
<form method="POST" action="{{ route('superadmin.amministratori.destroy', $amministratore) }}" class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo amministratore? Verrà eliminato anche l\'utente associato.')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900">Elimina</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
Nessun amministratore trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $amministratori->links() }}
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,82 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Crea Nuova Categoria Ticket</h2>
<a href="{{ route('superadmin.categorie-ticket.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.categorie-ticket.store') }}" class="space-y-6">
@csrf
<!-- Sezione Dati Categoria -->
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Informazioni Categoria</h3>
<div class="space-y-6">
<!-- Nome -->
<div>
<label for="nome" class="block text-sm font-medium text-gray-700">
Nome Categoria <span class="text-red-500">*</span>
</label>
<input type="text"
name="nome"
id="nome"
value="{{ old('nome') }}"
required
maxlength="255"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('nome') border-red-500 @enderror"
placeholder="Es. Manutenzione, Amministrativo, Tecnico...">
@error('nome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">Il nome deve essere unico nel sistema</p>
</div>
<!-- Descrizione -->
<div>
<label for="descrizione" class="block text-sm font-medium text-gray-700">
Descrizione
</label>
<textarea name="descrizione"
id="descrizione"
rows="4"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('descrizione') border-red-500 @enderror"
placeholder="Descrizione dettagliata della categoria (opzionale)">{{ old('descrizione') }}</textarea>
@error('descrizione')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">Fornisci una descrizione per aiutare gli utenti a comprendere quando utilizzare questa categoria</p>
</div>
</div>
</div>
<!-- Informazioni aggiuntive -->
<div class="bg-gray-50 p-4 rounded-md">
<h3 class="text-sm font-medium text-gray-700 mb-2">Informazioni</h3>
<ul class="text-sm text-gray-600 space-y-1">
<li> I campi contrassegnati con <span class="text-red-500">*</span> sono obbligatori</li>
<li> Il nome della categoria deve essere unico nel sistema</li>
<li> La descrizione aiuta gli utenti a scegliere la categoria corretta per i loro ticket</li>
</ul>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.categorie-ticket.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Crea Categoria
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,107 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Modifica Categoria: {{ $categoriaTicket->nome }}</h2>
<a href="{{ route('superadmin.categorie-ticket.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.categorie-ticket.update', $categoriaTicket) }}" class="space-y-6">
@csrf
@method('PUT')
<!-- Sezione Dati Categoria -->
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-medium text-gray-900 mb-4">Informazioni Categoria</h3>
<div class="space-y-6">
<!-- Nome -->
<div>
<label for="nome" class="block text-sm font-medium text-gray-700">
Nome Categoria <span class="text-red-500">*</span>
</label>
<input type="text"
name="nome"
id="nome"
value="{{ old('nome', $categoriaTicket->nome) }}"
required
maxlength="255"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('nome') border-red-500 @enderror"
placeholder="Es. Manutenzione, Amministrativo, Tecnico...">
@error('nome')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">Il nome deve essere unico nel sistema</p>
</div>
<!-- Descrizione -->
<div>
<label for="descrizione" class="block text-sm font-medium text-gray-700">
Descrizione
</label>
<textarea name="descrizione"
id="descrizione"
rows="4"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('descrizione') border-red-500 @enderror"
placeholder="Descrizione dettagliata della categoria (opzionale)">{{ old('descrizione', $categoriaTicket->descrizione) }}</textarea>
@error('descrizione')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
<p class="mt-1 text-sm text-gray-500">Fornisci una descrizione per aiutare gli utenti a comprendere quando utilizzare questa categoria</p>
</div>
</div>
</div>
<!-- Info aggiuntive -->
<div class="bg-gray-50 p-4 rounded-md">
<h3 class="text-sm font-medium text-gray-700 mb-2">Informazioni Record</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
<div>
<span class="font-medium">ID:</span> {{ $categoriaTicket->id }}
</div>
<div>
<span class="font-medium">Creato il:</span> {{ $categoriaTicket->created_at->format('d/m/Y H:i') }}
</div>
<div>
<span class="font-medium">Ultimo aggiornamento:</span> {{ $categoriaTicket->updated_at->format('d/m/Y H:i') }}
</div>
@if($categoriaTicket->tickets_count ?? 0 > 0)
<div>
<span class="font-medium">Ticket associati:</span>
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs">
{{ $categoriaTicket->tickets_count }}
</span>
</div>
@endif
</div>
</div>
<!-- Informazioni aggiuntive -->
<div class="bg-yellow-50 p-4 rounded-md">
<h3 class="text-sm font-medium text-yellow-800 mb-2">⚠️ Attenzione</h3>
<ul class="text-sm text-yellow-700 space-y-1">
<li> I campi contrassegnati con <span class="text-red-500">*</span> sono obbligatori</li>
<li> Il nome della categoria deve essere unico nel sistema</li>
<li> Le modifiche si applicheranno a tutti i ticket esistenti di questa categoria</li>
</ul>
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.categorie-ticket.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Aggiorna Categoria
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,87 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Gestione Categorie Ticket</h2>
<a href="{{ route('superadmin.categorie-ticket.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuova Categoria
</a>
</div>
<!-- Tabella Categorie Ticket -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Nome
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Descrizione
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Data Creazione
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($categorieTicket as $categoria)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $categoria->id }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{{ $categoria->nome }}
</td>
<td class="px-6 py-4 text-sm text-gray-900">
<div class="max-w-xs truncate" title="{{ $categoria->descrizione }}">
{{ $categoria->descrizione ?? '-' }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $categoria->created_at->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('superadmin.categorie-ticket.edit', $categoria) }}"
class="text-indigo-600 hover:text-indigo-900">
Modifica
</a>
<form method="POST" action="{{ route('superadmin.categorie-ticket.destroy', $categoria) }}"
class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questa categoria? Questa azione non può essere annullata.')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900">
Elimina
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
Nessuna categoria ticket trovata
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $categorieTicket->links() }}
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,139 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="space-y-6">
<!-- Header -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h2 class="text-3xl font-bold text-gray-800">Dashboard Super Admin</h2>
<p class="text-gray-600 mt-2">Benvenuto nel pannello di amministrazione del sistema</p>
</div>
</div>
<!-- Statistiche -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Totale Utenti -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Totale Utenti</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
<!-- Totale Amministratori -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3z"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Amministratori</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\Amministratore::count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
<!-- Utenti per Ruolo -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
</svg>
</div>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Super Admin</dt>
<dd class="text-lg font-medium text-gray-900">{{ \App\Models\User::role('super-admin')->count() }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Azioni Rapide -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">Azioni Rapide</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<a href="{{ route('superadmin.users.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Utente
</a>
<a href="{{ route('superadmin.amministratori.create') }}"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Nuovo Amministratore
</a>
<a href="{{ route('superadmin.users.index') }}"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Gestisci Utenti
</a>
<a href="{{ route('superadmin.amministratori.index') }}"
class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded text-center transition duration-200">
Gestisci Amministratori
</a>
</div>
</div>
</div>
<!-- Ultimi Utenti Creati -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">Ultimi Utenti Creati</h3>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nome</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ruolo</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data Creazione</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach(\App\Models\User::latest()->take(5)->get() as $user)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
@foreach($user->roles as $role)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
{{ $role->name }}
</span>
@endforeach
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->created_at->format('d/m/Y H:i') }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }} - Super Admin</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased bg-gray-100">
<div class="min-h-screen">
<!-- Navigation -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('superadmin.dashboard') }}" class="text-xl font-bold text-gray-800">
Super Admin Panel
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<a href="{{ route('superadmin.dashboard') }}"
class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('superadmin.dashboard') ? 'border-indigo-400 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }} text-sm font-medium">
Dashboard
</a>
<a href="{{ route('superadmin.users.index') }}"
class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('superadmin.users.*') ? 'border-indigo-400 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }} text-sm font-medium">
Utenti
</a>
<a href="{{ route('superadmin.amministratori.index') }}"
class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('superadmin.amministratori.*') ? 'border-indigo-400 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }} text-sm font-medium">
Amministratori
</a>
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<div class="ml-3 relative">
<div class="flex items-center space-x-4">
<span class="text-sm text-gray-700">{{ Auth::user()->name }}</span>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="text-sm text-gray-500 hover:text-gray-700">
Logout
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</nav>
<!-- Page Content -->
<main class="py-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<!-- Flash Messages -->
@if (session('success'))
<div class="mb-4 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{{ session('error') }}
</div>
@endif
@if (session('status'))
<div class="mb-4 bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded">
{{ session('status') }}
</div>
@endif
@yield('content')
</div>
</main>
</div>
</body>
</html>

View File

@ -0,0 +1,85 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Crea Nuovo Utente</h2>
<a href="{{ route('superadmin.users.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.users.store') }}" class="space-y-6">
@csrf
<!-- Nome -->
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Nome</label>
<input type="text" name="name" id="name" value="{{ old('name') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('name') border-red-500 @enderror">
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Email -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
<input type="email" name="email" id="email" value="{{ old('email') }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('email') border-red-500 @enderror">
@error('email')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<input type="password" name="password" id="password" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('password') border-red-500 @enderror">
@error('password')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Conferma Password -->
<div>
<label for="password_confirmation" class="block text-sm font-medium text-gray-700">Conferma Password</label>
<input type="password" name="password_confirmation" id="password_confirmation" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
</div>
<!-- Ruolo -->
<div>
<label for="role" class="block text-sm font-medium text-gray-700">Ruolo</label>
<select name="role" id="role" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('role') border-red-500 @enderror">
<option value="">Seleziona un ruolo</option>
@foreach($roles as $role)
<option value="{{ $role->name }}" {{ old('role') == $role->name ? 'selected' : '' }}>
{{ $role->name }}
</option>
@endforeach
</select>
@error('role')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.users.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Crea Utente
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,82 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Modifica Utente: {{ $user->name }}</h2>
<a href="{{ route('superadmin.users.index') }}"
class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Torna alla Lista
</a>
</div>
<form method="POST" action="{{ route('superadmin.users.update', $user) }}" class="space-y-6">
@csrf
@method('PUT')
<!-- Nome -->
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Nome</label>
<input type="text" name="name" id="name" value="{{ old('name', $user->name) }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('name') border-red-500 @enderror">
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Email -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
<input type="email" name="email" id="email" value="{{ old('email', $user->email) }}" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('email') border-red-500 @enderror">
@error('email')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Ruolo -->
<div>
<label for="role" class="block text-sm font-medium text-gray-700">Ruolo</label>
<select name="role" id="role" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 @error('role') border-red-500 @enderror">
<option value="">Seleziona un ruolo</option>
@foreach($roles as $role)
<option value="{{ $role->name }}"
{{ old('role', $user->roles->first()?->name) == $role->name ? 'selected' : '' }}>
{{ $role->name }}
</option>
@endforeach
</select>
@error('role')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Info aggiuntive -->
<div class="bg-gray-50 p-4 rounded-md">
<h3 class="text-sm font-medium text-gray-700 mb-2">Informazioni Account</h3>
<p class="text-sm text-gray-600">Creato il: {{ $user->created_at->format('d/m/Y H:i') }}</p>
<p class="text-sm text-gray-600">Ultimo aggiornamento: {{ $user->updated_at->format('d/m/Y H:i') }}</p>
@if($user->email_verified_at)
<p class="text-sm text-green-600">Email verificata il: {{ $user->email_verified_at->format('d/m/Y H:i') }}</p>
@else
<p class="text-sm text-red-600">Email non verificata</p>
@endif
</div>
<!-- Pulsanti -->
<div class="flex items-center justify-end space-x-4">
<a href="{{ route('superadmin.users.index') }}"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">
Annulla
</a>
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Aggiorna Utente
</button>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,109 @@
@extends('superadmin.layouts.app')
@section('content')
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Gestione Utenti</h2>
<a href="{{ route('superadmin.users.create') }}"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Nuovo Utente
</a>
</div>
<!-- Tabella Utenti -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ID
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Nome
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ruoli
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Data Creazione
</th>
<th class="px-6 py-3 border-b border-gray-200 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Azioni
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($users as $user)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $user->id }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{{ $user->name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $user->email }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
@foreach($user->roles as $role)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
{{ $role->name }}
</span>
@endforeach
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{ $user->created_at->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="{{ route('superadmin.users.edit', $user) }}"
class="text-indigo-600 hover:text-indigo-900">Modifica</a>
<!-- Form per cambiare ruolo -->
<form method="POST" action="{{ route('superadmin.users.updateRole', $user) }}" class="inline">
@csrf
@method('PATCH')
<select name="role" onchange="this.form.submit()" class="text-sm border-gray-300 rounded">
<option value="">Cambia Ruolo</option>
@foreach(\Spatie\Permission\Models\Role::all() as $role)
<option value="{{ $role->name }}" {{ $user->hasRole($role->name) ? 'selected' : '' }}>
{{ $role->name }}
</option>
@endforeach
</select>
</form>
@if($user->id !== auth()->id())
<a href="{{ route('superadmin.users.impersonate', $user) }}"
class="text-green-600 hover:text-green-900">Impersona</a>
<form method="POST" action="{{ route('superadmin.users.destroy', $user) }}" class="inline"
onsubmit="return confirm('Sei sicuro di voler eliminare questo utente?')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900">Elimina</button>
</form>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-4 text-center text-gray-500">
Nessun utente trovato
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginazione -->
<div class="mt-6">
{{ $users->links() }}
</div>
</div>
</div>
@endsection

181
routes/web.php Normal file
View File

@ -0,0 +1,181 @@
<?php
use App\Http\Controllers\Admin\DashboardController;
use App\Http\Controllers\SuperAdmin\UserController as SuperAdminUserController;
use App\Http\Controllers\SuperAdmin\CategoriaTicketController;
use App\Http\Controllers\SuperAdmin\AmministratoreController as SuperAdminAmministratoreController;
use App\Http\Controllers\Admin\StabileController;
use App\Http\Controllers\Admin\SoggettoController;
use App\Http\Controllers\Admin\UnitaImmobiliareController;
use App\Http\Controllers\Admin\FornitoreController;
use App\Http\Controllers\Admin\TicketController;
use App\Http\Controllers\Admin\ContabilitaController;
use App\Http\Controllers\Admin\DocumentoController;
use App\Http\Controllers\Admin\PreventivoController;
use App\Http\Controllers\Admin\BilancioController;
use App\Http\Controllers\Condomino\DashboardController as CondominoDashboardController;
use App\Http\Controllers\Condomino\TicketController as CondominoTicketController;
use App\Http\Controllers\Condomino\DocumentoController as CondominoDocumentoController;
use App\Http\Controllers\Condomino\UnitaController as CondominoUnitaController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\Admin\ImpostazioniController;
use App\Http\Controllers\Admin\ApiTokenController;
use App\Http\Controllers\Admin\RubricaController;
// --- Public Routes ---
Route::get('/', function () { return view('welcome'); });
// --- Authenticated Routes ---
Route::middleware(['auth', 'verified'])->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 "<h1>Diagnostica Permessi per: " . $user->name . "</h1>";
echo "<h2>Ruoli Assegnati:</h2>";
echo "<ul>";
foreach ($user->getRoleNames() as $role) {
echo "<li>" . $role . "</li>";
}
echo "</ul>";
});
});
// --- PUBLIC ROUTE TO LEAVE IMPERSONATION ---
Route::get('impersonate/leave', [\Lab404\Impersonate\Controllers\ImpersonateController::class, 'leave'])->name('impersonate.leave');
// --- AUTHENTICATION ROUTES ---
require __DIR__.'/auth.php';