mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch '291_workflow_pdfsignedmessagehandler' into 'signature-app-master'
Lorsque tous les usagers ont signé un workflow, le workflow retourne à l’envoyeur avec une étape « workflow signé » See merge request Chill-Projet/chill-bundles!726
This commit is contained in:
commit
f0d581b7f8
@ -12,12 +12,11 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
|
||||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Clock\ClockInterface;
|
|
||||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||||
|
|
||||||
final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
||||||
@ -33,7 +32,7 @@ final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
|||||||
private StoredObjectManagerInterface $storedObjectManager,
|
private StoredObjectManagerInterface $storedObjectManager,
|
||||||
private EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository,
|
private EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository,
|
||||||
private EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
private ClockInterface $clock,
|
private SignatureStepStateChanger $signatureStepStateChanger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function __invoke(PdfSignedMessage $message): void
|
public function __invoke(PdfSignedMessage $message): void
|
||||||
@ -54,8 +53,8 @@ final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
|||||||
|
|
||||||
$this->storedObjectManager->write($storedObject, $message->content);
|
$this->storedObjectManager->write($storedObject, $message->content);
|
||||||
|
|
||||||
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate($this->clock->now());
|
$this->signatureStepStateChanger->markSignatureAsSigned($signature, $message->signatureZoneIndex);
|
||||||
$signature->setZoneSignatureIndex($message->signatureZoneIndex);
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
$this->entityManager->clear();
|
$this->entityManager->clear();
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,12 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
|||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
||||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Clock\MockClock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -45,6 +45,9 @@ class PdfSignedMessageHandlerTest extends TestCase
|
|||||||
$entityWorkflow->setStep('new_step', $dto, 'new_transition', new \DateTimeImmutable(), new User());
|
$entityWorkflow->setStep('new_step', $dto, 'new_transition', new \DateTimeImmutable(), new User());
|
||||||
$step = $entityWorkflow->getCurrentStep();
|
$step = $entityWorkflow->getCurrentStep();
|
||||||
$signature = $step->getSignatures()->first();
|
$signature = $step->getSignatures()->first();
|
||||||
|
$stateChanger = $this->createMock(SignatureStepStateChanger::class);
|
||||||
|
$stateChanger->expects(self::once())->method('markSignatureAsSigned')
|
||||||
|
->with($signature, 99);
|
||||||
|
|
||||||
$handler = new PdfSignedMessageHandler(
|
$handler = new PdfSignedMessageHandler(
|
||||||
new NullLogger(),
|
new NullLogger(),
|
||||||
@ -52,15 +55,12 @@ class PdfSignedMessageHandlerTest extends TestCase
|
|||||||
$this->buildStoredObjectManager($storedObject, $expectedContent = '1234'),
|
$this->buildStoredObjectManager($storedObject, $expectedContent = '1234'),
|
||||||
$this->buildSignatureRepository($signature),
|
$this->buildSignatureRepository($signature),
|
||||||
$this->buildEntityManager(true),
|
$this->buildEntityManager(true),
|
||||||
new MockClock('now'),
|
$stateChanger,
|
||||||
);
|
);
|
||||||
|
|
||||||
// we simply call the handler. The mocked StoredObjectManager will check that the "write" method is invoked once
|
// we simply call the handler. The mocked StoredObjectManager will check that the "write" method is invoked once
|
||||||
// with the content "1234"
|
// with the content "1234"
|
||||||
$handler(new PdfSignedMessage(10, 99, $expectedContent));
|
$handler(new PdfSignedMessage(10, 99, $expectedContent));
|
||||||
|
|
||||||
self::assertEquals('signed', $signature->getState()->value);
|
|
||||||
self::assertEquals(99, $signature->getZoneSignatureIndex());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildSignatureRepository(EntityWorkflowStepSignature $signature): EntityWorkflowStepSignatureRepository
|
private function buildSignatureRepository(EntityWorkflowStepSignature $signature): EntityWorkflowStepSignatureRepository
|
||||||
|
@ -141,6 +141,32 @@ class EntityWorkflowStepSignature implements TrackCreationInterface, TrackUpdate
|
|||||||
return EntityWorkflowSignatureStateEnum::SIGNED == $this->getState();
|
return EntityWorkflowSignatureStateEnum::SIGNED == $this->getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPending(): bool
|
||||||
|
{
|
||||||
|
return EntityWorkflowSignatureStateEnum::PENDING == $this->getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether all signatures associated with a given workflow step are not pending.
|
||||||
|
*
|
||||||
|
* Iterates over each signature in the provided workflow step, and returns false if any signature
|
||||||
|
* is found to be pending. If all signatures are not pending, returns true.
|
||||||
|
*
|
||||||
|
* @param EntityWorkflowStep $step the workflow step whose signatures are to be checked
|
||||||
|
*
|
||||||
|
* @return bool true if all signatures are not pending, false otherwise
|
||||||
|
*/
|
||||||
|
public static function isAllSignatureNotPendingForStep(EntityWorkflowStep $step): bool
|
||||||
|
{
|
||||||
|
foreach ($step->getSignatures() as $signature) {
|
||||||
|
if ($signature->isPending()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 'person'|'user'
|
* @return 'person'|'user'
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
|
||||||
|
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||||
|
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Clock\MockClock;
|
||||||
|
use Symfony\Component\Workflow\DefinitionBuilder;
|
||||||
|
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
|
||||||
|
use Symfony\Component\Workflow\Transition;
|
||||||
|
use Symfony\Component\Workflow\Workflow;
|
||||||
|
use Symfony\Component\Workflow\WorkflowInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class SignatureStepStateChangerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testMarkSignatureAsSignedScenarioWhichExpectsTransition()
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$entityWorkflow->setWorkflowName('dummy');
|
||||||
|
$registry = $this->buildRegistry();
|
||||||
|
$workflow = $registry->get($entityWorkflow, 'dummy');
|
||||||
|
$clock = new MockClock();
|
||||||
|
$user = new User();
|
||||||
|
$changer = new SignatureStepStateChanger($registry, $clock);
|
||||||
|
|
||||||
|
// move it to signature
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures = [new Person(), new Person()];
|
||||||
|
$workflow->apply($entityWorkflow, 'to_signature', ['context' => $dto, 'transitionAt' => $clock->now(),
|
||||||
|
'byUser' => $user, 'transition' => 'to_signature']);
|
||||||
|
|
||||||
|
// get the signature created
|
||||||
|
$signatures = $entityWorkflow->getCurrentStep()->getSignatures();
|
||||||
|
|
||||||
|
if (2 !== count($signatures)) {
|
||||||
|
throw new \LogicException('there should have 2 signatures at this step');
|
||||||
|
}
|
||||||
|
|
||||||
|
// we mark the first signature as signed
|
||||||
|
$changer->markSignatureAsSigned($signatures[0], 1);
|
||||||
|
|
||||||
|
self::assertEquals('signature', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step');
|
||||||
|
self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState());
|
||||||
|
self::assertEquals(1, $signatures[0]->getZoneSignatureIndex());
|
||||||
|
self::assertNotNull($signatures[0]->getStateDate());
|
||||||
|
|
||||||
|
|
||||||
|
// we mark the second signature as signed
|
||||||
|
$changer->markSignatureAsSigned($signatures[1], 2);
|
||||||
|
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());
|
||||||
|
self::assertEquals(2, $signatures[1]->getZoneSignatureIndex());
|
||||||
|
self::assertNotNull($signatures[1]->getStateDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMarkSignatureAsSignedScenarioWithoutRequiredMetadata()
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$entityWorkflow->setWorkflowName('dummy');
|
||||||
|
$registry = $this->buildRegistry();
|
||||||
|
$workflow = $registry->get($entityWorkflow, 'dummy');
|
||||||
|
$clock = new MockClock();
|
||||||
|
$user = new User();
|
||||||
|
$changer = new SignatureStepStateChanger($registry, $clock);
|
||||||
|
|
||||||
|
// move it to signature
|
||||||
|
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$dto->futurePersonSignatures = [new Person()];
|
||||||
|
$workflow->apply($entityWorkflow, 'to_signature-without-metadata', ['context' => $dto, 'transitionAt' => $clock->now(),
|
||||||
|
'byUser' => $user, 'transition' => 'to_signature-without-metadata']);
|
||||||
|
|
||||||
|
// get the signature created
|
||||||
|
$signatures = $entityWorkflow->getCurrentStep()->getSignatures();
|
||||||
|
|
||||||
|
if (1 !== count($signatures)) {
|
||||||
|
throw new \LogicException('there should have 2 signatures at this step');
|
||||||
|
}
|
||||||
|
|
||||||
|
// we mark the first signature as signed
|
||||||
|
$changer->markSignatureAsSigned($signatures[0], 1);
|
||||||
|
|
||||||
|
self::assertEquals('signature-without-metadata', $entityWorkflow->getStep(), 'there should have any change in the entity workflow step');
|
||||||
|
self::assertEquals(EntityWorkflowSignatureStateEnum::SIGNED, $signatures[0]->getState());
|
||||||
|
self::assertEquals(1, $signatures[0]->getZoneSignatureIndex());
|
||||||
|
self::assertNotNull($signatures[0]->getStateDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRegistry(): Registry
|
||||||
|
{
|
||||||
|
$builder = new DefinitionBuilder();
|
||||||
|
$builder
|
||||||
|
->setInitialPlaces('initial')
|
||||||
|
->addPlaces(['initial', 'signature', 'signature-without-metadata', 'post-signature'])
|
||||||
|
->addTransition(new Transition('to_signature', 'initial', 'signature'))
|
||||||
|
->addTransition(new Transition('to_signature-without-metadata', 'initial', 'signature-without-metadata'))
|
||||||
|
->addTransition(new Transition('to_post-signature', 'signature', 'post-signature'))
|
||||||
|
->addTransition(new Transition('to_post-signature_2', 'signature-without-metadata', 'post-signature'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$metadata = new InMemoryMetadataStore(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'signature' => ['onSignatureCompleted' => ['transitionName' => 'to_post-signature']],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$builder->setMetadataStore($metadata);
|
||||||
|
|
||||||
|
$workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), name: 'dummy');
|
||||||
|
$registry = new Registry();
|
||||||
|
$registry->addWorkflow(
|
||||||
|
$workflow,
|
||||||
|
new class () implements WorkflowSupportStrategyInterface {
|
||||||
|
public function supports(WorkflowInterface $workflow, object $subject): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $registry;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
|
||||||
|
class SignatureStepStateChanger
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Registry $registry,
|
||||||
|
private readonly ClockInterface $clock,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function markSignatureAsSigned(EntityWorkflowStepSignature $signature, ?int $atIndex): void
|
||||||
|
{
|
||||||
|
$signature
|
||||||
|
->setState(EntityWorkflowSignatureStateEnum::SIGNED)
|
||||||
|
->setZoneSignatureIndex($atIndex)
|
||||||
|
->setStateDate($this->clock->now())
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!EntityWorkflowStepSignature::isAllSignatureNotPendingForStep($signature->getStep())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousUser = $this->getPreviousSender($signature->getStep());
|
||||||
|
|
||||||
|
if (null === $previousUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transitionDto = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||||
|
$transitionDto->futureDestUsers[] = $previousUser;
|
||||||
|
|
||||||
|
$workflow->apply($entityWorkflow, $transition, [
|
||||||
|
'context' => $transitionDto,
|
||||||
|
'transitionAt' => $this->clock->now(),
|
||||||
|
'transition' => $transition,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user