feat(project-management) : migrate core Projects/Tasks domain into module (back)

Tranche 2 of LST-65. Mechanical, behaviour-preserving move of the core
business domain into src/Module/ProjectManagement/. API operations,
securities, uriTemplates and the 38 MCP tool names are all unchanged.

- 10 entities + 2 enums moved to Domain/{Entity,Enum}; intra-module
  relations stay concrete, cross-module relations go through contracts
  (Project.client -> ClientInterface, Task/TaskDocument users ->
  UserInterface).
- 9 repositories split into Domain/Repository interfaces + Doctrine impls,
  bound in services.yaml; consumers inject the interfaces. find() kept off
  the interfaces (ServiceEntityRepository ?object compat) -> findById().
- State (7), MCP tools (38), controller, CalDavService/RecurrenceCalculator,
  3 Doctrine listeners and SwitchWorkflowOutput moved under Infrastructure/.
- doctrine.yaml: ProjectManagement mapping + resolve_target_entities of the
  3 module contracts repointed to the module (ClientInterface stays legacy).
- ProjectManagementModule registered (id project-management, 4 RBAC perms,
  not re-wired); sidebar my-tasks/projects gated by the module.
- Legacy not-yet-modularised consumers (Mail/Gitea/BookStack, Serializer,
  fixtures, tests) swapped to the module FQCN — transitional coupling to be
  cleaned in 2.4/2.5/2.6.

