netgescon-day0/docs/06-SISTEMA-MULTI-RUOLO.md

43 KiB
Executable File
Raw Blame History

6. SISTEMA MULTI-RUOLO - GUIDA COMPLETA

📋 INDICE CAPITOLO


6.1 Architettura Ruoli e Permessi

Gerarchia Ruoli NetGescon

SuperAdmin
├── Accesso completo sistema
├── Gestione utenti e ruoli
├── Configurazione globale
├── Archivi base (comuni)
└── Monitoring e logs

Admin
├── Gestione completa stabili assegnati
├── CRUD completo tutti i dati
├── Contabilità e documenti
├── Comunicazioni condomini
└── Backup e reports

Amministratore
├── Gestione stabili assegnati
├── CRUD limitato (no delete critici)
├── Contabilità base
├── Documenti e comunicazioni
└── Reports stabili

Condomino
├── Visualizzazione dati personali
├── Documenti unità di proprietà
├── Comunicazioni con amministratore
├── Consultazione movimenti
└── Modifica profilo personale

Matrice Permessi Dettagliata

FUNZIONALITÀ           | SuperAdmin | Admin | Amministratore | Condomino
-----------------------|------------|-------|----------------|----------
Gestione Utenti        |     ✅     |   ❌   |       ❌       |    ❌
Comuni Italiani        |     ✅     |   ❌   |       ❌       |    ❌
Stabili (CRUD)         |     ✅     |   ✅   |       ✅       |    ❌
Unità Immobiliari      |     ✅     |   ✅   |       ✅       |    👁️
Soggetti (CRUD)        |     ✅     |   ✅   |       ✅       |    ❌
Documenti (Upload)     |     ✅     |   ✅   |       ✅       |    ❌
Documenti (Download)   |     ✅     |   ✅   |       ✅       |    ✅
Contabilità (CRUD)     |     ✅     |   ✅   |       ⚠️       |    👁️
Tickets (Gestione)     |     ✅     |   ✅   |       ✅       |    📝
Backup Sistema         |     ✅     |   ✅   |       ❌       |    ❌
Reports Avanzati       |     ✅     |   ✅   |       ⚠️       |    ❌

Legenda: = Completo, ⚠️ = Limitato, 👁️ = Solo visualizzazione, 📝 = Creazione/Risposta, = Negato


6.2 Configurazione Spatie Permission

Installazione Package

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate

Configurazione

File: config/permission.php

<?php

return [
    'models' => [
        'permission' => Spatie\Permission\Models\Permission::class,
        'role' => Spatie\Permission\Models\Role::class,
    ],
    
    'table_names' => [
        'roles' => 'roles',
        'permissions' => 'permissions',
        'model_has_permissions' => 'model_has_permissions',
        'model_has_roles' => 'model_has_roles',
        'role_has_permissions' => 'role_has_permissions',
    ],
    
    'column_names' => [
        'role_pivot_key' => null,
        'permission_pivot_key' => null,
        'model_morph_key' => 'model_id',
        'team_foreign_key' => 'team_id',
    ],
    
    'register_permission_check_method' => true,
    'teams' => false,
    'use_passport_client_credentials' => false,
    'display_permission_in_exception' => false,
    'display_role_in_exception' => false,
    'enable_wildcard_permission' => false,
    'cache' => [
        'expiration_time' => \DateInterval::createFromDateString('24 hours'),
        'key' => 'spatie.permission.cache',
        'store' => 'default',
    ],
];

Registrazione Middleware

File: app/Http/Kernel.php

protected $routeMiddleware = [
    // ...existing middleware...
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
];

6.3 Modelli e Relazioni

Modello User Esteso

