Merge branch 'notification/improve' into 'master'

Improve notifications

See merge request Chill-Projet/chill-bundles!294
This commit is contained in:
Julien Fastré 2022-01-24 10:09:57 +00:00
commit 2b47868d88
26 changed files with 385 additions and 97 deletions

View File

@ -12,6 +12,12 @@ and this project adheres to
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377) * [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
* [notification: formulaire création] descend la box avec la description dans le bas du formulaire
* [notification for activity]: fix link to activity
* [notification] add "URGENT" before accompanying course with emergency = true
* [notification] add a "read more" button on system notification
* [notification] add `[Chill]` in the subject of each notification, automatically
* [notification] add a counter for notification in activity list and accompanying period list, and search results
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before. * [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
## Test releases ## Test releases

View File

@ -31,6 +31,7 @@ use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@ -38,8 +39,8 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use function array_key_exists; use function array_key_exists;
final class ActivityController extends AbstractController final class ActivityController extends AbstractController
@ -471,20 +472,21 @@ final class ActivityController extends AbstractController
public function showAction(Request $request, int $id): Response public function showAction(Request $request, int $id): Response
{ {
$view = null; $entity = $this->activityRepository->find($id);
[$person, $accompanyingPeriod] = $this->getEntity($request); if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
$accompanyingPeriod = $entity->getAccompanyingPeriod();
$person = $entity->getPerson();
if ($accompanyingPeriod instanceof AccompanyingPeriod) { if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig'; $view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) { } elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:showPerson.html.twig'; $view = 'ChillActivityBundle:Activity:showPerson.html.twig';
} } else {
throw new RuntimeException('the activity should be linked with a period or person');
$entity = $this->activityRepository->find($id);
if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
} }
if (null !== $accompanyingPeriod) { if (null !== $accompanyingPeriod) {
@ -493,8 +495,7 @@ final class ActivityController extends AbstractController
$entity->personsNotAssociated = $entity->getPersonsNotAssociated(); $entity->personsNotAssociated = $entity->getPersonsNotAssociated();
} }
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période $this->denyAccessUnlessGranted(ActivityVoter::SEE, $entity);
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_SEE', $entity);
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod); $deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);

View File

@ -1,5 +1,10 @@
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %} {% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
{% if no_action is not defined or no_action == false %} {% if no_action is not defined or no_action == false %}
{% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %}
{% if notif_counter.total > 0 %}
<li>{{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}</li>
{% endif %}
<li> <li>
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', { <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
@ -54,6 +59,7 @@
</li> </li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
{% endmacro %} {% endmacro %}
<div class="context-{{ context }}"> <div class="context-{{ context }}">

View File

@ -168,6 +168,7 @@ class ChillMainExtension extends Extension implements
$loader->load('services/timeline.yaml'); $loader->load('services/timeline.yaml');
$loader->load('services/search.yaml'); $loader->load('services/search.yaml');
$loader->load('services/serializer.yaml'); $loader->load('services/serializer.yaml');
$loader->load('services/mailer.yaml');
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader); $this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
} }

View File

@ -23,6 +23,9 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Entity * @ORM\Entity
* @ORM\Table( * @ORM\Table(
* name="chill_main_notification", * name="chill_main_notification",
* indexes={
* @ORM\Index(name="chill_main_notification_related_entity_idx", columns={"relatedentityclass", "relatedentityid"})
* }
* ) * )
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
*/ */

View File

