mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Add PDF signature zone availability checks
Introduce `PDFSignatureZoneAvailable` service to check available PDF signature zones. Updated `WorkflowAddSignatureController` to use the new service. Added unit tests to verify the correctness of the functionality.
This commit is contained in:
		| @@ -0,0 +1,69 @@ | ||||
| <?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\DocStoreBundle\Service\Signature; | ||||
|  | ||||
| use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
|  | ||||
| class PDFSignatureZoneAvailable | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly EntityWorkflowManager $entityWorkflowManager, | ||||
|         private readonly PDFSignatureZoneParser $pdfSignatureZoneParser, | ||||
|         private readonly StoredObjectManagerInterface $storedObjectManager, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @return list<PDFSignatureZone> | ||||
|      */ | ||||
|     public function getAvailableSignatureZones(EntityWorkflow $entityWorkflow): array | ||||
|     { | ||||
|         $storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($entityWorkflow); | ||||
|  | ||||
|         if (null === $storedObject) { | ||||
|             throw new \RuntimeException('No stored object found'); | ||||
|         } | ||||
|  | ||||
|         if ('application/pdf' !== $storedObject->getType()) { | ||||
|             throw new \RuntimeException('Only PDF documents are supported'); | ||||
|         } | ||||
|  | ||||
|         $zones = $this->pdfSignatureZoneParser->findSignatureZones($this->storedObjectManager->read($storedObject)); | ||||
|         $signatureZonesIndexes = array_map( | ||||
|             fn (EntityWorkflowStepSignature $step) => $step->getZoneSignatureIndex(), | ||||
|             $this->collectSignaturesInUse($entityWorkflow) | ||||
|         ); | ||||
|  | ||||
|         return array_filter($zones, fn (PDFSignatureZone $zone) => !in_array($zone->index, $signatureZonesIndexes, true)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return list<EntityWorkflowStepSignature> | ||||
|      */ | ||||
|     private function collectSignaturesInUse(EntityWorkflow $entityWorkflow): array | ||||
|     { | ||||
|         return array_reduce($entityWorkflow->getSteps()->toArray(), function (array $result, EntityWorkflowStep $step) { | ||||
|             $current = [...$result]; | ||||
|             foreach ($step->getSignatures() as $signature) { | ||||
|                 if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) { | ||||
|                     $current[] = $signature; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return $current; | ||||
|         }, []); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| <?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 Tests\Service\Signature; | ||||
|  | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFPage; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneAvailable; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneParser; | ||||
| use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
| use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| use Prophecy\PhpUnit\ProphecyTrait; | ||||
| use Symfony\Component\Clock\MockClock; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class PDFSignatureZoneAvailableTest extends TestCase | ||||
| { | ||||
|     use ProphecyTrait; | ||||
|  | ||||
|     public function testGetAvailableSignatureZones(): void | ||||
|     { | ||||
|         $clock = new MockClock(); | ||||
|  | ||||
|         $storedObject = new StoredObject(); | ||||
|         $storedObject->registerVersion(type: 'application/pdf'); | ||||
|  | ||||
|         $entityWorkflow = new EntityWorkflow(); | ||||
|         $dto1 = new WorkflowTransitionContextDTO($entityWorkflow); | ||||
|         $dto1->futurePersonSignatures[] = new Person(); | ||||
|         $entityWorkflow->setStep('step1', $dto1, 'transition1', $clock->now()); | ||||
|         $signature = $entityWorkflow->getCurrentStep()->getSignatures()->first(); | ||||
|         $signature->setZoneSignatureIndex(1)->setState(EntityWorkflowSignatureStateEnum::SIGNED); | ||||
|  | ||||
|         $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); | ||||
|         $entityWorkflowManager->getAssociatedStoredObject($entityWorkflow)->willReturn($storedObject); | ||||
|  | ||||
|         $storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class); | ||||
|         $storedObjectManager->read($storedObject)->willReturn('fake-content'); | ||||
|  | ||||
|         $parser = $this->prophesize(PDFSignatureZoneParser::class); | ||||
|         $parser->findSignatureZones('fake-content')->willReturn([ | ||||
|             $zone1 = new PDFSignatureZone(1, 0.0, 10.0, 20.0, 20.0, new PDFPage(1, 500, 500)), | ||||
|             $zone2 = new PDFSignatureZone(2, 0.0, 10.0, 20.0, 20.0, new PDFPage(1, 500, 500)), | ||||
|         ]); | ||||
|  | ||||
|         $filter = new PDFSignatureZoneAvailable( | ||||
|             $entityWorkflowManager->reveal(), | ||||
|             $parser->reveal(), | ||||
|             $storedObjectManager->reveal(), | ||||
|         ); | ||||
|  | ||||
|         $actual = $filter->getAvailableSignatureZones($entityWorkflow); | ||||
|  | ||||
|         self::assertNotContains($zone1, $actual); | ||||
|         self::assertContains($zone2, $actual); | ||||
|         self::assertCount(1, $actual, 'there should be only one remaining zone'); | ||||
|     } | ||||
| } | ||||
| @@ -11,11 +11,9 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Controller; | ||||
|  | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneParser; | ||||
| use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneAvailable; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||||
| @@ -23,21 +21,18 @@ use Symfony\Component\Routing\Annotation\Route; | ||||
| use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | ||||
| use Twig\Environment; | ||||
|  | ||||
| final class WorkflowAddSignatureController | ||||
| final readonly class WorkflowAddSignatureController | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly EntityManagerInterface $entityManager, | ||||
|         private readonly EntityWorkflowManager $entityWorkflowManager, | ||||
|         private readonly StoredObjectManagerInterface $storedObjectManager, | ||||
|         private readonly PDFSignatureZoneParser $PDFSignatureZoneParser, | ||||
|         private readonly NormalizerInterface $normalizer, | ||||
|         private readonly Environment $twig | ||||
|         private EntityWorkflowManager $entityWorkflowManager, | ||||
|         private PDFSignatureZoneAvailable $PDFSignatureZoneAvailable, | ||||
|         private NormalizerInterface $normalizer, | ||||
|         private Environment $twig | ||||
|     ) {} | ||||
|  | ||||
|     #[Route(path: '/{_locale}/main/workflow/signature/{signature_id}/sign', name: 'chill_main_workflow_signature', methods: 'GET')] | ||||
|     public function __invoke(int $signature_id, Request $request, WorkflowController $workflowController): Response | ||||
|     #[Route(path: '/{_locale}/main/workflow/signature/{id}/sign', name: 'chill_main_workflow_signature', methods: 'GET')] | ||||
|     public function __invoke(EntityWorkflowStepSignature $signature, Request $request): Response | ||||
|     { | ||||
|         $signature = $this->entityManager->getRepository(EntityWorkflowStepSignature::class)->find($signature_id); | ||||
|         $entityWorkflow = $signature->getStep()->getEntityWorkflow(); | ||||
|  | ||||
|         $storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($entityWorkflow); | ||||
| @@ -45,20 +40,18 @@ final class WorkflowAddSignatureController | ||||
|             throw new NotFoundHttpException('No stored object found'); | ||||
|         } | ||||
|  | ||||
|         $zones = []; | ||||
|         $content = $this->storedObjectManager->read($storedObject); | ||||
|         if (null != $content) { | ||||
|             $zones = $this->PDFSignatureZoneParser->findSignatureZones($content); | ||||
|         } | ||||
|         $zones = $this->PDFSignatureZoneAvailable->getAvailableSignatureZones($entityWorkflow); | ||||
|  | ||||
|         $signatureClient = []; | ||||
|         $signatureClient['id'] = $signature->getId(); | ||||
|         $signatureClient['storedObject'] = $this->normalizer->normalize($storedObject, 'json'); | ||||
|         $signatureClient['zones'] = $zones; | ||||
|  | ||||
|         return new Response($this->twig->render( | ||||
|             '@ChillMain/Workflow/_signature_sign.html.twig', | ||||
|             ['signature' => $signatureClient] | ||||
|         )); | ||||
|         return new Response( | ||||
|             $this->twig->render( | ||||
|                 '@ChillMain/Workflow/_signature_sign.html.twig', | ||||
|                 ['signature' => $signatureClient] | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation; | ||||
| use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | ||||
| use Doctrine\Common\Collections\Collection; | ||||
| use Doctrine\Common\Collections\Selectable; | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
| use Symfony\Component\Validator\Constraints as Assert; | ||||
| @@ -52,12 +53,12 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|     private int $relatedEntityId; | ||||
|  | ||||
|     /** | ||||
|      * @var Collection<int, EntityWorkflowStep> | ||||
|      * @var Collection<int, EntityWorkflowStep>&Selectable<int, EntityWorkflowStep> | ||||
|      */ | ||||
|     #[Assert\Valid(traverse: true)] | ||||
|     #[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowStep::class, cascade: ['persist'], orphanRemoval: true)] | ||||
|     #[ORM\OrderBy(['transitionAt' => \Doctrine\Common\Collections\Criteria::ASC, 'id' => 'ASC'])] | ||||
|     private Collection $steps; | ||||
|     private Collection&Selectable $steps; | ||||
|  | ||||
|     /** | ||||
|      * @var array|EntityWorkflowStep[]|null | ||||
| @@ -242,7 +243,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface | ||||
|         throw new \RuntimeException(); | ||||
|     } | ||||
|  | ||||
|     public function getSteps(): ArrayCollection|Collection | ||||
|     public function getSteps(): Collection&Selectable | ||||
|     { | ||||
|         return $this->steps; | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,72 @@ | ||||
| <?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\Controller; | ||||
|  | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFPage; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone; | ||||
| use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneAvailable; | ||||
| use Chill\MainBundle\Controller\WorkflowAddSignatureController; | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
| use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | ||||
| use Twig\Environment; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class WorkflowAddSignatureControllerTest extends TestCase | ||||
| { | ||||
|     public function testAddSignature(): void | ||||
|     { | ||||
|         $storedObject = new StoredObject(); | ||||
|         $entityWorkflow = new EntityWorkflow(); | ||||
|         $stepTransition = new WorkflowTransitionContextDTO($entityWorkflow); | ||||
|         $stepTransition->futurePersonSignatures[] = new Person(); | ||||
|         $entityWorkflow->setStep('step_signature', $stepTransition, 'to_signature', new \DateTimeImmutable('now'), new User()); | ||||
|  | ||||
|         $signature = $entityWorkflow->getCurrentStep()->getSignatures()->first(); | ||||
|  | ||||
|         $entityWorkflowManager = $this->createMock(EntityWorkflowManager::class); | ||||
|         $entityWorkflowManager->method('getAssociatedStoredObject') | ||||
|             ->with($entityWorkflow) | ||||
|             ->willReturn($storedObject); | ||||
|  | ||||
|         $pdfSignatureZoneAvailable = $this->createMock(PDFSignatureZoneAvailable::class); | ||||
|         $pdfSignatureZoneAvailable->method('getAvailableSignatureZones')->withAnyParameters() | ||||
|             ->willReturn([ | ||||
|                 new PDFSignatureZone(1, 0.0, 0.0, 100, 100, new PDFPage(1, 500.0, 500.0)), | ||||
|             ]); | ||||
|  | ||||
|         $normalizer = $this->createMock(NormalizerInterface::class); | ||||
|         $normalizer->method('normalize')->withAnyParameters() | ||||
|             ->willReturn([]); | ||||
|  | ||||
|         $twig = $this->createMock(Environment::class); | ||||
|         $twig->method('render')->with('@ChillMain/Workflow/_signature_sign.html.twig', $this->isType('array')) | ||||
|             ->willReturn('ok'); | ||||
|  | ||||
|         $controller = new WorkflowAddSignatureController($entityWorkflowManager, $pdfSignatureZoneAvailable, $normalizer, $twig); | ||||
|  | ||||
|         $actual = $controller($signature, new Request()); | ||||
|  | ||||
|         self::assertEquals(200, $actual->getStatusCode()); | ||||
|         self::assertEquals('ok', $actual->getContent()); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user