File: app/Models/User.php

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasApiTokens, HasRoles, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
        'is_active',
        'last_login_at',
        'profile_photo_path',
        'phone',
        'address',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'last_login_at' => 'datetime',
        'is_active' => 'boolean',
    ];

    // Relazioni
    public function stabiliAmministrati()
    {
        return $this->hasMany(Stabile::class, 'amministratore_id');
    }

    public function unitaImmobiliari()
    {
        return $this->belongsToMany(UnitaImmobiliare::class, 'diritti_reali')
                    ->withPivot('tipo_diritto', 'quota_proprieta', 'data_inizio', 'data_fine')
                    ->withTimestamps();
    }

    public function tickets()
    {
        return $this->hasMany(Ticket::class);
    }

    // Helper methods
    public function isSuperAdmin(): bool
    {
        return $this->hasRole('super-admin');
    }

    public function isAdmin(): bool
    {
        return $this->hasRole('admin');
    }

    public function isAmministratore(): bool
    {
        return $this->hasRole('amministratore');
    }

    public function isCondomino(): bool
    {
        return $this->hasRole('condomino');
    }

    public function canManageStabile(Stabile $stabile): bool
    {
        if ($this->isSuperAdmin()) {
            return true;
        }

        if ($this->isAdmin() || $this->isAmministratore()) {
            return $this->stabiliAmministrati()->where('id', $stabile->id)->exists();
        }

        return false;
    }

    public function canAccessUnita(UnitaImmobiliare $unita): bool
    {
        if ($this->isSuperAdmin()) {
            return true;
        }

        if ($this->isAdmin() || $this->isAmministratore()) {
            return $this->canManageStabile($unita->stabile);
        }

        if ($this->isCondomino()) {
            return $this->unitaImmobiliari()->where('id', $unita->id)->exists();
        }

        return false;
    }

    public function getDisplayRoleName(): string
    {
        $roleNames = [
            'super-admin' => 'Super Amministratore',
            'admin' => 'Amministratore',
            'amministratore' => 'Amministratore Condominio',
            'condomino' => 'Condomino',
        ];

        $role = $this->getRoleNames()->first();
        return $roleNames[$role] ?? 'Utente';
    }

    public function getRoleColor(): string
    {
        $roleColors = [
            'super-admin' => 'danger',
            'admin' => 'primary',
            'amministratore' => 'warning',
            'condomino' => 'success',
        ];

        $role = $this->getRoleNames()->first();
        return $roleColors[$role] ?? 'secondary';
    }
}

Modello Stabile con Controlli Accesso

File: app/Models/Stabile.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Stabile extends Model
{
    protected $fillable = [
        'denominazione',
        'indirizzo',
        'comune_id',
        'cap',
        'codice_fiscale',
        'partita_iva',
        'amministratore_id',
        'is_active',
        'note',
    ];

    protected $casts = [
        'is_active' => 'boolean',
    ];

    // Relazioni
    public function amministratore(): BelongsTo
    {
        return $this->belongsTo(User::class, 'amministratore_id');
    }

    public function comune(): BelongsTo
    {
        return $this->belongsTo(ComuneItaliano::class, 'comune_id');
    }

    public function unitaImmobiliari(): HasMany
    {
        return $this->hasMany(UnitaImmobiliare::class);
    }

    public function documenti(): HasMany
    {
        return $this->hasMany(Documento::class);
    }

    public function tickets(): HasMany
    {
        return $this->hasMany(Ticket::class);
    }

    public function movimentiBancari(): HasMany
    {
        return $this->hasMany(MovimentoBancario::class);
    }

    // Scope per filtri di accesso
    public function scopeAccessibleBy($query, User $user)
    {
        if ($user->isSuperAdmin()) {
            return $query;
        }

        if ($user->isAdmin() || $user->isAmministratore()) {
            return $query->where('amministratore_id', $user->id);
        }

        if ($user->isCondomino()) {
            return $query->whereHas('unitaImmobiliari.soggetti', function($q) use ($user) {
                $q->where('users.id', $user->id);
            });
        }

        return $query->whereRaw('1 = 0'); // Nessun accesso
    }

    // Helper methods
    public function getIndirizzoCompleto(): string
    {
        $indirizzo = $this->indirizzo;
        
        if ($this->comune) {
            $indirizzo .= ', ' . $this->comune->denominazione;
        }
        
        if ($this->cap) {
            $indirizzo .= ' (' . $this->cap . ')';
        }
        
        return $indirizzo;
    }

    public function getTotalUnita(): int
    {
        return $this->unitaImmobiliari()->count();
    }

    public function getTotalMillesimi(): float
    {
        return $this->unitaImmobiliari()->sum('millesimi_proprieta');
    }
}

6.4 Seeder Ruoli e Permessi

Seeder Completo

