diff --git a/migrations/Version20231009130251.php b/migrations/Version20231009130251.php new file mode 100644 index 0000000..0c1c968 --- /dev/null +++ b/migrations/Version20231009130251.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE application ADD uuid VARCHAR(36) NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_A45BDDC1D17F50A6 ON application (uuid)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_A45BDDC1D17F50A6 ON application'); + $this->addSql('ALTER TABLE application DROP uuid'); + } +} diff --git a/src/Controller/Teamer/ApplicationController.php b/src/Controller/Teamer/Application/CreateController.php similarity index 84% rename from src/Controller/Teamer/ApplicationController.php rename to src/Controller/Teamer/Application/CreateController.php index 1250ab5..7b5c172 100644 --- a/src/Controller/Teamer/ApplicationController.php +++ b/src/Controller/Teamer/Application/CreateController.php @@ -1,6 +1,6 @@ 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, diff --git a/src/Controller/Teamer/Application/DeleteController.php b/src/Controller/Teamer/Application/DeleteController.php new file mode 100644 index 0000000..f8d9327 --- /dev/null +++ b/src/Controller/Teamer/Application/DeleteController.php @@ -0,0 +1,40 @@ +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); + } +} \ No newline at end of file diff --git a/src/Controller/Teamer/AssignmentController.php b/src/Controller/Teamer/AssignmentController.php index 7782858..0080e9f 100644 --- a/src/Controller/Teamer/AssignmentController.php +++ b/src/Controller/Teamer/AssignmentController.php @@ -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), ]); } } \ No newline at end of file diff --git a/src/Controller/Teamer/Bookmark/ToggleController.php b/src/Controller/Teamer/Bookmark/ToggleController.php new file mode 100644 index 0000000..978ccd0 --- /dev/null +++ b/src/Controller/Teamer/Bookmark/ToggleController.php @@ -0,0 +1,44 @@ +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); + } +} \ No newline at end of file diff --git a/src/Entity/Assignment.php b/src/Entity/Assignment.php index dffd87e..69a71fb 100644 --- a/src/Entity/Assignment.php +++ b/src/Entity/Assignment.php @@ -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)] diff --git a/src/Form/TeamerApplicationType.php b/src/Form/TeamerApplicationType.php index 750f742..19609f8 100644 --- a/src/Form/TeamerApplicationType.php +++ b/src/Form/TeamerApplicationType.php @@ -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 diff --git a/src/Repository/AssignmentRepository.php b/src/Repository/AssignmentRepository.php index c8fd8c2..de53b61 100644 --- a/src/Repository/AssignmentRepository.php +++ b/src/Repository/AssignmentRepository.php @@ -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(); } } diff --git a/src/Security/Voter/ApplicationVoter.php b/src/Security/Voter/ApplicationVoter.php new file mode 100644 index 0000000..2dc4866 --- /dev/null +++ b/src/Security/Voter/ApplicationVoter.php @@ -0,0 +1,42 @@ +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; + } +} \ No newline at end of file diff --git a/src/Security/Voter/AssignmentVoter.php b/src/Security/Voter/AssignmentVoter.php new file mode 100644 index 0000000..059323c --- /dev/null +++ b/src/Security/Voter/AssignmentVoter.php @@ -0,0 +1,55 @@ +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; + } +} \ No newline at end of file diff --git a/templates/_partials/_assignment_info.html.twig b/templates/_partials/_assignment_info.html.twig new file mode 100644 index 0000000..beddd94 --- /dev/null +++ b/templates/_partials/_assignment_info.html.twig @@ -0,0 +1,52 @@ +
- Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? -
- - Hier bewerben! - + {% include '_partials/_assignment_requirements_info.html.twig' %} + {% if application is null %} +