From 0a34f9086f98e5a1d9b3b5d31ba4ea8b10799fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 14 Feb 2025 15:08:42 +0100 Subject: [PATCH] Add event subscriber for document restoration on cancel Implement an event subscriber to restore documents to their last kept version when a workflow transition ends in a non-positive final state. Includes corresponding unit tests and an unreleased feature change log entry. --- .../unreleased/Feature-20250214-150328.yaml | 6 + ...eDocumentToEditableEventSubscriberTest.php | 156 ++++++++++++++++++ ...storeDocumentToEditableEventSubscriber.php | 71 ++++++++ 3 files changed, 233 insertions(+) create mode 100644 .changes/unreleased/Feature-20250214-150328.yaml create mode 100644 src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriberTest.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriber.php diff --git a/.changes/unreleased/Feature-20250214-150328.yaml b/.changes/unreleased/Feature-20250214-150328.yaml new file mode 100644 index 000000000..9ca416984 --- /dev/null +++ b/.changes/unreleased/Feature-20250214-150328.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Restore document to previous kept version when a workflow is canceled +time: 2025-02-14T15:03:28.707250207+01:00 +custom: + Issue: "360" + SchemaChange: No schema change diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriberTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriberTest.php new file mode 100644 index 000000000..ebb90eda2 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriberTest.php @@ -0,0 +1,156 @@ +setMetadataStore( + new InMemoryMetadataStore( + placesMetadata: [ + 'final' => ['isFinal' => true], + 'cancel' => ['isFinal' => true, 'isFinalPositive' => false], + ] + ) + ); + + $registry = new Registry(); + $workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher = new EventDispatcher(), 'dummy'); + + $manager = $this->createMock(EntityWorkflowManager::class); + $manager->method('getAssociatedStoredObject')->willReturn($storedObject); + + $eventSubscriber = new OnCancelRestoreDocumentToEditableEventSubscriber( + $registry, + $manager, + $storedObjectRestore + ); + $eventDispatcher->addSubscriber($eventSubscriber); + + $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface { + public function supports(WorkflowInterface $workflow, object $subject): bool + { + return true; + } + }); + + return $registry; + } + + public function testOnCancelRestoreDocumentToEditableExpectsRestoring(): void + { + $storedObject = new StoredObject(); + $version = $storedObject->registerVersion(); + new StoredObjectPointInTime($version, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION); + $storedObject->registerVersion(); + + $restore = $this->createMock(StoredObjectRestoreInterface::class); + $restore->expects($this->once())->method('restore')->with($version); + + $registry = $this->buildRegistry($restore, $storedObject); + $entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy'); + + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $context = new WorkflowTransitionContextDTO($entityWorkflow); + + $workflow->apply($entityWorkflow, 'to_cancel', [ + 'context' => $context, + 'transition' => 'to_cancel', + 'transitionAt' => new \DateTimeImmutable('now'), + ]); + } + + public function testOnCancelRestoreDocumentDoNotExpectRestoring(): void + { + $storedObject = new StoredObject(); + $version = $storedObject->registerVersion(); + new StoredObjectPointInTime($version, StoredObjectPointInTimeReasonEnum::KEEP_BEFORE_CONVERSION); + $storedObject->registerVersion(); + + $restore = $this->createMock(StoredObjectRestoreInterface::class); + $restore->expects($this->never())->method('restore')->withAnyParameters(); + + $registry = $this->buildRegistry($restore, $storedObject); + $entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy'); + + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $context = new WorkflowTransitionContextDTO($entityWorkflow); + + $workflow->apply($entityWorkflow, 'to_intermediate', [ + 'context' => $context, + 'transition' => 'to_intermediate', + 'transitionAt' => new \DateTimeImmutable('now'), + ]); + + $workflow->apply($entityWorkflow, 'intermediate_to_final', [ + 'context' => $context, + 'transition' => 'intermediate_to_final', + 'transitionAt' => new \DateTimeImmutable('now'), + ]); + } + + public function testOnCancelRestoreDocumentToEditableToCancelStoredObjectWithoutKepts(): void + { + $storedObject = new StoredObject(); + $storedObject->registerVersion(); + + $restore = $this->createMock(StoredObjectRestoreInterface::class); + $restore->expects($this->never())->method('restore')->withAnyParameters(); + + $registry = $this->buildRegistry($restore, $storedObject); + $entityWorkflow = (new EntityWorkflow())->setWorkflowName('dummy'); + + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $context = new WorkflowTransitionContextDTO($entityWorkflow); + + $workflow->apply($entityWorkflow, 'to_cancel', [ + 'context' => $context, + 'transition' => 'to_cancel', + 'transitionAt' => new \DateTimeImmutable('now'), + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriber.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriber.php new file mode 100644 index 000000000..da5b3da5a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/OnCancelRestoreDocumentToEditableEventSubscriber.php @@ -0,0 +1,71 @@ + ['onCancelRestoreDocumentToEditable', 0]]; + } + + public function onCancelRestoreDocumentToEditable(TransitionEvent $event): void + { + $entityWorkflow = $event->getSubject(); + + if (!$entityWorkflow instanceof EntityWorkflow) { + return; + } + + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + foreach ($event->getTransition()->getTos() as $place) { + $metadata = $workflow->getMetadataStore()->getPlaceMetadata($place); + + if (($metadata['isFinal'] ?? false) && !($metadata['isFinalPositive'] ?? true)) { + $this->restoreDocument($entityWorkflow); + + return; + } + } + } + + private function restoreDocument(EntityWorkflow $entityWorkflow): void + { + $storedObject = $this->manager->getAssociatedStoredObject($entityWorkflow); + + if (null === $storedObject) { + return; + } + + $version = $storedObject->getLastKeptBeforeConversionVersion(); + + if (null === $version) { + return; + } + + $this->storedObjectRestore->restore($storedObject->getLastKeptBeforeConversionVersion()); + } +}