File: database/seeders/RolesAndPermissionsSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RolesAndPermissionsSeeder extends Seeder
{
    public function run(): void
    {
        // Reset cached roles and permissions
        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

        // Definizione permessi organizzati per area
        $permissions = [
            // Gestione Utenti
            'users.view',
            'users.create',
            'users.edit',
            'users.delete',
            'users.assign-roles',
            'users.toggle-status',

            // Gestione Stabili
            'stabili.view',
            'stabili.create',
            'stabili.edit',
            'stabili.delete',
            'stabili.assign-admin',

            // Gestione Unità Immobiliari
            'unita.view',
            'unita.create',
            'unita.edit',
            'unita.delete',
            'unita.assign-soggetti',

            // Gestione Soggetti
            'soggetti.view',
            'soggetti.create',
            'soggetti.edit',
            'soggetti.delete',
            'soggetti.merge',

            // Gestione Documenti
            'documenti.view',
            'documenti.upload',
            'documenti.download',
            'documenti.delete',
            'documenti.share',

            // Gestione Contabilità
            'contabilita.view',
            'contabilita.create',
            'contabilita.edit',
            'contabilita.delete',
            'contabilita.reports',
            'contabilita.export',

            // Gestione Tickets
            'tickets.view',
            'tickets.create',
            'tickets.edit',
            'tickets.delete',
            'tickets.assign',
            'tickets.close',

            // Configurazione Sistema
            'system.config',
            'system.backup',
            'system.logs',
            'system.maintenance',

            // Archivi Base
            'comuni.view',
            'comuni.edit',
            'comuni.import',
        ];

        // Crea tutti i permessi
        foreach ($permissions as $permission) {
            Permission::create(['name' => $permission]);
        }

        // Creazione ruoli
        $superAdmin = Role::create(['name' => 'super-admin']);
        $admin = Role::create(['name' => 'admin']);
        $amministratore = Role::create(['name' => 'amministratore']);
        $condomino = Role::create(['name' => 'condomino']);

        // SuperAdmin: tutti i permessi
        $superAdmin->givePermissionTo(Permission::all());

        // Admin: tutti tranne configurazione sistema
        $admin->givePermissionTo([
            'stabili.view', 'stabili.create', 'stabili.edit', 'stabili.delete',
            'unita.view', 'unita.create', 'unita.edit', 'unita.delete', 'unita.assign-soggetti',
            'soggetti.view', 'soggetti.create', 'soggetti.edit', 'soggetti.delete', 'soggetti.merge',
            'documenti.view', 'documenti.upload', 'documenti.download', 'documenti.delete', 'documenti.share',
            'contabilita.view', 'contabilita.create', 'contabilita.edit', 'contabilita.delete', 'contabilita.reports', 'contabilita.export',
            'tickets.view', 'tickets.create', 'tickets.edit', 'tickets.delete', 'tickets.assign', 'tickets.close',
            'system.backup', 'system.logs',
        ]);

        // Amministratore: gestione limitata
        $amministratore->givePermissionTo([
            'stabili.view', 'stabili.edit',
            'unita.view', 'unita.create', 'unita.edit', 'unita.assign-soggetti',
            'soggetti.view', 'soggetti.create', 'soggetti.edit',
            'documenti.view', 'documenti.upload', 'documenti.download', 'documenti.share',
            'contabilita.view', 'contabilita.create', 'contabilita.edit', 'contabilita.reports',
            'tickets.view', 'tickets.create', 'tickets.edit', 'tickets.assign', 'tickets.close',
        ]);

        // Condomino: solo visualizzazione e interazione limitata
        $condomino->givePermissionTo([
            'unita.view',
            'documenti.view', 'documenti.download',
            'contabilita.view',
            'tickets.view', 'tickets.create',
        ]);
    }
}

Seeder Utenti Base

File: database/seeders/UserSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class UserSeeder extends Seeder
{
    public function run(): void
    {
        // SuperAdmin
        $superAdmin = User::create([
            'name' => 'Super Amministratore',
            'email' => 'superadmin@netgescon.it',
            'password' => Hash::make('password'),
            'is_active' => true,
            'email_verified_at' => now(),
        ]);
        $superAdmin->assignRole('super-admin');

        // Admin
        $admin = User::create([
            'name' => 'Amministratore Principale',
            'email' => 'admin@netgescon.it',
            'password' => Hash::make('password'),
            'is_active' => true,
            'email_verified_at' => now(),
        ]);
        $admin->assignRole('admin');

        // Amministratore Condominio
        $amministratore = User::create([
            'name' => 'Mario Rossi',
            'email' => 'mario.rossi@netgescon.it',
            'password' => Hash::make('password'),
            'is_active' => true,
            'email_verified_at' => now(),
        ]);
        $amministratore->assignRole('amministratore');

        // Condomino
        $condomino = User::create([
            'name' => 'Luigi Bianchi',
            'email' => 'luigi.bianchi@netgescon.it',
            'password' => Hash::make('password'),
            'is_active' => true,
            'email_verified_at' => now(),
        ]);
        $condomino->assignRole('condomino');
    }
}

6.5 Middleware di Protezione

Middleware Personalizzato

File: app/Http/Middleware/RoleAccessMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class RoleAccessMiddleware
{
    public function handle(Request $request, Closure $next, ...$roles)
    {
        if (!Auth::check()) {
            return redirect()->route('login');
        }

        $user = Auth::user();

        // Verifica se utente è attivo
        if (!$user->is_active) {
            Auth::logout();
            return redirect()->route('login')
                ->with('error', 'Account disattivato. Contatta l\'amministratore.');
        }

        // Verifica ruoli
        if (!$user->hasAnyRole($roles)) {
            abort(403, 'Accesso non autorizzato per questo ruolo.');
        }

        // Aggiorna ultimo login
        $user->update(['last_login_at' => now()]);

        return $next($request);
    }
}

