Add task assignment notification system

- Introduced `AssignTaskEvent`, `TaskAssignEventSubscriber`, and `AssignTaskNotificationFlagProvider` for task assignment notifications
- WIP : Added notification templates for task assignment title and content
- Updated `SingleTaskController` to dispatch `AssignTaskEvent`
- Adjusted translations and service configurations accordingly
This commit is contained in:
2025-08-14 14:54:18 +02:00
parent 88dd7116f3
commit 5daa83c93e
14 changed files with 177 additions and 15 deletions

View File

@@ -23,6 +23,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\TaskBundle\Entity\SingleTask; use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Event\AssignTaskEvent;
use Chill\TaskBundle\Event\TaskEvent; use Chill\TaskBundle\Event\TaskEvent;
use Chill\TaskBundle\Event\UI\UIEvent; use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\TaskBundle\Form\SingleTaskType; use Chill\TaskBundle\Form\SingleTaskType;
@@ -525,6 +526,13 @@ final class SingleTaskController extends AbstractController
$this->eventDispatcher->dispatch(new TaskEvent($task), TaskEvent::PERSIST); $this->eventDispatcher->dispatch(new TaskEvent($task), TaskEvent::PERSIST);
if (null !== $task->getAssignee()) {
$this->eventDispatcher->dispatch(
new AssignTaskEvent($task, $user),
AssignTaskEvent::PERSIST
);
}
$em->flush(); $em->flush();
$this->addFlash('success', $this->translator->trans('The task is created')); $this->addFlash('success', $this->translator->trans('The task is created'));

View File

@@ -42,6 +42,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
$loader->load('services/timeline.yaml'); $loader->load('services/timeline.yaml');
$loader->load('services/fixtures.yaml'); $loader->load('services/fixtures.yaml');
$loader->load('services/form.yaml'); $loader->load('services/form.yaml');
$loader->load('services/notification.yaml');
} }
public function prepend(ContainerBuilder $container) public function prepend(ContainerBuilder $container)

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TaskBundle\Event;
use Chill\MainBundle\Entity\User;
use Chill\TaskBundle\Entity\SingleTask;
use Symfony\Contracts\EventDispatcher\Event;
class AssignTaskEvent extends Event
{
final public const PERSIST = 'chill_task.assign_task';
public function __construct(
private readonly SingleTask $task,
private readonly User $assignedUser,
) {}
public function getTask(): SingleTask
{
return $this->task;
}
public function getAssignedUser(): User
{
return $this->assignedUser;
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TaskBundle\Event;
use Chill\MainBundle\Entity\Notification;
use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Notification\AssignTaskNotificationFlagProvider;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
readonly class TaskAssignEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private \Twig\Environment $engine,
) {}
public static function getSubscribedEvents(): array
{
return [
AssignTaskEvent::PERSIST => ['onTaskAssigned', 0],
];
}
/**
* Send a notification when a user is assigned to a task.
*
* The notification will be sent to the newly assigned user
*/
public function onTaskAssigned(AssignTaskEvent $event): void
{
$task = $event->getTask();
$assignedUser = $event->getAssignedUser();
$title = $task->getTitle();
$context = [
'task' => $task,
'assignedUser' => $assignedUser,
'title' => $title,
];
$notification = new Notification();
$notification
->setRelatedEntityId($task->getId())
->setRelatedEntityClass(SingleTask::class)
->setTitle($this->engine->render('@ChillTask/Notification/task_assignment_notification_title.txt.twig', $context))
->setMessage($this->engine->render('@ChillTask/Notification/task_assignment_notification_content.txt.twig', $context))
->addAddressee($assignedUser)
->setType(AssignTaskNotificationFlagProvider::FLAG);
$this->entityManager->persist($notification);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Chill\TaskBundle\Notification;
use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\Translation\TranslatableInterface;
class AssignTaskNotificationFlagProvider implements NotificationFlagProviderInterface
{
public const FLAG = 'task-assign-notif';
public function getFlag(): string
{
return self::FLAG;
}
public function getLabel(): TranslatableInterface
{
return new TranslatableMessage('notification.flags.task_assign');
}
}

View File

@@ -46,7 +46,7 @@ final readonly class TaskNotificationHandler implements NotificationHandlerInter
return new TranslatableMessage('task.deleted'); return new TranslatableMessage('task.deleted');
} }
return new TranslatableMessage('task.title', ['id' => $task->getId()]); return new TranslatableMessage('notification.task.title %title%', ['title' => $task->getTitle()]);
} }
public function getAssociatedPersons(Notification $notification, array $options = []): array public function getAssociatedPersons(Notification $notification, array $options = []): array
@@ -62,7 +62,7 @@ final readonly class TaskNotificationHandler implements NotificationHandlerInter
return [$task->getPerson()]; return [$task->getPerson()];
} }
public function getRelatedEntity(Notification $notification): ?SingleTask public function getRelatedEntity(Notification $notification): object
{ {
return $this->taskRepository->find($notification->getRelatedEntityId()); return $this->taskRepository->find($notification->getRelatedEntityId());
} }

