From 3d20d5cf9435fb89b9c9d026f25cc8c8e1803d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 8 Jan 2025 18:00:25 +0100 Subject: [PATCH] feat: refactor teamer-document upload for admins --- .../Disposition/DocumentUploadController.php | 53 +++++-------------- .../DispositionWorkflowGuardSubscriber.php | 13 +---- src/Security/Voter/DispositionVoter.php | 20 +++++-- .../assignment/detail.html.twig | 2 +- .../modal_document_upload.html.twig | 2 +- 5 files changed, 32 insertions(+), 58 deletions(-) diff --git a/src/Controller/Administrative/Disposition/DocumentUploadController.php b/src/Controller/Administrative/Disposition/DocumentUploadController.php index e090c1a..ee4ed11 100644 --- a/src/Controller/Administrative/Disposition/DocumentUploadController.php +++ b/src/Controller/Administrative/Disposition/DocumentUploadController.php @@ -10,12 +10,10 @@ use App\Service\Upload\UploadHandler; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\DependencyInjection\Attribute\Target; 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\Component\Workflow\WorkflowInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class DocumentUploadController extends AbstractController @@ -23,8 +21,6 @@ class DocumentUploadController extends AbstractController public function __construct( private readonly UploadHandler $uploadHandler, private readonly EntityManagerInterface $entityManager, - #[Target('disposition')] - private readonly WorkflowInterface $workflow, private readonly EventDispatcherInterface $eventDispatcher, private readonly LoggerInterface $logger, ) { @@ -34,48 +30,35 @@ class DocumentUploadController extends AbstractController path: '/administrative/disposition/document-upload/{uuid}', name: 'app_administrative_disposition_document_upload' )] - #[IsGranted('ROLE_ADMINISTRATIVE')] + #[IsGranted('CONTRACT_SUPPLEMENTARY', subject: 'disposition')] public function index(Disposition $disposition, Request $request): Response { - if (true === $this->workflow->can($disposition, 'upload_contract')) { - $uploadType = Upload::TYPE_CONTRACT; - $uploadTypeLabel = 'Honorarvertrag'; - $uploaderRoute = '_uploader_upload_contract'; - } elseif (true === $this->workflow->can($disposition, 'upload_invoice')) { - $uploadType = Upload::TYPE_INVOICE; - $uploadTypeLabel = 'Honoranote'; - $uploaderRoute = '_uploader_upload_invoice'; - } else { - $this->addFlash('error', 'Kein Upload möglich beim aktuellen Status der Einteilung'); - - return $this->getRedirectResponse($disposition); - } - $teamer = $disposition->getTeamer(); - $modalTitle = sprintf('%s für %s hochladen', $uploadTypeLabel, $teamer); + $modalTitle = sprintf('Honorarvertrag für %s hochladen', $teamer); $form = $this->createFormBuilder()->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $this->processUpload($disposition, $uploadType); + $this->processUpload($disposition); - $this->addFlash('success', sprintf('%s wurde hochgeladen', $uploadTypeLabel)); + $this->addFlash('success', 'Der Honorarvertrag wurde hochgeladen'); - return $this->getRedirectResponse($disposition); + return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [ + 'uuid' => $disposition->getAssignment()->getUuid(), + ])); } return $this->render('administrative/disposition/modal_document_upload.html.twig', [ 'disposition' => $disposition, 'modalTitle' => $modalTitle, - 'uploaderRoute' => $uploaderRoute, 'form' => $form->createView(), ]); } - private function processUpload(Disposition $disposition, string $uploadType): void + private function processUpload(Disposition $disposition): void { - if (null !== $existingUpload = $disposition->getDocumentByType($uploadType)) { + if (null !== $existingUpload = $disposition->getDocumentByType(Upload::TYPE_CONTRACT)) { $this->entityManager->remove($existingUpload); } @@ -84,17 +67,14 @@ class DocumentUploadController extends AbstractController $uploadSession = $this->uploadHandler->getUploadSession(); - $upload = Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, $uploadType); - $upload->setStatus(Upload::STATUS_NEW); + $upload = Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, Upload::TYPE_CONTRACT); + $upload->setStatus(Upload::STATUS_CHECKED); $disposition->addDocument($upload); - $transitionName = sprintf('upload_%s', $uploadType); - $this->workflow->apply($disposition, $transitionName); - $this->entityManager->flush(); - $this->uploadHandler->moveUploadSessionFilesFromOrphanage($uploadType, $uploadSession); + $this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_CONTRACT, $uploadSession); $this->uploadHandler->destroyUploadSession(); $this->eventDispatcher->dispatch(new DocumentUploadedEvent($upload), DocumentUploadedEvent::NAME); @@ -104,14 +84,7 @@ class DocumentUploadController extends AbstractController 'teamer_name' => $teamer->getFullName(), 'disposition_id' => $disposition->getId(), 'document_id' => $upload->getId(), - 'upload_type' => $uploadType, + 'upload_type' => Upload::TYPE_CONTRACT, ]); } - - private function getRedirectResponse(Disposition $disposition): HxRedirectResponse - { - return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [ - 'uuid' => $disposition->getAssignment()->getUuid(), - ])); - } } diff --git a/src/EventListener/DispositionWorkflowGuardSubscriber.php b/src/EventListener/DispositionWorkflowGuardSubscriber.php index fbc5859..65fe97d 100644 --- a/src/EventListener/DispositionWorkflowGuardSubscriber.php +++ b/src/EventListener/DispositionWorkflowGuardSubscriber.php @@ -4,14 +4,13 @@ namespace App\EventListener; use App\Entity\Disposition; use App\Entity\Upload; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Workflow\Event\GuardEvent; use Symfony\Contracts\Translation\TranslatorInterface; class DispositionWorkflowGuardSubscriber implements EventSubscriberInterface { - public function __construct(private readonly TranslatorInterface $translator, private readonly Security $security) + public function __construct(private readonly TranslatorInterface $translator) {} public static function getSubscribedEvents(): array @@ -41,11 +40,6 @@ class DispositionWorkflowGuardSubscriber implements EventSubscriberInterface public function guardUploadContract(GuardEvent $event): void { - // no checks for administrative users - if ($this->security->isGranted('ROLE_ADMINISTRATIVE')) { - return; - } - /** @var Disposition $disposition */ $disposition = $event->getSubject(); $assignment = $disposition->getAssignment(); @@ -85,11 +79,6 @@ class DispositionWorkflowGuardSubscriber implements EventSubscriberInterface $event->setBlocked(true, $message); } - // no further checks for administrative users - if ($this->security->isGranted('ROLE_ADMINISTRATIVE')) { - return; - } - $earliestDate = $destination->getDateTo(); if ($earliestDate > $today) { diff --git a/src/Security/Voter/DispositionVoter.php b/src/Security/Voter/DispositionVoter.php index 3f2987d..f20b5d6 100644 --- a/src/Security/Voter/DispositionVoter.php +++ b/src/Security/Voter/DispositionVoter.php @@ -5,6 +5,7 @@ namespace App\Security\Voter; use App\BusProNet\DataProvider\HotelDataProvider; use App\BusProNet\Model\Hotel; use App\Entity\Disposition; +use App\Entity\Upload; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -15,13 +16,12 @@ class DispositionVoter extends Voter public const EDIT = 'EDIT'; public const DELETE = 'DELETE'; public const CONTRACT = 'CONTRACT'; + public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY'; public const INVOICE = 'INVOICE'; public const FEEDBACK = 'FEEDBACK'; - public function __construct( - private readonly Security $security, - private readonly HotelDataProvider $hotelDataProvider - ) { + public function __construct(private readonly Security $security) + { } protected function supports(string $attribute, mixed $subject): bool @@ -35,6 +35,7 @@ class DispositionVoter extends Voter static::EDIT, static::DELETE, static::CONTRACT, + static::CONTRACT_SUPPLEMENTARY, static::INVOICE, static::FEEDBACK, ]); @@ -49,6 +50,7 @@ class DispositionVoter extends Voter static::VIEW, static::EDIT, static::CONTRACT, static::INVOICE => $this->assertAdministrativeAccess() || $this->assertTeamerAccess($disposition), static::DELETE => $this->assertAdminAccess(), static::FEEDBACK => $this->assertHouseManagerAccess($token, $disposition), + static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition), default => false, }; } @@ -91,4 +93,14 @@ class DispositionVoter extends Voter return $this->security->getUser()->getTeamer() === $disposition->getTeamer(); } + + private function assertContractUploadAllowed(Disposition $disposition): bool + { + if (false === $this->security->isGranted('ROLE_ADMINISTRATIVE')) { + return false; + } + + return Disposition::STATUS_ENDED === $disposition->getStatus() + && null === $disposition->getDocumentByType(Upload::TYPE_CONTRACT); + } } \ No newline at end of file diff --git a/templates/administrative/assignment/detail.html.twig b/templates/administrative/assignment/detail.html.twig index aece30e..abe5b72 100644 --- a/templates/administrative/assignment/detail.html.twig +++ b/templates/administrative/assignment/detail.html.twig @@ -179,7 +179,7 @@
- {% if workflow_can(disposition, 'upload_contract') or workflow_can(disposition, 'upload_invoice') %} + {% if is_granted('CONTRACT_SUPPLEMENTARY', disposition) %}