517 lines
23 KiB
PHP
517 lines
23 KiB
PHP
<?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\DateTimePicker;
|
||
use Filament\Forms\Components\Select;
|
||
use Filament\Forms\Components\TextInput;
|
||
use Filament\Forms\Components\CheckboxList;
|
||
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, 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;
|
||
}
|
||
}
|