diff --git a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowCronJob.php b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowCronJob.php index cb6f1c971..786bae3c9 100644 --- a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowCronJob.php +++ b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowCronJob.php @@ -54,8 +54,8 @@ class CancelStaleWorkflowCronJob implements CronJobInterface foreach ($staleWorkflowIds as $wId) { try { - $this->messageBus->dispatch(new CancelStaleWorkflow($wId)); - $lastCanceled = $wId; + $this->messageBus->dispatch(new CancelStaleWorkflowMessage($wId)); + $lastCanceled = max($wId, $lastCanceled); ++$processedCount; } catch (\Exception $e) { $this->logger->error("Failed to dispatch CancelStaleWorkflow for ID {$wId}", ['exception' => $e]); diff --git a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php index 533283a59..a3eefb193 100644 --- a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php +++ b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Service\Workflow; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Workflow\Registry; @@ -21,13 +22,29 @@ use Symfony\Component\Workflow\Registry; #[AsMessageHandler] class CancelStaleWorkflowHandler { - public function __construct(private readonly EntityWorkflowRepository $workflowRepository, private readonly Registry $registry, private EntityManagerInterface $em, private LoggerInterface $logger) {} + public const string KEEP_INTERVAL = 'P90D'; - public function __invoke(CancelStaleWorkflow $message): void + public function __construct(private readonly EntityWorkflowRepository $workflowRepository, private readonly Registry $registry, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly ClockInterface $clock) {} + + public function __invoke(CancelStaleWorkflowMessage $message): void { $workflowId = $message->getWorkflowId(); + $olderThanDate = $this->clock->now()->sub(new \DateInterval(self::KEEP_INTERVAL)); + $staleWorkflows = $this->workflowRepository->findWorkflowsWithoutFinalStepAndOlderThan($olderThanDate); + $workflow = $this->workflowRepository->find($workflowId); + + if (in_array($workflow, $staleWorkflows, true)) { + $this->logger->alert('Workflow has transitioned in the meantime.', [$workflowId]); + return; + } + + if (null === $workflow) { + $this->logger->alert('Workflow was not found!', [$workflowId]); + return; + } + $workflowComponent = $this->registry->get($workflow, $workflow->getWorkflowName()); $metadataStore = $workflowComponent->getMetadataStore(); $transitions = $workflowComponent->getEnabledTransitions($workflow); @@ -44,15 +61,15 @@ class CancelStaleWorkflowHandler $isFinalPositive = $metadataStore->getMetadata('isFinalPositive', $transition); if ($isFinal && !$isFinalPositive) { - $workflowComponent->apply($workflow, 'annule'); - $this->logger->info(sprintf('EntityWorkflow %d has been transitioned.', $workflowId)); + $workflowComponent->apply($workflow, $transition->getName()); + $this->logger->info('EntityWorkflow has been cancelled automatically.', [$workflowId]); $transitionApplied = true; break; } } if (!$transitionApplied) { - $this->logger->error(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId)); + $this->logger->error('No valid transition found for EntityWorkflow %d.', [$workflowId]); throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId)); } } diff --git a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflow.php b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowMessage.php similarity index 92% rename from src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflow.php rename to src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowMessage.php index 118b9f57e..30d2b6ab8 100644 --- a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflow.php +++ b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowMessage.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\Workflow; -class CancelStaleWorkflow +class CancelStaleWorkflowMessage { public function __construct(public int $workflowId) {} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php index b56faf79b..f433e8b48 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php @@ -15,6 +15,10 @@ use Chill\MainBundle\Entity\CronJobExecution; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflow; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowCronJob; +use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage; +use DateInvalidTimeZoneException; +use DateMalformedStringException; +use DateTimeImmutable; use Doctrine\DBAL\Connection; use PHPUnit\Framework\MockObject\Exception; use Psr\Log\LoggerInterface; @@ -43,7 +47,7 @@ class CancelStaleWorkflowCronJobTest extends KernelTestCase */ public function testCanRun(?CronJobExecution $cronJobExecution, bool $expected): void { - $clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00'))); + $clock = new MockClock(new DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00'))); $logger = $this->createMock(LoggerInterface::class); $cronJob = new CancelStaleWorkflowCronJob($this->createMock(EntityWorkflowRepository::class), $clock, $this->buildMessageBus(), $logger); @@ -52,17 +56,17 @@ class CancelStaleWorkflowCronJobTest extends KernelTestCase } /** - * @throws \DateMalformedStringException - * @throws \DateInvalidTimeZoneException - * @throws Exception + * @throws DateMalformedStringException + * @throws DateInvalidTimeZoneException + * @throws \Exception|Exception */ public function testRun(): void { - $clock = new MockClock((new \DateTimeImmutable('now', new \DateTimeZone('+00:00')))->add(new \DateInterval('P120D'))); + $clock = new MockClock((new DateTimeImmutable('now', new \DateTimeZone('+00:00')))->add(new \DateInterval('P120D'))); $workflowRepository = $this->createMock(EntityWorkflowRepository::class); $logger = $this->createMock(LoggerInterface::class); - $workflowRepository->method('findWorkflowsWithoutFinalStepAndOlderThan')->willReturn([1, 2, 3]); + $workflowRepository->method('findWorkflowsWithoutFinalStepAndOlderThan')->willReturn([1, 3, 2]); $messageBus = $this->buildMessageBus(true); $cronJob = new CancelStaleWorkflowCronJob($workflowRepository, $clock, $messageBus, $logger); @@ -80,17 +84,17 @@ class CancelStaleWorkflowCronJobTest extends KernelTestCase public static function buildTestCanRunData(): iterable { yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:00', new \DateTimeZone('+00:00'))), + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new DateTimeImmutable('2023-12-31 00:00:00', new \DateTimeZone('+00:00'))), true, ]; yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-30 23:59:59', new \DateTimeZone('+00:00'))), + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new DateTimeImmutable('2023-12-30 23:59:59', new \DateTimeZone('+00:00'))), true, ]; yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:01', new \DateTimeZone('+00:00'))), + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new DateTimeImmutable('2023-12-31 00:00:01', new \DateTimeZone('+00:00'))), false, ]; } @@ -104,7 +108,7 @@ class CancelStaleWorkflowCronJobTest extends KernelTestCase false => $messageBus->method('dispatch'), }; - $methodDispatch->willReturnCallback(function (CancelStaleWorkflow $message) { + $methodDispatch->willReturnCallback(function (CancelStaleWorkflowMessage $message) { return new Envelope($message); }); diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php index f7887a6cd..dbf88098f 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflow; use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowHandler; +use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; @@ -51,7 +52,7 @@ class CancelStaleWorkflowHandlerTest extends TestCase $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); - $handler(new CancelStaleWorkflow(1)); + $handler(new CancelStaleWorkflowMessage(1)); } public function testInvokeWorkflowWithMultipleStepsAndValidTransition(): void @@ -92,7 +93,7 @@ class CancelStaleWorkflowHandlerTest extends TestCase $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); - $handler(new CancelStaleWorkflow(1)); + $handler(new CancelStaleWorkflowMessage(1)); } public function testInvokeWorkflowWithMultipleStepsAndNoValidTransition(): void @@ -134,6 +135,6 @@ class CancelStaleWorkflowHandlerTest extends TestCase $handler = new CancelStaleWorkflowHandler($workflowRepository, $registry, $em, $logger); - $handler(new CancelStaleWorkflow(1)); + $handler(new CancelStaleWorkflowMessage(1)); } }