diff --git a/assets/images/icons.svg b/assets/images/icons.svg
index 53df675..9e62d41 100644
--- a/assets/images/icons.svg
+++ b/assets/images/icons.svg
@@ -197,6 +197,7 @@
+
diff --git a/config/packages/oneup_uploader.yaml b/config/packages/oneup_uploader.yaml
index d6ab107..984c6e7 100644
--- a/config/packages/oneup_uploader.yaml
+++ b/config/packages/oneup_uploader.yaml
@@ -30,10 +30,16 @@ oneup_uploader:
namer: app.upload_namer
storage:
directory: '%kernel.project_dir%/uploads/document'
+ driver_license:
+ frontend: dropzone
+ use_orphanage: true
+ namer: app.upload_namer
+ storage:
+ directory: '%kernel.project_dir%/uploads/driver_license'
chunks:
maxage: 86400
storage:
directory: '%kernel.project_dir%/uploads/temp'
orphanage:
maxage: 3600
- directory: '%kernel.project_dir%/uploads/orphanage'
\ No newline at end of file
+ directory: '%kernel.project_dir%/uploads/orphanage'
diff --git a/config/services.yaml b/config/services.yaml
index 0fb7c9a..b7ee4eb 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -160,6 +160,13 @@ services:
$datevEmailRecipient: '%env(DATEV_EMAIL_RECIPIENT)%'
$datevEmailSender: '%env(DATEV_EMAIL_SENDER)%'
+ App\Security\RequiredCheck\RequiredTeamerCheckRegistry:
+ arguments:
+ $checks: !tagged_iterator app.required_teamer_check
+
+ App\Security\RequiredCheck\DriverLicenseRequiredCheck:
+ tags: [ 'app.required_teamer_check' ]
+
app.upload_namer:
class: App\Service\Upload\UploadNamer
public: true
diff --git a/migrations/Version20260227111223.php b/migrations/Version20260227111223.php
new file mode 100644
index 0000000..7ca816f
--- /dev/null
+++ b/migrations/Version20260227111223.php
@@ -0,0 +1,33 @@
+addSql('ALTER TABLE teamer ADD driver_license_upload_id INT DEFAULT NULL, ADD driver_license_declaration TINYINT(1) DEFAULT NULL, ADD driver_license_verified TINYINT(1) NOT NULL DEFAULT 0, ADD driver_license_status VARCHAR(64) NOT NULL DEFAULT \'none\', ADD driver_license_review_comment LONGTEXT DEFAULT NULL, ADD driver_license_reviewed_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD driver_license_reviewed_by VARCHAR(8) DEFAULT NULL');
+ $this->addSql('ALTER TABLE teamer ADD CONSTRAINT FK_15B751AE1C882DBD FOREIGN KEY (driver_license_upload_id) REFERENCES upload (id)');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_15B751AE1C882DBD ON teamer (driver_license_upload_id)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE teamer DROP FOREIGN KEY FK_15B751AE1C882DBD');
+ $this->addSql('DROP INDEX UNIQ_15B751AE1C882DBD ON teamer');
+ $this->addSql('ALTER TABLE teamer DROP driver_license_upload_id, DROP driver_license_declaration, DROP driver_license_verified, DROP driver_license_status, DROP driver_license_review_comment, DROP driver_license_reviewed_at, DROP driver_license_reviewed_by');
+ }
+}
diff --git a/src/Controller/Admin/IndexController.php b/src/Controller/Admin/IndexController.php
index 771d189..1be426c 100644
--- a/src/Controller/Admin/IndexController.php
+++ b/src/Controller/Admin/IndexController.php
@@ -44,6 +44,8 @@ class IndexController extends AbstractController
$newInvoices = $uploadRepository->getNewByType(Upload::TYPE_INVOICE);
$newInvoicesCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_INVOICE);
$pendingInvoicesCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_PENDING, Upload::TYPE_INVOICE);
+ $newDriverLicenses = $uploadRepository->getNewByType(Upload::TYPE_DRIVER_LICENSE);
+ $newDriverLicensesCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_DRIVER_LICENSE);
$dispositionRepository = $this->entityManager->getRepository(Disposition::class);
$overdueContracts = $dispositionRepository->findOverdueContracts();
@@ -64,6 +66,8 @@ class IndexController extends AbstractController
'newInvoices' => $newInvoices,
'newInvoicesCount' => $newInvoicesCount,
'pendingInvoicesCount' => $pendingInvoicesCount,
+ 'newDriverLicenses' => $newDriverLicenses,
+ 'newDriverLicensesCount' => $newDriverLicensesCount,
'overdueContracts' => $overdueContracts,
'newDispositions' => $newDispositions,
'overdueFeedbacks' => $overdueFeedbacks,
diff --git a/src/Controller/Administrative/Teamer/DriverLicense/CheckController.php b/src/Controller/Administrative/Teamer/DriverLicense/CheckController.php
new file mode 100644
index 0000000..a13455e
--- /dev/null
+++ b/src/Controller/Administrative/Teamer/DriverLicense/CheckController.php
@@ -0,0 +1,107 @@
+getType()) {
+ throw $this->createAccessDeniedException();
+ }
+
+ $teamer = $document->getOwner()?->getTeamer();
+ if (false === $teamer instanceof Teamer) {
+ throw $this->createNotFoundException('Teamer not found for upload');
+ }
+
+ $formData = new DriverLicenseCheckDto($document);
+ $form = $this->createForm(DriverLicenseCheckType::class, $formData);
+ $form->handleRequest($request);
+
+ if (true === $form->isSubmitted() && true === $form->isValid()) {
+ $status = $formData->getStatus();
+ if (null === $status) {
+ throw $this->createNotFoundException('Status missing');
+ }
+
+ /** @var User $user */
+ $user = $this->getUser();
+
+ $document
+ ->setStatus($status)
+ ->setComment($formData->getComment())
+ ->setApprovedAt(Upload::STATUS_CHECKED === $status ? new \DateTimeImmutable() : null)
+ ->setApprovedBy(Upload::STATUS_CHECKED === $status ? $user->getInitials() : null)
+ ;
+
+ $teamer
+ ->setDriverLicenseDeclaration(true)
+ ->setDriverLicenseReviewComment($formData->getComment())
+ ->setDriverLicenseReviewedAt(new \DateTimeImmutable())
+ ->setDriverLicenseReviewedBy($user->getInitials())
+ ;
+
+ if (Upload::STATUS_CHECKED === $status) {
+ $teamer
+ ->setDriverLicenseVerified(true)
+ ->setDriverLicenseStatus(Teamer::DRIVER_LICENSE_STATUS_APPROVED)
+ ->setDriverLicenseUpload(null)
+ ;
+ $this->entityManager->remove($document);
+ $this->addFlash('success', 'Der Führerschein wurde bestätigt');
+ } elseif (Upload::STATUS_REJECTED === $status) {
+ $teamer
+ ->setDriverLicenseVerified(false)
+ ->setDriverLicenseStatus(Teamer::DRIVER_LICENSE_STATUS_DENIED)
+ ;
+ $this->addFlash('success', 'Der Führerschein wurde abgelehnt');
+ } else {
+ $teamer
+ ->setDriverLicenseVerified(false)
+ ->setDriverLicenseStatus(Teamer::DRIVER_LICENSE_STATUS_PENDING_REVIEW)
+ ;
+ $this->addFlash('success', 'Der Führerscheinstatus wurde aktualisiert');
+ }
+
+ $this->entityManager->flush();
+
+ $this->logger->info('Update driver license check', [
+ 'teamer_id' => $teamer->getId(),
+ 'teamer_name' => (string) $teamer,
+ 'status' => $status,
+ ]);
+
+ return new HxRedirectResponse($this->generateUrl('app_admin_index'));
+ }
+
+ return $this->render('administrative/teamer/driver_license/modal_check.html.twig', [
+ 'document' => $document,
+ 'teamer' => $teamer,
+ 'form' => $form->createView(),
+ ]);
+ }
+}
diff --git a/src/Controller/Teamer/Check/DriverLicenseController.php b/src/Controller/Teamer/Check/DriverLicenseController.php
new file mode 100644
index 0000000..61742c2
--- /dev/null
+++ b/src/Controller/Teamer/Check/DriverLicenseController.php
@@ -0,0 +1,147 @@
+getUser();
+ $teamer = $user->getTeamer();
+
+ if (null === $teamer) {
+ $teamer = new Teamer();
+ $user->setTeamer($teamer);
+ $this->entityManager->persist($teamer);
+ $this->entityManager->flush();
+ }
+
+ $uploadSession = $this->uploadHandler->getUploadSession();
+ if (true === $request->isMethod('POST') && 0 < $uploadSession->getCount()) {
+ $this->updateDriverLicenseUpload($user, $teamer, $uploadSession);
+ }
+
+ $formData = new DriverLicenseDeclarationDto($teamer->getDriverLicenseDeclaration());
+ $form = $this->createForm(DriverLicenseDeclarationType::class, $formData, ['upload_session' => $uploadSession]);
+ $form->handleRequest($request);
+
+ if (true === $form->isSubmitted() && true === $form->isValid()) {
+ if (false === $formData->hasDriverLicense()) {
+ $this->handleNoDriverLicense($teamer);
+ $this->entityManager->flush();
+
+ return $this->redirectToRoute('app_teamer_index');
+ }
+
+ if (0 < $form->getErrors(true)->count()) {
+ // Continue rendering form with errors.
+ } elseif (null === $teamer->getDriverLicenseUpload()) {
+ $form->addError(new FormError('Bitte lade ein Foto oder PDF deines Führerscheins hoch.'));
+ } elseif (
+ Teamer::DRIVER_LICENSE_STATUS_DENIED === $teamer->getDriverLicenseStatus()
+ && Upload::STATUS_REJECTED === $teamer->getDriverLicenseUpload()->getStatus()
+ ) {
+ $form->addError(new FormError('Bitte lade einen neuen Führerschein hoch, damit wir ihn erneut prüfen können.'));
+ } else {
+ $teamer
+ ->setDriverLicenseDeclaration(true)
+ ->setDriverLicenseVerified(false)
+ ->setDriverLicenseStatus(Teamer::DRIVER_LICENSE_STATUS_PENDING_REVIEW)
+ ->setDriverLicenseReviewComment(null)
+ ->setDriverLicenseReviewedAt(null)
+ ->setDriverLicenseReviewedBy(null)
+ ;
+
+ $upload = $teamer->getDriverLicenseUpload();
+ $upload
+ ->setStatus(Upload::STATUS_NEW)
+ ->setComment(null)
+ ->setApprovedAt(null)
+ ->setApprovedBy(null)
+ ;
+
+ $this->entityManager->flush();
+
+ $this->addFlash('success', 'Dein Führerschein wurde hochgeladen und wird geprüft.');
+ $this->logger->info('Submit driver license for review', [
+ 'teamer_id' => $teamer->getId(),
+ 'teamer_name' => (string) $teamer,
+ ]);
+
+ return $this->redirectToRoute('app_teamer_index');
+ }
+ }
+
+ return $this->render('teamer/check/driver_license.html.twig', [
+ 'form' => $form->createView(),
+ 'teamer' => $teamer,
+ ]);
+ }
+
+ private function handleNoDriverLicense(Teamer $teamer): void
+ {
+ if (null !== $existingUpload = $teamer->getDriverLicenseUpload()) {
+ $this->entityManager->remove($existingUpload);
+ }
+
+ $teamer
+ ->setDriverLicenseUpload(null)
+ ->setDriverLicenseDeclaration(false)
+ ->setDriverLicenseVerified(false)
+ ->setDriverLicenseStatus(Teamer::DRIVER_LICENSE_STATUS_NONE)
+ ->setDriverLicenseReviewComment(null)
+ ->setDriverLicenseReviewedAt(null)
+ ->setDriverLicenseReviewedBy(null)
+ ;
+
+ $this->addFlash('success', 'Deine Angabe wurde gespeichert.');
+ $this->logger->info('Set no driver license', [
+ 'teamer_id' => $teamer->getId(),
+ 'teamer_name' => (string) $teamer,
+ ]);
+ }
+
+ private function updateDriverLicenseUpload(User $user, Teamer $teamer, UploadSessionDto $uploadSession): void
+ {
+ if (null !== $existingUpload = $teamer->getDriverLicenseUpload()) {
+ $this->entityManager->remove($existingUpload);
+ }
+
+ $upload = Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, Upload::TYPE_DRIVER_LICENSE);
+ $upload->setStatus(Upload::STATUS_NEW);
+
+ $teamer->setDriverLicenseUpload($upload);
+ $this->entityManager->flush();
+
+ $this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_DRIVER_LICENSE, $uploadSession);
+ $this->uploadHandler->destroyUploadSession();
+
+ $this->addFlash('success', 'Der Führerschein wurde hochgeladen.');
+ }
+}
diff --git a/src/Entity/Teamer.php b/src/Entity/Teamer.php
index b2ea2de..e2a68ce 100644
--- a/src/Entity/Teamer.php
+++ b/src/Entity/Teamer.php
@@ -23,6 +23,11 @@ class Teamer implements TimestampableEntityInterface
public const STATUS_NEW = 'new';
public const STATUS_EXISTING = 'existing';
+ public const DRIVER_LICENSE_STATUS_NONE = 'none';
+ public const DRIVER_LICENSE_STATUS_PENDING_REVIEW = 'pending_review';
+ public const DRIVER_LICENSE_STATUS_APPROVED = 'approved';
+ public const DRIVER_LICENSE_STATUS_DENIED = 'denied';
+
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
@@ -140,6 +145,27 @@ class Teamer implements TimestampableEntityInterface
#[ORM\Column]
private bool $allowOverlappingApplications = false;
+ #[ORM\Column(nullable: true)]
+ private ?bool $driverLicenseDeclaration = null;
+
+ #[ORM\Column]
+ private bool $driverLicenseVerified = false;
+
+ #[ORM\Column(length: 64)]
+ private string $driverLicenseStatus = self::DRIVER_LICENSE_STATUS_NONE;
+
+ #[ORM\OneToOne(cascade: ['persist', 'remove'])]
+ private ?Upload $driverLicenseUpload = null;
+
+ #[ORM\Column(type: Types::TEXT, nullable: true)]
+ private ?string $driverLicenseReviewComment = null;
+
+ #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
+ private ?\DateTimeImmutable $driverLicenseReviewedAt = null;
+
+ #[ORM\Column(length: 8, nullable: true)]
+ private ?string $driverLicenseReviewedBy = null;
+
public function __construct()
{
$this->uuid = Uuid::v4();
@@ -750,7 +776,91 @@ class Teamer implements TimestampableEntityInterface
public function hasPickup(int $pickupId): bool
{
- return in_array($pickupId, $this->getPickups());
+ return true === in_array($pickupId, $this->getPickups(), true);
+ }
+
+ public function getDriverLicenseDeclaration(): ?bool
+ {
+ return $this->driverLicenseDeclaration;
+ }
+
+ public function setDriverLicenseDeclaration(?bool $driverLicenseDeclaration): static
+ {
+ $this->driverLicenseDeclaration = $driverLicenseDeclaration;
+
+ return $this;
+ }
+
+ public function isDriverLicenseVerified(): bool
+ {
+ return $this->driverLicenseVerified;
+ }
+
+ public function setDriverLicenseVerified(bool $driverLicenseVerified): static
+ {
+ $this->driverLicenseVerified = $driverLicenseVerified;
+
+ return $this;
+ }
+
+ public function getDriverLicenseStatus(): string
+ {
+ return $this->driverLicenseStatus;
+ }
+
+ public function setDriverLicenseStatus(string $driverLicenseStatus): static
+ {
+ $this->driverLicenseStatus = $driverLicenseStatus;
+
+ return $this;
+ }
+
+ public function getDriverLicenseUpload(): ?Upload
+ {
+ return $this->driverLicenseUpload;
+ }
+
+ public function setDriverLicenseUpload(?Upload $driverLicenseUpload): static
+ {
+ $this->driverLicenseUpload = $driverLicenseUpload;
+
+ return $this;
+ }
+
+ public function getDriverLicenseReviewComment(): ?string
+ {
+ return $this->driverLicenseReviewComment;
+ }
+
+ public function setDriverLicenseReviewComment(?string $driverLicenseReviewComment): static
+ {
+ $this->driverLicenseReviewComment = $driverLicenseReviewComment;
+
+ return $this;
+ }
+
+ public function getDriverLicenseReviewedAt(): ?\DateTimeImmutable
+ {
+ return $this->driverLicenseReviewedAt;
+ }
+
+ public function setDriverLicenseReviewedAt(?\DateTimeImmutable $driverLicenseReviewedAt): static
+ {
+ $this->driverLicenseReviewedAt = $driverLicenseReviewedAt;
+
+ return $this;
+ }
+
+ public function getDriverLicenseReviewedBy(): ?string
+ {
+ return $this->driverLicenseReviewedBy;
+ }
+
+ public function setDriverLicenseReviewedBy(?string $driverLicenseReviewedBy): static
+ {
+ $this->driverLicenseReviewedBy = $driverLicenseReviewedBy;
+
+ return $this;
}
public function getLanguage(): ?string
diff --git a/src/Entity/Upload.php b/src/Entity/Upload.php
index d0db200..f98761b 100644
--- a/src/Entity/Upload.php
+++ b/src/Entity/Upload.php
@@ -21,6 +21,7 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
public const TYPE_CONTRACT = 'contract';
public const TYPE_INVOICE = 'invoice';
public const TYPE_DOCUMENT = 'document';
+ public const TYPE_DRIVER_LICENSE = 'driver_license';
public const STATUS_NEW = 'new';
public const STATUS_PENDING = 'pending';
@@ -133,6 +134,7 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
self::TYPE_CONTRACT => 'Honorarvertrag',
self::TYPE_INVOICE => 'Honorarnote',
self::TYPE_DOCUMENT => 'Info',
+ self::TYPE_DRIVER_LICENSE => 'Führerschein',
default => 'Dokument',
};
}
diff --git a/src/EventListener/RequiredTeamerCheckSubscriber.php b/src/EventListener/RequiredTeamerCheckSubscriber.php
new file mode 100644
index 0000000..9d594d4
--- /dev/null
+++ b/src/EventListener/RequiredTeamerCheckSubscriber.php
@@ -0,0 +1,65 @@
+ ['onKernelRequest', 5],
+ ];
+ }
+
+ public function onKernelRequest(RequestEvent $event): void
+ {
+ if (false === $event->isMainRequest()) {
+ return;
+ }
+
+ $request = $event->getRequest();
+ $route = (string) $request->attributes->get('_route');
+
+ if ('' === $route || false === str_starts_with($route, 'app_teamer_')) {
+ return;
+ }
+
+ $user = $this->security->getUser();
+
+ if (false === $user instanceof User || false === $this->security->isGranted('ROLE_TEAMER')) {
+ return;
+ }
+
+ $check = $this->requiredTeamerCheckRegistry->getFirstUnresolvedCheck($user);
+
+ if (null === $check) {
+ return;
+ }
+
+ if (true === in_array($route, [
+ $check->getRouteName(),
+ 'app_upload_delete',
+ 'app_security_logout',
+ ], true)) {
+ return;
+ }
+
+ $event->setResponse(new RedirectResponse($this->urlGenerator->generate($check->getRouteName())));
+ }
+}
diff --git a/src/Form/DriverLicenseCheckType.php b/src/Form/DriverLicenseCheckType.php
new file mode 100644
index 0000000..d5a92ae
--- /dev/null
+++ b/src/Form/DriverLicenseCheckType.php
@@ -0,0 +1,43 @@
+add('status', ChoiceType::class, [
+ 'label' => 'neuer Status',
+ 'choices' => [
+ 'in Bearbeitung' => Upload::STATUS_NEW,
+ 'bestätigt' => Upload::STATUS_CHECKED,
+ 'abgelehnt' => Upload::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' => DriverLicenseCheckDto::class,
+ 'anti_xss' => true,
+ ]);
+ }
+}
diff --git a/src/Form/DriverLicenseDeclarationType.php b/src/Form/DriverLicenseDeclarationType.php
new file mode 100644
index 0000000..44b8fa6
--- /dev/null
+++ b/src/Form/DriverLicenseDeclarationType.php
@@ -0,0 +1,49 @@
+add('hasDriverLicense', ChoiceType::class, [
+ 'label' => 'Besitzt du einen gültigen Führerschein?',
+ 'choices' => [
+ 'Ja' => true,
+ 'Nein' => false,
+ ],
+ 'expanded' => true,
+ ]);
+ }
+
+ public function buildView(FormView $view, FormInterface $form, array $options): void
+ {
+ /** @var UploadSessionDto $uploadSession */
+ $uploadSession = $options['upload_session'];
+
+ $view->vars['upload_session_params'] = [
+ UploadHandler::SESSION_KEY => $uploadSession->getUid(),
+ ];
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver
+ ->setDefaults([
+ 'data_class' => DriverLicenseDeclarationDto::class,
+ ])
+ ->setRequired(['upload_session'])
+ ->setAllowedTypes('upload_session', UploadSessionDto::class)
+ ;
+ }
+}
diff --git a/src/Form/TeamerFilterType.php b/src/Form/TeamerFilterType.php
index 6ae506a..3844db9 100644
--- a/src/Form/TeamerFilterType.php
+++ b/src/Form/TeamerFilterType.php
@@ -36,6 +36,10 @@ class TeamerFilterType extends AbstractType
'label' => 'Snowboardlehrer:innen-Lizenz',
'required' => false,
])
+ ->add('driverLicenseVerified', CheckboxType::class, [
+ 'label' => 'Führerschein bestätigt',
+ 'required' => false,
+ ])
->add('jobProfiles', MultiselectEntityType::class, [
'label' => 'Job-Profile',
'class' => JobProfile::class,
diff --git a/src/Model/DriverLicenseCheckDto.php b/src/Model/DriverLicenseCheckDto.php
new file mode 100644
index 0000000..47c2a30
--- /dev/null
+++ b/src/Model/DriverLicenseCheckDto.php
@@ -0,0 +1,48 @@
+upload = $upload;
+ $this->status = $upload->getStatus();
+ $this->comment = $upload->getComment();
+ }
+
+ public function getUpload(): Upload
+ {
+ return $this->upload;
+ }
+
+ 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;
+ }
+
+ public function setComment(?string $comment): static
+ {
+ $this->comment = $comment;
+
+ return $this;
+ }
+}
diff --git a/src/Model/DriverLicenseDeclarationDto.php b/src/Model/DriverLicenseDeclarationDto.php
new file mode 100644
index 0000000..b020da4
--- /dev/null
+++ b/src/Model/DriverLicenseDeclarationDto.php
@@ -0,0 +1,28 @@
+hasDriverLicense = $hasDriverLicense;
+ }
+
+ public function hasDriverLicense(): ?bool
+ {
+ return $this->hasDriverLicense;
+ }
+
+ public function setHasDriverLicense(?bool $hasDriverLicense): static
+ {
+ $this->hasDriverLicense = $hasDriverLicense;
+
+ return $this;
+ }
+}
diff --git a/src/Model/TeamerFilterDto.php b/src/Model/TeamerFilterDto.php
index 16ccf33..3368c32 100644
--- a/src/Model/TeamerFilterDto.php
+++ b/src/Model/TeamerFilterDto.php
@@ -10,6 +10,7 @@ class TeamerFilterDto extends AbstractFilterDto
protected string $pickupId = '';
protected bool $skiLicense = false;
protected bool $snowboardLicense = false;
+ protected bool $driverLicenseVerified = false;
protected bool $noTrainings = false;
public function getName(): ?string
@@ -89,6 +90,18 @@ class TeamerFilterDto extends AbstractFilterDto
return $this->noTrainings;
}
+ public function hasDriverLicenseVerified(): bool
+ {
+ return $this->driverLicenseVerified;
+ }
+
+ public function setDriverLicenseVerified(bool $driverLicenseVerified): static
+ {
+ $this->driverLicenseVerified = $driverLicenseVerified;
+
+ return $this;
+ }
+
public function setNoTrainings(bool $noTrainings): void
{
$this->noTrainings = $noTrainings;
diff --git a/src/Repository/TeamerRepository.php b/src/Repository/TeamerRepository.php
index 7600a53..366dd85 100644
--- a/src/Repository/TeamerRepository.php
+++ b/src/Repository/TeamerRepository.php
@@ -97,6 +97,13 @@ class TeamerRepository extends ServiceEntityRepository
;
}
+ if (true === $filterDto->hasDriverLicenseVerified()) {
+ $qb
+ ->andWhere($qb->expr()->eq('teamer.driverLicenseVerified', ':driverLicenseVerified'))
+ ->setParameter('driverLicenseVerified', true)
+ ;
+ }
+
return $qb->getQuery();
}
diff --git a/src/Security/BpnAuthenticator.php b/src/Security/BpnAuthenticator.php
index 3bfd09d..ef36e0b 100644
--- a/src/Security/BpnAuthenticator.php
+++ b/src/Security/BpnAuthenticator.php
@@ -8,6 +8,7 @@ use App\BusProNet\Model\CrmAttributesResponse;
use App\BusProNet\Model\ProfileResponse;
use App\BusProNet\UserDataHandler;
use App\Entity\User;
+use App\Security\RequiredCheck\RequiredTeamerCheckRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -31,6 +32,7 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
private readonly ApiClient $apiClient,
private readonly LoggerInterface $logger,
private readonly UserDataHandler $userDataHandler,
+ private readonly RequiredTeamerCheckRegistry $requiredTeamerCheckRegistry,
) {
}
@@ -91,7 +93,15 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent
$this->entityManager->flush();
- $url = $this->urlGenerator->generate($user->getDefaultRoute());
+ $route = $user->getDefaultRoute();
+ if ('app_teamer_index' === $route) {
+ $check = $this->requiredTeamerCheckRegistry->getFirstUnresolvedCheck($user);
+ if (null !== $check) {
+ $route = $check->getRouteName();
+ }
+ }
+
+ $url = $this->urlGenerator->generate($route);
return new RedirectResponse($url);
}
diff --git a/src/Security/RequiredCheck/DriverLicenseRequiredCheck.php b/src/Security/RequiredCheck/DriverLicenseRequiredCheck.php
new file mode 100644
index 0000000..4d56b0d
--- /dev/null
+++ b/src/Security/RequiredCheck/DriverLicenseRequiredCheck.php
@@ -0,0 +1,72 @@
+hasRole('ROLE_TEAMER');
+ }
+
+ public function isSatisfied(User $user): bool
+ {
+ $teamer = $user->getTeamer();
+
+ if (null === $teamer) {
+ return false;
+ }
+
+ $declaration = $teamer->getDriverLicenseDeclaration();
+
+ if (null === $declaration) {
+ return false;
+ }
+
+ if (false === $declaration) {
+ return true;
+ }
+
+ if (true === $teamer->isDriverLicenseVerified()) {
+ return true;
+ }
+
+ if (Teamer::DRIVER_LICENSE_STATUS_APPROVED === $teamer->getDriverLicenseStatus()) {
+ return true;
+ }
+
+ $upload = $teamer->getDriverLicenseUpload();
+
+ if (null === $upload) {
+ return false;
+ }
+
+ if (Upload::TYPE_DRIVER_LICENSE !== $upload->getType()) {
+ return false;
+ }
+
+ if (Teamer::DRIVER_LICENSE_STATUS_DENIED === $teamer->getDriverLicenseStatus()) {
+ return false;
+ }
+
+ if (Upload::STATUS_REJECTED === $upload->getStatus()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getRouteName(): string
+ {
+ return 'app_teamer_check_driver_license';
+ }
+}
diff --git a/src/Security/RequiredCheck/RequiredTeamerCheckInterface.php b/src/Security/RequiredCheck/RequiredTeamerCheckInterface.php
new file mode 100644
index 0000000..a545fe4
--- /dev/null
+++ b/src/Security/RequiredCheck/RequiredTeamerCheckInterface.php
@@ -0,0 +1,16 @@
+ $checks
+ */
+ public function __construct(private readonly iterable $checks)
+ {
+ }
+
+ public function getFirstUnresolvedCheck(User $user): ?RequiredTeamerCheckInterface
+ {
+ foreach ($this->checks as $check) {
+ if (false === $check->appliesTo($user)) {
+ continue;
+ }
+
+ if (false === $check->isSatisfied($user)) {
+ return $check;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Service/Common/TeamerFilterHandler.php b/src/Service/Common/TeamerFilterHandler.php
index 39382f8..4c09f36 100644
--- a/src/Service/Common/TeamerFilterHandler.php
+++ b/src/Service/Common/TeamerFilterHandler.php
@@ -25,6 +25,7 @@ class TeamerFilterHandler extends AbstractFilterHandler
->setPickupId($data['pickup_id'])
->setSkiLicense($data['ski_license'])
->setSnowboardLicense($data['snowboard_license'])
+ ->setDriverLicenseVerified($data['driver_license_verified'])
->setNoTrainings($data['no_trainings'])
;
@@ -49,6 +50,7 @@ class TeamerFilterHandler extends AbstractFilterHandler
'pickup_id' => $filterDto->getPickupId(),
'ski_license' => $filterDto->hasSkiLicense(),
'snowboard_license' => $filterDto->hasSnowboardLicense(),
+ 'driver_license_verified' => $filterDto->hasDriverLicenseVerified(),
'no_trainings' => $filterDto->hasNoTrainings(),
'job_profiles' => array_map(function (JobProfile $jobProfile) {
return $jobProfile->getId();
diff --git a/templates/_partials/_teamer_license_info.html.twig b/templates/_partials/_teamer_license_info.html.twig
index 25d19e3..dbb3372 100644
--- a/templates/_partials/_teamer_license_info.html.twig
+++ b/templates/_partials/_teamer_license_info.html.twig
@@ -2,6 +2,11 @@
Lizenzen
+ {% if teamer.driverLicenseVerified %}
+ -
+ Führerschein
+
+ {% endif %}
{% for license in teamer.licenses %}
-
@@ -18,9 +23,10 @@
- {% else %}
+ {% endfor %}
+ {% if not teamer.driverLicenseVerified and teamer.licenses | length == 0 %}
-
-
- {% endfor %}
+ {% endif %}
diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig
index 299896a..eb3c4cc 100644
--- a/templates/admin/index.html.twig
+++ b/templates/admin/index.html.twig
@@ -121,6 +121,37 @@
+
+
+ Neue Führerscheine
+ {% if newDriverLicensesCount > 0 %}
+ {{ newDriverLicensesCount }} neu
+ {% endif %}
+
+
+
+
Neue Einsätze
diff --git a/templates/administrative/teamer/driver_license/modal_check.html.twig b/templates/administrative/teamer/driver_license/modal_check.html.twig
new file mode 100644
index 0000000..56388cd
--- /dev/null
+++ b/templates/administrative/teamer/driver_license/modal_check.html.twig
@@ -0,0 +1,25 @@
+{% extends 'htmx_modal.html.twig' %}
+
+{% block title %}Führerschein prüfen{% endblock %}
+
+{% block content %}
+ {{ form_start(form, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
+
+
+ Führerschein {{ teamer }} vom {{ document.createdAt|date('d.m.Y') }}
+
+ {{ icon('eye') }}
+
+
+ {{ form_row(form.status) }}
+ {{ form_row(form.comment) }}
+
+
+ {{ form_rest(form) }}
+ {{ form_end(form) }}
+{% endblock %}
diff --git a/templates/administrative/teamer/index.html.twig b/templates/administrative/teamer/index.html.twig
index 03416a3..54545e7 100644
--- a/templates/administrative/teamer/index.html.twig
+++ b/templates/administrative/teamer/index.html.twig
@@ -94,6 +94,9 @@
+ {% if teamer.driverLicenseVerified %}
+ {{ icon('car', 'w-5 h-5 text-green-500') }}
+ {% endif %}
{% if not teamer.viewed %}
|