entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $signature ->setState(EntityWorkflowSignatureStateEnum::SIGNED) ->setZoneSignatureIndex($atIndex) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as signed', ['signatureId' => $signature->getId(), 'index' => (string) $atIndex]); ['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); if (null === $transition) { return $signature->getStep()->getEntityWorkflow()->getStep(); } return $transition->getTos()[0]; } /** * Marks a signature as canceled. * * This method will acquire a lock on the database side, so it must be wrapped into an explicit * transaction. * * This method updates the signature state to 'canceled' and logs the action. * It also dispatches a message to notify about the state change. * * @return string The expected new workflow's step, after transition is applyied */ public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): string { $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $signature ->setState(EntityWorkflowSignatureStateEnum::CANCELED) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as canceled', ['signatureId' => $signature->getId()]); ['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); if (null === $transition) { return $signature->getStep()->getEntityWorkflow()->getStep(); } return $transition->getTos()[0]; } /** * Marks the given signature as rejected and updates its state and state date accordingly. * * This method will acquire a lock on the database side, so it must be wrapped into an explicit * transaction. * * This method logs the rejection of the signature and dispatches a message indicating * a state change has occurred. * * @param EntityWorkflowStepSignature $signature the signature entity to be marked as rejected * * @return string The expected new workflow's step, after transition is applyied */ public function markSignatureAsRejected(EntityWorkflowStepSignature $signature): string { $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $signature ->setState(EntityWorkflowSignatureStateEnum::REJECTED) ->setStateDate($this->clock->now()); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as rejected', ['signatureId' => $signature->getId()]); ['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); if (null === $transition) { return $signature->getStep()->getEntityWorkflow()->getStep(); } return $transition->getTos()[0]; } /** * Executed after a signature has a new state. * * This method will acquire a lock on the database side, so it must be wrapped into an explicit * transaction. * * This should be executed only by a system user (without any user registered) */ public function onPostMark(EntityWorkflowStepSignature $signature): void { $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_READ); ['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature); if (null === $transition) { return; } $entityWorkflow = $signature->getStep()->getEntityWorkflow(); $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); $transitionDto = new WorkflowTransitionContextDTO($entityWorkflow); $transitionDto->futureDestUsers[] = $futureUser; $workflow->apply($entityWorkflow, $transition->getName(), [ 'context' => $transitionDto, 'transitionAt' => $this->clock->now(), 'transition' => $transition->getName(), ]); $this->logger->info(self::LOG_PREFIX.'Transition automatically applied', ['signatureId' => $signature->getId()]); } /** * @return array{transition: Transition|null, futureUser: User|null} */ private function decideTransition(EntityWorkflowStepSignature $signature): array { 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()]); return ['transition' => null, 'futureUser' => null]; } $this->logger->debug(self::LOG_PREFIX.'Continuing the process to find a transition', ['signatureId' => $signature->getId()]); $entityWorkflow = $signature->getStep()->getEntityWorkflow(); $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); $metadataStore = $workflow->getMetadataStore(); // find a transition $marking = $workflow->getMarking($entityWorkflow); $places = $marking->getPlaces(); $transition = null; foreach ($places as $place => $int) { $metadata = $metadataStore->getPlaceMetadata($place); if (array_key_exists('onSignatureCompleted', $metadata)) { $transition = $metadata['onSignatureCompleted']['transitionName']; } } if (null === $transition) { $this->logger->info(self::LOG_PREFIX.'The transition is not configured, will not apply a transition', ['signatureId' => $signature->getId()]); return ['transition' => null, 'futureUser' => null]; } if ('person' === $signature->getSignerKind()) { $futureUser = $this->getPreviousSender($signature->getStep()); } else { $futureUser = $signature->getSigner(); } if (null === $futureUser) { $this->logger->info(self::LOG_PREFIX.'No previous user, will not apply a transition', ['signatureId' => $signature->getId()]); return ['transition' => null, 'futureUser' => null]; } foreach ($workflow->getDefinition()->getTransitions() as $transitionObj) { if ($transitionObj->getName() === $transition) { return ['transition' => $transitionObj, 'futureUser' => $futureUser]; } } throw new \RuntimeException('Transition not found'); } private function getPreviousSender(EntityWorkflowStep $entityWorkflowStep): ?User { $stepsChained = $entityWorkflowStep->getEntityWorkflow()->getStepsChained(); foreach ($stepsChained as $stepChained) { if ($stepChained === $entityWorkflowStep) { if (null === $previous = $stepChained->getPrevious()) { return null; } if (null !== $previousUser = $previous->getTransitionBy()) { return $previousUser; } return $this->getPreviousSender($previous); } } throw new \LogicException('no same step found'); } }