159 tests green, mapping valid, no API route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 16:54:59 +02:00
parent f119ec30ca
commit 23809f165e
119 changed files with 779 additions and 454 deletions
+2
View File
@@ -8,9 +8,11 @@ declare(strict_types=1);
*/
use App\Module\Core\CoreModule;
use App\Module\ProjectManagement\ProjectManagementModule;
use App\Module\TimeTracking\TimeTrackingModule;
return [
CoreModule::class,
TimeTrackingModule::class,
ProjectManagementModule::class,
];
+8 -3
View File
@@ -22,9 +22,9 @@ doctrine:
auto_mapping: true
resolve_target_entities:
App\Shared\Domain\Contract\UserInterface: App\Module\Core\Domain\Entity\User
App\Shared\Domain\Contract\ProjectInterface: App\Entity\Project
App\Shared\Domain\Contract\TaskInterface: App\Entity\Task
App\Shared\Domain\Contract\TaskTagInterface: App\Entity\TaskTag
App\Shared\Domain\Contract\ProjectInterface: App\Module\ProjectManagement\Domain\Entity\Project
App\Shared\Domain\Contract\TaskInterface: App\Module\ProjectManagement\Domain\Entity\Task
App\Shared\Domain\Contract\TaskTagInterface: App\Module\ProjectManagement\Domain\Entity\TaskTag
App\Shared\Domain\Contract\ClientInterface: App\Entity\Client
mappings:
App:
@@ -43,6 +43,11 @@ doctrine:
is_bundle: false
dir: '%kernel.project_dir%/src/Module/TimeTracking/Domain/Entity'
prefix: 'App\Module\TimeTracking\Domain\Entity'
ProjectManagement:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Module/ProjectManagement/Domain/Entity'
prefix: 'App\Module\ProjectManagement\Domain\Entity'
controller_resolver:
auto_mapping: false
+23 -5
View File
@@ -31,25 +31,25 @@ services:
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\EventListener\TaskDocumentListener:
App\Module\ProjectManagement\Infrastructure\EventListener\TaskDocumentListener:
arguments:
$uploadDir: '%task_document_upload_dir%'
tags:
- { name: doctrine.orm.entity_listener }
App\State\TaskDocumentProcessor:
App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\TaskDocumentProcessor:
arguments:
$uploadDir: '%task_document_upload_dir%'
App\Controller\TaskDocumentDownloadController:
App\Module\ProjectManagement\Infrastructure\Controller\TaskDocumentDownloadController:
arguments:
$uploadDir: '%task_document_upload_dir%'
App\Mcp\Tool\Task\AddTaskDocumentTool:
App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task\AddTaskDocumentTool:
arguments:
$uploadDir: '%task_document_upload_dir%'
App\Mcp\Tool\Task\UpdateTaskDocumentTool:
App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task\UpdateTaskDocumentTool:
arguments:
$uploadDir: '%task_document_upload_dir%'
@@ -75,4 +75,22 @@ services:
App\Module\TimeTracking\Domain\Repository\TimeEntryRepositoryInterface: '@App\Module\TimeTracking\Infrastructure\Doctrine\DoctrineTimeEntryRepository'
App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineProjectRepository'
App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskRepository'
App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineWorkflowRepository'
App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskStatusRepository'
App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskGroupRepository'
App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskEffortRepository'
App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskPriorityRepository'
App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskTagRepository'
App\Module\ProjectManagement\Domain\Repository\TaskRecurrenceRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskRecurrenceRepository'
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
+2 -2
View File
@@ -20,8 +20,8 @@ return [
'icon' => 'mdi:view-dashboard-outline',
'items' => [
['label' => 'sidebar.general.dashboard', 'to' => '/', 'icon' => 'mdi:view-dashboard-outline'],
['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline'],
['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline'],
['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline', 'module' => 'project-management'],
['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline', 'module' => 'project-management'],
['label' => 'sidebar.general.timeTracking', 'to' => '/time-tracking', 'icon' => 'mdi:calendar-edit-outline', 'module' => 'time-tracking'],
],
],
+1 -1
View File
@@ -9,8 +9,8 @@ use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Post;
use App\Entity\Task;
use App\Entity\TaskBookStackLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\State\BookStackLinkProcessor;
use App\State\BookStackLinkProvider;
use Symfony\Component\Serializer\Attribute\Groups;
+1 -1
View File
@@ -7,7 +7,7 @@ namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use App\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\State\BookStackSearchResultProvider;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace App\Controller\Mail;
use App\Entity\Project;
use App\Entity\Task;
use App\Entity\TaskGroup;
use App\Entity\TaskMailLink;
use App\Entity\TaskStatus;
use App\Module\Core\Domain\Entity\User;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Repository\MailMessageRepository;
use App\Repository\TaskRepository;
use App\Security\MailAccessChecker;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
@@ -31,7 +31,7 @@ class MailCreateTaskController extends AbstractController
private readonly MailMessageRepository $messageRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
) {}
public function __invoke(Request $request, int $id): JsonResponse
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Controller\Mail;
use App\Entity\Task;
use App\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Mail;
use App\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Mail;
use App\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use DateTimeInterface;
+11 -11
View File
@@ -9,23 +9,23 @@ use App\Entity\AbsencePolicy;
use App\Entity\AbsenceRequest;
use App\Entity\Client;
use App\Entity\MailConfiguration;
use App\Entity\Project;
use App\Entity\Task;
use App\Entity\TaskEffort;
use App\Entity\TaskGroup;
use App\Entity\TaskPriority;
use App\Entity\TaskRecurrence;
use App\Entity\TaskStatus;
use App\Entity\TaskTag;
use App\Entity\Workflow;
use App\Entity\ZimbraConfiguration;
use App\Enum\AbsenceStatus;
use App\Enum\AbsenceType;
use App\Enum\ContractType;
use App\Enum\RecurrenceType;
use App\Enum\StatusCategory;
use App\Module\Core\Application\Rbac\RbacSeeder;
use App\Module\Core\Domain\Entity\User;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskPriority;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Entity\TaskTag;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use App\Module\ProjectManagement\Domain\Enum\StatusCategory;
use App\Module\TimeTracking\Domain\Entity\TimeEntry;
use DateTimeImmutable;
use DateTimeZone;
+1
View File
@@ -10,6 +10,7 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Repository\ClientRepository;
use App\Shared\Domain\Contract\ClientInterface;
use Doctrine\Common\Collections\ArrayCollection;
+1
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Entity;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskBookStackLinkRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
+1
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Entity;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
+8 -8
View File
@@ -8,15 +8,15 @@ use App\Entity\AbsenceBalance;
use App\Entity\AbsencePolicy;
use App\Entity\AbsenceRequest;
use App\Entity\Client;
use App\Entity\Project;
use App\Entity\Task;
use App\Entity\TaskDocument;
use App\Entity\TaskEffort;
use App\Entity\TaskGroup;
use App\Entity\TaskPriority;
use App\Entity\TaskStatus;
use App\Entity\TaskTag;
use App\Module\Core\Domain\Entity\User;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskPriority;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Entity\TaskTag;
use App\Module\TimeTracking\Domain\Entity\TimeEntry;
use Doctrine\Common\Collections\Collection;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Metadata\ApiFilter;
@@ -13,11 +13,11 @@ use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\ApiResource\SwitchWorkflowOutput;
use App\Repository\ProjectRepository;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\Resource\SwitchWorkflowOutput;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\SwitchProjectWorkflowProcessor;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineProjectRepository;
use App\Shared\Domain\Contract\ClientInterface;
use App\Shared\Domain\Contract\ProjectInterface;
use App\State\SwitchProjectWorkflowProcessor;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
@@ -54,7 +54,7 @@ use Symfony\Component\Validator\Constraints as Assert;
order: ['name' => 'ASC'],
)]
#[ApiFilter(BooleanFilter::class, properties: ['archived'])]
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineProjectRepository::class)]
#[UniqueEntity(fields: ['code'], message: 'Ce code de projet est déjà utilisé.')]
class Project implements ProjectInterface
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
@@ -15,11 +15,11 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\TaskRepository;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\TaskCalendarProcessor;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\TaskNumberProcessor;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskRepository;
use App\Shared\Domain\Contract\TaskInterface;
use App\Shared\Domain\Contract\UserInterface;
use App\State\TaskCalendarProcessor;
use App\State\TaskNumberProcessor;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -44,7 +44,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[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\Entity(repositoryClass: DoctrineTaskRepository::class)]
#[ORM\Table(name: 'task')]
#[ORM\UniqueConstraint(name: 'uniq_task_project_number', columns: ['project_id', 'number'])]
class Task implements TaskInterface
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
@@ -11,10 +11,10 @@ use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\EventListener\TaskDocumentListener;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\TaskDocumentProcessor;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\TaskDocumentProvider;
use App\Module\ProjectManagement\Infrastructure\EventListener\TaskDocumentListener;
use App\Shared\Domain\Contract\UserInterface;
use App\State\TaskDocumentProcessor;
use App\State\TaskDocumentProvider;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,7 +10,7 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\TaskEffortRepository;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskEffortRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -26,7 +26,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
denormalizationContext: ['groups' => ['task_effort:write']],
order: ['label' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: TaskEffortRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskEffortRepository::class)]
class TaskEffort
{
#[ORM\Id]
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
@@ -13,7 +13,7 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\TaskGroupRepository;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskGroupRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -31,7 +31,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
)]
#[ApiFilter(SearchFilter::class, properties: ['project' => 'exact'])]
#[ApiFilter(BooleanFilter::class, properties: ['archived'])]
#[ORM\Entity(repositoryClass: TaskGroupRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskGroupRepository::class)]
class TaskGroup
{
#[ORM\Id]
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,7 +10,7 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\TaskPriorityRepository;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskPriorityRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -26,7 +26,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
denormalizationContext: ['groups' => ['task_priority:write']],
order: ['label' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: TaskPriorityRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskPriorityRepository::class)]
class TaskPriority
{
#[ORM\Id]
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,8 +10,8 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Enum\RecurrenceType;
use App\Repository\TaskRecurrenceRepository;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskRecurrenceRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
normalizationContext: ['groups' => ['task_recurrence:read']],
denormalizationContext: ['groups' => ['task_recurrence:write']],
)]
#[ORM\Entity(repositoryClass: TaskRecurrenceRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskRecurrenceRepository::class)]
class TaskRecurrence
{
#[ORM\Id]
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,8 +10,8 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Enum\StatusCategory;
use App\Repository\TaskStatusRepository;
use App\Module\ProjectManagement\Domain\Enum\StatusCategory;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskStatusRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
@@ -28,7 +28,7 @@ use Symfony\Component\Validator\Constraints as Assert;
denormalizationContext: ['groups' => ['task_status:write']],
order: ['position' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: TaskStatusRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskStatusRepository::class)]
class TaskStatus
{
#[ORM\Id]
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,7 +10,7 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\TaskTagRepository;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskTagRepository;
use App\Shared\Domain\Contract\TaskTagInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -27,7 +27,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
denormalizationContext: ['groups' => ['task_tag:write']],
order: ['label' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: TaskTagRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskTagRepository::class)]
#[ORM\Table(name: 'task_type')]
class TaskTag implements TaskTagInterface
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\ProjectManagement\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
@@ -10,8 +10,8 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Repository\WorkflowRepository;
use App\State\WorkflowDeleteProcessor;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\WorkflowDeleteProcessor;
use App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineWorkflowRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
@@ -31,7 +31,7 @@ use Symfony\Component\Validator\Constraints as Assert;
denormalizationContext: ['groups' => ['workflow:write']],
order: ['position' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: WorkflowRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineWorkflowRepository::class)]
#[UniqueEntity(fields: ['name'], message: 'Ce nom de workflow est déjà utilisé.')]
class Workflow
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Enum;
namespace App\Module\ProjectManagement\Domain\Enum;
enum RecurrenceType: string
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Enum;
namespace App\Module\ProjectManagement\Domain\Enum;
enum StatusCategory: string
{
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\Project;
interface ProjectRepositoryInterface
{
public function findById(int $id): ?Project;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return Project[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
interface TaskEffortRepositoryInterface
{
public function findById(int $id): ?TaskEffort;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return TaskEffort[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
interface TaskGroupRepositoryInterface
{
public function findById(int $id): ?TaskGroup;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return TaskGroup[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskPriority;
interface TaskPriorityRepositoryInterface
{
public function findById(int $id): ?TaskPriority;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return TaskPriority[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
interface TaskRecurrenceRepositoryInterface
{
public function findById(int $id): ?TaskRecurrence;
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use Doctrine\ORM\QueryBuilder;
interface TaskRepositoryInterface
{
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder;
public function findById(int $id): ?Task;
/**
* Returns the max task number for a project, using an advisory lock
* to prevent race conditions when creating tasks concurrently.
*/
public function findMaxNumberByProjectForUpdate(Project $project): int;
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
interface TaskStatusRepositoryInterface
{
public function findById(int $id): ?TaskStatus;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return TaskStatus[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
public function findFirstNonFinal(): ?TaskStatus;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\TaskTag;
interface TaskTagRepositoryInterface
{
public function findById(int $id): ?TaskTag;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return TaskTag[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Domain\Repository;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
interface WorkflowRepositoryInterface
{
public function findById(int $id): ?Workflow;
/**
* @param array<string, mixed> $criteria
* @param null|array<string, string> $orderBy
*
* @return Workflow[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
public function findDefault(): ?Workflow;
}
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\ApiResource;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\Resource;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -2,21 +2,21 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use App\Entity\Task;
use App\Repository\TaskRepository;
use App\Repository\TaskStatusRepository;
use App\Service\CalDavService;
use App\Service\RecurrenceCalculator;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use App\Module\ProjectManagement\Infrastructure\Service\RecurrenceCalculator;
use Doctrine\ORM\EntityManagerInterface;
final readonly class RecurrenceHandler
{
public function __construct(
private RecurrenceCalculator $calculator,
private TaskRepository $taskRepository,
private TaskStatusRepository $statusRepository,
private TaskRepositoryInterface $taskRepository,
private TaskStatusRepositoryInterface $statusRepository,
private CalDavService $calDavService,
private EntityManagerInterface $entityManager,
) {}
@@ -2,14 +2,14 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\SwitchWorkflowOutput;
use App\Entity\Project;
use App\Entity\TaskStatus;
use App\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\Resource\SwitchWorkflowOutput;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -2,14 +2,14 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Task;
use App\Entity\TaskStatus;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Task;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Service\Share\Exception\InvalidPathException;
use App\Service\Share\Exception\ShareConnectionException;
use App\Service\Share\Exception\ShareNotConfiguredException;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Shared\Domain\Contract\UserInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
@@ -2,14 +2,14 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Task;
use App\Repository\TaskRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
@@ -24,7 +24,7 @@ final readonly class TaskNumberProcessor implements ProcessorInterface
public function __construct(
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
private ProcessorInterface $persistProcessor,
private TaskRepository $taskRepository,
private TaskRepositoryInterface $taskRepository,
private EntityManagerInterface $entityManager,
private CalDavService $calDavService,
) {}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Controller;
namespace App\Module\ProjectManagement\Infrastructure\Controller;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Service\Share\Exception\ShareConnectionException;
use App\Service\Share\Exception\ShareNotConfiguredException;
use App\Service\Share\FileSource;
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Project>
*/
class DoctrineProjectRepository extends ServiceEntityRepository implements ProjectRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Project::class);
}
public function findById(int $id): ?Project
{
return $this->find($id);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TaskEffort>
*/
class DoctrineTaskEffortRepository extends ServiceEntityRepository implements TaskEffortRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskEffort::class);
}
public function findById(int $id): ?TaskEffort
{
return $this->find($id);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TaskGroup>
*/
class DoctrineTaskGroupRepository extends ServiceEntityRepository implements TaskGroupRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskGroup::class);
}
public function findById(int $id): ?TaskGroup
{
return $this->find($id);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\TaskPriority;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TaskPriority>
*/
class DoctrineTaskPriorityRepository extends ServiceEntityRepository implements TaskPriorityRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskPriority::class);
}
public function findById(int $id): ?TaskPriority
{
return $this->find($id);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
use App\Module\ProjectManagement\Domain\Repository\TaskRecurrenceRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TaskRecurrence>
*/
class DoctrineTaskRecurrenceRepository extends ServiceEntityRepository implements TaskRecurrenceRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskRecurrence::class);
}
public function findById(int $id): ?TaskRecurrence
{
return $this->find($id);
}
}
@@ -2,23 +2,29 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Entity\Project;
use App\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Task>
*/
class TaskRepository extends ServiceEntityRepository
class DoctrineTaskRepository extends ServiceEntityRepository implements TaskRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Task::class);
}
public function findById(int $id): ?Task
{
return $this->find($id);
}
/**
* Returns the max task number for a project, using an advisory lock
* to prevent race conditions when creating tasks concurrently.
@@ -2,19 +2,28 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class TaskStatusRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<TaskStatus>
*/
class DoctrineTaskStatusRepository extends ServiceEntityRepository implements TaskStatusRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskStatus::class);
}
public function findById(int $id): ?TaskStatus
{
return $this->find($id);
}
public function findFirstNonFinal(): ?TaskStatus
{
return $this->createQueryBuilder('s')
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Module\ProjectManagement\Domain\Entity\TaskTag;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TaskTag>
*/
class DoctrineTaskTagRepository extends ServiceEntityRepository implements TaskTagRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TaskTag::class);
}
public function findById(int $id): ?TaskTag
{
return $this->find($id);
}
}
@@ -2,22 +2,28 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\ProjectManagement\Infrastructure\Doctrine;
use App\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Workflow>
*/
class WorkflowRepository extends ServiceEntityRepository
class DoctrineWorkflowRepository extends ServiceEntityRepository implements WorkflowRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Workflow::class);
}
public function findById(int $id): ?Workflow
{
return $this->find($id);
}
public function findDefault(): ?Workflow
{
return $this->findOneBy(['isDefault' => true]);
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\EventListener;
namespace App\Module\ProjectManagement\Infrastructure\EventListener;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Psr\Log\LoggerInterface;
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\EventListener;
namespace App\Module\ProjectManagement\Infrastructure\EventListener;
use App\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Shared\Domain\Contract\NotifierInterface;
use App\Shared\Domain\Contract\UserInterface;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\EventListener;
namespace App\Module\ProjectManagement\Infrastructure\EventListener;
use App\Entity\Workflow;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Project;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Entity\Project;
use App\Mcp\Tool\Serializer;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Repository\ClientRepository;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Project;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Repository\ProjectRepository;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteProjectTool
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeleteProjectTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
}
$project = $this->projectRepository->find($id);
$project = $this->projectRepository->findById($id);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $id));
}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Project;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Mcp\Tool\Serializer;
use App\Repository\ProjectRepository;
use App\Repository\TaskRepository;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -18,8 +18,8 @@ use function sprintf;
class GetProjectTool
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly TaskRepository $taskRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly Security $security,
) {}
@@ -29,7 +29,7 @@ class GetProjectTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$project = $this->projectRepository->find($id);
$project = $this->projectRepository->findById($id);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $id));
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Project;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Mcp\Tool\Serializer;
use App\Repository\ProjectRepository;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListProjectsTool
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly Security $security,
) {}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Project;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Project;
use App\Mcp\Tool\Serializer;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use App\Repository\ClientRepository;
use App\Repository\ProjectRepository;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -19,7 +19,7 @@ use function sprintf;
class UpdateProjectTool
{
public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly ClientRepository $clientRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
@@ -38,7 +38,7 @@ class UpdateProjectTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$project = $this->projectRepository->find($id);
$project = $this->projectRepository->findById($id);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $id));
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Entity\TaskDocument;
use App\Repository\TaskRepository;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -33,7 +33,7 @@ class AddTaskDocumentTool
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly Security $security,
private readonly string $uploadDir,
) {}
@@ -52,7 +52,7 @@ class AddTaskDocumentTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$task = $this->taskRepository->find($taskId);
$task = $this->taskRepository->findById($taskId);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $taskId));
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Entity\TaskRecurrence;
use App\Enum\RecurrenceType;
use App\Repository\TaskRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -22,7 +22,7 @@ class CreateTaskRecurrenceTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly Security $security,
private readonly CalDavService $calDavService,
) {}
@@ -41,7 +41,7 @@ class CreateTaskRecurrenceTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$task = $this->taskRepository->find($taskId);
$task = $this->taskRepository->findById($taskId);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $taskId));
}
@@ -2,19 +2,19 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Entity\Task;
use App\Mcp\Tool\Serializer;
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
use App\Repository\ProjectRepository;
use App\Repository\TaskEffortRepository;
use App\Repository\TaskGroupRepository;
use App\Repository\TaskPriorityRepository;
use App\Repository\TaskRepository;
use App\Repository\TaskStatusRepository;
use App\Repository\TaskTagRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -29,13 +29,13 @@ class CreateTaskTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly ProjectRepository $projectRepository,
private readonly TaskRepository $taskRepository,
private readonly TaskStatusRepository $taskStatusRepository,
private readonly TaskPriorityRepository $taskPriorityRepository,
private readonly TaskEffortRepository $taskEffortRepository,
private readonly TaskGroupRepository $taskGroupRepository,
private readonly TaskTagRepository $taskTagRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskStatusRepositoryInterface $taskStatusRepository,
private readonly TaskPriorityRepositoryInterface $taskPriorityRepository,
private readonly TaskEffortRepositoryInterface $taskEffortRepository,
private readonly TaskGroupRepositoryInterface $taskGroupRepository,
private readonly TaskTagRepositoryInterface $taskTagRepository,
private readonly DoctrineUserRepository $userRepository,
private readonly Security $security,
private readonly CalDavService $calDavService,
@@ -65,7 +65,7 @@ class CreateTaskTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$project = $this->projectRepository->find($projectId);
$project = $this->projectRepository->findById($projectId);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $projectId));
}
@@ -78,21 +78,21 @@ class CreateTaskTool
$task->setDescription($description);
}
if (null !== $statusId) {
$status = $this->taskStatusRepository->find($statusId);
$status = $this->taskStatusRepository->findById($statusId);
if (null === $status) {
throw new InvalidArgumentException(sprintf('TaskStatus with ID %d not found.', $statusId));
}
$task->setStatus($status);
}
if (null !== $priorityId) {
$priority = $this->taskPriorityRepository->find($priorityId);
$priority = $this->taskPriorityRepository->findById($priorityId);
if (null === $priority) {
throw new InvalidArgumentException(sprintf('TaskPriority with ID %d not found.', $priorityId));
}
$task->setPriority($priority);
}
if (null !== $effortId) {
$effort = $this->taskEffortRepository->find($effortId);
$effort = $this->taskEffortRepository->findById($effortId);
if (null === $effort) {
throw new InvalidArgumentException(sprintf('TaskEffort with ID %d not found.', $effortId));
}
@@ -106,7 +106,7 @@ class CreateTaskTool
$task->setAssignee($assignee);
}
if (null !== $groupId) {
$group = $this->taskGroupRepository->find($groupId);
$group = $this->taskGroupRepository->findById($groupId);
if (null === $group) {
throw new InvalidArgumentException(sprintf('TaskGroup with ID %d not found.', $groupId));
}
@@ -114,7 +114,7 @@ class CreateTaskTool
}
if (null !== $tagIds) {
foreach ($tagIds as $tagId) {
$tag = $this->taskTagRepository->find($tagId);
$tag = $this->taskTagRepository->findById($tagId);
if (null === $tag) {
throw new InvalidArgumentException(sprintf('TaskTag with ID %d not found.', $tagId));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Repository\TaskRecurrenceRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Repository\TaskRecurrenceRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -19,7 +19,7 @@ class DeleteTaskRecurrenceTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TaskRecurrenceRepository $taskRecurrenceRepository,
private readonly TaskRecurrenceRepositoryInterface $taskRecurrenceRepository,
private readonly Security $security,
private readonly CalDavService $calDavService,
) {}
@@ -30,7 +30,7 @@ class DeleteTaskRecurrenceTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$recurrence = $this->taskRecurrenceRepository->find($recurrenceId);
$recurrence = $this->taskRecurrenceRepository->findById($recurrenceId);
if (null === $recurrence) {
throw new InvalidArgumentException(sprintf('TaskRecurrence with ID %d not found.', $recurrenceId));
}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Repository\TaskRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -18,7 +18,7 @@ use function sprintf;
class DeleteTaskTool
{
public function __construct(
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
private readonly CalDavService $calDavService,
@@ -30,7 +30,7 @@ class DeleteTaskTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$task = $this->taskRepository->find($id);
$task = $this->taskRepository->findById($id);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $id));
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Mcp\Tool\Serializer;
use App\Repository\TaskRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -17,7 +17,7 @@ use function sprintf;
class GetTaskTool
{
public function __construct(
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly Security $security,
) {}
@@ -27,7 +27,7 @@ class GetTaskTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$task = $this->taskRepository->find($id);
$task = $this->taskRepository->findById($id);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $id));
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Mcp\Tool\Serializer;
use App\Repository\TaskRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListTasksTool
{
public function __construct(
private readonly TaskRepository $taskRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly Security $security,
) {}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Entity\TaskDocument;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Enum\RecurrenceType;
use App\Repository\TaskRecurrenceRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use App\Module\ProjectManagement\Domain\Repository\TaskRecurrenceRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -21,7 +21,7 @@ class UpdateTaskRecurrenceTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TaskRecurrenceRepository $taskRecurrenceRepository,
private readonly TaskRecurrenceRepositoryInterface $taskRecurrenceRepository,
private readonly Security $security,
private readonly CalDavService $calDavService,
) {}
@@ -40,7 +40,7 @@ class UpdateTaskRecurrenceTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$recurrence = $this->taskRecurrenceRepository->find($recurrenceId);
$recurrence = $this->taskRecurrenceRepository->findById($recurrenceId);
if (null === $recurrence) {
throw new InvalidArgumentException(sprintf('TaskRecurrence with ID %d not found.', $recurrenceId));
}
@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Task;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Task;
use App\Mcp\Tool\Serializer;
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
use App\Repository\TaskEffortRepository;
use App\Repository\TaskGroupRepository;
use App\Repository\TaskPriorityRepository;
use App\Repository\TaskRepository;
use App\Repository\TaskStatusRepository;
use App\Repository\TaskTagRepository;
use App\Service\CalDavService;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use App\Module\ProjectManagement\Infrastructure\Service\CalDavService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -27,12 +27,12 @@ class UpdateTaskTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TaskRepository $taskRepository,
private readonly TaskStatusRepository $taskStatusRepository,
private readonly TaskPriorityRepository $taskPriorityRepository,
private readonly TaskEffortRepository $taskEffortRepository,
private readonly TaskGroupRepository $taskGroupRepository,
private readonly TaskTagRepository $taskTagRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskStatusRepositoryInterface $taskStatusRepository,
private readonly TaskPriorityRepositoryInterface $taskPriorityRepository,
private readonly TaskEffortRepositoryInterface $taskEffortRepository,
private readonly TaskGroupRepositoryInterface $taskGroupRepository,
private readonly TaskTagRepositoryInterface $taskTagRepository,
private readonly DoctrineUserRepository $userRepository,
private readonly Security $security,
private readonly CalDavService $calDavService,
@@ -63,7 +63,7 @@ class UpdateTaskTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$task = $this->taskRepository->find($id);
$task = $this->taskRepository->findById($id);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $id));
@@ -76,21 +76,21 @@ class UpdateTaskTool
$task->setDescription($description);
}
if (null !== $statusId) {
$status = $this->taskStatusRepository->find($statusId);
$status = $this->taskStatusRepository->findById($statusId);
if (null === $status) {
throw new InvalidArgumentException(sprintf('TaskStatus with ID %d not found.', $statusId));
}
$task->setStatus($status);
}
if (null !== $priorityId) {
$priority = $this->taskPriorityRepository->find($priorityId);
$priority = $this->taskPriorityRepository->findById($priorityId);
if (null === $priority) {
throw new InvalidArgumentException(sprintf('TaskPriority with ID %d not found.', $priorityId));
}
$task->setPriority($priority);
}
if (null !== $effortId) {
$effort = $this->taskEffortRepository->find($effortId);
$effort = $this->taskEffortRepository->findById($effortId);
if (null === $effort) {
throw new InvalidArgumentException(sprintf('TaskEffort with ID %d not found.', $effortId));
}
@@ -104,7 +104,7 @@ class UpdateTaskTool
$task->setAssignee($assignee);
}
if (null !== $groupId) {
$group = $this->taskGroupRepository->find($groupId);
$group = $this->taskGroupRepository->findById($groupId);
if (null === $group) {
throw new InvalidArgumentException(sprintf('TaskGroup with ID %d not found.', $groupId));
}
@@ -116,7 +116,7 @@ class UpdateTaskTool
$task->removeTag($existingTag);
}
foreach ($tagIds as $tagId) {
$tag = $this->taskTagRepository->find($tagId);
$tag = $this->taskTagRepository->findById($tagId);
if (null === $tag) {
throw new InvalidArgumentException(sprintf('TaskTag with ID %d not found.', $tagId));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\TaskEffort;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\TaskGroup;
use App\Mcp\Tool\Serializer;
use App\Repository\ProjectRepository;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -20,7 +20,7 @@ class CreateGroupTool
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly ProjectRepository $projectRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly Security $security,
) {}
@@ -34,7 +34,7 @@ class CreateGroupTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$project = $this->projectRepository->find($projectId);
$project = $this->projectRepository->findById($projectId);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $projectId));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\TaskPriority;
use App\Module\ProjectManagement\Domain\Entity\TaskPriority;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\TaskStatus;
use App\Enum\StatusCategory;
use App\Repository\WorkflowRepository;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Enum\StatusCategory;
use App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -19,7 +19,7 @@ use function sprintf;
class CreateStatusTool
{
public function __construct(
private readonly WorkflowRepository $workflowRepository,
private readonly WorkflowRepositoryInterface $workflowRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -36,7 +36,7 @@ class CreateStatusTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
}
$workflow = $this->workflowRepository->find($workflowId);
$workflow = $this->workflowRepository->findById($workflowId);
if (null === $workflow) {
throw new InvalidArgumentException(sprintf('Workflow with ID %d not found.', $workflowId));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\TaskTag;
use App\Module\ProjectManagement\Domain\Entity\TaskTag;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskEffortRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteEffortTool
{
public function __construct(
private readonly TaskEffortRepository $effortRepository,
private readonly TaskEffortRepositoryInterface $effortRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeleteEffortTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$effort = $this->effortRepository->find($id);
$effort = $this->effortRepository->findById($id);
if (null === $effort) {
throw new InvalidArgumentException(sprintf('TaskEffort with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskGroupRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteGroupTool
{
public function __construct(
private readonly TaskGroupRepository $taskGroupRepository,
private readonly TaskGroupRepositoryInterface $taskGroupRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeleteGroupTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$group = $this->taskGroupRepository->find($id);
$group = $this->taskGroupRepository->findById($id);
if (null === $group) {
throw new InvalidArgumentException(sprintf('TaskGroup with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskPriorityRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeletePriorityTool
{
public function __construct(
private readonly TaskPriorityRepository $priorityRepository,
private readonly TaskPriorityRepositoryInterface $priorityRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeletePriorityTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$priority = $this->priorityRepository->find($id);
$priority = $this->priorityRepository->findById($id);
if (null === $priority) {
throw new InvalidArgumentException(sprintf('TaskPriority with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskStatusRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteStatusTool
{
public function __construct(
private readonly TaskStatusRepository $statusRepository,
private readonly TaskStatusRepositoryInterface $statusRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeleteStatusTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
}
$status = $this->statusRepository->find($id);
$status = $this->statusRepository->findById($id);
if (null === $status) {
throw new InvalidArgumentException(sprintf('TaskStatus with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskTagRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class DeleteTagTool
{
public function __construct(
private readonly TaskTagRepository $tagRepository,
private readonly TaskTagRepositoryInterface $tagRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class DeleteTagTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$tag = $this->tagRepository->find($id);
$tag = $this->tagRepository->findById($id);
if (null === $tag) {
throw new InvalidArgumentException(sprintf('TaskTag with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskEffortRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -13,7 +13,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListEffortsTool
{
public function __construct(
private readonly TaskEffortRepository $taskEffortRepository,
private readonly TaskEffortRepositoryInterface $taskEffortRepository,
private readonly Security $security,
) {}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Mcp\Tool\Serializer;
use App\Repository\TaskGroupRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListGroupsTool
{
public function __construct(
private readonly TaskGroupRepository $taskGroupRepository,
private readonly TaskGroupRepositoryInterface $taskGroupRepository,
private readonly Security $security,
) {}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskPriorityRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -13,7 +13,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListPrioritiesTool
{
public function __construct(
private readonly TaskPriorityRepository $taskPriorityRepository,
private readonly TaskPriorityRepositoryInterface $taskPriorityRepository,
private readonly Security $security,
) {}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Entity\Project;
use App\Repository\TaskStatusRepository;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -18,7 +18,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListStatusesTool
{
public function __construct(
private readonly TaskStatusRepository $taskStatusRepository,
private readonly TaskStatusRepositoryInterface $taskStatusRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskTagRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -13,7 +13,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListTagsTool
{
public function __construct(
private readonly TaskTagRepository $taskTagRepository,
private readonly TaskTagRepositoryInterface $taskTagRepository,
private readonly Security $security,
) {}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskEffortRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskEffortRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class UpdateEffortTool
{
public function __construct(
private readonly TaskEffortRepository $effortRepository,
private readonly TaskEffortRepositoryInterface $effortRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class UpdateEffortTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$effort = $this->effortRepository->find($id);
$effort = $this->effortRepository->findById($id);
if (null === $effort) {
throw new InvalidArgumentException(sprintf('TaskEffort with ID %d not found.', $id));
}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Mcp\Tool\Serializer;
use App\Repository\TaskGroupRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskGroupRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -18,7 +18,7 @@ use function sprintf;
class UpdateGroupTool
{
public function __construct(
private readonly TaskGroupRepository $taskGroupRepository,
private readonly TaskGroupRepositoryInterface $taskGroupRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -34,7 +34,7 @@ class UpdateGroupTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$group = $this->taskGroupRepository->find($id);
$group = $this->taskGroupRepository->findById($id);
if (null === $group) {
throw new InvalidArgumentException(sprintf('TaskGroup with ID %d not found.', $id));
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskPriorityRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskPriorityRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class UpdatePriorityTool
{
public function __construct(
private readonly TaskPriorityRepository $priorityRepository,
private readonly TaskPriorityRepositoryInterface $priorityRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class UpdatePriorityTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$priority = $this->priorityRepository->find($id);
$priority = $this->priorityRepository->findById($id);
if (null === $priority) {
throw new InvalidArgumentException(sprintf('TaskPriority with ID %d not found.', $id));
}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Enum\StatusCategory;
use App\Repository\TaskStatusRepository;
use App\Module\ProjectManagement\Domain\Enum\StatusCategory;
use App\Module\ProjectManagement\Domain\Repository\TaskStatusRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -18,7 +18,7 @@ use function sprintf;
class UpdateStatusTool
{
public function __construct(
private readonly TaskStatusRepository $statusRepository,
private readonly TaskStatusRepositoryInterface $statusRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -35,7 +35,7 @@ class UpdateStatusTool
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
}
$status = $this->statusRepository->find($id);
$status = $this->statusRepository->findById($id);
if (null === $status) {
throw new InvalidArgumentException(sprintf('TaskStatus with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\TaskMeta;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\TaskMeta;
use App\Repository\TaskTagRepository;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
@@ -17,7 +17,7 @@ use function sprintf;
class UpdateTagTool
{
public function __construct(
private readonly TaskTagRepository $tagRepository,
private readonly TaskTagRepositoryInterface $tagRepository,
private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
) {}
@@ -28,7 +28,7 @@ class UpdateTagTool
throw new AccessDeniedException('Access denied: ROLE_USER required.');
}
$tag = $this->tagRepository->find($id);
$tag = $this->tagRepository->findById($id);
if (null === $tag) {
throw new InvalidArgumentException(sprintf('TaskTag with ID %d not found.', $id));
}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Workflow;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Workflow;
use App\Repository\WorkflowRepository;
use App\Module\ProjectManagement\Domain\Repository\WorkflowRepositoryInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -16,7 +16,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ListWorkflowsTool
{
public function __construct(
private readonly WorkflowRepository $workflowRepository,
private readonly WorkflowRepositoryInterface $workflowRepository,
private readonly Security $security,
) {}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Mcp\Tool\Workflow;
namespace App\Module\ProjectManagement\Infrastructure\Mcp\Tool\Workflow;
use ApiPlatform\Metadata\Post;
use App\Entity\Project;
use App\State\SwitchProjectWorkflowProcessor;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\State\SwitchProjectWorkflowProcessor;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
@@ -2,12 +2,13 @@
declare(strict_types=1);
namespace App\Service;
namespace App\Module\ProjectManagement\Infrastructure\Service;
use App\Entity\Task;
use App\Entity\TaskRecurrence;
use App\Enum\RecurrenceType;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use App\Repository\ZimbraConfigurationRepository;
use App\Service\TokenEncryptor;
use DateTimeZone;
use Psr\Log\LoggerInterface;
use Sabre\VObject\Component\VCalendar;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Service;
namespace App\Module\ProjectManagement\Infrastructure\Service;
use App\Entity\Task;
use App\Entity\TaskRecurrence;
use App\Enum\RecurrenceType;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskRecurrence;
use App\Module\ProjectManagement\Domain\Enum\RecurrenceType;
use DateTimeImmutable;
final class RecurrenceCalculator
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Module\ProjectManagement;
use App\Shared\Domain\Module\ModuleInterface;
final class ProjectManagementModule implements ModuleInterface
{
public static function id(): string
{
return 'project-management';
}
public static function label(): string
{
return 'Projets & Tâches';
}
public static function isRequired(): bool
{
return false;
}
/**
* Permissions RBAC fin du Module ProjectManagement (2.2).
*
* Additif : alimente le catalogue RBAC. La sécurité des opérations API
* reste en ROLE_USER (non recâblée ici).
*
* @return list<array{code: string, label: string}>
*/
public static function permissions(): array
{
return [
['code' => 'project-management.projects.view', 'label' => 'Voir les projets'],
['code' => 'project-management.projects.manage', 'label' => 'Gérer les projets'],
['code' => 'project-management.tasks.view', 'label' => 'Voir les tâches'],
['code' => 'project-management.tasks.manage', 'label' => 'Gérer les tâches'],
];
}
}
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Module\TimeTracking\Infrastructure\Controller;
use App\Entity\Project;
use App\Module\Core\Domain\Entity\User;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\TimeTracking\Domain\Repository\TimeEntryRepositoryInterface;
use App\Module\TimeTracking\Infrastructure\Export\TimeEntryExportService;
use DateTimeImmutable;
@@ -6,11 +6,11 @@ namespace App\Module\TimeTracking\Infrastructure\Mcp\Tool;
use App\Mcp\Tool\Serializer;
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
use App\Module\ProjectManagement\Domain\Repository\ProjectRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Module\ProjectManagement\Domain\Repository\TaskTagRepositoryInterface;
use App\Module\TimeTracking\Domain\Entity\TimeEntry;
use App\Module\TimeTracking\Domain\Repository\TimeEntryRepositoryInterface;
use App\Repository\ProjectRepository;
use App\Repository\TaskRepository;
use App\Repository\TaskTagRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@@ -26,9 +26,9 @@ class CreateTimeEntryTool
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly DoctrineUserRepository $userRepository,
private readonly ProjectRepository $projectRepository,
private readonly TaskRepository $taskRepository,
private readonly TaskTagRepository $taskTagRepository,
private readonly ProjectRepositoryInterface $projectRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskTagRepositoryInterface $taskTagRepository,
private readonly TimeEntryRepositoryInterface $timeEntryRepository,
private readonly Security $security,
) {}
@@ -77,14 +77,14 @@ class CreateTimeEntryTool
$entry->setDescription($description);
}
if (null !== $projectId) {
$project = $this->projectRepository->find($projectId);
$project = $this->projectRepository->findById($projectId);
if (null === $project) {
throw new InvalidArgumentException(sprintf('Project with ID %d not found.', $projectId));
}
$entry->setProject($project);
}
if (null !== $taskId) {
$task = $this->taskRepository->find($taskId);
$task = $this->taskRepository->findById($taskId);
if (null === $task) {
throw new InvalidArgumentException(sprintf('Task with ID %d not found.', $taskId));
}
@@ -92,7 +92,7 @@ class CreateTimeEntryTool
}
if (null !== $tagIds) {
foreach ($tagIds as $tagId) {
$tag = $this->taskTagRepository->find($tagId);
$tag = $this->taskTagRepository->findById($tagId);
if (null === $tag) {
throw new InvalidArgumentException(sprintf('TaskTag with ID %d not found.', $tagId));
}

Some files were not shown because too many files have changed in this diff Show More