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')]
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
public function index(Assignment $assignment, Request $request): Response
|
public function index(Assignment $assignment, Request $request): Response
|
||||||
{
|
{
|
||||||
$applications = $this->applicationRepository->findBy([
|
$applications = $this
|
||||||
'assignment' => $assignment,
|
->applicationRepository
|
||||||
]);
|
->getCurrentByAssignment($assignment)
|
||||||
|
;
|
||||||
|
|
||||||
$dispositions = $this->dispositionRepository->findBy([
|
$dispositions = $this
|
||||||
'assignment' => $assignment,
|
->dispositionRepository
|
||||||
]);
|
->getCurrentByAssignment($assignment)
|
||||||
|
;
|
||||||
|
|
||||||
return $this->render('admin/assignment/detail.html.twig', [
|
return $this->render('admin/assignment/detail.html.twig', [
|
||||||
'assignment' => $assignment,
|
'assignment' => $assignment,
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use App\Entity\Application;
|
|||||||
use App\Model\ApplicationCheckDto;
|
use App\Model\ApplicationCheckDto;
|
||||||
use Symfony\Contracts\EventDispatcher\Event;
|
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 Application $application;
|
||||||
private ?string $comment;
|
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
|
class ApplicationCheckDto
|
||||||
{
|
{
|
||||||
private Application $application;
|
private Application $application;
|
||||||
|
private string $status;
|
||||||
private ?string $comment = null;
|
private ?string $comment = null;
|
||||||
|
|
||||||
public function __construct(Application $application)
|
public function __construct(Application $application)
|
||||||
{
|
{
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
|
$this->status = $application->getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getApplication(): Application
|
public function getApplication(): Application
|
||||||
@@ -19,6 +21,18 @@ class ApplicationCheckDto
|
|||||||
return $this->application;
|
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
|
public function getComment(): ?string
|
||||||
{
|
{
|
||||||
return $this->comment;
|
return $this->comment;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\Application;
|
use App\Entity\Application;
|
||||||
|
use App\Entity\Assignment;
|
||||||
use App\Entity\Teamer;
|
use App\Entity\Teamer;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
@@ -53,4 +54,22 @@ class ApplicationRepository extends ServiceEntityRepository
|
|||||||
->getQuery()
|
->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;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Application;
|
||||||
|
use App\Entity\Assignment;
|
||||||
use App\Entity\Disposition;
|
use App\Entity\Disposition;
|
||||||
use App\Entity\Teamer;
|
use App\Entity\Teamer;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
@@ -78,4 +80,18 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
->getQuery()
|
->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 VIEW = 'VIEW';
|
||||||
public const DELETE = 'DELETE';
|
public const DELETE = 'DELETE';
|
||||||
public const DISPOSE = 'DISPOSE';
|
public const DISPOSE = 'DISPOSE';
|
||||||
public const REJECT = 'REJECT';
|
public const STATUS = 'STATUS';
|
||||||
|
|
||||||
protected function supports(string $attribute, mixed $subject): bool
|
protected function supports(string $attribute, mixed $subject): bool
|
||||||
{
|
{
|
||||||
@@ -20,7 +20,7 @@ class ApplicationVoter extends Voter
|
|||||||
return false;
|
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
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||||
@@ -34,9 +34,8 @@ class ApplicationVoter extends Voter
|
|||||||
if ($user->hasRole('ROLE_ADMINISTRATIVE')) {
|
if ($user->hasRole('ROLE_ADMINISTRATIVE')) {
|
||||||
$assignment = $application->getAssignment();
|
$assignment = $application->getAssignment();
|
||||||
return match ($attribute) {
|
return match ($attribute) {
|
||||||
static::VIEW => true,
|
static::VIEW, static::STATUS => Application::STATUS_REJECTED !== $application->getStatus(),
|
||||||
static::DELETE => 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()
|
static::DISPOSE => Application::STATUS_REJECTED !== $application->getStatus()
|
||||||
&& $assignment->getAvailableDispositions() > $assignment->getDispositions()->count(),
|
&& $assignment->getAvailableDispositions() > $assignment->getDispositions()->count(),
|
||||||
default => false,
|
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
|
einteilen
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('REJECT', application) %}
|
{% if is_granted('STATUS', application) %}
|
||||||
<button type="button" class="btn btn--secondary btn--small"
|
<button type="button" class="btn btn--secondary btn--small"
|
||||||
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
|
{{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }}
|
||||||
{{ stimulus_action('modal-button', 'confirmation', null, {
|
{{ stimulus_action('modal-button', 'ajax', null, {
|
||||||
'title': 'Bist du sicher?',
|
'title': 'Bewerbungsstatus bearbeiten',
|
||||||
'content': 'Möchtest du die Bewerbung von ' ~ application.teamer ~ ' wirklich ablehnen?',
|
'url': path('app_admin_application_status', { 'uuid': application.uuid })
|
||||||
'target-url': path('app_admin_application_reject', { 'uuid': application.uuid })
|
|
||||||
}) }}>
|
}) }}>
|
||||||
ablehnen
|
Status ändern
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('DELETE', application) %}
|
{% if is_granted('DELETE', application) %}
|
||||||
|
|||||||
@@ -68,14 +68,17 @@
|
|||||||
{% if assignment.applications|length %}
|
{% if assignment.applications|length %}
|
||||||
{% set icon = 'hourglass' %}
|
{% set icon = 'hourglass' %}
|
||||||
{% set class = 'bg-yellow-100 text-yellow-800 hover:bg-yellow-50' %}
|
{% 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 %}
|
{% elseif assignment.dispositions|length %}
|
||||||
{% set icon = 'check' %}
|
{% set icon = 'check' %}
|
||||||
{% set class = 'bg-green-100 text-green-700 hover:bg-green-50' %}
|
{% 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 %}
|
{% else %}
|
||||||
{% set icon = 'info' %}
|
{% set icon = 'info' %}
|
||||||
{% set class = 'btn--info' %}
|
{% set class = 'btn--info' %}
|
||||||
|
{% set url = path('app_teamer_assignment', { 'uuid': assignment.uuid, 'r': return_url() }) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ path('app_teamer_assignment', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
|
<a href="{{ url }}"
|
||||||
class="btn btn--small {{ class }}">
|
class="btn btn--small {{ class }}">
|
||||||
{{ icon(icon, 'w-4 h-4') }}
|
{{ icon(icon, 'w-4 h-4') }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user