Middleware Permessi Specifici

File: app/Http/Middleware/PermissionMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class PermissionMiddleware
{
    public function handle(Request $request, Closure $next, $permission)
    {
        if (!Auth::check()) {
            return redirect()->route('login');
        }

        $user = Auth::user();

        if (!$user->can($permission)) {
            if ($request->ajax()) {
                return response()->json([
                    'error' => 'Permesso negato',
                    'message' => "Non hai i permessi per: {$permission}"
                ], 403);
            }

            abort(403, "Permesso negato: {$permission}");
        }

        return $next($request);
    }
}

6.6 Controllers per Ruoli

Controller SuperAdmin

File: app/Http/Controllers/SuperAdmin/SuperAdminDashboardController.php

<?php

namespace App\Http\Controllers\SuperAdmin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Stabile;
use App\Models\ComuneItaliano;
use App\Models\Ticket;

class SuperAdminDashboardController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth', 'role:super-admin']);
    }

    public function index()
    {
        $stats = [
            'users_total' => User::count(),
            'users_active' => User::where('is_active', true)->count(),
            'stabili_total' => Stabile::count(),
            'stabili_active' => Stabile::where('is_active', true)->count(),
            'comuni_loaded' => ComuneItaliano::count(),
            'tickets_open' => Ticket::where('stato', 'aperto')->count(),
            'users_by_role' => User::join('model_has_roles', 'users.id', '=', 'model_has_roles.model_id')
                ->join('roles', 'model_has_roles.role_id', '=', 'roles.id')
                ->selectRaw('roles.name, COUNT(*) as count')
                ->groupBy('roles.name')
                ->pluck('count', 'name')
                ->toArray(),
        ];

        return view('superadmin.dashboard', [
            'pageTitle' => 'Dashboard Super Amministratore',
            'stats' => $stats,
        ]);
    }
}

Controller Admin

File: app/Http/Controllers/Admin/AdminDashboardController.php

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Stabile;
use App\Models\UnitaImmobiliare;
use App\Models\Documento;
use App\Models\Ticket;
use App\Models\MovimentoBancario;
use Illuminate\Support\Facades\Auth;

class AdminDashboardController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth', 'role:admin|amministratore']);
    }

    public function index()
    {
        $user = Auth::user();
        $stabiliIds = $user->stabiliAmministrati()->pluck('id');

        $stats = [
            'stabili_count' => $stabiliIds->count(),
            'stabili_attivi' => $user->stabiliAmministrati()->where('is_active', true)->count(),
            'unita_total' => UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)->count(),
            'documenti_count' => Documento::whereIn('stabile_id', $stabiliIds)->count(),
            'documenti_mese' => Documento::whereIn('stabile_id', $stabiliIds)
                ->whereMonth('created_at', now()->month)
                ->count(),
            'tickets_aperti' => Ticket::whereIn('stabile_id', $stabiliIds)
                ->where('stato', 'aperto')->count(),
            'tickets_totali' => Ticket::whereIn('stabile_id', $stabiliIds)->count(),
            'saldo_totale' => MovimentoBancario::whereIn('stabile_id', $stabiliIds)
                ->sum('importo'),
            'condomini_count' => UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)
                ->has('soggetti')->count(),
            'proprietari_count' => UnitaImmobiliare::whereIn('stabile_id', $stabiliIds)
                ->whereHas('soggetti', function($q) {
                    $q->where('tipo_diritto', 'proprietario');
                })->count(),
            'movimenti_count' => MovimentoBancario::whereIn('stabile_id', $stabiliIds)->count(),
        ];

        return view('admin.dashboard', [
            'pageTitle' => 'Dashboard Amministrazione',
            'stats' => $stats,
            'user' => $user,
        ]);
    }
}

Controller Condomino

File: app/Http/Controllers/Condomino/CondominoDashboardController.php

<?php

namespace App\Http\Controllers\Condomino;

use App\Http\Controllers\Controller;
use App\Models\UnitaImmobiliare;
use App\Models\Documento;
use App\Models\Ticket;
use App\Models\MovimentoBancario;
use Illuminate\Support\Facades\Auth;

