feat: timeline for role house manager

closes #869b9vmmx
This commit is contained in:
Björn Fromme
2026-03-15 12:42:28 +01:00
parent 6dfd8fd3ef
commit 1a536c044e
7 changed files with 87 additions and 27 deletions
@@ -2,10 +2,12 @@
namespace App\Controller\Administrative\Assignment;
use App\Entity\User;
use App\Repository\AssignmentRepository;
use App\Service\Assignment\TimelineService;
use App\Service\Common\TimelineFilterHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -20,17 +22,26 @@ class TimelineController extends AbstractController
}
#[Route('/administrative/timeline', name: 'app_administrative_timeline')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
#[IsGranted(new Expression('is_granted("ROLE_ADMINISTRATIVE") or is_granted("ROLE_HOUSE_MANAGER")'))]
public function index(): Response
{
/** @var User $user */
$user = $this->getUser();
$period = $this->timelineService->getActiveWindow();
$this->filterHandler->setPeriod($period);
$filterDto = $this->filterHandler->getFilterSettings();
$hotelCodes = null;
if ($this->isGranted('ROLE_HOUSE_MANAGER')) {
$filterDto->setHotels([]);
$hotelCodes = $user->getHotelCodes();
}
$assignments = $this
->assignmentRepository
->getListQuery($filterDto, 'destination.dateFrom')
->getListQuery($filterDto, 'destination.dateFrom', $hotelCodes)
->getResult()
;
$timelineData = $this->timelineService->preprocess($assignments);
@@ -8,6 +8,7 @@ use App\Htmx\HxRedirectResponse;
use App\Service\Assignment\TimelineService;
use App\Service\Common\TimelineFilterHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
@@ -26,10 +27,13 @@ class TimelineFilterController extends AbstractController
}
#[Route('/common/timeline/filter', name: 'app_common_timeline_filter')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
#[IsGranted(new Expression('is_granted("ROLE_ADMINISTRATIVE") or is_granted("ROLE_HOUSE_MANAGER")'))]
public function index(Request $request): Response
{
$formData = $this->filterHandler->getFilterSettings();
if ($this->isGranted('ROLE_HOUSE_MANAGER')) {
$formData->setHotels([]);
}
$form = $this->createForm(TimelineFilterType::class, $formData);
$form->handleRequest($request);
+18 -14
View File
@@ -4,6 +4,7 @@ namespace App\Form;
use App\Model\TimelineFilterDto;
use App\Service\Assignment\TimelineService;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -13,6 +14,7 @@ class TimelineFilterType extends AbstractType
{
public function __construct(
private readonly TimelineService $timelineService,
private readonly Security $security,
private readonly array $destinations,
) {
}
@@ -50,21 +52,23 @@ class TimelineFilterType extends AbstractType
])
;
$destinations = $this->destinations;
$hotelChoices = [];
sort($destinations);
foreach ($destinations as $item) {
$hotelChoices[$item] = $item;
}
if (false === $this->security->isGranted('ROLE_HOUSE_MANAGER')) {
$destinations = $this->destinations;
$hotelChoices = [];
sort($destinations);
foreach ($destinations as $item) {
$hotelChoices[$item] = $item;
}
$builder
->add('hotels', MultiselectType::class, [
'label' => 'Haus/Destination',
'required' => false,
'empty_label' => 'nicht filtern',
'choices' => $hotelChoices,
])
;
$builder
->add('hotels', MultiselectType::class, [
'label' => 'Haus/Destination',
'required' => false,
'empty_label' => 'nicht filtern',
'choices' => $hotelChoices,
])
;
}
}
public function configureOptions(OptionsResolver $resolver): void
+9
View File
@@ -31,6 +31,15 @@ class HouseManagerMenuBuilder extends AbstractMenuBuilder
],
],
]);
$menu->addChild('Zeitleiste', [
'route' => 'app_administrative_timeline',
'linkAttributes' => [
'title' => 'Zeitleiste',
],
'extras' => [
'icon' => 'table',
],
]);
$this->addAdminItem($menu);
$this->addManagerItem($menu);
+23 -1
View File
@@ -32,7 +32,7 @@ class AssignmentRepository extends ServiceEntityRepository
parent::__construct($registry, Assignment::class);
}
public function getListQuery(AssignmentFilterDto $filterDto, ?string $orderBy = null): Query
public function getListQuery(AssignmentFilterDto $filterDto, ?string $orderBy = null, ?array $hotelCodes = null): Query
{
$qb = $this->createQueryBuilder('assignment');
@@ -56,6 +56,7 @@ class AssignmentRepository extends ServiceEntityRepository
}
$this->applyFilterSettings($filterDto, $qb);
$this->applyHotelCodeRestriction($qb, $hotelCodes);
return $qb->getQuery();
}
@@ -231,6 +232,27 @@ class AssignmentRepository extends ServiceEntityRepository
}
}
private function applyHotelCodeRestriction(QueryBuilder $qb, ?array $hotelCodes = null): void
{
if (null === $hotelCodes) {
return;
}
if ([] === $hotelCodes) {
$qb->andWhere('1 = 0');
return;
}
$qb
->andWhere($qb->expr()->orX(
$qb->expr()->in($qb->expr()->substring('destination.hotelCode', 1, 3), ':hotelCodes'),
$qb->expr()->in($qb->expr()->substring('destination.hotelCode', -3, 3), ':hotelCodes'),
))
->setParameter('hotelCodes', $hotelCodes)
;
}
public function getBookmarkQueryForTeamer(Teamer $teamer): Query
{
// Find ids of assignments with available slots first
@@ -11,10 +11,14 @@
}) }}" colspan="{{ colspan }}">
{% if item.type == 'disposition' %}
<div class="flex items-center space-x-2">
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="inline-block text-xs font-semibold">
{{ item.jobProfile }}
</a>
{% if is_granted('ROLE_HOUSE_MANAGER') %}
<span class="inline-block text-xs font-semibold">{{ item.jobProfile }}</span>
{% else %}
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="inline-block text-xs font-semibold">
{{ item.jobProfile }}
</a>
{% endif %}
<span>|</span>
<button type="button"
class="flex items-center space-x-2"
@@ -26,10 +30,14 @@
</button>
</div>
{% else %}
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="text-xs font-semibold">
{{ item.jobProfile }}
</a>
{% if is_granted('ROLE_HOUSE_MANAGER') %}
<span class="text-xs font-semibold">{{ item.jobProfile }}</span>
{% else %}
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="text-xs font-semibold">
{{ item.jobProfile }}
</a>
{% endif %}
{% endif %}
</td>
{% endmacro %}
@@ -9,7 +9,9 @@
{{ form_row(filterForm.dateFrom) }}
{{ form_row(filterForm.dateTo) }}
</div>
{{ form_row(filterForm.hotels) }}
{% if filterForm.hotels is defined %}
{{ form_row(filterForm.hotels) }}
{% endif %}
<div class="flex items-center space-x-2">
{{ form_widget(filterForm.apply, { 'attr': { 'class': 'btn' } }) }}
{{ form_widget(filterForm.reset, { 'attr': { 'class': 'btn btn--secondary' } }) }}