hasAnyRole(['super-admin', 'admin', 'amministratore']) || $user->can('manage-users') ); } public function mount(): void { $this->mountInteractsWithTable(); } protected function getTableQuery(): Builder { $actor = Auth::user(); if (! $actor instanceof User) { return User::query()->whereRaw('1 = 0'); } // Super-admin/admin: tutto. if ($actor->hasAnyRole(['super-admin', 'admin'])) { return User::query(); } // Amministratore: solo utenti subordinati (tenant) + sé stesso. $amministratoreId = $actor->amministratore?->id; if (! $amministratoreId || ! Schema::hasColumn('users', 'amministratore_id')) { return User::query()->whereKey($actor->id); } return User::query() ->where(function (Builder $q) use ($actor, $amministratoreId) { $q->where('amministratore_id', $amministratoreId) ->orWhere('id', (int) $actor->id); }); } /** * @return array */ protected function getAssignableStabiliOptions(User $actor): array { if (! Schema::hasTable('stabili')) { return []; } $cols = ['id', 'codice_stabile', 'cod_stabile', 'denominazione']; if (Schema::hasColumn('stabili', 'codice_operatore')) { $cols[] = 'codice_operatore'; } $q = Stabile::query(); if (method_exists($q->getModel(), 'scopeAttivi')) { $q->attivi(); } if (! $actor->hasAnyRole(['super-admin', 'admin'])) { $allowedIds = StabileContext::accessibleStabili($actor) ->pluck('id') ->map(fn($v) => (int) $v) ->values() ->all(); if (empty($allowedIds)) { return []; } $q->whereIn('id', $allowedIds); } return $q ->orderBy('codice_stabile') ->get($cols) ->mapWithKeys(function (Stabile $s): array { $primary = trim((string) ($s->codice_operatore ?: $s->codice_stabile)); $mnemo = trim((string) ($s->cod_stabile ?? '')); $code = ($mnemo !== '' && $primary !== '' && $mnemo !== $primary) ? ($mnemo . ' · ' . $primary) : ($primary !== '' ? $primary : ($mnemo !== '' ? $mnemo : ('#' . (string) $s->id))); return [(int) $s->id => $code . ' — ' . (string) ($s->denominazione ?? '')]; }) ->all(); } /** * @return array */ protected function getAssignableRoles(User $actor): array { $roles = Role::query()->orderBy('name')->pluck('name', 'name')->all(); if ($actor->hasRole('super-admin')) { return $roles; } if ($actor->hasRole('admin')) { unset($roles['super-admin']); return $roles; } // Amministratore: ruoli subordinati. $allowed = ['collaboratore', 'impiegato', 'fornitore']; return array_intersect_key($roles, array_flip($allowed)); } /** * @return array */ protected function getAssignablePermissions(User $actor): array { $all = Permission::query()->orderBy('name')->pluck('name', 'name')->all(); if ($actor->hasAnyRole(['super-admin', 'admin'])) { return $this->labelPermissions($all); } // Amministratore: può delegare solo permessi che possiede. $mine = array_flip($actor->getAllPermissions()->pluck('name')->all()); $filtered = array_intersect_key($all, $mine); return $this->labelPermissions($filtered); } /** * @param array $perms * @return array */ protected function labelPermissions(array $perms): array { $out = []; foreach ($perms as $name) { $out[$name] = $this->permissionLabel($name); } asort($out); return $out; } protected function permissionLabel(string $name): string { // Mappa “a gruppi” via prefisso. if (str_starts_with($name, 'contabilita.')) { return 'Contabilità · ' . str_replace(['contabilita.', '-'], ['', ' '], $name); } if (str_starts_with($name, 'gescon-import.')) { return 'Import · ' . str_replace('gescon-import.', '', $name); } if (str_starts_with($name, 'manage-')) { return 'Gestione · ' . str_replace('manage-', '', $name); } if (str_starts_with($name, 'view-')) { return 'Visibilità · ' . str_replace('view-', '', $name); } return $name; } public function table(Table $table): Table { $actor = Auth::user(); if (! $actor instanceof User) { $actor = new User(); } $assignableRoles = $this->getAssignableRoles($actor); $assignablePerms = $this->getAssignablePermissions($actor); $assignableStabili = $this->getAssignableStabiliOptions($actor); $tbl = $table ->striped() ->defaultSort('name') ->filters([ SelectFilter::make('role') ->label('Ruolo') ->options(fn(): array => Role::query()->orderBy('name')->pluck('name', 'name')->all()) ->query(function (Builder $query, array $data): Builder { $role = $data['value'] ?? null; if (! is_string($role) || $role === '') { return $query; } return $query->role($role); }), ]) ->columns([ TextColumn::make('name') ->label('Nome') ->searchable() ->sortable(), TextColumn::make('email') ->label('Email') ->searchable() ->sortable(), TextColumn::make('roles') ->label('Ruoli') ->getStateUsing(fn(User $record): string => $record->getRoleNames()->implode(', ')) ->toggleable(), TextColumn::make('amministratoreOwner.denominazione_studio') ->label('Studio') ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('createdBy.name') ->label('Creato da') ->toggleable(isToggledHiddenByDefault: true), ]); $tbl ->headerActions([ Action::make('create_user') ->label('Nuovo utente') ->icon('heroicon-o-plus') ->form([ TextInput::make('name')->label('Nome')->required()->maxLength(255), TextInput::make('email')->label('Email')->required()->email()->maxLength(255), TextInput::make('password') ->label('Password') ->password() ->required() ->minLength(8), DateTimePicker::make('expires_at') ->label('Scadenza accesso') ->seconds(false) ->helperText('Opzionale: se impostata, l’utente scade dopo questa data.'), Select::make('role') ->label('Gruppo / ruolo') ->options($assignableRoles) ->required(), CheckboxList::make('extra_permissions') ->label('Autorizzazioni extra (fuori gruppo)') ->options($assignablePerms) ->columns(2) ->searchable(), Select::make('stabile_ids') ->label('Stabili visibili') ->multiple() ->options($assignableStabili) ->searchable() ->preload() ->visible(fn() => Schema::hasTable('collaboratore_stabile')) ->helperText('Assegna qui gli stabili che il collaboratore potrà vedere. Se vuoto, non vedrà nessuno stabile.'), Select::make('amministratore_id') ->label('Amministratore (proprietario)') ->options(fn() => Amministratore::query() ->orderBy('denominazione_studio') ->get(['id', 'denominazione_studio', 'codice_amministratore', 'codice_univoco']) ->mapWithKeys(fn(Amministratore $a) => [ (string) $a->id => ( (string) (trim((string) ($a->denominazione_studio ?? '')) !== '' ? $a->denominazione_studio : (trim((string) ($a->codice_amministratore ?? '')) !== '' ? ('Amministratore ' . (string) $a->codice_amministratore) : (trim((string) ($a->codice_univoco ?? '')) !== '' ? ('Amministratore ' . (string) $a->codice_univoco) : ('Amministratore #' . (int) $a->id) ) ) ) ), ]) ->all()) ->visible(fn() => $actor->hasAnyRole(['super-admin', 'admin']) && Schema::hasColumn('users', 'amministratore_id')) ->helperText('Serve per legare l’utente allo studio dell’amministratore.'), ]) ->action(function (array $data) use ($actor, $assignableRoles, $assignablePerms): void { $role = (string) ($data['role'] ?? ''); if (! array_key_exists($role, $assignableRoles)) { return; } $extra = $data['extra_permissions'] ?? []; $extra = is_array($extra) ? array_values(array_unique(array_filter($extra, 'is_string'))) : []; $extra = array_values(array_intersect($extra, array_keys($assignablePerms))); $amministratoreId = null; if (Schema::hasColumn('users', 'amministratore_id')) { if ($actor->hasAnyRole(['super-admin', 'admin'])) { $amministratoreId = isset($data['amministratore_id']) && is_numeric($data['amministratore_id']) ? (int) $data['amministratore_id'] : null; } else { $amministratoreId = $actor->amministratore?->id; } } $user = User::create([ 'name' => (string) ($data['name'] ?? ''), 'email' => (string) ($data['email'] ?? ''), 'password' => (string) ($data['password'] ?? ''), 'expires_at' => $data['expires_at'] ?? null, 'amministratore_id' => $amministratoreId, 'created_by_user_id' => $actor->id ?: null, ]); $user->syncRoles([$role]); $user->syncPermissions($extra); if (Schema::hasTable('collaboratore_stabile') && method_exists($user, 'stabiliAssegnati')) { $requested = $data['stabile_ids'] ?? []; $requested = is_array($requested) ? $requested : []; $ids = []; foreach ($requested as $v) { if (is_numeric($v)) { $ids[] = (int) $v; } } $ids = array_values(array_unique($ids)); // Scope: non permettere di assegnare stabili fuori lista consentita. $allowed = $actor->hasAnyRole(['super-admin', 'admin']) ? null : StabileContext::accessibleStabili($actor)->pluck('id')->map(fn($v) => (int) $v)->all(); if (is_array($allowed)) { $ids = array_values(array_intersect($ids, $allowed)); } $user->stabiliAssegnati()->sync($ids); } }), ]) ->actions([ Action::make('edit_user') ->label('Modifica') ->icon('heroicon-o-pencil-square') ->visible(fn(User $record): bool => $actor->hasAnyRole(['super-admin', 'admin']) || (int) $record->id !== (int) $actor->id) ->form([ TextInput::make('name')->label('Nome')->required()->maxLength(255), TextInput::make('email')->label('Email')->required()->email()->maxLength(255), TextInput::make('password') ->label('Nuova password') ->password() ->minLength(8) ->helperText('Lascia vuoto per non modificare.'), DateTimePicker::make('expires_at')->label('Scadenza accesso')->seconds(false), Select::make('role') ->label('Gruppo / ruolo') ->options($assignableRoles) ->required(), CheckboxList::make('extra_permissions') ->label('Autorizzazioni extra (fuori gruppo)') ->options($assignablePerms) ->columns(2) ->searchable(), Select::make('stabile_ids') ->label('Stabili visibili') ->multiple() ->options($assignableStabili) ->searchable() ->preload() ->visible(fn() => Schema::hasTable('collaboratore_stabile')), Select::make('amministratore_id') ->label('Amministratore (proprietario)') ->options(fn() => Amministratore::query() ->orderBy('denominazione_studio') ->get(['id', 'denominazione_studio', 'codice_amministratore', 'codice_univoco']) ->mapWithKeys(fn(Amministratore $a) => [ (string) $a->id => ( (string) (trim((string) ($a->denominazione_studio ?? '')) !== '' ? $a->denominazione_studio : (trim((string) ($a->codice_amministratore ?? '')) !== '' ? ('Amministratore ' . (string) $a->codice_amministratore) : (trim((string) ($a->codice_univoco ?? '')) !== '' ? ('Amministratore ' . (string) $a->codice_univoco) : ('Amministratore #' . (int) $a->id) ) ) ) ), ]) ->all()) ->visible(fn() => $actor->hasAnyRole(['super-admin', 'admin']) && Schema::hasColumn('users', 'amministratore_id')), ]) ->fillForm(function (User $record): array { return [ 'name' => $record->name, 'email' => $record->email, 'expires_at' => $record->expires_at, 'role' => $record->getRoleNames()->first(), 'extra_permissions' => $record->getDirectPermissions()->pluck('name')->all(), 'amministratore_id' => $record->amministratore_id ?? null, 'stabile_ids' => method_exists($record, 'stabiliAssegnati') ? $record->stabiliAssegnati()->pluck('stabili.id')->map(fn($v) => (int) $v)->all() : [], ]; }) ->action(function (User $record, array $data) use ($actor, $assignableRoles, $assignablePerms): void { // Enforce scope: non super-admin/admin non può toccare utenti fuori tenant. if (! $actor->hasAnyRole(['super-admin', 'admin'])) { $amministratoreId = $actor->amministratore?->id; if (! $amministratoreId || (int) ($record->amministratore_id ?? 0) !== (int) $amministratoreId) { return; } } $role = (string) ($data['role'] ?? ''); if (! array_key_exists($role, $assignableRoles)) { return; } $extra = $data['extra_permissions'] ?? []; $extra = is_array($extra) ? array_values(array_unique(array_filter($extra, 'is_string'))) : []; $extra = array_values(array_intersect($extra, array_keys($assignablePerms))); $patch = [ 'name' => (string) ($data['name'] ?? $record->name), 'email' => (string) ($data['email'] ?? $record->email), 'expires_at' => $data['expires_at'] ?? null, ]; if (!empty($data['password'])) { $patch['password'] = (string) $data['password']; } if ($actor->hasAnyRole(['super-admin', 'admin']) && Schema::hasColumn('users', 'amministratore_id')) { $patch['amministratore_id'] = isset($data['amministratore_id']) && is_numeric($data['amministratore_id']) ? (int) $data['amministratore_id'] : null; } $record->update($patch); $record->syncRoles([$role]); $record->syncPermissions($extra); if (Schema::hasTable('collaboratore_stabile') && method_exists($record, 'stabiliAssegnati')) { $requested = $data['stabile_ids'] ?? []; $requested = is_array($requested) ? $requested : []; $ids = []; foreach ($requested as $v) { if (is_numeric($v)) { $ids[] = (int) $v; } } $ids = array_values(array_unique($ids)); $allowed = $actor->hasAnyRole(['super-admin', 'admin']) ? null : StabileContext::accessibleStabili($actor)->pluck('id')->map(fn($v) => (int) $v)->all(); if (is_array($allowed)) { $ids = array_values(array_intersect($ids, $allowed)); } $record->stabiliAssegnati()->sync($ids); } }), DeleteAction::make() ->label('Elimina') ->visible(function (User $record) use ($actor): bool { if (! $actor instanceof User) { return false; } if ($actor->hasAnyRole(['super-admin', 'admin'])) { return $record->id !== $actor->id; } $amministratoreId = $actor->amministratore?->id; return $amministratoreId && (int) ($record->amministratore_id ?? 0) === (int) $amministratoreId && $record->id !== $actor->id; }), ]); return $tbl; } }