class CondominoDashboardController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth', 'role:condomino']);
    }

    public function index()
    {
        $user = Auth::user();
        $unitaIds = $user->unitaImmobiliari()->pluck('unita_immobiliari.id');
        $stabiliIds = $user->unitaImmobiliari()->pluck('stabile_id');

        $stats = [
            'unita_count' => $unitaIds->count(),
            'documenti_count' => Documento::whereIn('stabile_id', $stabiliIds)->count(),
            'tickets_aperti' => Ticket::where('user_id', $user->id)
                ->where('stato', 'aperto')->count(),
            'tickets_totali' => Ticket::where('user_id', $user->id)->count(),
            'movimenti_mese' => MovimentoBancario::whereIn('stabile_id', $stabiliIds)
                ->whereMonth('data_movimento', now()->month)
                ->count(),
        ];

        $unita = $user->unitaImmobiliari()
            ->with(['stabile', 'stabile.comune'])
            ->get();

        return view('condomino.dashboard', [
            'pageTitle' => 'Area Condomino',
            'stats' => $stats,
            'unita' => $unita,
            'user' => $user,
        ]);
    }
}

6.7 Dashboard Condizionali

Router Principale

File: app/Http/Controllers/DashboardController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class DashboardController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index()
    {
        $user = Auth::user();

        // Redirect in base al ruolo
        if ($user->hasRole('super-admin')) {
            return redirect()->route('superadmin.dashboard');
        }

        if ($user->hasAnyRole(['admin', 'amministratore'])) {
            return redirect()->route('admin.dashboard');
        }

        if ($user->hasRole('condomino')) {
            return redirect()->route('condomino.dashboard');
        }

        // Fallback per utenti senza ruolo
        return view('dashboard.no-role', [
            'pageTitle' => 'Accesso Negato',
            'message' => 'Nessun ruolo assegnato. Contatta l\'amministratore.',
        ]);
    }
}

6.8 Gestione Utenti

Form Assegnazione Ruoli

File: resources/views/superadmin/users/edit-roles.blade.php

<form action="{{ route('superadmin.users.update-roles', $user) }}" method="POST">
    @csrf
    @method('PUT')
    
    <div class="card">
        <div class="card-header">
            <h5>Gestione Ruoli: {{ $user->name }}</h5>
        </div>
        <div class="card-body">
            <div class="row">
                @foreach($roles as $role)
                    <div class="col-md-6 mb-3">
                        <div class="form-check">
                            <input class="form-check-input" type="checkbox" 
                                   name="roles[]" value="{{ $role->name }}" 
                                   id="role_{{ $role->id }}"
                                   {{ $user->hasRole($role->name) ? 'checked' : '' }}>
                            <label class="form-check-label" for="role_{{ $role->id }}">
                                <strong>{{ $role->name }}</strong>
                                <small class="text-muted d-block">
                                    {{ $roleDescriptions[$role->name] ?? '' }}
                                </small>
                            </label>
                        </div>
                    </div>
                @endforeach
            </div>
        </div>
        <div class="card-footer">
            <button type="submit" class="btn btn-primary">
                <i class="fas fa-save me-2"></i>Salva Ruoli
            </button>
            <a href="{{ route('superadmin.users.index') }}" class="btn btn-secondary">
                <i class="fas fa-times me-2"></i>Annulla
            </a>
        </div>
    </div>
</form>

Controller Gestione Ruoli

File: app/Http/Controllers/SuperAdmin/UserRoleController.php

<?php

namespace App\Http\Controllers\SuperAdmin;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;

class UserRoleController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth', 'role:super-admin']);
    }

    public function editRoles(User $user)
    {
        $roles = Role::all();
        $roleDescriptions = [
            'super-admin' => 'Accesso completo al sistema',
            'admin' => 'Gestione completa stabili assegnati',
            'amministratore' => 'Gestione limitata stabili assegnati',
            'condomino' => 'Visualizzazione dati personali',
        ];

        return view('superadmin.users.edit-roles', compact('user', 'roles', 'roleDescriptions'));
    }

    public function updateRoles(Request $request, User $user)
    {
        $request->validate([
            'roles' => 'array',
            'roles.*' => 'exists:roles,name',
        ]);

        $user->syncRoles($request->get('roles', []));

        return redirect()->route('superadmin.users.index')
            ->with('success', "Ruoli aggiornati per {$user->name}");
    }
}

6.9 Esempi Pratici

Verifica Permessi in Blade

@can('stabili.create')
    <a href="{{ route('admin.stabili.create') }}" class="btn btn-primary">
        <i class="fas fa-plus me-2"></i>Nuovo Stabile
    </a>
@endcan

@role('super-admin')
    <div class="alert alert-info">
        <i class="fas fa-crown me-2"></i>
        Modalità Super Amministratore attiva
    </div>
@endrole

@hasrole('admin|amministratore')
    <div class="card">
        <div class="card-header">Pannello Gestione</div>
        <!-- Contenuto per admin -->
    </div>
@endhasrole

Controllo Accesso in Controller

