feat: rework automatic job profile assignment

This commit is contained in:
Björn Fromme
2024-11-26 16:46:29 +01:00
parent d52cdfca85
commit 97d658e71a
11 changed files with 220 additions and 68 deletions
@@ -3,10 +3,10 @@
namespace App\Controller\Admin\Teamer\TrainingAttendance;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\JobProfile;
use App\Entity\Teamer;
use App\Entity\Training;
use App\Entity\TrainingAttendance;
use App\Event\TrainingAttendanceCreatedEvent;
use App\Form\AbbreviatedDateType;
use App\Htmx\HxRedirectResponse;
use Doctrine\ORM\EntityManagerInterface;
@@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class CreateController extends AbstractController
{
@@ -24,11 +25,15 @@ class CreateController extends AbstractController
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
#[Route('/admin/teamer/skills/training-attendance/create/{training_id}/{teamer_id}', name: 'app_admin_teamer_skills_training_attendance_create')]
#[Route(
path: '/admin/teamer/skills/training-attendance/create/{training_id}/{teamer_id}',
name: 'app_admin_teamer_skills_training_attendance_create'
)]
#[IsGranted('ROLE_ADMIN')]
public function index(
#[MapEntity(mapping: ['training_id' => 'id'])]
@@ -58,9 +63,13 @@ class CreateController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($attendance);
// $this->assignJobProfiles($teamer, $attendance);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(
new TrainingAttendanceCreatedEvent($attendance),
TrainingAttendanceCreatedEvent::NAME
);
$this->addFlash('success', 'Die Teilnahme wurde hinzugefügt');
$this->logger->info('Create training attendance', [
'attendance_id' => $attendance->getId(),
@@ -81,25 +90,4 @@ class CreateController extends AbstractController
'form' => $form->createView(),
]);
}
private function assignJobProfiles(Teamer $teamer, TrainingAttendance $attendance): void
{
$qb = $this
->entityManager
->getRepository(JobProfile::class)
->createQueryBuilder('job_profile')
;
$jobProfiles = $qb
->leftJoin('job_profile.requiredTrainings', 'required_trainings')
->where($qb->expr()->in('required_trainings', ':training'))
->setParameter('training', $attendance->getTraining())
->getQuery()
->getResult()
;
foreach ($jobProfiles as $jobProfile) {
$teamer->addJobProfile($jobProfile);
}
}
}
@@ -4,6 +4,7 @@ namespace App\Controller\Admin\Teamer\TrainingAttendance;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\TrainingAttendance;
use App\Event\TrainingAttendanceDeletedEvent;
use App\Htmx\HxRedirectResponse;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
@@ -12,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class DeleteController extends AbstractController
{
@@ -19,11 +21,15 @@ class DeleteController extends AbstractController
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
#[Route('/admin/teamer/skills/training-attendance/delete/{uuid}', name: 'app_admin_teamer_skills_training_attendance_delete')]
#[Route(
path: '/admin/teamer/skills/training-attendance/delete/{uuid}',
name: 'app_admin_teamer_skills_training_attendance_delete'
)]
#[IsGranted('ROLE_ADMIN')]
public function index(TrainingAttendance $attendance, Request $request): Response
{
@@ -33,6 +39,11 @@ class DeleteController extends AbstractController
$this->entityManager->remove($attendance);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(
new TrainingAttendanceDeletedEvent($attendance),
TrainingAttendanceDeletedEvent::NAME
);
$this->addFlash('success', 'Die Teilnahme an der Fortbilundg wurde gelöscht');
$this->logger->info('Delete training attendance', [
'attendance_id' => $attendance->getId(),
@@ -2,11 +2,10 @@
namespace App\Controller\Teamer\Profile\License;
use App\Entity\JobProfile;
use App\Entity\License;
use App\Entity\Teamer;
use App\Entity\Upload;
use App\Entity\User;
use App\Event\LicenseAddedEvent;
use App\Form\LicenseType;
use App\Htmx\HxRedirectResponse;
use App\Model\UploadSessionDto;
@@ -18,12 +17,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class AddController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly UploadHandler $uploadHandler,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
@@ -49,11 +50,12 @@ class AddController extends AbstractController
$teamer = $user->getTeamer();
$teamer->addLicense($license);
$this->assignJobProfiles($license, $teamer);
$this->entityManager->persist($license);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(new LicenseAddedEvent($license), LicenseAddedEvent::NAME);
$this->addFlash('success', 'Die Lizenz wurde hinzugefügt.');
$this->logger->info('Add license', [
'teamer_id' => $teamer->getId(),
@@ -69,23 +71,6 @@ class AddController extends AbstractController
]);
}
private function assignJobProfiles(License $license, Teamer $teamer): void
{
if (false === in_array($license->getType(), [License::TYPE_SKI, License::TYPE_SNOWBOARD])) {
return;
}
$matchingJobProfiles = $this
->entityManager
->getRepository(JobProfile::class)
->findByLicenseType($license->getType())
;
foreach ($matchingJobProfiles as $matchingJobProfile) {
$teamer->addJobProfile($matchingJobProfile);
}
}
private function updateCertificate(User $user, License $license, UploadSessionDto $uploadSession): void
{
$upload = Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, Upload::TYPE_CERTIFICATE);
@@ -3,6 +3,7 @@
namespace App\Controller\Teamer\Profile\License;
use App\Entity\License;
use App\Event\LicenseDeletedEvent;
use App\Htmx\HxRedirectResponse;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
@@ -11,11 +12,13 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class DeleteController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger
) {
}
@@ -30,6 +33,8 @@ class DeleteController extends AbstractController
$this->entityManager->remove($license);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(new LicenseDeletedEvent($license), LicenseDeletedEvent::NAME);
$this->addFlash('success', 'Die Lizenz wurde gelöscht.');
$this->logger->info('Delete license', [
'teamer_id' => $teamer->getId(),
@@ -2,7 +2,6 @@
namespace App\Controller\Teamer\Profile;
use App\Entity\JobProfile;
use App\Entity\User;
use App\Form\TeamerJobProfileType;
use App\Repository\JobProfileRepository;
@@ -35,28 +34,7 @@ class SkillsController extends AbstractController
$selectableJobProfiles = [];
foreach ($jobProfiles as $profile) {
/** @var JobProfile $profile */
$requiredTrainingsSatisfied = false;
if (0 === $profile->getRequiredTrainings()->count()) {
$requiredTrainingsSatisfied = true;
}
foreach ($profile->getRequiredTrainings() as $training) {
if (null !== $teamer->getTrainingAttendance($training)) {
$requiredTrainingsSatisfied = true;
break;
}
}
$requiredLicensesSatisfied = false;
if (0 === count($profile->getRequiredLicenses())) {
$requiredLicensesSatisfied = true;
}
foreach ($profile->getRequiredLicenses() as $license) {
if (null !== $teamer->getLicenseOfType($license)) {
$requiredLicensesSatisfied = true;
break;
}
}
$selectableJobProfiles[$profile->getId()] = $requiredTrainingsSatisfied && $requiredLicensesSatisfied;
$selectableJobProfiles[$profile->getId()] = $profile->isQualified($teamer);
}
$form = $this->createForm(TeamerJobProfileType::class, $teamer, [
+33
View File
@@ -118,4 +118,37 @@ class JobProfile implements BlameableEntityInterface, TimestampableEntityInterfa
return $this;
}
public function isQualified(Teamer $teamer): bool
{
// Check licenses
$qualifiedByLicenses = true;
if (0 < $this->requiredTrainings->count()) {
$teamerLicenseTypes = $teamer->getLicenses()->map(function (License $license) {
return $license->getType();
})->toArray();
foreach ($this->requiredLicenses as $requiredLicense) {
if (false === in_array($requiredLicense, $teamerLicenseTypes)) {
$qualifiedByLicenses = false;
}
}
}
// Check trainings
$qualifiedByTrainings = true;
if (0 < $this->requiredTrainings->count()) {
$teamerTrainings = $teamer->getTrainingAttendances()->map(function (TrainingAttendance $trainingAttendance) {
return $trainingAttendance->getTraining();
});
foreach ($this->requiredTrainings as $requiredTraining) {
if (false === $teamerTrainings->contains($requiredTraining)) {
$qualifiedByTrainings = false;
}
}
}
return $qualifiedByLicenses && $qualifiedByTrainings;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Event;
use App\Entity\License;
use Symfony\Contracts\EventDispatcher\Event;
class LicenseAddedEvent extends Event
{
public const NAME = 'license.added';
public function __construct(private readonly License $license)
{
}
public function getLicense(): License
{
return $this->license;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Event;
use App\Entity\License;
use Symfony\Contracts\EventDispatcher\Event;
class LicenseDeletedEvent extends Event
{
public const NAME = 'license.deleted';
public function __construct(private readonly License $license)
{
}
public function getLicense(): License
{
return $this->license;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Event;
use App\Entity\TrainingAttendance;
use Symfony\Contracts\EventDispatcher\Event;
class TrainingAttendanceCreatedEvent extends Event
{
public const NAME = 'training_attendance.created';
public function __construct(private readonly TrainingAttendance $trainingAttendance)
{
}
public function getTrainingAttendance(): TrainingAttendance
{
return $this->trainingAttendance;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Event;
use App\Entity\TrainingAttendance;
use Symfony\Contracts\EventDispatcher\Event;
class TrainingAttendanceDeletedEvent extends Event
{
public const NAME = 'training_attendance.deleted';
public function __construct(private readonly TrainingAttendance $trainingAttendance)
{
}
public function getTrainingAttendance(): TrainingAttendance
{
return $this->trainingAttendance;
}
}
@@ -0,0 +1,72 @@
<?php
namespace App\EventListener;
use App\Entity\JobProfile;
use App\Entity\Teamer;
use App\Event\LicenseAddedEvent;
use App\Event\LicenseDeletedEvent;
use App\Event\TrainingAttendanceCreatedEvent;
use App\Event\TrainingAttendanceDeletedEvent;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class JobProfileAssignmentSubscriber implements EventSubscriberInterface
{
public function __construct(private readonly EntityManagerInterface $entityManager)
{
}
public static function getSubscribedEvents(): array
{
return [
TrainingAttendanceCreatedEvent::NAME => 'onTrainingAttendanceCreated',
TrainingAttendanceDeletedEvent::NAME => 'onTrainingAttendanceDeleted',
LicenseAddedEvent::NAME => 'onLicenseAdded',
LicenseDeletedEvent::NAME => 'onLicenseDeleted',
];
}
public function onTrainingAttendanceCreated(TrainingAttendanceCreatedEvent $event): void
{
$teamer = $event->getTrainingAttendance()->getTeamer();
$this->updateJobProfiles($teamer);
}
public function onTrainingAttendanceDeleted(TrainingAttendanceDeletedEvent $event): void
{
$teamer = $event->getTrainingAttendance()->getTeamer();
$this->updateJobProfiles($teamer);
}
public function onLicenseAdded(LicenseAddedEvent $event): void
{
$teamer = $event->getLicense()->getTeamer();
$this->updateJobProfiles($teamer);
}
public function onLicenseDeleted(LicenseDeletedEvent $event): void
{
$teamer = $event->getLicense()->getTeamer();
$this->updateJobProfiles($teamer);
}
private function updateJobProfiles(Teamer $teamer): void
{
$jobProfiles = $this
->entityManager
->getRepository(JobProfile::class)
->findAll()
;
foreach ($jobProfiles as $jobProfile) {
if ($jobProfile->isQualified($teamer)) {
$teamer->addJobProfile($jobProfile);
} else {
$teamer->removeJobProfile($jobProfile);
}
}
$this->entityManager->flush();
}
}