From 390f2a40a85f4945345e87f99ffea4b2cbd20bb7 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 15 Jun 2026 11:44:12 +0200 Subject: [PATCH] =?UTF-8?q?feat(notification)=20:=20notifier=20le=20nouvel?= =?UTF-8?q?=20assign=C3=A9=20d'une=20t=C3=A2che?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskNotificationListener.php | 107 +++++++++++++ .../TaskNotificationListenerTest.php | 145 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 src/EventListener/TaskNotificationListener.php create mode 100644 tests/Functional/EventListener/TaskNotificationListenerTest.php diff --git a/src/EventListener/TaskNotificationListener.php b/src/EventListener/TaskNotificationListener.php new file mode 100644 index 0000000..26455d1 --- /dev/null +++ b/src/EventListener/TaskNotificationListener.php @@ -0,0 +1,107 @@ + */ + private array $pending = []; + + public function __construct(private readonly Security $security) {} + + public function onFlush(OnFlushEventArgs $args): void + { + $actor = $this->security->getUser(); + if (!$actor instanceof User) { + return; + } + + $uow = $args->getObjectManager()->getUnitOfWork(); + + // Assignation sur une tâche nouvellement créée. + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if (!$entity instanceof Task) { + continue; + } + $assignee = $entity->getAssignee(); + if ($assignee instanceof User && $assignee !== $actor) { + $this->pending[] = ['user' => $assignee, 'type' => 'task_assigned', 'task' => $entity]; + } + } + + // Changement d'assignation sur une tâche existante. + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if (!$entity instanceof Task) { + continue; + } + $changeSet = $uow->getEntityChangeSet($entity); + if (!isset($changeSet['assignee'])) { + continue; + } + $new = $changeSet['assignee'][1]; + if ($new instanceof User && $new !== $actor) { + $this->pending[] = ['user' => $new, 'type' => 'task_assigned', 'task' => $entity]; + } + } + } + + public function postFlush(PostFlushEventArgs $args): void + { + if ([] === $this->pending) { + return; + } + + $pending = $this->pending; + $this->pending = []; + + $em = $args->getObjectManager(); + foreach ($pending as $item) { + $em->persist($this->buildNotification($item['user'], $item['type'], $item['task'])); + } + $em->flush(); + } + + private function buildNotification(User $user, string $type, Task $task): Notification + { + [$title, $message] = $this->render($type, $task); + + $notification = new Notification(); + $notification->setUser($user); + $notification->setType($type); + $notification->setTitle($title); + $notification->setMessage($message); + $notification->setCreatedAt(new DateTimeImmutable()); + + return $notification; + } + + /** + * @return array{0: string, 1: string} + */ + private function render(string $type, Task $task): array + { + $projectName = $task->getProject()?->getName() ?? ''; + $suffix = '' !== $projectName ? sprintf(' — %s', $projectName) : ''; + $context = sprintf('« %s »%s', (string) $task->getTitle(), $suffix); + + return match ($type) { + 'task_assigned' => ['Nouvelle tâche assignée', $context], + 'task_collaborator_added' => ['Ajout à une tâche', $context], + default => ['Notification', $context], + }; + } +} diff --git a/tests/Functional/EventListener/TaskNotificationListenerTest.php b/tests/Functional/EventListener/TaskNotificationListenerTest.php new file mode 100644 index 0000000..57ad4e0 --- /dev/null +++ b/tests/Functional/EventListener/TaskNotificationListenerTest.php @@ -0,0 +1,145 @@ +em = $c->get(EntityManagerInterface::class); + $this->notifications = $c->get(NotificationRepository::class); + $this->tokenStorage = $c->get(TokenStorageInterface::class); + + $project = $this->em->getRepository(Project::class)->findOneBy([]); + self::assertNotNull($project, 'Les fixtures doivent fournir au moins un projet.'); + $this->project = $project; + + $this->actor = $this->makeUser('actor'); + $this->alice = $this->makeUser('alice'); + $this->bob = $this->makeUser('bob'); + $this->em->flush(); + } + + public function testAssignmentToOtherUserCreatesNotification(): void + { + $this->loginAs($this->actor); + + $task = $this->makeTask(); + $task->setAssignee($this->alice); + $this->em->persist($task); + $this->em->flush(); + + $rows = $this->notifications->findBy(['user' => $this->alice]); + self::assertCount(1, $rows); + self::assertSame('task_assigned', $rows[0]->getType()); + self::assertStringContainsString((string) $task->getTitle(), (string) $rows[0]->getMessage()); + } + + public function testSelfAssignmentCreatesNoNotification(): void + { + $this->loginAs($this->actor); + + $task = $this->makeTask(); + $task->setAssignee($this->actor); + $this->em->persist($task); + $this->em->flush(); + + self::assertCount(0, $this->notifications->findBy(['user' => $this->actor])); + } + + public function testReassignmentNotifiesOnlyNewAssignee(): void + { + $this->loginAs($this->actor); + + $task = $this->makeTask(); + $task->setAssignee($this->alice); + $this->em->persist($task); + $this->em->flush(); + + $task->setAssignee($this->bob); + $this->em->flush(); + + self::assertCount(1, $this->notifications->findBy(['user' => $this->alice])); + self::assertCount(1, $this->notifications->findBy(['user' => $this->bob])); + } + + public function testAssigneeSetToNullCreatesNoNotificationForNull(): void + { + $this->loginAs($this->actor); + + $task = $this->makeTask(); + $task->setAssignee($this->alice); + $this->em->persist($task); + $this->em->flush(); + + $task->setAssignee(null); + $this->em->flush(); + + // alice a reçu la 1re notif, mais le passage à null n'en crée aucune autre. + self::assertCount(1, $this->notifications->findBy(['user' => $this->alice])); + } + + public function testNoActorCreatesNoNotification(): void + { + $this->tokenStorage->setToken(null); + + $task = $this->makeTask(); + $task->setAssignee($this->alice); + $this->em->persist($task); + $this->em->flush(); + + self::assertCount(0, $this->notifications->findBy(['user' => $this->alice])); + } + + private function makeUser(string $prefix): User + { + $user = new User(); + $user->setUsername($prefix.'-'.uniqid()); + $user->setPassword('x'); + $user->setRoles(['ROLE_USER']); + $this->em->persist($user); + + return $user; + } + + private function makeTask(): Task + { + $task = new Task(); + $task->setNumber(random_int(100000, 999999)); + $task->setTitle('Tâche de test '.uniqid()); + $task->setProject($this->project); + + return $task; + } + + private function loginAs(User $user): void + { + $this->tokenStorage->setToken( + new UsernamePasswordToken($user, 'main', $user->getRoles()), + ); + } +}