feat: admin-managed roles and hotel codes

This commit is contained in:
Björn Fromme
2026-08-10 10:07:24 +02:00
parent 124c0af0f5
commit 6c1073e41c
19 changed files with 718 additions and 34 deletions
+2 -19
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Form\Admin\Filter;
use App\Form\Model\Filter\UserFilterDto;
use App\Security\Role;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -13,28 +14,12 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class UserFilterType extends AbstractListFilterType
{
/**
* The roles that are actually assigned to accounts. ROLE_USER is left out because every
* account has it implicitly and filtering by it would match everything.
*
* @var string[]
*/
public const ROLES = [
'ROLE_ADMIN',
'ROLE_MANAGER',
'ROLE_TEAMER',
'ROLE_CUSTOMER',
'ROLE_HOUSE_MANAGER',
'ROLE_GROUPS_ADMIN',
'ROLE_GROUPS_MANAGER',
];
protected function filterFields(array $options): array
{
return [
'role' => [ChoiceType::class, [
'label' => 'Rolle',
'choices' => array_combine($options['roles'], $options['roles']),
'choices' => array_flip(Role::labels()),
'placeholder' => 'alle',
'required' => false,
]],
@@ -47,9 +32,7 @@ class UserFilterType extends AbstractListFilterType
$resolver->setDefaults([
'data_class' => UserFilterDto::class,
'roles' => [],
]);
$resolver->setAllowedTypes('roles', 'string[]');
}
protected function searchPlaceholder(): string
+86
View File
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Form\Admin;
use App\Entity\User;
use App\Security\Role;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Lets an administrator assign roles and hotel codes to an existing account.
*
* This is the only way those two get changed after the account was created — BpnAuthenticator
* imports them once and never touches them again.
*
* @extends AbstractType<User>
*/
class UserType extends AbstractType
{
/**
* @param array<string, string> $hotelCodes code => label
*/
public function __construct(private readonly array $hotelCodes = [])
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$user = $builder->getData();
$builder
->add('roles', ChoiceType::class, [
'label' => 'Rollen',
'choices' => array_flip(Role::labels()),
'multiple' => true,
'expanded' => true,
'required' => false,
// User::getRoles() prepends the implicit ROLE_USER, which must not be written back.
'getter' => static fn (User $user): array => array_values(array_diff($user->getRoles(), ['ROLE_USER'])),
'setter' => static function (User $user, array $roles): void {
$user->setRoles(array_values(array_unique($roles)));
},
])
->add('hotelCodes', ChoiceType::class, [
'label' => 'Häuser',
'choices' => $this->hotelCodeChoices($user),
'multiple' => true,
'expanded' => true,
'required' => false,
'setter' => static function (User $user, array $hotelCodes): void {
$user->setHotelCodes(array_values(array_unique($hotelCodes)));
},
])
;
}
/**
* Codes already stored on the account are always offered, even when they are missing from
* the configured catalog — otherwise saving the form would silently drop them.
*
* @return array<string, string> label => code
*/
private function hotelCodeChoices(?User $user): array
{
$codes = $this->hotelCodes;
foreach ($user?->getHotelCodes() ?? [] as $code) {
$codes[$code] ??= $code;
}
ksort($codes);
return array_flip($codes);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
+2 -1
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Form\Model\Filter;
use App\Model\ListFilterChip;
use App\Security\Role;
class UserFilterDto extends AbstractListFilterDto
{
@@ -15,7 +16,7 @@ class UserFilterDto extends AbstractListFilterDto
$chips = parent::activeFilters();
if (null !== $this->role && '' !== $this->role) {
$chips[] = new ListFilterChip('Rolle', $this->role, ['role']);
$chips[] = new ListFilterChip('Rolle', Role::labels()[$this->role] ?? $this->role, ['role']);
}
return $chips;