feat : add project code and task auto-numbering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-13 08:20:31 +01:00
parent 56275a9ebe
commit 517511177c
5 changed files with 98 additions and 1 deletions

View File

@@ -12,13 +12,18 @@ use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\ProjectRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(security: "is_granted('ROLE_ADMIN')"),
new Post(
security: "is_granted('ROLE_ADMIN')",
denormalizationContext: ['groups' => ['project:write', 'project:create']],
),
new Patch(security: "is_granted('ROLE_ADMIN')"),
new Delete(security: "is_granted('ROLE_ADMIN')"),
],
@@ -27,6 +32,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
order: ['name' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
#[UniqueEntity(fields: ['code'], message: 'Ce code de projet est déjà utilisé.')]
class Project
{
#[ORM\Id]
@@ -35,6 +41,12 @@ class Project
#[Groups(['project:read', 'time_entry:read'])]
private ?int $id = null;
#[ORM\Column(length: 10, unique: true)]
#[Groups(['project:read', 'project:create', 'task:read'])]
#[Assert\NotBlank]
#[Assert\Regex(pattern: '/^[A-Z]{2,10}$/', message: 'Le code doit contenir entre 2 et 10 lettres majuscules.')]
private ?string $code = null;
#[ORM\Column(length: 255)]
#[Groups(['project:read', 'project:write', 'time_entry:read'])]
private ?string $name = null;
@@ -57,6 +69,18 @@ class Project
return $this->id;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): static
{
$this->code = $code;
return $this;
}
public function getName(): ?string
{
return $this->name;

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Repository;
use App\Entity\Project;
use App\Entity\Task;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -14,4 +15,17 @@ class TaskRepository extends ServiceEntityRepository
{
parent::__construct($registry, Task::class);
}
public function findMaxNumberByProject(Project $project): int
{
$result = $this->createQueryBuilder('t')
->select('MAX(t.number)')
->where('t.project = :project')
->setParameter('project', $project)
->getQuery()
->getSingleScalarResult()
;
return (int) ($result ?? 0);
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Task;
use App\Repository\TaskRepository;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* @implements ProcessorInterface<Task, Task>
*/
final readonly class TaskNumberProcessor implements ProcessorInterface
{
/**
* @param ProcessorInterface<Task, Task> $persistProcessor
*/
public function __construct(
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
private ProcessorInterface $persistProcessor,
private TaskRepository $taskRepository,
) {}
/**
* @param Task $data
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
{
if ($operation instanceof Post && null !== $data->getProject()) {
$maxNumber = $this->taskRepository->findMaxNumberByProject($data->getProject());
$data->setNumber($maxNumber + 1);
}
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
}
}