@ -50,7 +50,7 @@ class NotificationMailer
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email
->to($dest->getEmail()) ->to($dest->getEmail())
->subject('Re: [Chill] ' . $comment->getNotification()->getTitle()) ->subject('Re: ' . $comment->getNotification()->getTitle())
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig') ->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
->context([ ->context([
'comment' => $comment, 'comment' => $comment,
@ -79,11 +79,13 @@ class NotificationMailer
continue; continue;
} }
if ($notification->isSystem()) {
$email = new Email(); $email = new Email();
$email $email
->text($notification->getMessage()) ->subject($notification->getTitle());
->subject('[Chill] ' . $notification->getTitle());
if ($notification->isSystem()) {
$email
->text($notification->getMessage());
} else { } else {
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email

View File

@ -15,12 +15,15 @@ use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\NotificationRepository; use Chill\MainBundle\Repository\NotificationRepository;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use function array_key_exists;
/** /**
* Helps to find if a notification exist for a given entity. * Helps to find if a notification exist for a given entity.
*/ */
class NotificationPresence class NotificationPresence
{ {
private array $cache = [];
private NotificationRepository $notificationRepository; private NotificationRepository $notificationRepository;
private Security $security; private Security $security;
@ -31,6 +34,29 @@ class NotificationPresence
$this->notificationRepository = $notificationRepository; $this->notificationRepository = $notificationRepository;
} }
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
{
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
return $this->cache[$relatedEntityClass][$relatedEntityId];
}
$user = $this->security->getUser();
if ($user instanceof User) {
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass,
$relatedEntityId,
$user
);
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
return $counter;
}
return ['unread' => 0, 'read' => 0];
}
/** /**
* @return array|Notification[] * @return array|Notification[]
*/ */

View File

@ -23,6 +23,13 @@ class NotificationTwigExtension extends AbstractExtension
'needs_environment' => true, 'needs_environment' => true,
'is_safe' => ['html'], 'is_safe' => ['html'],
]), ]),
new TwigFunction('chill_count_notifications', [NotificationTwigExtensionRuntime::class, 'countNotificationsFor'], [
'is_safe' => [],
]),
new TwigFunction('chill_counter_notifications', [NotificationTwigExtensionRuntime::class, 'counterNotificationFor'], [
'needs_environment' => true,
'is_safe' => ['html'],
]),
]; ];
} }
} }

View File

@ -24,6 +24,21 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
$this->notificationPresence = $notificationPresence; $this->notificationPresence = $notificationPresence;
} }
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
{
return $environment->render(
'@ChillMain/Notification/extension_counter_notifications_for.html.twig',
[
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId),
]
);
}
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array
{
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
}
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
{ {
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId); $notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);

View File

@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Doctrine\DBAL\Statement;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
@ -24,6 +25,8 @@ final class NotificationRepository implements ObjectRepository
{ {
private EntityManagerInterface $em; private EntityManagerInterface $em;
private ?Statement $notificationByRelatedEntityAndUserAssociatedStatement = null;
private EntityRepository $repository; private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager) public function __construct(EntityManagerInterface $entityManager)
@ -48,6 +51,30 @@ final class NotificationRepository implements ObjectRepository
->getSingleScalarResult(); ->getSingleScalarResult();
} }
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
{
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
$sql =
'SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = 1812 and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = 1812)::int) AS sent,
COUNT(cmn.*) AS total
FROM chill_main_notification cmn
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
$this->notificationByRelatedEntityAndUserAssociatedStatement =
$this->em->getConnection()->prepare($sql);
}
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]);
$result = $results->fetchAssociative();
$results->free();
return $result;
}
public function countUnreadByUser(User $user): int public function countUnreadByUser(User $user): int
{ {
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId'; $sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
@ -153,11 +180,29 @@ final class NotificationRepository implements ObjectRepository
* @return array|Notification[] * @return array|Notification[]
*/ */
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
{
return
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user)
->select('n')
->getQuery()
->getResult();
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
{
return $this->repository->findOneBy($criteria, $orderBy);
}
public function getClassName()
{
return Notification::class;
}
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder
{ {
$qb = $this->repository->createQueryBuilder('n'); $qb = $this->repository->createQueryBuilder('n');
$qb $qb
->select('n')
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass')) ->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')) ->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
->andWhere($qb->expr()->isNotNull('n.sender')) ->andWhere($qb->expr()->isNotNull('n.sender'))
@ -171,17 +216,7 @@ final class NotificationRepository implements ObjectRepository
->setParameter('relatedEntityId', $relatedEntityId) ->setParameter('relatedEntityId', $relatedEntityId)
->setParameter('user', $user); ->setParameter('user', $user);
return $qb->getQuery()->getResult(); return $qb;
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
{
return $this->repository->findOneBy($criteria, $orderBy);
}
public function getClassName()
{
return Notification::class;
} }
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder

View File

@ -62,6 +62,7 @@
{{ c.notification.message|chill_markdown_to_html }} {{ c.notification.message|chill_markdown_to_html }}
{% else %} {% else %}
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }} {{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -85,7 +86,13 @@
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %} {% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}" <a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
class="btn btn-show change-icon" title="{{ 'notification.see_comments_thread'|trans }}"><i class="fa fa-comment"></i></a> class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
{% if not c.notification.isSystem() %}
<i class="fa fa-comment"></i>
{% else %}
{{ 'Read more'|trans }}
{% endif %}
</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -21,8 +21,6 @@
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
{% include handler.template(notification) with handler.templateData(notification) %}
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-form-label col-sm-4" for="notification_message">{{ form_label(form.message) }}</label> <label class="col-form-label col-sm-4" for="notification_message">{{ form_label(form.message) }}</label>
<div class="col-12"> <div class="col-12">
@ -30,6 +28,8 @@
</div> </div>
</div> </div>
{% include handler.template(notification) with handler.templateData(notification) %}
{{ form_end(form) }} {{ form_end(form) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">

View File

@ -0,0 +1,2 @@
{% if counter.total > 0 %}<span class="badge rounded-pill bg-primary">{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}</span>{% endif %}
{% if counter.unread > 0 %}<span class="badge rounded-pill bg-danger">{{ 'notification.counter unread notifications'|trans({'unread': counter.unread })}}</span>{% endif %}

View File

@ -18,7 +18,7 @@
data-notification-id="{{ notification.id }}" data-notification-id="{{ notification.id }}"
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}" data-notification-current-is-read="{{ notification.isReadBy(app.user) }}"
data-container="notification-status" data-container="notification-status"
data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}" data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}, false) }}"
data-button-class="btn-outline-primary" data-button-class="btn-outline-primary"
data-button-text="false" data-button-text="false"
></span> ></span>

View File

@ -0,0 +1,50 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Service\Mailer;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\RawMessage;
class ChillMailer implements MailerInterface
{
private LoggerInterface $chillLogger;
private MailerInterface $initial;
private string $prefix = '[Chill] ';
public function __construct(MailerInterface $initial, LoggerInterface $chillLogger)
{
$this->initial = $initial;
$this->chillLogger = $chillLogger;
}
public function send(RawMessage $message, ?Envelope $envelope = null): void
{
if ($message instanceof Email) {
$message->subject($this->prefix . $message->getSubject());
}
$this->chillLogger->info('chill email sent', [
'to' => array_map(static function (Address $address) {
return $address->getAddress();
}, $message->getTo()),
'subject' => $message->getSubject(),
]);
$this->initial->send($message, $envelope);
}
}

View File

@ -0,0 +1,10 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\MainBundle\Service\Mailer\:
resource: '../../Service/Mailer'
Chill\MainBundle\Service\Mailer\ChillMailer:
decorates: Symfony\Component\Mailer\MailerInterface

View File

@ -0,0 +1,33 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220120155303 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX chill_main_notification_related_entity_idx');
}
public function getDescription(): string
{
return 'Create index for counting notifications';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE INDEX chill_main_notification_related_entity_idx ON chill_main_notification (relatedentityclass, relatedentityid DESC)');
}
}

