mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Refactor PDF signature handling and add signature state changer
Simplified PdfSignedMessageHandler by delegating signature state changes to a new SignatureStepStateChanger class. Added utility method to EntityWorkflowStepSignature for checking pending signatures and created new test cases for the SignatureStepStateChanger.
This commit is contained in:
parent
00e878892e
commit
1197a46f5f
@ -12,12 +12,11 @@ declare(strict_types=1);
|
||||
namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
||||
@ -33,7 +32,7 @@ final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
||||
private StoredObjectManagerInterface $storedObjectManager,
|
||||
private EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private ClockInterface $clock,
|
||||
private SignatureStepStateChanger $signatureStepStateChanger,
|
||||
) {}
|
||||
|
||||
public function __invoke(PdfSignedMessage $message): void
|
||||
@ -54,8 +53,8 @@ final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
|
||||
|
||||
$this->storedObjectManager->write($storedObject, $message->content);
|
||||
|
||||
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate($this->clock->now());
|
||||
$signature->setZoneSignatureIndex($message->signatureZoneIndex);
|
||||
$this->signatureStepStateChanger->markSignatureAsSigned($signature, $message->signatureZoneIndex);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->clear();
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -45,6 +45,9 @@ class PdfSignedMessageHandlerTest extends TestCase
|
||||
$entityWorkflow->setStep('new_step', $dto, 'new_transition', new \DateTimeImmutable(), new User());
|
||||
$step = $entityWorkflow->getCurrentStep();
|
||||
$signature = $step->getSignatures()->first();
|
||||
$stateChanger = $this->createMock(SignatureStepStateChanger::class);
|
||||
$stateChanger->expects(self::once())->method('markSignatureAsSigned')
|
||||
->with($signature, 99);
|
||||
|
||||
$handler = new PdfSignedMessageHandler(
|
||||
new NullLogger(),
|
||||
@ -52,15 +55,12 @@ class PdfSignedMessageHandlerTest extends TestCase
|
||||
$this->buildStoredObjectManager($storedObject, $expectedContent = '1234'),
|
||||
$this->buildSignatureRepository($signature),
|
||||
$this->buildEntityManager(true),
|
||||
new MockClock('now'),
|
||||
$stateChanger,
|
||||
);
|
||||
|
||||
// we simply call the handler. The mocked StoredObjectManager will check that the "write" method is invoked once
|
||||
// with the content "1234"
|
||||
$handler(new PdfSignedMessage(10, 99, $expectedContent));
|
||||
|
||||
self::assertEquals('signed', $signature->getState()->value);
|
||||
self::assertEquals(99, $signature->getZoneSignatureIndex());
|
||||
}
|
||||
|
||||
private function buildSignatureRepository(EntityWorkflowStepSignature $signature): EntityWorkflowStepSignatureRepository
|
||||
|
@ -141,6 +141,32 @@ class EntityWorkflowStepSignature implements TrackCreationInterface, TrackUpdate
|
||||
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'
|
||||
*/
|
||||
|
@ -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