WIP: Implement application process

This commit is contained in:
Björn Fromme
2023-10-09 16:59:35 +02:00
parent abced8c924
commit 3a458df94f
17 changed files with 441 additions and 219 deletions
@@ -1,6 +1,6 @@
<?php
namespace App\Controller\Teamer;
namespace App\Controller\Teamer\Application;
use App\Entity\Application;
use App\Entity\Assignment;
@@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class ApplicationController extends AbstractController
class CreateController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
@@ -22,8 +22,9 @@ class ApplicationController extends AbstractController
) {
}
#[Route('/teamer/application/{uuid}', name: 'app_teamer_application')]
#[Route('/teamer/apply/{uuid}', name: 'app_teamer_application_create')]
#[IsGranted('ROLE_TEAMER')]
#[IsGranted('APPLY', subject: 'assignment')]
public function index(Assignment $assignment, Request $request): Response
{
/** @var User $user */
@@ -47,7 +48,7 @@ class ApplicationController extends AbstractController
return $this->redirectToRoute('app_teamer_index');
}
return $this->render('teamer/application.html.twig', [
return $this->render('teamer/application/create.html.twig', [
'assignment' => $assignment,
'teamer' => $teamer,
'form' => $form,
@@ -0,0 +1,40 @@
<?php
namespace App\Controller\Teamer\Application;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\Application;
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;
class DeleteController extends AbstractController
{
use ReturnUrlTrait;
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly LoggerInterface $logger
) {
}
#[Route('/teamer/application/delete/{uuid}', name: 'app_teamer_application_delete')]
#[IsGranted('DELETE', subject: 'application')]
public function index(Application $application, Request $request): Response
{
$this->entityManager->remove($application);
$this->entityManager->flush();
$this->logger->info('Delete application', [
'application' => $application->getUuid(),
]);
$returnUrl = $this->getReturnUrl($request, 'app_teamer_index');
return $this->redirect($returnUrl);
}
}
@@ -3,6 +3,8 @@
namespace App\Controller\Teamer;
use App\Entity\Assignment;
use App\Entity\User;
use App\Repository\ApplicationRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@@ -10,12 +12,30 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class AssignmentController extends AbstractController
{
public function __construct(private readonly ApplicationRepository $applicationRepository)
{}
#[Route('/teamer/assignment/{uuid}', name: 'app_teamer_assignment')]
#[IsGranted('ROLE_TEAMER')]
public function index(Assignment $assignment): Response
{
/** @var User $user */
$user = $this->getUser();
$teamer = $user->getTeamer();
$application = $this
->applicationRepository
->findOneBy([
'assignment' => $assignment,
'teamer' => $teamer,
])
;
return $this->render('teamer/assignment.html.twig', [
'teamer' => $teamer,
'assignment' => $assignment,
'application' => $application,
'isBookmarked' => $teamer->getBookmarks()->contains($assignment),
]);
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Controller\Teamer\Bookmark;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\Assignment;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
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 ToggleController extends AbstractController
{
use ReturnUrlTrait;
public function __construct(private readonly EntityManagerInterface $entityManager)
{}
#[Route('/teamer/bookmark/toggle/{uuid}', name: 'app_teamer_bookmark_toggle')]
#[IsGranted('ROLE_TEAMER')]
public function index(Assignment $assignment, Request $request): Response
{
/** @var User $user */
$user = $this->getUser();
$teamer = $user->getTeamer();
if ($teamer->getBookmarks()->contains($assignment)) {
$teamer->removeBookmark($assignment);
$this->addFlash('success', 'Der Einsatz wurde von der Merkliste entfernt');
} else {
$teamer->addBookmark($assignment);
$this->addFlash('success', 'Der Einsatz wurde auf die Merkliste gesetzt');
}
$this->entityManager->flush();
$returnUrl = $this->getReturnUrl($request, 'app_teamer_index');
return $this->redirect($returnUrl);
}
}
-1
View File
@@ -62,7 +62,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
private ?string $remarks = null;
#[ORM\ManyToMany(targetEntity: Fee::class)]
#[Assert\Count(min: 1, minMessage: 'Bitte triff mindestens eine Auswahl')]
private Collection $fees;
#[ORM\OneToMany(mappedBy: 'assignment', targetEntity: Application::class)]
+39 -29
View File
@@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Application;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -15,36 +16,45 @@ class TeamerApplicationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var Application $application */
$application = $event->getData();
if (null === $application) {
return;
}
$assignment = $application->getAssignment();
if (null === $assignment->getPickup()) {
return;
}
$teamer = $application->getTeamer();
if (in_array($assignment->getPickup(), $teamer->getPickups())) {
return;
}
$event->getForm()->add('confirmPickup', CheckboxType::class, [
'label' => 'Ich bestätige den abweichenden Buszustieg',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
$builder
->add('requests', TextareaType::class, [
'label' => 'Wünsche',
'required' => false,
'attr' => [
'rows' => 3,
],
]);
});
])
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var Application $application */
$application = $event->getData();
if (null === $application) {
return;
}
$assignment = $application->getAssignment();
if (null === $assignment->getPickup()) {
return;
}
$teamer = $application->getTeamer();
if (in_array($assignment->getPickup(), $teamer->getPickups())) {
return;
}
$event->getForm()->add('confirmPickup', CheckboxType::class, [
'label' => 'Ich bestätige den abweichenden Buszustieg',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
],
]);
})
;
}
public function configureOptions(OptionsResolver $resolver): void
+18 -4
View File
@@ -3,6 +3,7 @@
namespace App\Repository;
use App\Entity\Assignment;
use App\Entity\Teamer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query;
use Doctrine\Persistence\ManagerRegistry;
@@ -22,11 +23,24 @@ class AssignmentRepository extends ServiceEntityRepository
parent::__construct($registry, Assignment::class);
}
public function getListQuery(): Query
public function getListQuery(Teamer $teamer = null): Query
{
return $this
->createQueryBuilder('assignment')
->getQuery()
$qb = $this->createQueryBuilder('assignment');
$qb
->select('assignment', 'destination', 'job_profile', 'application')
->innerJoin('assignment.destination', 'destination')
->innerJoin('assignment.jobProfile', 'job_profile')
->leftJoin('assignment.applications', 'application')
;
if (null !== $teamer) {
$qb
->where($qb->expr()->eq('application.teamer', ':teamer'))
->setParameter('teamer', $teamer)
;
}
return $qb->getQuery();
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Security\Voter;
use App\Entity\Application;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class ApplicationVoter extends Voter
{
public const VIEW = 'VIEW';
public const DELETE = 'DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
if (!$subject instanceof Application) {
return false;
}
return in_array($attribute, [static::VIEW, static::DELETE]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
/** @var User $user */
$user = $token->getUser();
/** @var Application $application */
$application = $subject;
if ($user->hasRole('ROLE_ADMINISTRATIVE')) {
return true;
}
if ($user->hasRole('ROLE_TEAMER')) {
return $user->getTeamer() === $application->getTeamer();
}
return false;
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Security\Voter;
use App\Entity\Assignment;
use App\Entity\User;
use App\Repository\ApplicationRepository;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AssignmentVoter extends Voter
{
public const VIEW = 'VIEW';
public const EDIT = 'EDIT';
public const APPLY = 'APPLY';
public function __construct(private readonly ApplicationRepository $applicationRepository)
{}
protected function supports(string $attribute, mixed $subject): bool
{
if (!$subject instanceof Assignment) {
return false;
}
return in_array($attribute, [static::VIEW, static::EDIT, static::APPLY]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
// All authenticated users may view assignments
if (static::VIEW === $attribute && null !== $token->getUser()) {
return true;
}
// Only users with administrative role may edit assignments
if (static::EDIT === $attribute && in_array('ROLE_ADMINISTRATIVE', $token->getRoleNames())) {
return true;
}
// Only teamers without existing applications may apply to the assignment
if (in_array('ROLE_TEAMER', $token->getRoleNames())) {
/** @var User $user */
$user = $token->getUser();
$teamer = $user->getTeamer();
/** @var Assignment $assignment */
$assignment = $subject;
$application = $this->applicationRepository->findOneBy(['teamer' => $teamer, 'assignment' => $assignment]);
return null === $application;
}
return false;
}
}