View File

@ -13,3 +13,19 @@ notification:
few {# notifications} few {# notifications}
other {# notifications} other {# notifications}
} }
counter total notifications: >-
{total, plural,
=0 {Aucune notification}
one {# notification}
few {# notifications}
other {# notifications}
}
counter unread notifications: >-
{unread, plural,
=0 {Aucune non-lue}
one {# non-lue}
few {# non-lues}
other {# non-lues}
}

View File

@ -58,6 +58,7 @@ comment: commentaire
Comment: Commentaire Comment: Commentaire
Pinned comment: Commentaire épinglé Pinned comment: Commentaire épinglé
Any comment: Aucun commentaire Any comment: Aucun commentaire
Read more: Lire la suite
# comment embeddable # comment embeddable
No comment associated: Aucun commentaire No comment associated: Aucun commentaire

View File

@ -9,19 +9,20 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\PersonBundle\AccompanyingPeriod\Workflow; namespace Chill\PersonBundle\AccompanyingPeriod\Events;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Workflow\Event\EnteredEvent; use Symfony\Component\Workflow\Event\EnteredEvent;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class WorkflowEventSubscriber implements EventSubscriberInterface class UserRefEventSubscriber implements EventSubscriberInterface
{ {
private EntityManagerInterface $em; private EntityManagerInterface $em;
@ -55,15 +56,30 @@ class WorkflowEventSubscriber implements EventSubscriberInterface
} }
} }
private function onPeriodConfirmed(AccompanyingPeriod $period) public function postUpdate(AccompanyingPeriod $period, LifecycleEventArgs $args): void
{
if ($period->hasPreviousUser()
&& $period->getUser() !== $this->security->getUser()
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
) {
$this->generateNotificationToUser($period);
}
// we are just out of a flush operation. Launch a new one
$this->em->flush();
}
private function generateNotificationToUser(AccompanyingPeriod $period)
{ {
if ($period->getUser() instanceof User
&& $period->getUser() !== $this->security->getUser()) {
$notification = new Notification(); $notification = new Notification();
$urgentStatement =
$period->isEmergency() ? strtoupper($this->translator->trans('accompanying_period.emergency')) . ' ' : '';
$notification $notification
->setRelatedEntityId($period->getId()) ->setRelatedEntityId($period->getId())
->setRelatedEntityClass(AccompanyingPeriod::class) ->setRelatedEntityClass(AccompanyingPeriod::class)
->setTitle($this->translator->trans('period_notification.period_designated_subject')) ->setTitle($urgentStatement . $this->translator->trans('period_notification.period_designated_subject'))
->setMessage($this->engine->render( ->setMessage($this->engine->render(
'@ChillPerson/Notification/accompanying_course_designation.md.twig', '@ChillPerson/Notification/accompanying_course_designation.md.twig',
[ [
@ -71,7 +87,15 @@ class WorkflowEventSubscriber implements EventSubscriberInterface
] ]
)) ))
->addAddressee($period->getUser()); ->addAddressee($period->getUser());
$this->em->persist($notification); $this->em->persist($notification);
} }
private function onPeriodConfirmed(AccompanyingPeriod $period)
{
if ($period->getUser() instanceof User
&& $period->getUser() !== $this->security->getUser()) {
$this->generateNotificationToUser($period);
}
} }
} }

View File

@ -327,6 +327,13 @@ class AccompanyingPeriod implements
*/ */
private ?User $user = null; private ?User $user = null;
/**
* Temporary field, which is filled when the user is changed.
*
* Used internally for listener when user change
*/
private ?User $userPrevious = null;
/** /**
* @ORM\OneToMany( * @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWork::class, * targetEntity=AccompanyingPeriodWork::class,
@ -755,6 +762,11 @@ class AccompanyingPeriod implements
return $this->pinnedComment; return $this->pinnedComment;
} }
public function getPreviousUser(): ?User
{
return $this->userPrevious;
}
/** /**
* @return Collection|SocialAction[] All the descendant social actions of all * @return Collection|SocialAction[] All the descendant social actions of all
* the descendants of the entity * the descendants of the entity
@ -868,6 +880,11 @@ class AccompanyingPeriod implements
return $this->works; return $this->works;
} }
public function hasPreviousUser(): bool
{
return null !== $this->userPrevious;
}
/** /**
* Returns true if the closing date is after the opening date. * Returns true if the closing date is after the opening date.
*/ */
@ -1172,6 +1189,10 @@ class AccompanyingPeriod implements
public function setUser(User $user): self public function setUser(User $user): self
{ {
if ($this->user !== $user) {
$this->userPrevious = $this->user;
}
$this->user = $user; $this->user = $user;
return $this; return $this;

View File

@ -61,13 +61,13 @@ final class CreationPersonType extends AbstractType
'required' => false, 'required' => false,
]) ])
->add('phonenumber', TelType::class, [ ->add('phonenumber', TelType::class, [
'required' => false 'required' => false,
]) ])
->add('mobilenumber', TelType::class, [ ->add('mobilenumber', TelType::class, [
'required' => false 'required' => false,
]) ])
->add('email', EmailType::class, [ ->add('email', EmailType::class, [
'required' => false 'required' => false,
]); ]);
if ($this->askCenters) { if ($this->askCenters) {

View File

@ -131,6 +131,10 @@
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }} {{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
</div> </div>
<ul class="record_actions"> <ul class="record_actions">
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
{% if notif_counter.total > 0 %}
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}</li>
{% endif %}
<li> <li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}" <a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}" href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"

View File

@ -5,6 +5,10 @@
{% macro recordAction(period, contextEntity) %} {% macro recordAction(period, contextEntity) %}
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #} {# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
{% if notif_counter.total > 0 %}
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}</li>
{% endif %}
<li> <li>
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}" <a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a> class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a>

View File

@ -118,6 +118,10 @@
{{ 'File number'|trans }} {{ acp.id }} {{ 'File number'|trans }} {{ acp.id }}
</div> </div>
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %}
{% if notif_counter.total > 0 %}
<div class="counter">{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }}</div>
{% endif %}
</div> </div>
<div class="wl-col list"> <div class="wl-col list">

View File

@ -20,9 +20,19 @@ services:
Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface: '@Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion' Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface: '@Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion'
Chill\PersonBundle\AccompanyingPeriod\Workflow\: Chill\PersonBundle\AccompanyingPeriod\Events\:
resource: './../../AccompanyingPeriod/Workflow' resource: './../../AccompanyingPeriod/Events'
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\PersonBundle\AccompanyingPeriod\Events\UserRefEventSubscriber:
autowire: true
autoconfigure: true
tags:
- # these are the options required to define the entity listener
name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
lazy: true