public function show(Stabile $stabile)
{
    $user = Auth::user();
    
    // Verifica accesso al singolo stabile
    if (!$user->canManageStabile($stabile)) {
        abort(403, 'Non puoi accedere a questo stabile');
    }
    
    return view('admin.stabili.show', compact('stabile'));
}

Policy per Modelli

File: app/Policies/StabilePolicy.php

<?php

namespace App\Policies;

use App\Models\Stabile;
use App\Models\User;

class StabilePolicy
{
    public function viewAny(User $user): bool
    {
        return $user->can('stabili.view');
    }

    public function view(User $user, Stabile $stabile): bool
    {
        return $user->can('stabili.view') && $user->canManageStabile($stabile);
    }

    public function create(User $user): bool
    {
        return $user->can('stabili.create');
    }

    public function update(User $user, Stabile $stabile): bool
    {
        return $user->can('stabili.edit') && $user->canManageStabile($stabile);
    }

    public function delete(User $user, Stabile $stabile): bool
    {
        return $user->can('stabili.delete') && $user->canManageStabile($stabile);
    }
}


6.10 CREAZIONE MODULI NETGESCON - GUIDA COMPLETA 🚀

📋 PROBLEMI COMUNI E SOLUZIONI DEFINITIVE

🔥 ERRORI CRITICI DA EVITARE

  1. Layout NON NetGescon

    // ❌ SBAGLIATO
    @extends('modules.layouts.base')
    
    // ✅ CORRETTO
    @extends('admin.layouts.netgescon')
    
  2. Database Columns Inesistenti

    // ❌ SBAGLIATO - Campo 'codice' non esiste
    $piano_conti = DB::table('piano_conti')->select('codice', 'descrizione')->get();
    
    // ✅ CORRETTO - Campi reali della tabella
    $piano_conti = DB::table('piano_conti')->select('mastro', 'conto', 'sottoconto', 'descrizione')->get();
    
  3. Route Conflicts

    // ❌ SBAGLIATO - Conflitti con routes esistenti
    Route::resource('contabilita', ContabilitaController::class);
    
    // ✅ CORRETTO - Prefix specifico per moduli
    Route::prefix('admin')->name('admin.')->group(function () {
        Route::resource('contabilita', ContabilitaController::class)->names([
            'index' => 'contabilita.index',
            'create' => 'contabilita.create',
            'store' => 'contabilita.store',
            'show' => 'contabilita.show',
            'edit' => 'contabilita.edit',
            'update' => 'contabilita.update',
            'destroy' => 'contabilita.destroy'
        ]);
    });
    

🏗️ STRUTTURA MODULO STANDARD

app/Modules/[NomeModulo]/
├── Controllers/
│   └── [NomeModulo]Controller.php
├── Models/
│   └── [NomeModulo].php
├── Routes/
│   └── web.php
└── Views/
    ├── index.blade.php
    ├── create.blade.php
    ├── edit.blade.php
    └── show.blade.php

resources/views/modules/[nomemodulo]/
├── index.blade.php
├── create.blade.php
├── edit.blade.php
└── show.blade.php

🎨 TEMPLATE BASE NETGESCON

Index.blade.php Standard

@extends('admin.layouts.netgescon')

@section('title', '[Nome Modulo]')

@section('content')
<div class="content-wrapper">
    <div class="content-header">
        <div class="container-fluid">
            <div class="row mb-2">
                <div class="col-sm-6">
                    <h1 class="m-0">[Nome Modulo]</h1>
                </div>
                <div class="col-sm-6">
                    <ol class="breadcrumb float-sm-right">
                        <li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Home</a></li>
                        <li class="breadcrumb-item active">[Nome Modulo]</li>
                    </ol>
                </div>
            </div>
        </div>
    </div>

    <section class="content">
        <div class="container-fluid">
            <div class="row">
                <div class="col-12">
                    <div class="card">
                        <div class="card-header">
                            <h3 class="card-title">Elenco [Nome Modulo]</h3>
                            <div class="card-tools">
                                <a href="{{ route('admin.[nomemodulo].create') }}" class="btn btn-primary btn-sm">
                                    <i class="fas fa-plus"></i> Nuovo
                                </a>
                            </div>
                        </div>
                        <div class="card-body">
                            <!-- Contenuto tabella/lista -->
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
</div>
@endsection

Create.blade.php Standard

@extends('admin.layouts.netgescon')

@section('title', 'Nuovo [Nome Modulo]')

