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

1507 lines
43 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6. SISTEMA MULTI-RUOLO - GUIDA COMPLETA
## 📋 **INDICE CAPITOLO**
- [6.1 Architettura Ruoli e Permessi](#61-architettura-ruoli-e-permessi)
- [6.2 Configurazione Spatie Permission](#62-configurazione-spatie-permission)
- [6.3 Modelli e Relazioni](#63-modelli-e-relazioni)
- [6.4 Seeder Ruoli e Permessi](#64-seeder-ruoli-e-permessi)
- [6.5 Middleware di Protezione](#65-middleware-di-protezione)
- [6.6 Controllers per Ruoli](#66-controllers-per-ruoli)
- [6.7 Dashboard Condizionali](#67-dashboard-condizionali)
- [6.8 Gestione Utenti](#68-gestione-utenti)
- [6.9 Esempi Pratici](#69-esempi-pratici)
---
## 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
```bash
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
```
### Configurazione
**File**: `config/permission.php`
```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`
```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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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`
```blade
<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
<?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
```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
```php
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
<?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**
```php
// ❌ SBAGLIATO
@extends('modules.layouts.base')
// ✅ CORRETTO
@extends('admin.layouts.netgescon')
```
2. **Database Columns Inesistenti**
```php
// ❌ 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**
```php
// ❌ 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
```php
@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
```php
@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
```
### <20> **CONTROLLER STANDARD**
```php
<?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
```env
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
```sql
-- ✅ 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
```php
// ===== 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:
```php
<!-- 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>
```
### <20>📝 **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:
```bash
# 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**