netgescon-day0/app/Filament/Pages/SuperAdmin/Utenti.php

516 lines
23 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Filament\Pages\SuperAdmin;
use App\Models\Amministratore;
use App\Models\Stabile;
use App\Models\User;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use UnitEnum;
class Utenti extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationLabel = 'Utenti';
protected static ?string $title = 'Gestione utenti';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-user';
protected static UnitEnum|string|null $navigationGroup = 'Impostazioni';
protected static ?int $navigationSort = 1;
protected static ?string $slug = 'superadmin/utenti';
protected string $view = 'filament.pages.gescon.table';
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& (
$user->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<int, string>
*/
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<string, string>
*/
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<string, string>
*/
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<string, string> $perms
* @return array<string, string>
*/
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, lutente 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 lutente allo studio dellamministratore.'),
])
->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;
}
}