@section('content')
<div class="content-wrapper">
    <div class="content-header">
        <div class="container-fluid">
            <div class="row mb-2">
                <div class="col-sm-6">
                    <h1 class="m-0">Nuovo [Nome Modulo]</h1>
                </div>
                <div class="col-sm-6">
                    <ol class="breadcrumb float-sm-right">
                        <li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Home</a></li>
                        <li class="breadcrumb-item"><a href="{{ route('admin.[nomemodulo].index') }}">[Nome Modulo]</a></li>
                        <li class="breadcrumb-item active">Nuovo</li>
                    </ol>
                </div>
            </div>
        </div>
    </div>

    <section class="content">
        <div class="container-fluid">
            <div class="row">
                <div class="col-12">
                    <div class="card">
                        <div class="card-header">
                            <h3 class="card-title">Nuovo [Nome Modulo]</h3>
                        </div>
                        <form action="{{ route('admin.[nomemodulo].store') }}" method="POST">
                            @csrf
                            <div class="card-body">
                                <!-- Form fields -->
                            </div>
                            <div class="card-footer">
                                <button type="submit" class="btn btn-primary">
                                    <i class="fas fa-save"></i> Salva
                                </button>
                                <a href="{{ route('admin.[nomemodulo].index') }}" class="btn btn-secondary">
                                    <i class="fas fa-arrow-left"></i> Annulla
                                </a>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </section>
</div>
@endsection

<EFBFBD> CONTROLLER STANDARD

<?php

namespace App\Modules\[NomeModulo]\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class [NomeModulo]Controller extends Controller
{
    public function index()
    {
        // Verifica credenziali database corrette
        // netgescon_user / NetGescon2024!
        
        $items = DB::table('[nome_tabella]')->get();
        
        return view('modules.[nomemodulo].index', compact('items'));
    }

    public function create()
    {
        // Carica dati necessari per il form
        // ATTENZIONE: Usa SEMPRE i nomi colonne reali del DB
        
        return view('modules.[nomemodulo].create');
    }

    public function store(Request $request)
    {
        $request->validate([
            // Validazioni
        ]);

        DB::table('[nome_tabella]')->insert([
            // Campi corretti del database
            'created_at' => now(),
            'updated_at' => now()
        ]);

        return redirect()->route('admin.[nomemodulo].index')
                        ->with('success', '[Nome Modulo] creato con successo!');
    }

    public function show($id)
    {
        $item = DB::table('[nome_tabella]')->where('id', $id)->first();
        
        if (!$item) {
            abort(404);
        }
        
        return view('modules.[nomemodulo].show', compact('item'));
    }

    public function edit($id)
    {
        $item = DB::table('[nome_tabella]')->where('id', $id)->first();
        
        if (!$item) {
            abort(404);
        }
        
        return view('modules.[nomemodulo].edit', compact('item'));
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            // Validazioni
        ]);

        DB::table('[nome_tabella]')
            ->where('id', $id)
            ->update([
                // Campi da aggiornare
                'updated_at' => now()
            ]);

        return redirect()->route('admin.[nomemodulo].index')
                        ->with('success', '[Nome Modulo] aggiornato con successo!');
    }

    public function destroy($id)
    {
        DB::table('[nome_tabella]')->where('id', $id)->delete();
        
        return redirect()->route('admin.[nomemodulo].index')
                        ->with('success', '[Nome Modulo] eliminato con successo!');
    }
}

🗄️ STRUTTURA DATABASE STANDARD

Credenziali Database NetGescon

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=netgescon
DB_USERNAME=netgescon_user
DB_PASSWORD=NetGescon2024!

Convenzioni Naming Database

