Feat: Refactor application rejecting process
This commit is contained in:
@@ -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;
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }}
|
||||
@@ -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) %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user