feat: align role assignment logic with myep-team

This commit is contained in:
Björn Fromme
2026-08-19 12:14:09 +02:00
parent 5a3957e143
commit 0c667d6b69
23 changed files with 1109 additions and 527 deletions
-98
View File
@@ -1,98 +0,0 @@
<?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 the privileged roles and the hotel codes of an existing account.
*
* Only Role::PRIVILEGED is offered: the remaining roles are synced from the BusPro CRM on every
* login (see BpnAuthenticator), so editing them here would be undone at the user's next login.
* Hotel codes are seeded once at account creation, which makes this form the only way to change
* them afterwards.
*
* @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' => $this->privilegedChoices(),
'multiple' => true,
'expanded' => true,
'required' => false,
'help' => 'Alle übrigen Rollen kommen bei jeder Anmeldung aus BusPro und lassen sich hier nicht ändern.',
// Only the administrator-granted half is editable; the synced half is preserved,
// as is the implicit ROLE_USER, which must never be written back.
'getter' => static fn (User $user): array => Role::privilegedOnly($user->getRoles()),
'setter' => static function (User $user, array $roles): void {
$user->setRoles(Role::combine($user->getRoles(), $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)));
},
])
;
}
/**
* @return array<string, string> label => role
*/
private function privilegedChoices(): array
{
return array_flip(array_intersect_key(Role::labels(), array_flip(Role::PRIVILEGED)));
}
/**
* 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,
]);
}
}