From e688c69438025e509a1c47a4eb88a7c23c17860e Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 19 Mar 2026 10:08:06 +0100 Subject: [PATCH] feat : add calendar fields to Task entity (dates, sync, recurrence) Co-Authored-By: Claude Sonnet 4.6 --- src/Entity/Task.php | 154 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/src/Entity/Task.php b/src/Entity/Task.php index 001c461..a15c794 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace App\Entity; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; @@ -15,10 +17,13 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use App\Repository\TaskRepository; use App\State\TaskNumberProcessor; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ApiResource( operations: [ @@ -33,7 +38,9 @@ use Symfony\Component\Serializer\Attribute\Groups; order: ['id' => 'DESC'], )] #[ApiFilter(SearchFilter::class, properties: ['project' => 'exact', 'group' => 'exact', 'assignee' => 'exact', 'priority' => 'exact', 'effort' => 'exact', 'tags' => 'exact', 'status' => 'exact'])] -#[ApiFilter(BooleanFilter::class, properties: ['archived'])] +#[ApiFilter(DateFilter::class, properties: ['scheduledStart', 'scheduledEnd', 'deadline'])] +#[ApiFilter(BooleanFilter::class, properties: ['archived', 'syncToCalendar'])] +#[ApiFilter(OrderFilter::class, properties: ['scheduledStart', 'deadline'])] #[ORM\Entity(repositoryClass: TaskRepository::class)] #[ORM\Table(name: 'task')] #[ORM\UniqueConstraint(name: 'uniq_task_project_number', columns: ['project_id', 'number'])] @@ -111,6 +118,37 @@ class Task #[Groups(['task:read', 'task:write'])] private ?ClientTicket $clientTicket = null; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[Groups(['task:read', 'task:write'])] + private ?DateTimeImmutable $scheduledStart = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[Groups(['task:read', 'task:write'])] + private ?DateTimeImmutable $scheduledEnd = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + #[Groups(['task:read', 'task:write'])] + private ?DateTimeImmutable $deadline = null; + + #[ORM\Column(type: 'boolean', options: ['default' => false])] + #[Groups(['task:read', 'task:write'])] + private bool $syncToCalendar = false; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $calendarEventUid = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $calendarTodoUid = null; + + #[ORM\Column(type: 'text', nullable: true)] + #[Groups(['task:read'])] + private ?string $calendarSyncError = null; + + #[ORM\ManyToOne(targetEntity: TaskRecurrence::class, inversedBy: 'tasks')] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] + #[Groups(['task:read', 'task:write'])] + private ?TaskRecurrence $recurrence = null; + public function __construct() { $this->tags = new ArrayCollection(); @@ -281,4 +319,118 @@ class Task return $this; } + + public function getScheduledStart(): ?DateTimeImmutable + { + return $this->scheduledStart; + } + + public function setScheduledStart(?DateTimeImmutable $scheduledStart): static + { + $this->scheduledStart = $scheduledStart; + + return $this; + } + + public function getScheduledEnd(): ?DateTimeImmutable + { + return $this->scheduledEnd; + } + + public function setScheduledEnd(?DateTimeImmutable $scheduledEnd): static + { + $this->scheduledEnd = $scheduledEnd; + + return $this; + } + + public function getDeadline(): ?DateTimeImmutable + { + return $this->deadline; + } + + public function setDeadline(?DateTimeImmutable $deadline): static + { + $this->deadline = $deadline; + + return $this; + } + + public function isSyncToCalendar(): bool + { + return $this->syncToCalendar; + } + + public function setSyncToCalendar(bool $syncToCalendar): static + { + $this->syncToCalendar = $syncToCalendar; + + return $this; + } + + public function getCalendarEventUid(): ?string + { + return $this->calendarEventUid; + } + + public function setCalendarEventUid(?string $calendarEventUid): static + { + $this->calendarEventUid = $calendarEventUid; + + return $this; + } + + public function getCalendarTodoUid(): ?string + { + return $this->calendarTodoUid; + } + + public function setCalendarTodoUid(?string $calendarTodoUid): static + { + $this->calendarTodoUid = $calendarTodoUid; + + return $this; + } + + public function getCalendarSyncError(): ?string + { + return $this->calendarSyncError; + } + + public function setCalendarSyncError(?string $calendarSyncError): static + { + $this->calendarSyncError = $calendarSyncError; + + return $this; + } + + public function getRecurrence(): ?TaskRecurrence + { + return $this->recurrence; + } + + public function setRecurrence(?TaskRecurrence $recurrence): static + { + $this->recurrence = $recurrence; + + return $this; + } + + #[Assert\Callback] + public function validateScheduledDates(ExecutionContextInterface $context): void + { + if ((null === $this->scheduledStart) !== (null === $this->scheduledEnd)) { + $context->buildViolation('scheduledStart and scheduledEnd must both be set or both be null.') + ->atPath('scheduledEnd') + ->addViolation() + ; + } + if (null !== $this->scheduledStart && null !== $this->scheduledEnd + && $this->scheduledEnd <= $this->scheduledStart) { + $context->buildViolation('scheduledEnd must be after scheduledStart.') + ->atPath('scheduledEnd') + ->addViolation() + ; + } + } }