WIP: Implement role house manager and feedback providing
This commit is contained in:
@@ -37,14 +37,16 @@ class HotelDataProvider
|
||||
return $this->getAll()[$busProId] ?? null;
|
||||
}
|
||||
|
||||
public function findByCode(string $code): ?Hotel
|
||||
public function findByCode(string $code): array
|
||||
{
|
||||
$hotels = [];
|
||||
|
||||
foreach ($this->getAll() as $hotel) {
|
||||
if ($code === $hotel->getCode()) {
|
||||
return $hotel;
|
||||
if (str_starts_with($hotel->getCode(), $code)) {
|
||||
$hotels[] = $hotel;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return $hotels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\HouseManager\Feedback;
|
||||
|
||||
use App\BusProNet\DataProvider\HotelDataProvider;
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\Entity\User;
|
||||
use App\Repository\DispositionRepository;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DispositionRepository $dispositionRepository,
|
||||
private readonly HotelDataProvider $hotelDataProvider,
|
||||
private readonly PaginatorInterface $paginator
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/house-manager/pending-feedback', name: 'app_house_manager_feedback_index')]
|
||||
#[IsGranted('ROLE_HOUSE_MANAGER')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$hotels = $this
|
||||
->hotelDataProvider
|
||||
->findByCode($user->getHotelCode())
|
||||
;
|
||||
$hotelBusProIds = array_map(function (Hotel $hotel) {
|
||||
return $hotel->getBusProId();
|
||||
}, $hotels);
|
||||
|
||||
$query = $this
|
||||
->dispositionRepository
|
||||
->getPendingFeedbackQuery($hotelBusProIds)
|
||||
;
|
||||
|
||||
$pagination = $this->paginator->paginate(
|
||||
$query,
|
||||
$request->query->getInt('page', 1),
|
||||
10,
|
||||
[
|
||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
||||
'defaultSortDirection' => 'asc',
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render('house_manager/feedback/index.html.twig', [
|
||||
'pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\HouseManager\Feedback;
|
||||
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\Feedback;
|
||||
use App\Event\FeedbackProvidedEvent;
|
||||
use App\Form\FeedbackType;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class ProvideController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/house-manager/feedback/provide/{uuid}', name: 'app_house_manager_feedback_provide')]
|
||||
#[IsGranted('FEEDBACK', subject: 'disposition')]
|
||||
public function index(Disposition $disposition, Request $request): Response
|
||||
{
|
||||
$assignment = $disposition->getAssignment();
|
||||
$destination = $assignment->getDestination();
|
||||
$feebackSet = $assignment
|
||||
->getJobProfile()
|
||||
->getFeedbackSet()
|
||||
;
|
||||
$feedback = new Feedback();
|
||||
|
||||
$form = $this->createForm(FeedbackType::class, $feedback, ['feedback_set' => $feebackSet]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$feedback
|
||||
->setAssignmentDestination($destination)
|
||||
->setAssignmentDate($assignment->getEffectivePeriod()->start->toDateTimeImmutable())
|
||||
;
|
||||
$teamer = $disposition->getTeamer();
|
||||
$teamer->addFeedback($feedback);
|
||||
$disposition->setFeedback($feedback);
|
||||
|
||||
$this->entityManager->persist($feedback);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new FeedbackProvidedEvent($feedback), FeedbackProvidedEvent::NAME);
|
||||
$this->logger->info('Feedback provided', [
|
||||
'feedback_id' => $feedback->getId(),
|
||||
'teamer' => (string) $teamer,
|
||||
'destination' => (string) $destination,
|
||||
]);
|
||||
|
||||
$this->addFlash('success', 'Das Feedback wurde entgegengenommen');
|
||||
|
||||
return $this->redirectToRoute('app_house_manager_feedback_index');
|
||||
}
|
||||
|
||||
return $this->render('house_manager/feedback/provide.html.twig', [
|
||||
'form' => $form,
|
||||
'disposition' => $disposition,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ class IndexController extends AbstractController
|
||||
#[Route('/house-manager', name: 'app_house_manager_index')]
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('');
|
||||
return $this->render('house_manager/index.html.twig');
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
||||
private Collection $documents;
|
||||
|
||||
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
|
||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||
private ?Feedback $feedback = null;
|
||||
|
||||
public function __construct(Application $application)
|
||||
|
||||
@@ -35,6 +35,12 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
#[ORM\Column]
|
||||
private array $ratings = [];
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $comment = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $commentPublic = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uuid = Uuid::v4();
|
||||
@@ -97,4 +103,28 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function setComment(?string $comment): static
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isCommentPublic(): ?bool
|
||||
{
|
||||
return $this->commentPublic;
|
||||
}
|
||||
|
||||
public function setCommentPublic(bool $commentPublic): static
|
||||
{
|
||||
$this->commentPublic = $commentPublic;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Entity\Feedback;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class FeedbackProvidedEvent extends Event
|
||||
{
|
||||
public const NAME = 'feedback.provided';
|
||||
|
||||
public function __construct(private readonly Feedback $feedback)
|
||||
{
|
||||
}
|
||||
|
||||
public function getFeedback(): Feedback
|
||||
{
|
||||
return $this->feedback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\FeedbackSet;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class FeedbackRatingsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$ratings = $options['feedback_set']->getRatings();
|
||||
|
||||
foreach ($ratings as $index => $rating) {
|
||||
$builder->add('rating_'.$index, ChoiceType::class, [
|
||||
'label' => $rating,
|
||||
'choices' => array_combine(range(1, 6), range(1,6)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver
|
||||
->setRequired(['feedback_set'])
|
||||
->setAllowedTypes('feedback_set', FeedbackSet::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Feedback;
|
||||
use App\Entity\FeedbackSet;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class FeedbackType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('ratings', FeedbackRatingsType::class, [
|
||||
'label' => false,
|
||||
'feedback_set' => $options['feedback_set'],
|
||||
])
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'Kommentar',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'rows' => 5,
|
||||
],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'data_class' => Feedback::class,
|
||||
])
|
||||
->setRequired(['feedback_set'])
|
||||
->setAllowedTypes('feedback_set', FeedbackSet::class)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,17 @@ abstract class AbstractMenuBuilder
|
||||
}
|
||||
}
|
||||
|
||||
protected function addHouseManagerItem(ItemInterface $menu): void
|
||||
{
|
||||
if ($this->security->isGranted('ROLE_HOUSE_MANAGER')) {
|
||||
$menu
|
||||
->addChild('zum Hausmanagerbereich', ['route' => 'app_house_manager_index'])
|
||||
->setChildrenAttribute('title', 'zum Hausmanagerbereich')
|
||||
->setExtra('icon', 'user')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
protected function addLogoutItem(ItemInterface $menu): void
|
||||
{
|
||||
$token = $this->security->getToken();
|
||||
|
||||
@@ -39,7 +39,7 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
],
|
||||
[
|
||||
'route' => 'app_admin_teamer_index',
|
||||
'title' => 'Teamer:innenübersicht',
|
||||
'title' => 'Teamübersicht',
|
||||
'icon' => 'users',
|
||||
'hideChildren' => true,
|
||||
'children' => $this->getTeamerMenuItems(),
|
||||
@@ -165,6 +165,10 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
|
||||
$this->addDivider($menu);
|
||||
|
||||
$this->addHouseManagerItem($menu);
|
||||
|
||||
$this->addDivider($menu);
|
||||
|
||||
$this->addLogoutItem($menu);
|
||||
|
||||
return $menu;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Menu;
|
||||
|
||||
use Knp\Menu\ItemInterface;
|
||||
|
||||
class HouseManagerMenuBuilder extends AbstractMenuBuilder
|
||||
{
|
||||
public function createMainMenu(array $options): ItemInterface
|
||||
{
|
||||
$menuItems = [
|
||||
[
|
||||
'route' => 'app_house_manager_index',
|
||||
'title' => 'Dashboard',
|
||||
'icon' => 'chart',
|
||||
],
|
||||
[
|
||||
'route' => 'app_house_manager_feedback_index',
|
||||
'title' => 'Feedback',
|
||||
'icon' => 'feedback',
|
||||
'hideChildren' => true,
|
||||
'children' => [
|
||||
[
|
||||
'route' => 'app_house_manager_feedback_provide',
|
||||
'title' => 'Feedback abgeben',
|
||||
'routeParameters' => $this->getDefaultRouteParameters('uuid'),
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$menu = $this->createMenu($menuItems);
|
||||
|
||||
$this->addDivider($menu);
|
||||
|
||||
$this->addAdminItem($menu);
|
||||
|
||||
$this->addDivider($menu);
|
||||
|
||||
$this->addLogoutItem($menu);
|
||||
|
||||
return $menu;
|
||||
}
|
||||
}
|
||||
@@ -109,4 +109,29 @@ class DispositionRepository extends ServiceEntityRepository
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
public function getPendingFeedbackQuery(array $hotelBusProIds = null): Query
|
||||
{
|
||||
$qb = $this->createQueryBuilder('disposition');
|
||||
|
||||
$qb
|
||||
->select('disposition', 'assignment', 'destination')
|
||||
->innerJoin('disposition.assignment', 'assignment')
|
||||
->innerJoin('assignment.destination', 'destination')
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->isNull('disposition.feedback'),
|
||||
$qb->expr()->lt('destination.dateTo', ':dateTo')
|
||||
))
|
||||
->setParameter('dateTo', new \DateTimeImmutable())
|
||||
;
|
||||
|
||||
if (null !== $hotelBusProIds) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->in('destination.hotelBusProId', ':hotelBusProIds'))
|
||||
->setParameter('hotelBusProIds', $hotelBusProIds)
|
||||
;
|
||||
}
|
||||
|
||||
return $qb->getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,29 +38,4 @@ class FeedbackRepository extends ServiceEntityRepository
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Feedback[] Returns an array of Feedback objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('f')
|
||||
// ->andWhere('f.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('f.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Feedback
|
||||
// {
|
||||
// return $this->createQueryBuilder('f')
|
||||
// ->andWhere('f.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\BusProNet\DataProvider\HotelDataProvider;
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
@@ -14,6 +16,11 @@ class DispositionVoter extends Voter
|
||||
public const DELETE = 'DELETE';
|
||||
public const CONTRACT = 'CONTRACT';
|
||||
public const INVOICE = 'INVOICE';
|
||||
public const FEEDBACK = 'FEEDBACK';
|
||||
|
||||
public function __construct(private readonly HotelDataProvider $hotelDataProvider)
|
||||
{
|
||||
}
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
@@ -26,18 +33,38 @@ class DispositionVoter extends Voter
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
/** @var Disposition $disposition */
|
||||
$disposition = $subject;
|
||||
/** @var User $user */
|
||||
$user = $token->getUser();
|
||||
|
||||
// Administrative users have full access to all dispositions
|
||||
if (in_array('ROLE_ADMINISTRATIVE', $token->getRoleNames())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// House managers may provide feedback for dispositions that are past
|
||||
// and are associated with their hotel
|
||||
if (in_array('ROLE_HOUSE_MANAGER', $token->getRoleNames()) && static::FEEDBACK === $attribute) {
|
||||
$hotels = $this
|
||||
->hotelDataProvider
|
||||
->findByCode($user->getHotelCode())
|
||||
;
|
||||
$hotelBusProIds = array_map(function (Hotel $hotel) {
|
||||
return $hotel->getBusProId();
|
||||
}, $hotels);
|
||||
$destination = $disposition
|
||||
->getAssignment()
|
||||
->getDestination()
|
||||
;
|
||||
|
||||
return in_array($destination->getHotelBusProId(), $hotelBusProIds)
|
||||
&& $destination->getDateTo() < new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
// Teamers may only view or edit their own dispositions
|
||||
if (in_array('ROLE_TEAMER', $token->getRoleNames())) {
|
||||
/** @var User $user */
|
||||
$user = $token->getUser();
|
||||
$teamer = $user->getTeamer();
|
||||
/** @var Disposition $disposition */
|
||||
$disposition = $subject;
|
||||
|
||||
switch ($attribute) {
|
||||
case static::VIEW:
|
||||
|
||||
Reference in New Issue
Block a user