Feat: Refactor application rejecting process

This commit is contained in:
Björn Fromme
2023-10-19 10:24:53 +02:00
parent 2c49c142b8
commit 21968b833c
12 changed files with 185 additions and 66 deletions
@@ -1,46 +0,0 @@
<?php
namespace App\Controller\Admin\Application;
use App\Entity\Application;
use App\Event\ApplicationRejectedEvent;
use App\Model\ApplicationCheckDto;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class RejectController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
#[Route('/admin/application/reject/{uuid}', name: 'app_admin_application_reject')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
#[IsGranted('REJECT', subject: 'application')]
public function index(Application $application): Response
{
$applicationCheckDto = new ApplicationCheckDto($application);
$this->eventDispatcher->dispatch(new ApplicationRejectedEvent($applicationCheckDto), ApplicationRejectedEvent::NAME);
$application->setStatus(Application::STATUS_REJECTED);
$this->entityManager->flush();
$this->addFlash('success', 'Die Bewerbung wurde abgelehnt');
$this->logger->info('Reject application', [
'application' => $application->getUuid(),
]);
return $this->redirectToRoute('app_admin_assignment_detail', [
'uuid' => $application->getAssignment()->getUuid(),
]);
}
}
@@ -0,0 +1,62 @@
<?php
namespace App\Controller\Admin\Application;
use App\Entity\Application;
use App\Event\ApplicationStatusEvent;
use App\Form\ApplicationCheckType;
use App\Model\AjaxModalResponseDto;
use App\Model\ApplicationCheckDto;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class StatusController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
#[Route('/admin/application/status/{uuid}', name: 'app_admin_application_status')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
#[IsGranted('STATUS', subject: 'application')]
public function index(Application $application, Request $request): JsonResponse
{
$response = new AjaxModalResponseDto();
$formAction = $this->generateUrl('app_admin_application_status', ['uuid' => $application->getUuid()]);
$formData = new ApplicationCheckDto($application);
$form = $this->createForm(ApplicationCheckType::class, $formData, ['action' => $formAction, 'ajax_submit' => true]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->eventDispatcher->dispatch(new ApplicationStatusEvent($formData), ApplicationStatusEvent::NAME);
$application->setStatus($formData->getStatus());
$this->entityManager->flush();
$this->addFlash('success', 'Der Status der Bewerbung wurde aktualisiert');
$this->logger->info('Update application status', [
'application' => $application->getUuid(),
'status_new' => $formData->getStatus(),
]);
$response->setCloseAndRedirect($this->generateUrl('app_admin_assignment_detail', [
'uuid' => $application->getAssignment()->getUuid(),
]));
} else {
$response->setContent($this->renderView('admin/application/status.html.twig', [
'form' => $form,
]));
}
return $this->json($response);
}
}
@@ -26,13 +26,15 @@ class DetailController extends AbstractController
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function index(Assignment $assignment, Request $request): Response
{
$applications = $this->applicationRepository->findBy([
'assignment' => $assignment,
]);
$applications = $this
->applicationRepository
->getCurrentByAssignment($assignment)
;
$dispositions = $this->dispositionRepository->findBy([
'assignment' => $assignment,
]);
$dispositions = $this
->dispositionRepository
->getCurrentByAssignment($assignment)
;
return $this->render('admin/assignment/detail.html.twig', [
'assignment' => $assignment,
@@ -6,9 +6,9 @@ use App\Entity\Application;
use App\Model\ApplicationCheckDto;
use Symfony\Contracts\EventDispatcher\Event;
class ApplicationRejectedEvent extends Event
class ApplicationStatusEvent extends Event
{
public const NAME = 'application.rejected';
public const NAME = 'application.status';
private Application $application;
private ?string $comment;
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Form;
use App\Entity\Application;
use App\Model\ApplicationCheckDto;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ApplicationCheckType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('status', ChoiceType::class, [
'label' => 'neuer Status',
'choices' => [
'in Bearbeitung' => Application::STATUS_PENDING,
'abgelehnt' => Application::STATUS_REJECTED,
],
])
->add('comment', TextareaType::class, [
'label' => 'Kommentar/Begründung',
'required' => false,
'attr' => [
'rows' => 3,
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ApplicationCheckDto::class,
]);
}
}
+14
View File
@@ -7,11 +7,13 @@ use App\Entity\Application;
class ApplicationCheckDto
{
private Application $application;
private string $status;
private ?string $comment = null;
public function __construct(Application $application)
{
$this->application = $application;
$this->status = $application->getStatus();
}
public function getApplication(): Application
@@ -19,6 +21,18 @@ class ApplicationCheckDto
return $this->application;
}
public function getStatus(): string
{
return $this->status;
}
public function setStatus(string $status): static
{
$this->status = $status;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
+19
View File
@@ -3,6 +3,7 @@
namespace App\Repository;
use App\Entity\Application;
use App\Entity\Assignment;
use App\Entity\Teamer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query;
@@ -53,4 +54,22 @@ class ApplicationRepository extends ServiceEntityRepository
->getQuery()
;
}
public function getCurrentByAssignment(Assignment $assignment): array
{
$qb = $this->createQueryBuilder('application');
return $qb
->select('application', 'teamer')
->innerJoin('application.teamer', 'teamer')
->where($qb->expr()->andX(
$qb->expr()->eq('application.assignment', ':assignment'),
$qb->expr()->neq('application.status', ':status')
))
->setParameter('assignment', $assignment)
->setParameter('status', Application::STATUS_REJECTED)
->getQuery()
->getResult()
;
}
}
+16
View File
@@ -2,6 +2,8 @@
namespace App\Repository;
use App\Entity\Application;
use App\Entity\Assignment;
use App\Entity\Disposition;
use App\Entity\Teamer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
@@ -78,4 +80,18 @@ class DispositionRepository extends ServiceEntityRepository
->getQuery()
;
}
public function getCurrentByAssignment(Assignment $assignment): array
{
$qb = $this->createQueryBuilder('disposition');
return $qb
->select('disposition', 'teamer')
->innerJoin('disposition.teamer', 'teamer')
->where($qb->expr()->eq('disposition.assignment', ':assignment'))
->setParameter('assignment', $assignment)
->getQuery()
->getResult()
;
}
}
+3 -4
View File
@@ -12,7 +12,7 @@ class ApplicationVoter extends Voter
public const VIEW = 'VIEW';
public const DELETE = 'DELETE';
public const DISPOSE = 'DISPOSE';
public const REJECT = 'REJECT';
public const STATUS = 'STATUS';
protected function supports(string $attribute, mixed $subject): bool
{
@@ -20,7 +20,7 @@ class ApplicationVoter extends Voter
return false;
}
return in_array($attribute, [static::VIEW, static::DELETE, static::DISPOSE, static::REJECT]);
return in_array($attribute, [static::VIEW, static::DELETE, static::DISPOSE, static::STATUS]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
@@ -34,9 +34,8 @@ class ApplicationVoter extends Voter
if ($user->hasRole('ROLE_ADMINISTRATIVE')) {
$assignment = $application->getAssignment();
return match ($attribute) {
static::VIEW => true,
static::VIEW, static::STATUS => Application::STATUS_REJECTED !== $application->getStatus(),
static::DELETE => Application::STATUS_REJECTED === $application->getStatus(),
static::REJECT => Application::STATUS_REJECTED !== $application->getStatus(),
static::DISPOSE => Application::STATUS_REJECTED !== $application->getStatus()
&& $assignment->getAvailableDispositions() > $assignment->getDispositions()->count(),
default => false,
@@ -0,0 +1,10 @@
{{ form_start(form) }}
<div class="flex flex-col space-y-4 pb-4">
{{ form_row(form.status) }}
{{ form_row(form.comment) }}
</div>
<button type="submit" class="btn">
Aktualisieren
</button>
{{ form_rest(form) }}
{{ form_end(form) }}
+6 -7
View File
@@ -58,15 +58,14 @@
einteilen
</button>
{% endif %}
{% if is_granted('REJECT', application) %}
{% if is_granted('STATUS', application) %}
<button type="button" class="btn btn--secondary btn--small"
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
{{ stimulus_action('modal-button', 'confirmation', null, {
'title': 'Bist du sicher?',
'content': 'Möchtest du die Bewerbung von ' ~ application.teamer ~ ' wirklich ablehnen?',
'target-url': path('app_admin_application_reject', { 'uuid': application.uuid })
{{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }}
{{ stimulus_action('modal-button', 'ajax', null, {
'title': 'Bewerbungsstatus bearbeiten',
'url': path('app_admin_application_status', { 'uuid': application.uuid })
}) }}>
ablehnen
Status ändern
</button>
{% endif %}
{% if is_granted('DELETE', application) %}
+4 -1
View File
@@ -68,14 +68,17 @@
{% if assignment.applications|length %}
{% set icon = 'hourglass' %}
{% set class = 'bg-yellow-100 text-yellow-800 hover:bg-yellow-50' %}
{% set url = path('app_teamer_assignment', { 'uuid': assignment.uuid, 'r': return_url() }) %}
{% elseif assignment.dispositions|length %}
{% set icon = 'check' %}
{% set class = 'bg-green-100 text-green-700 hover:bg-green-50' %}
{% set url = path('app_teamer_disposition_detail', { 'uuid': assignment.dispositions[0].uuid }) %}
{% else %}
{% set icon = 'info' %}
{% set class = 'btn--info' %}
{% set url = path('app_teamer_assignment', { 'uuid': assignment.uuid, 'r': return_url() }) %}
{% endif %}
<a href="{{ path('app_teamer_assignment', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
<a href="{{ url }}"
class="btn btn--small {{ class }}">
{{ icon(icon, 'w-4 h-4') }}
</a>