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; namespace App\Controller\Administrative\Assignment;
use App\Entity\User;
use App\Repository\AssignmentRepository; use App\Repository\AssignmentRepository;
use App\Service\Assignment\TimelineService; use App\Service\Assignment\TimelineService;
use App\Service\Common\TimelineFilterHandler; use App\Service\Common\TimelineFilterHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Security\Http\Attribute\IsGranted;
@@ -20,17 +22,26 @@ class TimelineController extends AbstractController
} }
#[Route('/administrative/timeline', name: 'app_administrative_timeline')] #[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 public function index(): Response
{ {
/** @var User $user */
$user = $this->getUser();
$period = $this->timelineService->getActiveWindow(); $period = $this->timelineService->getActiveWindow();
$this->filterHandler->setPeriod($period); $this->filterHandler->setPeriod($period);
$filterDto = $this->filterHandler->getFilterSettings(); $filterDto = $this->filterHandler->getFilterSettings();
$hotelCodes = null;
if ($this->isGranted('ROLE_HOUSE_MANAGER')) {
$filterDto->setHotels([]);
$hotelCodes = $user->getHotelCodes();
}
$assignments = $this $assignments = $this
->assignmentRepository ->assignmentRepository
->getListQuery($filterDto, 'destination.dateFrom') ->getListQuery($filterDto, 'destination.dateFrom', $hotelCodes)
->getResult() ->getResult()
; ;
$timelineData = $this->timelineService->preprocess($assignments); $timelineData = $this->timelineService->preprocess($assignments);
@@ -8,6 +8,7 @@ use App\Htmx\HxRedirectResponse;
use App\Service\Assignment\TimelineService; use App\Service\Assignment\TimelineService;
use App\Service\Common\TimelineFilterHandler; use App\Service\Common\TimelineFilterHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
@@ -26,10 +27,13 @@ class TimelineFilterController extends AbstractController
} }
#[Route('/common/timeline/filter', name: 'app_common_timeline_filter')] #[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 public function index(Request $request): Response
{ {
$formData = $this->filterHandler->getFilterSettings(); $formData = $this->filterHandler->getFilterSettings();
if ($this->isGranted('ROLE_HOUSE_MANAGER')) {
$formData->setHotels([]);
}
$form = $this->createForm(TimelineFilterType::class, $formData); $form = $this->createForm(TimelineFilterType::class, $formData);
$form->handleRequest($request); $form->handleRequest($request);
+4
View File
@@ -4,6 +4,7 @@ namespace App\Form;
use App\Model\TimelineFilterDto; use App\Model\TimelineFilterDto;
use App\Service\Assignment\TimelineService; use App\Service\Assignment\TimelineService;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -13,6 +14,7 @@ class TimelineFilterType extends AbstractType
{ {
public function __construct( public function __construct(
private readonly TimelineService $timelineService, private readonly TimelineService $timelineService,
private readonly Security $security,
private readonly array $destinations, private readonly array $destinations,
) { ) {
} }
@@ -50,6 +52,7 @@ class TimelineFilterType extends AbstractType
]) ])
; ;
if (false === $this->security->isGranted('ROLE_HOUSE_MANAGER')) {
$destinations = $this->destinations; $destinations = $this->destinations;
$hotelChoices = []; $hotelChoices = [];
sort($destinations); sort($destinations);
@@ -66,6 +69,7 @@ class TimelineFilterType extends AbstractType
]) ])
; ;
} }
}
public function configureOptions(OptionsResolver $resolver): void 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->addAdminItem($menu);
$this->addManagerItem($menu); $this->addManagerItem($menu);
+23 -1
View File
@@ -32,7 +32,7 @@ class AssignmentRepository extends ServiceEntityRepository
parent::__construct($registry, Assignment::class); 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'); $qb = $this->createQueryBuilder('assignment');
@@ -56,6 +56,7 @@ class AssignmentRepository extends ServiceEntityRepository
} }
$this->applyFilterSettings($filterDto, $qb); $this->applyFilterSettings($filterDto, $qb);
$this->applyHotelCodeRestriction($qb, $hotelCodes);
return $qb->getQuery(); 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 public function getBookmarkQueryForTeamer(Teamer $teamer): Query
{ {
// Find ids of assignments with available slots first // Find ids of assignments with available slots first
@@ -11,10 +11,14 @@
}) }}" colspan="{{ colspan }}"> }) }}" colspan="{{ colspan }}">
{% if item.type == 'disposition' %} {% if item.type == 'disposition' %}
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
{% 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() }) }}" <a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="inline-block text-xs font-semibold"> class="inline-block text-xs font-semibold">
{{ item.jobProfile }} {{ item.jobProfile }}
</a> </a>
{% endif %}
<span>|</span> <span>|</span>
<button type="button" <button type="button"
class="flex items-center space-x-2" class="flex items-center space-x-2"
@@ -25,12 +29,16 @@
<span class="text-xs font-semibold">{{ item.teamerName | raw }}</span> <span class="text-xs font-semibold">{{ item.teamerName | raw }}</span>
</button> </button>
</div> </div>
{% else %}
{% if is_granted('ROLE_HOUSE_MANAGER') %}
<span class="text-xs font-semibold">{{ item.jobProfile }}</span>
{% else %} {% else %}
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}" <a href="{{ path('app_administrative_assignment_detail', { 'uuid': item.assignmentUuid, 'r': return_url() }) }}"
class="text-xs font-semibold"> class="text-xs font-semibold">
{{ item.jobProfile }} {{ item.jobProfile }}
</a> </a>
{% endif %} {% endif %}
{% endif %}
</td> </td>
{% endmacro %} {% endmacro %}
@@ -9,7 +9,9 @@
{{ form_row(filterForm.dateFrom) }} {{ form_row(filterForm.dateFrom) }}
{{ form_row(filterForm.dateTo) }} {{ form_row(filterForm.dateTo) }}
</div> </div>
{% if filterForm.hotels is defined %}
{{ form_row(filterForm.hotels) }} {{ form_row(filterForm.hotels) }}
{% endif %}
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
{{ form_widget(filterForm.apply, { 'attr': { 'class': 'btn' } }) }} {{ form_widget(filterForm.apply, { 'attr': { 'class': 'btn' } }) }}
{{ form_widget(filterForm.reset, { 'attr': { 'class': 'btn btn--secondary' } }) }} {{ form_widget(filterForm.reset, { 'attr': { 'class': 'btn btn--secondary' } }) }}