From 3a458df94f56a1252fb9dbffcc5d9f72de5c405c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 9 Oct 2023 16:59:35 +0200 Subject: [PATCH] WIP: Implement application process --- migrations/Version20231009130251.php | 33 ++++++ .../CreateController.php} | 9 +- .../Teamer/Application/DeleteController.php | 40 +++++++ .../Teamer/AssignmentController.php | 20 ++++ .../Teamer/Bookmark/ToggleController.php | 44 ++++++++ src/Entity/Assignment.php | 1 - src/Form/TeamerApplicationType.php | 68 +++++++----- src/Repository/AssignmentRepository.php | 22 +++- src/Security/Voter/ApplicationVoter.php | 42 ++++++++ src/Security/Voter/AssignmentVoter.php | 55 ++++++++++ .../_partials/_assignment_info.html.twig | 52 +++++++++ .../_assignment_requirements_info.html.twig | 26 +++++ templates/manager/index.html.twig | 4 - templates/manager/layout.html.twig | 9 -- templates/teamer/application.html.twig | 101 ----------------- templates/teamer/application/create.html.twig | 32 ++++++ templates/teamer/assignment.html.twig | 102 ++++++------------ 17 files changed, 441 insertions(+), 219 deletions(-) create mode 100644 migrations/Version20231009130251.php rename src/Controller/Teamer/{ApplicationController.php => Application/CreateController.php} (84%) create mode 100644 src/Controller/Teamer/Application/DeleteController.php create mode 100644 src/Controller/Teamer/Bookmark/ToggleController.php create mode 100644 src/Security/Voter/ApplicationVoter.php create mode 100644 src/Security/Voter/AssignmentVoter.php create mode 100644 templates/_partials/_assignment_info.html.twig create mode 100644 templates/_partials/_assignment_requirements_info.html.twig delete mode 100644 templates/manager/index.html.twig delete mode 100644 templates/manager/layout.html.twig delete mode 100644 templates/teamer/application.html.twig create mode 100644 templates/teamer/application/create.html.twig 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 @@ +
+
+ Jobprofil +
+
+ {{ assignment.jobProfile.name }} +
+
+ Destination +
+
+ {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+
+ Einsatztermin +
+
+ {{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }} +
+ {% if assignment.pickup and assignment.pickupDate %} +
+ Busabfahrt +
+
+ {{ assignment.pickupDate|date('d.m.Y') }} +
+
+ Busbegleitung +
+
+ ab {{ assignment.pickup|bpn_pickup_label }} +
+ {% endif %} +
+ Du bekommst +
+
+ {{ assignment.benefits|nl2list }} +
+
+ Honorar +
+ {% if assignment.fees|length %} +
+ {% for fee in assignment.fees %} + {{ fee.name }} {{ fee.value|format_money }} + {% endfor %} +
+ {% endif %} +
diff --git a/templates/_partials/_assignment_requirements_info.html.twig b/templates/_partials/_assignment_requirements_info.html.twig new file mode 100644 index 0000000..a3f56be --- /dev/null +++ b/templates/_partials/_assignment_requirements_info.html.twig @@ -0,0 +1,26 @@ + diff --git a/templates/manager/index.html.twig b/templates/manager/index.html.twig deleted file mode 100644 index 7b6109e..0000000 --- a/templates/manager/index.html.twig +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'manager/layout.html.twig' %} - -{% block content %} -{% endblock %} \ No newline at end of file diff --git a/templates/manager/layout.html.twig b/templates/manager/layout.html.twig deleted file mode 100644 index 039e670..0000000 --- a/templates/manager/layout.html.twig +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'layout.html.twig' %} - -{% block main_menu %} - {{ knp_menu_render(knp_menu_get('manager_main')) }} -{% endblock %} - -{% block mobile_menu %} - {{ knp_menu_render(knp_menu_get('manager_main')) }} -{% endblock %} diff --git a/templates/teamer/application.html.twig b/templates/teamer/application.html.twig deleted file mode 100644 index 3b61ac8..0000000 --- a/templates/teamer/application.html.twig +++ /dev/null @@ -1,101 +0,0 @@ -{% extends 'teamer/layout.html.twig' %} - -{% block title %}Bewerbung{% endblock %} - -{% block content %} -

- Bewerbung -

