feat: assignable managers for accommodation bookings

This commit is contained in:
Björn Fromme committed 2026-08-05 13:45:33 +02:00
1 parent 8e4afc0813
commit 736128476b
9 files changed
+247 -1

No files matched your search

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Controller\Admin\AccommodationBooking;
use App\Entity\Groups\AccommodationBooking;
use App\Entity\User;
use App\Form\Admin\Groups\AccommodationBookingCreateType;
use App\Htmx\HxRedirectResponse;
use App\Service\AccommodationBookingService;
@@ -31,6 +32,14 @@ class CreateController extends AbstractController
{
$booking = new AccommodationBooking();
// Whoever creates a booking in the backoffice looks after it until someone else is
// assigned in the edit form. The route is behind ROLE_GROUPS_MANAGER, so the creator
// is always eligible as Betreuer.
$user = $this->getUser();
if ($user instanceof User) {
$booking->setManagedBy($user);
}
$form = $this->createForm(AccommodationBookingCreateType::class, $booking, [
'hx_post' => $request->getRequestUri(),
]);
@@ -11,6 +11,7 @@ use App\Enum\Groups\AccommodationBookingStatus;
use App\Form\Admin\Groups\AccommodationBookingType;
use App\Repository\Groups\AdditionalServiceRepository;
use App\Repository\Groups\BoardServiceRepository;
use App\Repository\UserRepository;
use App\Service\AccommodationBookingService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
@@ -28,6 +29,7 @@ class EditController extends AbstractController
private readonly LoggerInterface $logger,
private readonly BoardServiceRepository $boardServiceRepo,
private readonly AdditionalServiceRepository $additionalServiceRepo,
private readonly UserRepository $userRepo,
private readonly AccommodationBookingService $bookingService,
) {
}
@@ -69,12 +71,26 @@ class EditController extends AbstractController
));
}
// ROLE_ADMIN inherits ROLE_GROUPS_ADMIN, so this single check covers both.
$assignableManagers = [];
if ($this->isGranted('ROLE_GROUPS_ADMIN')) {
$assignableManagers = $this->userRepo->findGroupsStaff();
// A user assigned earlier may have lost the role since — keep them in the choices
// so the field can render the current value instead of failing to transform it.
$currentManager = $booking->getManagedBy();
if (null !== $currentManager && !in_array($currentManager, $assignableManagers, true)) {
$assignableManagers[] = $currentManager;
}
}
$form = $this->createForm(AccommodationBookingType::class, $booking, [
'max_adolescent_age' => $accommodation?->getMaxAdolescentAge() ?? 0,
'board_services' => $boardServices,
'additional_services' => $additionalServices,
'current_board_service' => $currentBoardService,
'current_additional_services' => $currentAdditionalServices,
'assignable_managers' => $assignableManagers,
]);
$previousStatus = $booking->getStatus();
@@ -29,7 +29,9 @@ class IndexController extends AbstractController
$qb = $this
->bookingRepository
->createQueryBuilder('booking')
->select('booking', 'managed_by')
->leftJoin('booking.accommodation', 'accommodation')
->leftJoin('booking.managedBy', 'managed_by')
->addSelect('accommodation')
->where('booking.dateTo >= :today')
->setParameter('today', $today)
@@ -7,6 +7,7 @@ namespace App\Form\Admin\Groups;
use App\Entity\Groups\AccommodationBooking;
use App\Entity\Groups\AdditionalService;
use App\Entity\Groups\BoardService;
use App\Entity\User;
use App\Enum\Groups\AccommodationBookingStatus;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
@@ -121,6 +122,20 @@ class AccommodationBookingType extends AbstractType
]);
}
// Assigning a manager is reserved for the groups admins; the controller leaves the
// choices empty for everyone else, so the field never enters the form tree and a
// plain manager cannot set it by hand-crafting the request either.
if (!empty($options['assignable_managers'])) {
$builder->add('managedBy', EntityType::class, [
'class' => User::class,
'required' => false,
'choices' => $options['assignable_managers'],
'choice_label' => 'email',
'placeholder' => 'keine Zuordnung',
'label' => 'Bearbeiter:in',
]);
}
$builder
->add('status', EnumType::class, [
'label' => 'Status',
@@ -176,11 +191,13 @@ class AccommodationBookingType extends AbstractType
'additional_services' => [],
'current_board_service' => null,
'current_additional_services' => [],
'assignable_managers' => [],
]);
$resolver->setAllowedTypes('max_adolescent_age', 'int');
$resolver->setAllowedTypes('board_services', 'array');
$resolver->setAllowedTypes('additional_services', 'array');
$resolver->setAllowedTypes('current_board_service', ['null', BoardService::class]);
$resolver->setAllowedTypes('current_additional_services', 'array');
$resolver->setAllowedTypes('assignable_managers', 'array');
}
}
+18
View File
@@ -17,4 +17,22 @@ class UserRepository extends ServiceEntityRepository
{
parent::__construct($registry, User::class);
}
/**
* Users who can look after a group booking. The roles are a JSON array column and no
* JSON_CONTAINS is registered, so the needles carry their quotes to stay anchored to
* whole array entries. Only the literal roles count — a plain ROLE_ADMIN is not offered.
*
* @return User[]
*/
public function findGroupsStaff(): array
{
return $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :groupsAdmin OR u.roles LIKE :groupsManager')
->setParameter('groupsAdmin', '%"ROLE_GROUPS_ADMIN"%')
->setParameter('groupsManager', '%"ROLE_GROUPS_MANAGER"%')
->orderBy('u.email', 'ASC')
->getQuery()
->getResult();
}
}