From 5a467ae38de2212e31fb2d881ae0a410053e35de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Sep 2024 10:57:52 +0200 Subject: [PATCH 1/4] Add ChillUrlGenerator and ChillUrlGeneratorInterface Introduced a new interface ChillUrlGeneratorInterface for URL generation with return path handling. Implemented this interface in the ChillUrlGenerator class, which uses Symfony components to manage URL generation and request information. --- .../Routing/ChillUrlGenerator.php | 43 +++++++++++++++++++ .../Routing/ChillUrlGeneratorInterface.php | 35 +++++++++++++++ .../config/services/routing.yaml | 5 +++ 3 files changed, 83 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/Routing/ChillUrlGenerator.php create mode 100644 src/Bundle/ChillMainBundle/Routing/ChillUrlGeneratorInterface.php diff --git a/src/Bundle/ChillMainBundle/Routing/ChillUrlGenerator.php b/src/Bundle/ChillMainBundle/Routing/ChillUrlGenerator.php new file mode 100644 index 000000000..eaecee628 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Routing/ChillUrlGenerator.php @@ -0,0 +1,43 @@ +urlGenerator->generate($name, $parameters, $referenceType); + } + + public function generateWithReturnPath(string $name, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + $uri = $this->requestStack->getCurrentRequest()->getRequestUri(); + + return $this->urlGenerator->generate($name, [$parameters, 'returnPath' => $uri], $referenceType); + } + + public function returnPathOr(string $name, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + $request = $this->requestStack->getCurrentRequest(); + + if ($request->query->has('returnPath')) { + return $request->query->get('returnPath'); + } + + return $this->urlGenerator->generate($name, $parameters, $referenceType); + } +} diff --git a/src/Bundle/ChillMainBundle/Routing/ChillUrlGeneratorInterface.php b/src/Bundle/ChillMainBundle/Routing/ChillUrlGeneratorInterface.php new file mode 100644 index 000000000..5c00fa665 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Routing/ChillUrlGeneratorInterface.php @@ -0,0 +1,35 @@ + Date: Wed, 25 Sep 2024 10:58:53 +0200 Subject: [PATCH 2/4] Implement signature cancellation feature Added functionality to cancel signatures in workflow, including controller, view, and tests. Updated translations and adjusted templates to support and display cancellation actions. --- .../WorkflowSignatureCancelController.php | 69 +++++++++++++++ .../Workflow/EntityWorkflowStepSignature.php | 10 +++ .../views/Workflow/_signature.html.twig | 30 ++++--- .../views/WorkflowSignature/cancel.html.twig | 20 +++++ .../EntityWorkflowStepSignatureVoter.php | 5 +- .../Workflow/SignatureStepStateChanger.php | 7 ++ .../translations/messages+intl-icu.fr.yaml | 6 +- .../translations/messages.fr.yml | 5 ++ ...kflowSignatureCancelControllerStepTest.php | 86 +++++++++++++++++++ 9 files changed, 223 insertions(+), 15 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php create mode 100644 src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Tests/Controller/WorkflowSignatureCancelControllerStepTest.php diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php new file mode 100644 index 000000000..20fce559a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php @@ -0,0 +1,69 @@ +security->isGranted(EntityWorkflowStepSignatureVoter::CANCEL, $signature)) { + throw new AccessDeniedHttpException('not allowed to cancel this signature'); + } + + $form = $this->formFactory->create(); + $form->add('confirm', SubmitType::class, ['label' => 'Confirm']); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->signatureStepStateChanger->markSignatureAsCanceled($signature); + $this->entityManager->flush(); + + return new RedirectResponse( + $this->chillUrlGenerator->returnPathOr('chill_main_workflow_show', ['id' => $signature->getStep()->getEntityWorkflow()->getId()]) + ); + } + + return + new Response( + $this->twig->render( + '@ChillMain/WorkflowSignature/cancel.html.twig', + ['form' => $form->createView(), 'signature' => $signature] + ) + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStepSignature.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStepSignature.php index 5b659d844..945e0d024 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStepSignature.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStepSignature.php @@ -161,6 +161,16 @@ class EntityWorkflowStepSignature implements TrackCreationInterface, TrackUpdate return EntityWorkflowSignatureStateEnum::PENDING == $this->getState(); } + public function isCanceled(): bool + { + return EntityWorkflowSignatureStateEnum::CANCELED === $this->getState(); + } + + public function isRejected(): bool + { + return EntityWorkflowSignatureStateEnum::REJECTED === $this->getState(); + } + /** * Checks whether all signatures associated with a given workflow step are not pending. * diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig index b0e401645..3cefd2a41 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig @@ -3,7 +3,7 @@
{% for s in signatures %}
-
+
{% if s.signerKind == 'person' %} {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { action: 'show', displayBadge: true, @@ -19,21 +19,27 @@ } %} {% endif %}
-
+
{% if s.isSigned %} - {{ 'workflow.signature_zone.has_signed_statement'|trans({ 'datetime' : s.stateDate }) }} + {{ 'workflow.signature.signed_statement'|trans({ 'datetime' : s.stateDate }) }} + {% elseif s.isCanceled %} + {{ 'workflow.signature.canceled_statement'|trans({ 'datetime' : s.stateDate }) }} + {% elseif s.isRejected%} + {{ 'workflow.signature.rejected_statement'|trans({ 'datetime' : s.stateDate }) }} {% else %} - {% if is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_SIGN', s) %} + {% if (is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_CANCEL', s) or is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_SIGN', s)) %} - {% else %} - {{ 'workflow.waiting_for_signature'|trans }} {% endif %} {% endif %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig new file mode 100644 index 000000000..aaad933cf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig @@ -0,0 +1,20 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %}{{ 'workflow.signature.cancel_signature_of'|trans({ '%signer%': signature.signer|chill_entity_render_string }) }}{% endblock %} + +{% block content %} +

{{ block('title') }}

+ +

{{ 'workflow.signature.are_you_sure'|trans({'%signer%': signature.signer|chill_entity_render_string}) }}

+ + {{ form_start(form) }} +
    +
  • + {{ 'Cancel'|trans }} +
  • +
  • + {{ form_widget(form.confirm, {'attr': {'class': 'btn btn-misc'}}) }} +
  • +
+ {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php index 683f1352f..35a640a07 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php @@ -20,9 +20,12 @@ final class EntityWorkflowStepSignatureVoter extends Voter { public const SIGN = 'CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_SIGN'; + public const CANCEL = 'CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_CANCEL'; + protected function supports(string $attribute, $subject) { - return $subject instanceof EntityWorkflowStepSignature && self::SIGN === $attribute; + return $subject instanceof EntityWorkflowStepSignature + && in_array($attribute, [self::SIGN, self::CANCEL], true); } protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) diff --git a/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php b/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php index 3e2871810..06046d111 100644 --- a/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php +++ b/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php @@ -89,6 +89,13 @@ class SignatureStepStateChanger $this->logger->info(self::LOG_PREFIX.'Transition automatically applied', ['signatureId' => $signature->getId()]); } + public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): void + { + $signature + ->setState(EntityWorkflowSignatureStateEnum::CANCELED) + ->setStateDate($this->clock->now()); + } + private function getPreviousSender(EntityWorkflowStep $entityWorkflowStep): ?User { $stepsChained = $entityWorkflowStep->getEntityWorkflow()->getStepsChained(); diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index a0753f7a6..7f941b13a 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -45,8 +45,10 @@ workflow: few {# workflows} other {# workflows} } - signature_zone: - has_signed_statement: 'A signé le {datetime, date, short} à {datetime, time, short}' + signature: + signed_statement: 'Signature appliquée le {datetime, date, short} à {datetime, time, short}' + rejected_statement: 'Signature rejectée le {datetime, date, short} à {datetime, time, short}' + canceled_statement: 'Signature annulée le {datetime, date, short} à {datetime, time, short}' duration: diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 495b9a3bd..12ecb09a7 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -538,6 +538,7 @@ workflow: signature_zone: title: Signatures électroniques button_sign: Signer + button_cancel: Annuler metadata: sign_by: 'Signature pour %name%' docType: Type de document @@ -550,6 +551,10 @@ workflow: user: Utilisateur already_signed_alert: La signature a déjà été appliquée + signature: + cancel_signature_of: Annulation de la signature de %signer% + are_you_sure: Êtes-vous sûr de vouloir annuler la signature de %signer% + Subscribe final: Recevoir une notification à l'étape finale Subscribe all steps: Recevoir une notification à chaque étape diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/WorkflowSignatureCancelControllerStepTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/WorkflowSignatureCancelControllerStepTest.php new file mode 100644 index 000000000..727f525d3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/WorkflowSignatureCancelControllerStepTest.php @@ -0,0 +1,86 @@ +formFactory = self::getContainer()->get('form.factory'); + $this->signatureStepStateChanger = self::getContainer()->get(SignatureStepStateChanger::class); + $this->chillUrlGenerator = self::getContainer()->get(ChillUrlGeneratorInterface::class); + + $requestContext = self::getContainer()->get(RequestContext::class); + $requestContext->setParameter('_locale', 'fr'); + + $this->requestStack = self::getContainer()->get(RequestStack::class); + + } + + public function testCancelSignatureGet(): void + { + $entityWorkflow = new EntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + $dto->futureUserSignature = new User(); + $entityWorkflow->setStep('signature', $dto, 'to_signature', new \DateTimeImmutable(), new User()); + $signature = $entityWorkflow->getCurrentStep()->getSignatures()->first(); + + $security = $this->createMock(Security::class); + $security->expects($this->once())->method('isGranted') + ->with(EntityWorkflowStepSignatureVoter::CANCEL, $signature)->willReturn(true); + + $entityManager = $this->createMock(EntityManager::class); + + $twig = $this->createMock(Environment::class); + $twig->expects($this->once())->method('render')->withAnyParameters() + ->willReturn('template'); + + $controller = new WorkflowSignatureCancelController($entityManager, $security, $this->formFactory, $twig, $this->signatureStepStateChanger, $this->chillUrlGenerator); + + $request = new Request(); + $request->setMethod('GET'); + + $this->requestStack->push($request); + + $response = $controller->cancelSignature($signature, $request); + + self::assertEquals(200, $response->getStatusCode()); + } +} From cfce5317540058a576bfd3f2633dd8bdfe1d4f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Sep 2024 11:31:27 +0200 Subject: [PATCH 3/4] Add reject functionality for workflow signatures Implemented the ability to reject workflow signatures by adding necessary templates, routes, and authorization checks. Updated the `WorkflowSignatureCancelController` to handle rejection and modified existing templates and translations to support the new feature. --- .../WorkflowSignatureCancelController.php | 35 +++++++++++++++++-- .../views/Workflow/_signature.html.twig | 5 +++ .../views/WorkflowSignature/cancel.html.twig | 2 +- .../views/WorkflowSignature/reject.html.twig | 20 +++++++++++ .../EntityWorkflowStepSignatureVoter.php | 4 ++- .../Workflow/SignatureStepStateChanger.php | 31 ++++++++++------ .../translations/messages.fr.yml | 6 ++-- 7 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/reject.html.twig diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php index 20fce559a..95cd1b779 100644 --- a/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowSignatureCancelController.php @@ -40,7 +40,36 @@ final readonly class WorkflowSignatureCancelController #[Route('/{_locale}/main/workflow/signature/{id}/cancel', name: 'chill_main_workflow_signature_cancel')] public function cancelSignature(EntityWorkflowStepSignature $signature, Request $request): Response { - if (!$this->security->isGranted(EntityWorkflowStepSignatureVoter::CANCEL, $signature)) { + return $this->markSignatureAction( + $signature, + $request, + EntityWorkflowStepSignatureVoter::CANCEL, + function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsCanceled($signature); }, + '@ChillMain/WorkflowSignature/cancel.html.twig', + ); + } + + #[Route('/{_locale}/main/workflow/signature/{id}/reject', name: 'chill_main_workflow_signature_reject')] + public function rejectSignature(EntityWorkflowStepSignature $signature, Request $request): Response + { + return $this->markSignatureAction( + $signature, + $request, + EntityWorkflowStepSignatureVoter::REJECT, + function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsRejected($signature); }, + '@ChillMain/WorkflowSignature/reject.html.twig', + ); + } + + private function markSignatureAction( + EntityWorkflowStepSignature $signature, + Request $request, + string $permissionAttribute, + callable $markSignature, + string $template, + ): Response { + + if (!$this->security->isGranted($permissionAttribute, $signature)) { throw new AccessDeniedHttpException('not allowed to cancel this signature'); } @@ -50,7 +79,7 @@ final readonly class WorkflowSignatureCancelController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $this->signatureStepStateChanger->markSignatureAsCanceled($signature); + $markSignature($signature); $this->entityManager->flush(); return new RedirectResponse( @@ -61,7 +90,7 @@ final readonly class WorkflowSignatureCancelController return new Response( $this->twig->render( - '@ChillMain/WorkflowSignature/cancel.html.twig', + $template, ['form' => $form->createView(), 'signature' => $signature] ) ); diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig index 3cefd2a41..42a3b68d8 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_signature.html.twig @@ -29,6 +29,11 @@ {% else %} {% if (is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_CANCEL', s) or is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_SIGN', s)) %}
    + {% if is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_REJECT', s) %} +
  • + {{ 'workflow.signature_zone.button_reject'|trans }} +
  • + {% endif %} {% if is_granted('CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_CANCEL', s) %}
  • {{ 'workflow.signature_zone.button_cancel'|trans }} diff --git a/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig index aaad933cf..583a5af4c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/cancel.html.twig @@ -5,7 +5,7 @@ {% block content %}

    {{ block('title') }}

    -

    {{ 'workflow.signature.are_you_sure'|trans({'%signer%': signature.signer|chill_entity_render_string}) }}

    +

    {{ 'workflow.signature.cancel_are_you_sure'|trans({'%signer%': signature.signer|chill_entity_render_string}) }}

    {{ form_start(form) }}
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/reject.html.twig b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/reject.html.twig new file mode 100644 index 000000000..f4047268e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/WorkflowSignature/reject.html.twig @@ -0,0 +1,20 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %}{{ 'workflow.signature.reject_signature_of'|trans({ '%signer%': signature.signer|chill_entity_render_string }) }}{% endblock %} + +{% block content %} +

      {{ block('title') }}

      + +

      {{ 'workflow.signature.reject_are_you_sure'|trans({'%signer%': signature.signer|chill_entity_render_string}) }}

      + + {{ form_start(form) }} +
        +
      • + {{ 'Cancel'|trans }} +
      • +
      • + {{ form_widget(form.confirm, {'attr': {'class': 'btn btn-misc'}}) }} +
      • +
      + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php index 35a640a07..ef2813e30 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowStepSignatureVoter.php @@ -22,10 +22,12 @@ final class EntityWorkflowStepSignatureVoter extends Voter public const CANCEL = 'CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_CANCEL'; + public const REJECT = 'CHILL_MAIN_ENTITY_WORKFLOW_SIGNATURE_REJECT'; + protected function supports(string $attribute, $subject) { return $subject instanceof EntityWorkflowStepSignature - && in_array($attribute, [self::SIGN, self::CANCEL], true); + && in_array($attribute, [self::SIGN, self::CANCEL, self::REJECT], true); } protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) diff --git a/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php b/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php index 06046d111..08e1773cc 100644 --- a/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php +++ b/src/Bundle/ChillMainBundle/Workflow/SignatureStepStateChanger.php @@ -34,11 +34,29 @@ class SignatureStepStateChanger $signature ->setState(EntityWorkflowSignatureStateEnum::SIGNED) ->setZoneSignatureIndex($atIndex) - ->setStateDate($this->clock->now()) - ; - + ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as signed', ['signatureId' => $signature->getId(), 'index' => (string) $atIndex]); + $this->onPostMark($signature); + } + public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): void + { + $signature + ->setState(EntityWorkflowSignatureStateEnum::CANCELED) + ->setStateDate($this->clock->now()); + $this->logger->info(self::LOG_PREFIX.'Mark signature entity as canceled', ['signatureId' => $signature->getId()]); + } + + public function markSignatureAsRejected(EntityWorkflowStepSignature $signature): void + { + $signature + ->setState(EntityWorkflowSignatureStateEnum::REJECTED) + ->setStateDate($this->clock->now()); + $this->logger->info(self::LOG_PREFIX.'Mark signature entity as rejected', ['signatureId' => $signature->getId()]); + } + + private function onPostMark(EntityWorkflowStepSignature $signature): void + { if (!EntityWorkflowStepSignature::isAllSignatureNotPendingForStep($signature->getStep())) { $this->logger->info(self::LOG_PREFIX.'This is not the last signature, skipping transition to another place', ['signatureId' => $signature->getId()]); @@ -89,13 +107,6 @@ class SignatureStepStateChanger $this->logger->info(self::LOG_PREFIX.'Transition automatically applied', ['signatureId' => $signature->getId()]); } - public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): void - { - $signature - ->setState(EntityWorkflowSignatureStateEnum::CANCELED) - ->setStateDate($this->clock->now()); - } - private function getPreviousSender(EntityWorkflowStep $entityWorkflowStep): ?User { $stepsChained = $entityWorkflowStep->getEntityWorkflow()->getStepsChained(); diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 12ecb09a7..8bade3ce6 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -539,6 +539,7 @@ workflow: title: Signatures électroniques button_sign: Signer button_cancel: Annuler + button_reject: Rejeter metadata: sign_by: 'Signature pour %name%' docType: Type de document @@ -553,8 +554,9 @@ workflow: signature: cancel_signature_of: Annulation de la signature de %signer% - are_you_sure: Êtes-vous sûr de vouloir annuler la signature de %signer% - + cancel_are_you_sure: Êtes-vous sûr de vouloir annuler la signature de %signer% + reject_signature_of: Rejet de la signature de %signer% + reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer% Subscribe final: Recevoir une notification à l'étape finale Subscribe all steps: Recevoir une notification à chaque étape From 5287824dbefda465479bdcf2df14b688b86cd050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Sep 2024 11:58:41 +0200 Subject: [PATCH 4/4] Add async handling for signature state changes Introduce MessageBus to handle post-signature operations asynchronously. This ensures that further steps are executed through dispatched messages, improving system scalability and performance. Implement new handlers and messages for the workflow state transitions. --- .../SignatureStepStateChangerTest.php | 13 +++++- .../PostSignatureStateChangeHandler.php | 41 +++++++++++++++++++ .../PostSignatureStateChangeMessage.php | 22 ++++++++++ .../Workflow/SignatureStepStateChanger.php | 14 ++++++- 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeHandler.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeMessage.php diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/SignatureStepStateChangerTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/SignatureStepStateChangerTest.php index 16579cbb5..fa3cd5c2d 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/SignatureStepStateChangerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/SignatureStepStateChangerTest.php @@ -21,6 +21,7 @@ use Chill\PersonBundle\Entity\Person; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Symfony\Component\Clock\MockClock; +use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Workflow\DefinitionBuilder; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Registry; @@ -44,7 +45,9 @@ class SignatureStepStateChangerTest extends TestCase $workflow = $registry->get($entityWorkflow, 'dummy'); $clock = new MockClock(); $user = new User(); - $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger()); + + $messengerBus = new MessageBus([]); + $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger(), $messengerBus); // move it to signature $dto = new WorkflowTransitionContextDTO($entityWorkflow); @@ -61,6 +64,8 @@ class SignatureStepStateChangerTest extends TestCase // we mark the first signature as signed $changer->markSignatureAsSigned($signatures[0], 1); + // the next step should be done by handling an async message + $changer->onPostMark($signatures[0]); self::assertEquals('signature', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step'); self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState()); @@ -70,6 +75,8 @@ class SignatureStepStateChangerTest extends TestCase // we mark the second signature as signed $changer->markSignatureAsSigned($signatures[1], 2); + // the next step should be done by handling an async message + $changer->onPostMark($signatures[1]); self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[1]->getState()); self::assertEquals('post-signature', $entityWorkflow->getStep(), 'the entity workflow step should be post-signature'); self::assertContains($user, $entityWorkflow->getCurrentStep()->getAllDestUser()); @@ -85,7 +92,7 @@ class SignatureStepStateChangerTest extends TestCase $workflow = $registry->get($entityWorkflow, 'dummy'); $clock = new MockClock(); $user = new User(); - $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger()); + $changer = new SignatureStepStateChanger($registry, $clock, new NullLogger(), new MessageBus([])); // move it to signature $dto = new WorkflowTransitionContextDTO($entityWorkflow); @@ -102,6 +109,8 @@ class SignatureStepStateChangerTest extends TestCase // we mark the first signature as signed $changer->markSignatureAsSigned($signatures[0], 1); + // the next step should be done by handling an async message + $changer->onPostMark($signatures[0]); self::assertEquals('signature-without-metadata', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step'); self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState()); diff --git a/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeHandler.php b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeHandler.php new file mode 100644 index 000000000..1a0d7bc5d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeHandler.php @@ -0,0 +1,41 @@ +entityWorkflowStepSignatureRepository->find($message->signatureId); + + if (null === $signature) { + throw new UnrecoverableMessageHandlingException('signature not found'); + } + + $this->signatureStepStateChanger->onPostMark($signature); + + $this->entityManager->flush(); + $this->entityManager->clear(); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeMessage.php b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeMessage.php new file mode 100644 index 000000000..44db0c500 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSignatureStateChangeMessage.php @@ -0,0 +1,22 @@ +setZoneSignatureIndex($atIndex) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as signed', ['signatureId' => $signature->getId(), 'index' => (string) $atIndex]); - $this->onPostMark($signature); + $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); } public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): void @@ -45,6 +48,7 @@ class SignatureStepStateChanger ->setState(EntityWorkflowSignatureStateEnum::CANCELED) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as canceled', ['signatureId' => $signature->getId()]); + $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); } public function markSignatureAsRejected(EntityWorkflowStepSignature $signature): void @@ -53,9 +57,15 @@ class SignatureStepStateChanger ->setState(EntityWorkflowSignatureStateEnum::REJECTED) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as rejected', ['signatureId' => $signature->getId()]); + $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); } - private function onPostMark(EntityWorkflowStepSignature $signature): void + /** + * Executed after a signature has a new state. + * + * This should be executed only by a system user (without any user registered) + */ + public function onPostMark(EntityWorkflowStepSignature $signature): void { if (!EntityWorkflowStepSignature::isAllSignatureNotPendingForStep($signature->getStep())) { $this->logger->info(self::LOG_PREFIX.'This is not the last signature, skipping transition to another place', ['signatureId' => $signature->getId()]);