-- ✅ CORRETTO - Nomi italiani senza accenti
CREATE TABLE piano_conti (
    id INT PRIMARY KEY AUTO_INCREMENT,
    mastro VARCHAR(10) NOT NULL,
    conto VARCHAR(10) NOT NULL,
    sottoconto VARCHAR(10) NOT NULL,
    descrizione VARCHAR(255) NOT NULL,
    tipo ENUM('DARE', 'AVERE') NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Esempio tabella contabilita
CREATE TABLE registrazioni_contabili (
    id INT PRIMARY KEY AUTO_INCREMENT,
    data_registrazione DATE NOT NULL,
    numero_documento VARCHAR(50),
    descrizione TEXT NOT NULL,
    totale_dare DECIMAL(10,2) DEFAULT 0,
    totale_avere DECIMAL(10,2) DEFAULT 0,
    stabile_id INT NOT NULL,
    gestione_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (stabile_id) REFERENCES stabili(id),
    FOREIGN KEY (gestione_id) REFERENCES gestioni(id)
);

-- Tabella movimenti contabili
CREATE TABLE movimenti_contabili (
    id INT PRIMARY KEY AUTO_INCREMENT,
    registrazione_id INT NOT NULL,
    piano_conti_id INT NOT NULL,
    dare DECIMAL(10,2) DEFAULT 0,
    avere DECIMAL(10,2) DEFAULT 0,
    descrizione VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (registrazione_id) REFERENCES registrazioni_contabili(id) ON DELETE CASCADE,
    FOREIGN KEY (piano_conti_id) REFERENCES piano_conti(id)
);

🔧 ROUTES CONFIGURATION

File: routes/web.php - Sezione Moduli

// ===== MODULI AMMINISTRATIVI =====
Route::prefix('admin')->name('admin.')->middleware(['auth', 'role:Admin|SuperAdmin'])->group(function () {
    
    // Contabilità Module
    Route::resource('contabilita', App\Modules\Contabilita\Controllers\ContabilitaController::class)->names([
        'index' => 'contabilita.index',
        'create' => 'contabilita.create',
        'store' => 'contabilita.store',
        'show' => 'contabilita.show',
        'edit' => 'contabilita.edit',
        'update' => 'contabilita.update',
        'destroy' => 'contabilita.destroy'
    ]);
    
    // Altri moduli...
    
});

🎨 SUB-MENU STYLE COME STABILI

Per implementare il sub-menu come nella pagina stabili, aggiungi questa sezione dopo il content-header:

<!-- Sub-menu tabs come nella pagina stabili -->
<div class="content-header-tabs">
    <div class="container-fluid">
        <div class="row">
            <div class="col-12">
                <ul class="nav nav-tabs" id="content-tabs" role="tablist">
                    <li class="nav-item">
                        <a class="nav-link active" href="{{ route('admin.[modulo].index') }}">
                            <i class="fas fa-list"></i> Elenco
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('admin.[modulo].create') }}">
                            <i class="fas fa-plus"></i> Nuovo
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('admin.[modulo].reports') }}">
                            <i class="fas fa-chart-bar"></i> Reports
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('admin.[modulo].config') }}">
                            <i class="fas fa-cog"></i> Configurazione
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</div>

<EFBFBD>📝 CHECKLIST MODULO COMPLETO

Pre-Sviluppo

  • Verificare struttura database esistente
  • Controllare credenziali database (netgescon_user/NetGescon2024!)
  • Verificare nomi tabelle e colonne reali
  • Pianificare routes senza conflitti

Durante Sviluppo

  • Controller con namespace corretto
  • Routes con prefix 'admin' e name 'admin.'
  • Views che estendono 'admin.layouts.netgescon'
  • Query database con colonne esistenti
  • Validazioni form corrette
  • Breadcrumb navigation funzionante

Post-Sviluppo

  • Test tutte le routes (index, create, store, show, edit, update, destroy)
  • Verifica layout NetGescon caricato correttamente
  • Test permessi e autorizzazioni
  • Validazione form funzionante
  • Test CRUD completo
  • Verifica responsive design

🚨 PROBLEMI RISOLTI - CASO CONTABILITÀ

  1. Layout Wrong: Cambiato da modules.layouts.base a admin.layouts.netgescon
  2. Database Columns: Corretto codice inesistente con mastro, conto, sottoconto
  3. Route Conflicts: Aggiunto prefix admin e namespace specifico
  4. Credentials: Usato netgescon_user/NetGescon2024! invece di default
  5. Display Format: Piano conti ora mostra mastro.conto.sottoconto - descrizione

🎯 TEMPLATE RAPIDO NUOVO MODULO

Usa questo comando per creare rapidamente la struttura:

# Crea directories
mkdir -p app/Modules/[NomeModulo]/Controllers
mkdir -p app/Modules/[NomeModulo]/Models
mkdir -p resources/views/modules/[nomemodulo]

# Copia template files dal modulo Contabilità funzionante
cp -r app/Modules/Contabilita/* app/Modules/[NomeModulo]/
cp -r resources/views/modules/contabilita/* resources/views/modules/[nomemodulo]/

# Sostituisci tutti i riferimenti
find app/Modules/[NomeModulo] -type f -name "*.php" -exec sed -i 's/Contabilita/[NomeModulo]/g' {} \;
find resources/views/modules/[nomemodulo] -type f -name "*.php" -exec sed -i 's/contabilita/[nomemodulo]/g' {} \;

📝 COMPLETATO: Capitolo 6 - Sistema Multi-Ruolo + Creazione Moduli

Questo capitolo fornisce una guida completa per:

  • Configurare architettura ruoli e permessi
  • Implementare Spatie Permission
  • Creare modelli con controlli accesso
  • Configurare seeder ruoli e permessi
  • Implementare middleware protezione
  • Creare controllers per ogni ruolo
  • Gestire dashboard condizionali
  • Amministrare utenti e ruoli
  • Esempi pratici implementazione
  • GUIDA COMPLETA CREAZIONE MODULI NETGESCON
  • RISOLUZIONE ERRORI COMUNI
  • TEMPLATE STANDARD E CHECKLIST

🔄 Prossimo capitolo: Database e Strutture Avanzate