View File

@@ -0,0 +1,16 @@
{{ assignedUser.label }},
Une tâche vous a été assignée.
Titre de la tâche: "{{ task.title }}".
{% if task.endDate %}
Vous êtes invités à accomplir cette tâche avant le {{ task.endDate|format_date('long') }}
{% endif %}
Vous pouvez visualiser la tâche sur cette page:
{{ absolute_url(path('chill_task_single_task_show', {'id': task.id, '_locale': 'fr'})) }}
Cordialement,

View File

@@ -0,0 +1 @@
Une tâche demande votre attention

View File

@@ -18,14 +18,14 @@
<div> <div>
{% if task.person is not null %} {% if task.person is not null %}
<span class="chill-task-list__row__person"> <span class="chill-task-list__row__person">
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: task.person.id }, targetEntity: { name: 'person', id: task.person.id },
action: 'show', action: 'show',
displayBadge: true, displayBadge: true,
buttonText: task.person|chill_entity_render_string, buttonText: task.person|chill_entity_render_string,
isDead: task.person.deathdate is not null isDead: task.person.deathdate is not null
} %} } %}
</span> </span>
{% elseif task.course is not null %} {% elseif task.course is not null %}
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
{% for part in task.course.currentParticipations %} {% for part in task.course.currentParticipations %}

View File

@@ -110,4 +110,5 @@
</li> </li>
{% endif %} {% endif %}
</ul></div> </ul>
</div>

View File

@@ -6,10 +6,7 @@
{% endmacro %} {% endmacro %}
{% if task is not null %} {% if task is not null %}
<div class="flex-table"> {# <div>Todo : display task? </div>#}
{# TODO task item #}
<p>Some task</p>
</div>
{% else %} {% else %}
<div class="alert alert-warning border-warning border-1"> <div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a task which does not exist any more'|trans }} {{ 'You are getting a notification for a task which does not exist any more'|trans }}

View File

@@ -1,7 +1,13 @@
services: services:
_defaults:
autowire: true
autoconfigure: true
Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent: Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent:
arguments: arguments:
$em: '@Doctrine\ORM\EntityManagerInterface' $em: '@Doctrine\ORM\EntityManagerInterface'
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
tags: tags:
- { name: kernel.event_subscriber } - { name: kernel.event_subscriber }
Chill\TaskBundle\Event\TaskAssignEventSubscriber: ~

View File

@@ -0,0 +1,7 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\TaskBundle\Notification\TaskNotificationHandler: ~
Chill\TaskBundle\Notification\AssignTaskNotificationFlagProvider: ~

View File

@@ -116,3 +116,9 @@ CHILL_TASK_TASK_UPDATE: Modifier une tâche
CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours
CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour un usager CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour un usager
notification:
task:
title %title%: "Tâche: title"
flags:
task_assign: Lorsqu'un autre utilisateur m'assigne à une tâche.