*/ final readonly class SwitchProjectWorkflowProcessor implements ProcessorInterface { public function __construct( private EntityManagerInterface $entityManager, ) {} public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): SwitchWorkflowOutput { /** @var Project $project */ $project = $data; $request = $context['request'] ?? null; $body = $request ? json_decode($request->getContent(), true) : []; $workflowId = $body['workflowId'] ?? null; $mapping = $body['mapping'] ?? []; if (!is_int($workflowId) || !is_array($mapping)) { throw new HttpException(422, 'Body must contain workflowId (int) and mapping (object).'); } $targetWorkflow = $this->entityManager->find(Workflow::class, $workflowId); if (!$targetWorkflow instanceof Workflow) { throw new NotFoundHttpException('Target workflow not found.'); } // 1) Lister les statuts source effectivement référencés par les tâches du projet $rows = $this->entityManager->getConnection()->fetchAllAssociative( 'SELECT DISTINCT status_id FROM task WHERE project_id = :pid AND status_id IS NOT NULL', ['pid' => $project->getId()], ); $referencedSourceIds = array_map(static fn ($r) => (int) $r['status_id'], $rows); // 2) Vérifier que chaque source a un mapping $missing = []; foreach ($referencedSourceIds as $srcId) { if (!array_key_exists((string) $srcId, $mapping)) { $missing[] = $srcId; } } if ([] !== $missing) { throw new HttpException(422, 'Missing mapping for source status IDs: '.implode(', ', $missing)); } // 3) Valider que chaque target appartient au workflow cible (ou est null) foreach ($mapping as $srcId => $targetId) { if (null === $targetId) { continue; } $target = $this->entityManager->find(TaskStatus::class, $targetId); if (!$target instanceof TaskStatus || $target->getWorkflow()?->getId() !== $targetWorkflow->getId()) { throw new HttpException(422, sprintf( 'Target status %s does not belong to workflow %d.', var_export($targetId, true), $targetWorkflow->getId(), )); } } // 4) Transaction unique $conn = $this->entityManager->getConnection(); $conn->beginTransaction(); try { $migrated = 0; foreach ($mapping as $srcId => $targetId) { $affected = $conn->executeStatement( 'UPDATE task SET status_id = :tid WHERE project_id = :pid AND status_id = :sid', ['tid' => $targetId, 'pid' => $project->getId(), 'sid' => (int) $srcId], ); $migrated += $affected; } $project->setWorkflow($targetWorkflow); $this->entityManager->flush(); $conn->commit(); } catch (Throwable $e) { $conn->rollBack(); throw $e; } return new SwitchWorkflowOutput( projectId: $project->getId(), workflowId: $targetWorkflow->getId(), migratedTaskCount: $migrated, ); } }