-
-
-
- Jobprofil -
-
- {{ assignment.jobProfile.name }} -
-
- Destination -
-
- {{ assignment.destination.product }} -
- {{ assignment.destination.hotel }} -
-
- Einsatztermin -
-
- {{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }} -
- {% if assignment.pickup and assignment.pickupDate %} -
- Busabfahrt -
-
- {{ assignment.pickupDate|date('d.m.Y') }} -
-
- Busbegleitung -
-
- ab {{ assignment.pickup|bpn_pickup_label }} -
- {% endif %} -
- Du bekommst -
-
- {{ assignment.benefits|nl2list }} -
-
- Honorar -
-
- {% for fee in assignment.fees %} - {{ fee.name }} {{ fee.value|format_money }} - {% endfor %} -
-
-
-

- Voraussetzungen für diesen Einsatz -

-
    - {% if assignment.pickup and assignment.pickupDate %} -
  • -
    - Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }} - {% if teamer.hasPickup(assignment.pickup) %} - {{ icon('check', 'w-5 h-5 text-emerald-500') }} - {% else %} - {{ icon('close', 'w-5 h-5 text-red-500') }} - {% endif %} -
    -
  • - {% endif %} - {% for training in assignment.jobProfile.requiredTrainings %} -
  • -
    - {{ training.name }} absolviert - {% if teamer.trainingAttendance(training) %} - {{ icon('check', 'w-5 h-5 text-emerald-500') }} - {% else %} - {{ icon('close', 'w-5 h-5 text-red-500') }} - {% endif %} -
    -
  • - {% endfor %} -
- {{ form_start(form) }} - {% if form.confirmPickup is defined %} - {{ form_row(form.confirmPickup) }} - {% endif %} - - {{ form_rest(form) }} - {{ form_end(form) }} -
-
-{% endblock %} \ No newline at end of file diff --git a/templates/teamer/application/create.html.twig b/templates/teamer/application/create.html.twig new file mode 100644 index 0000000..145c2fc --- /dev/null +++ b/templates/teamer/application/create.html.twig @@ -0,0 +1,32 @@ +{% extends 'teamer/layout.html.twig' %} + +{% block title %}Bewerbung{% endblock %} + +{% block content %} +

+ Bewerbung +

+
+
+ {% include '_partials/_assignment_info.html.twig' %} +
+
+

+ Voraussetzungen für diesen Einsatz +

+ {% include '_partials/_assignment_requirements_info.html.twig' %} + {{ form_start(form) }} +
+ {% if form.confirmPickup is defined %} + {{ form_row(form.confirmPickup) }} + {% endif %} + {{ form_row(form.requests) }} +
+ + {{ form_rest(form) }} + {{ form_end(form) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/teamer/assignment.html.twig b/templates/teamer/assignment.html.twig index 7c3642c..e4488fc 100644 --- a/templates/teamer/assignment.html.twig +++ b/templates/teamer/assignment.html.twig @@ -7,78 +7,46 @@ Einsatzübersicht
-
-
- Jobprofil -
-
- {{ assignment.jobProfile.name }} -
-
- Destination -
-
- {{ assignment.destination.product }} -
- {{ assignment.destination.hotel }} -
-
- Einsatztermin -
-
- {{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }} -
- {% if assignment.pickup and assignment.pickupDate %} -
- Busabfahrt -
-
- {{ assignment.pickupDate|date('d.m.Y') }} -
-
- Busbegleitung -
-
- ab {{ assignment.pickup|bpn_pickup_label }} -
- {% endif %} -
- Du bekommst -
-
- {{ assignment.benefits|nl2list }} -
-
- Honorar -
-
- {% for fee in assignment.fees %} - {{ fee.name }} {{ fee.value|format_money }} - {% endfor %} -
+
+ {% include '_partials/_assignment_info.html.twig' %}

Voraussetzungen für diesen Einsatz

-
    - {% if assignment.pickup and assignment.pickupDate %} -
  • - Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }} -
  • - {% endif %} - {% for training in assignment.jobProfile.requiredTrainings %} -
  • - {{ training.name }} absolviert -
  • - {% endfor %} -
-

- Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? -

- - Hier bewerben! - + {% include '_partials/_assignment_requirements_info.html.twig' %} + {% if application is null %} +
+ Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz? +
+ + {% else %} +
+ Du hast dich am {{ application.createdAt|date('d.m.Y') }} auf diesen Einsatz beworben. +
+
+ +
+ {% endif %} + {% if not isBookmarked %} + + auf die Merkliste setzen + + {% endif %}
{% endblock %} \ No newline at end of file