fix(project) : permet de choisir un workflow à la création + filet par défaut
La création de projet échouait : `Project.workflow` est obligatoire mais n'était jamais fourni (formulaire frontend, MCP create-project), tout POST /api/projects partait en erreur de validation/contrainte NOT NULL. - ProjectDefaultWorkflowListener (prePersist) : assigne le workflow par défaut quand aucun n'est fourni, couvrant API Platform, API brute et MCP. - retrait de l'Assert\NotNull sur Project::workflow (la validation tournait avant le flush et empêchait le filet) ; la contrainte DB reste le garde-fou. - CreateProjectTool (MCP) : paramètre optionnel workflowId. - ProjectDrawer : sélecteur Workflow en création, pré-rempli sur le défaut, IRI envoyée dans le payload. - tests fonctionnels : création avec et sans workflow.
This commit is contained in:
@@ -92,10 +92,11 @@ class Project implements ProjectInterface, TimestampableInterface, BlamableInter
|
||||
#[Groups(['project:read', 'project:write'])]
|
||||
private ?ClientInterface $client = null;
|
||||
|
||||
// workflow_id reste NOT NULL en base ; quand l'appelant n'en fournit pas,
|
||||
// ProjectDefaultWorkflowListener assigne le workflow par défaut au prePersist.
|
||||
#[ORM\ManyToOne(targetEntity: Workflow::class)]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'RESTRICT')]
|
||||
#[Groups(['project:read', 'project:write', 'task:read'])]
|
||||
#[Assert\NotNull(message: 'Un projet doit avoir un workflow.')]
|
||||
private ?Workflow $workflow = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\ProjectManagement\Infrastructure\EventListener;
|
||||
|
||||
use App\Module\ProjectManagement\Domain\Entity\Project;
|
||||
use App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
|
||||
/**
|
||||
* Assigns the default workflow to a project when none was provided.
|
||||
* Guarantees the NOT NULL workflow_id constraint across every persistence
|
||||
* path (API Platform, raw API, MCP) without forcing the caller to supply one.
|
||||
*/
|
||||
final readonly class ProjectDefaultWorkflowListener
|
||||
{
|
||||
public function __construct(private WorkflowRepositoryInterface $workflowRepository) {}
|
||||
|
||||
public function prePersist(Project $project, PrePersistEventArgs $args): void
|
||||
{
|
||||
if (null !== $project->getWorkflow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$default = $this->workflowRepository->findDefault()
|
||||
?? ($this->workflowRepository->findBy([], ['position' => 'ASC'], 1)[0] ?? null);
|
||||
|
||||
if (null !== $default) {
|
||||
$project->setWorkflow($default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
|
||||
|
||||
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
|
||||
use App\Module\ProjectManagement\Domain\Entity\Project;
|
||||
use App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface;
|
||||
use App\Shared\Infrastructure\Mcp\Serializer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
@@ -15,12 +16,13 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
#[McpTool(name: 'create-project', description: 'Create a new project. Code must be 2-10 uppercase letters.')]
|
||||
#[McpTool(name: 'create-project', description: 'Create a new project. Code must be 2-10 uppercase letters. Optional workflowId selects the kanban workflow; the default workflow is used when omitted.')]
|
||||
class CreateProjectTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ClientRepositoryInterface $clientRepository,
|
||||
private readonly WorkflowRepositoryInterface $workflowRepository,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
@@ -30,6 +32,7 @@ class CreateProjectTool
|
||||
?string $description = null,
|
||||
?string $color = null,
|
||||
?int $clientId = null,
|
||||
?int $workflowId = null,
|
||||
): string {
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedException('Access denied: ROLE_USER required.');
|
||||
@@ -52,6 +55,14 @@ class CreateProjectTool
|
||||
}
|
||||
$project->setClient($client);
|
||||
}
|
||||
if (null !== $workflowId) {
|
||||
$workflow = $this->workflowRepository->findById($workflowId);
|
||||
if (null === $workflow) {
|
||||
throw new InvalidArgumentException(sprintf('Workflow with ID %d not found.', $workflowId));
|
||||
}
|
||||
$project->setWorkflow($workflow);
|
||||
}
|
||||
// When no workflow is supplied, ProjectDefaultWorkflowListener assigns the default at prePersist.
|
||||
|
||||
$this->entityManager->persist($project);
|
||||
$this->entityManager->flush();
|
||||
|
||||
Reference in New Issue
Block a user