mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-10 08:44:58 +00:00
Compare commits
12 Commits
v4.2.1
...
330-notifi
Author | SHA1 | Date | |
---|---|---|---|
f88f1f1859 | |||
e6cbba8c63 | |||
c2782be56a | |||
cf391d60fe | |||
acad9d1553 | |||
0a19255a22 | |||
bef5dcce14 | |||
33540f58d7 | |||
cf780b6e36 | |||
c9c565809a | |||
c917c42789 | |||
1c426f560e |
@@ -12,3 +12,7 @@ framework:
|
||||
adapter: cache.adapter.redis
|
||||
public: false
|
||||
default_lifetime: 300
|
||||
cache.daily_notifications:
|
||||
adapter: cache.adapter.redis
|
||||
public: true
|
||||
default_lifetime: 90000 # 25 hours
|
||||
|
@@ -45,6 +45,27 @@ framework:
|
||||
|
||||
auto_setup: false
|
||||
|
||||
immediate_email:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%/priority'
|
||||
options:
|
||||
queue_name: immediate_notifications
|
||||
exchange:
|
||||
name: notifications
|
||||
type: direct
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
delay: 1000
|
||||
multiplier: 2
|
||||
|
||||
daily_email:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
queue_name: daily_notifications
|
||||
exchange:
|
||||
name: notifications
|
||||
type: direct
|
||||
# No automatic consumption - handled by cron job
|
||||
|
||||
routing:
|
||||
# routes added by chill-bundles recipes
|
||||
'Chill\CalendarBundle\Messenger\Message\CalendarRangeMessage': async
|
||||
@@ -61,6 +82,9 @@ framework:
|
||||
'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
|
||||
'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
|
||||
'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
|
||||
'Chill\MainBundle\Notification\Email\SendImmediateNotificationEmailMessage': immediate_email
|
||||
'Chill\MainBundle\Notification\Email\ScheduleDailyNotificationEmailMessage': daily_email
|
||||
'Chill\MainBundle\Notification\Email\SendDailyDigestMessage': daily_email
|
||||
# end of routes added by chill-bundles recipes
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
@@ -11,8 +11,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\NotificationComment;
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Chill\MainBundle\Form\NotificationCommentType;
|
||||
use Chill\MainBundle\Form\NotificationType;
|
||||
use Chill\MainBundle\Notification\Exception\NotificationHandlerNotFound;
|
||||
@@ -22,6 +24,9 @@ use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
use Chill\MainBundle\Security\Authorization\NotificationVoter;
|
||||
use Chill\MainBundle\Security\ChillSecurity;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -53,11 +58,29 @@ class NotificationController extends AbstractController
|
||||
throw new BadRequestHttpException('missing entityId parameter');
|
||||
}
|
||||
|
||||
$notificationType = '';
|
||||
|
||||
switch ($request->query->get('entityClass')) {
|
||||
case Activity::class:
|
||||
$notificationType = NotificationFlagEnum::ACTIVITY;
|
||||
break;
|
||||
case AccompanyingPeriod::class:
|
||||
$notificationType = NotificationFlagEnum::ACC_COURSE;
|
||||
break;
|
||||
case AccompanyingPeriodWork::class:
|
||||
$notificationType = NotificationFlagEnum::ACC_COURSE_WORK;
|
||||
break;
|
||||
case AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class:
|
||||
$notificationType = NotificationFlagEnum::ACC_COURSE_WORK_EVAL_DOC;
|
||||
break;
|
||||
}
|
||||
|
||||
$notification = new Notification();
|
||||
$notification
|
||||
->setRelatedEntityClass($request->query->get('entityClass'))
|
||||
->setRelatedEntityId($request->query->getInt('entityId'))
|
||||
->setSender($this->security->getUser());
|
||||
->setSender($this->security->getUser())
|
||||
->setType($notificationType);
|
||||
|
||||
$tos = $request->query->all('tos');
|
||||
|
||||
|
@@ -11,14 +11,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Form\UserPhonenumberType;
|
||||
use Chill\MainBundle\Form\UserProfileType;
|
||||
use Chill\MainBundle\Security\ChillSecurity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
@@ -41,16 +39,21 @@ final class UserProfileController extends AbstractController
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
$editForm = $this->createPhonenumberEditForm($user);
|
||||
$editForm = $this->createForm(UserProfileType::class, $user);
|
||||
|
||||
$editForm->get('notificationFlags')->setData($user->getNotificationFlags());
|
||||
|
||||
$editForm->add('submit', SubmitType::class);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
$phonenumber = $editForm->get('phonenumber')->getData();
|
||||
$notificationFlagsData = $editForm->get('notificationFlags')->getData();
|
||||
$user->setNotificationFlags($notificationFlagsData);
|
||||
|
||||
$user->setPhonenumber($phonenumber);
|
||||
|
||||
$this->managerRegistry->getManager()->flush();
|
||||
$this->addFlash('success', $this->translator->trans('user.profile.Phonenumber successfully updated!'));
|
||||
$em = $this->managerRegistry->getManager();
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', $this->translator->trans('user.profile.Profile successfully updated!'));
|
||||
|
||||
return $this->redirectToRoute('chill_main_user_profile');
|
||||
}
|
||||
@@ -60,13 +63,4 @@ final class UserProfileController extends AbstractController
|
||||
'form' => $editForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createPhonenumberEditForm(UserInterface $user): FormInterface
|
||||
{
|
||||
return $this->createForm(
|
||||
UserPhonenumberType::class,
|
||||
$user,
|
||||
)
|
||||
->add('submit', SubmitType::class, ['label' => $this->translator->trans('Save')]);
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Entity;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
@@ -24,7 +25,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
#[ORM\Index(name: 'chill_main_notification_related_entity_idx', columns: ['relatedentityclass', 'relatedentityid'])]
|
||||
class Notification implements TrackUpdateInterface
|
||||
{
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
|
||||
#[ORM\Column(type: Types::TEXT, nullable: false)]
|
||||
private string $accessKey;
|
||||
|
||||
private array $addedAddresses = [];
|
||||
@@ -41,7 +42,7 @@ class Notification implements TrackUpdateInterface
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
#[ORM\Column(type: Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
private array $addressesEmails = [];
|
||||
|
||||
/**
|
||||
@@ -60,21 +61,21 @@ class Notification implements TrackUpdateInterface
|
||||
#[ORM\OrderBy(['createdAt' => \Doctrine\Common\Collections\Criteria::ASC])]
|
||||
private Collection $comments;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)]
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
private \DateTimeImmutable $date;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private string $message = '';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||
private string $relatedEntityClass = '';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private int $relatedEntityId;
|
||||
|
||||
private array $removedAddresses = [];
|
||||
@@ -84,7 +85,7 @@ class Notification implements TrackUpdateInterface
|
||||
private ?User $sender = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'notification.Title must be defined')]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
|
||||
#[ORM\Column(type: Types::TEXT, options: ['default' => ''])]
|
||||
private string $title = '';
|
||||
|
||||
/**
|
||||
@@ -94,12 +95,15 @@ class Notification implements TrackUpdateInterface
|
||||
#[ORM\JoinTable(name: 'chill_main_notification_addresses_unread')]
|
||||
private Collection $unreadBy;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)]
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
private ?User $updatedBy = null;
|
||||
|
||||
#[ORM\Column(name: 'type', type: Types::STRING, nullable: true, enumType: NotificationFlagEnum::class)]
|
||||
private NotificationFlagEnum $type;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addressees = new ArrayCollection();
|
||||
@@ -389,4 +393,16 @@ class Notification implements TrackUpdateInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setType(NotificationFlagEnum $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): NotificationFlagEnum
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
|
23
src/Bundle/ChillMainBundle/Entity/NotificationFlagEnum.php
Normal file
23
src/Bundle/ChillMainBundle/Entity/NotificationFlagEnum.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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\MainBundle\Entity;
|
||||
|
||||
enum NotificationFlagEnum: string
|
||||
{
|
||||
case REFERRER_ACC_COURSE = 'referrer-acc-course-notif';
|
||||
case PERSON_MOVE = 'person-move-notif';
|
||||
case ACC_COURSE = 'acc-course-notif';
|
||||
case WORKFLOW_TRANS = 'workflow-trans-notif';
|
||||
case ACC_COURSE_WORK = 'acc-course-work-notif';
|
||||
case ACC_COURSE_WORK_EVAL_DOC = 'acc-course-work-eval-doc-notif';
|
||||
case ACTIVITY = 'activity-notif';
|
||||
}
|
@@ -116,6 +116,9 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
#[PhonenumberConstraint]
|
||||
private ?PhoneNumber $phonenumber = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])]
|
||||
private $notificationFlags = [];
|
||||
|
||||
/**
|
||||
* User constructor.
|
||||
*/
|
||||
@@ -613,4 +616,24 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNotificationFlags(): array
|
||||
{
|
||||
return $this->notificationFlags;
|
||||
}
|
||||
|
||||
public function setNotificationFlags(array $notificationFlags)
|
||||
{
|
||||
$this->notificationFlags = $notificationFlags;
|
||||
}
|
||||
|
||||
public function getNotificationFlagData(string $flag): array
|
||||
{
|
||||
return $this->notificationFlags[$flag] ?? [];
|
||||
}
|
||||
|
||||
public function setNotificationFlagData(string $flag, array $data): void
|
||||
{
|
||||
$this->notificationFlags[$flag] = $data;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,78 @@
|
||||
<?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\MainBundle\Form\DataMapper;
|
||||
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
|
||||
final readonly class NotificationFlagDataMapper implements DataMapperInterface
|
||||
{
|
||||
private array $notificationFlagProviders;
|
||||
|
||||
public function __construct(array $notificationFlagProviders)
|
||||
{
|
||||
$this->notificationFlagProviders = $notificationFlagProviders;
|
||||
}
|
||||
|
||||
public function mapDataToForms($viewData, $forms): void
|
||||
{
|
||||
if (null === $viewData) {
|
||||
$viewData = [];
|
||||
}
|
||||
|
||||
$formsArray = iterator_to_array($forms);
|
||||
|
||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||
$flag = $flagProvider->getFlag();
|
||||
|
||||
if (isset($formsArray[$flag])) {
|
||||
$flagForm = $formsArray[$flag];
|
||||
|
||||
$immediateEmailChecked = in_array('immediate-email', $viewData[$flag] ?? []);
|
||||
$dailyEmailChecked = in_array('daily-email', $viewData[$flag] ?? []);
|
||||
|
||||
if ($flagForm->has('immediate_email')) {
|
||||
$flagForm->get('immediate_email')->setData($immediateEmailChecked);
|
||||
}
|
||||
if ($flagForm->has('daily_email')) {
|
||||
$flagForm->get('daily_email')->setData($dailyEmailChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function mapFormsToData($forms, &$viewData): void
|
||||
{
|
||||
$formsArray = iterator_to_array($forms);
|
||||
$viewData = [];
|
||||
|
||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||
$flag = $flagProvider->getFlag();
|
||||
|
||||
if (isset($formsArray[$flag])) {
|
||||
$flagForm = $formsArray[$flag];
|
||||
$viewData[$flag] = [];
|
||||
|
||||
if ($flagForm['immediate_email']->getData()) {
|
||||
$viewData[$flag][] = 'immediate-email';
|
||||
}
|
||||
|
||||
if ($flagForm['daily_email']->getData()) {
|
||||
$viewData[$flag][] = 'daily-email';
|
||||
}
|
||||
|
||||
if (empty($viewData[$flag])) {
|
||||
$viewData[$flag][] = 'no-email';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
<?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\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper;
|
||||
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class NotificationFlagsType extends AbstractType
|
||||
{
|
||||
private array $notificationFlagProviders;
|
||||
|
||||
public function __construct(NotificationFlagManager $notificationFlagManager)
|
||||
{
|
||||
$this->notificationFlagProviders = $notificationFlagManager->getAllNotificationFlagProviders();
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->setDataMapper(new NotificationFlagDataMapper($this->notificationFlagProviders));
|
||||
|
||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||
$flag = $flagProvider->getFlag();
|
||||
$builder->add($flag, FormType::class, [
|
||||
'label' => $flagProvider->getLabel(),
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->get($flag)
|
||||
->add('immediate_email', CheckboxType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
'mapped' => false, // Keep this here for the individual checkboxes
|
||||
])
|
||||
->add('daily_email', CheckboxType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
'mapped' => false, // Keep this here for the individual checkboxes
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => null,
|
||||
]);
|
||||
}
|
||||
}
|
41
src/Bundle/ChillMainBundle/Form/UserProfileType.php
Normal file
41
src/Bundle/ChillMainBundle/Form/UserProfileType.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
||||
use Chill\MainBundle\Form\Type\NotificationFlagsType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserProfileType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('phonenumber', ChillPhoneNumberType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
->add('notificationFlags', NotificationFlagsType::class, [
|
||||
'label' => false,
|
||||
'mapped' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => \Chill\MainBundle\Entity\User::class,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailHandlers;
|
||||
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationEmailMessage;
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendDailyDigestMessage;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
readonly class ScheduleDailyNotificationEmailHandler
|
||||
{
|
||||
public function __construct(
|
||||
private CacheInterface $dailyNotificationsCache,
|
||||
private MessageBusInterface $messageBus,
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __invoke(ScheduleDailyNotificationEmailMessage $message): void
|
||||
{
|
||||
$userId = $message->getAddresseeId();
|
||||
$notificationId = $message->getNotificationId();
|
||||
|
||||
// Store notification in cache grouped by user
|
||||
$cacheKey = "daily_notifications_user_{$userId}";
|
||||
$existingNotifications = $this->dailyNotificationsCache->get($cacheKey, function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
$existingNotifications[] = $notificationId;
|
||||
|
||||
$this->dailyNotificationsCache->get($cacheKey, function () use ($existingNotifications) {
|
||||
return $existingNotifications;
|
||||
});
|
||||
|
||||
// Only send the daily digest message if this is the first notification for today otherwise it already exists
|
||||
if (1 === count($existingNotifications)) {
|
||||
$digestMessage = new SendDailyDigestMessage($userId);
|
||||
|
||||
// Calculate delay until next 9 AM
|
||||
$now = new \DateTimeImmutable();
|
||||
$nextNineAM = $now->modify('tomorrow 09:00');
|
||||
$delay = $nextNineAM->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
$this->messageBus->dispatch($digestMessage, [
|
||||
new DelayStamp($delay * 1000),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->logger->info('[ScheduleDailyNotificationEmailHandler] Added notification to daily cache', [
|
||||
'notification_id' => $notificationId,
|
||||
'user_id' => $userId,
|
||||
'total_pending' => count($existingNotifications),
|
||||
]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailHandlers;
|
||||
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendDailyDigestMessage;
|
||||
use Chill\MainBundle\Notification\Email\NotificationMailer;
|
||||
use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
#[AsMessageHandler]
|
||||
class SendDailyDigestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CacheInterface $dailyNotificationsCache,
|
||||
private readonly NotificationRepository $notificationRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly NotificationMailer $notificationMailer,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __invoke(SendDailyDigestMessage $message): void
|
||||
{
|
||||
$userId = $message->getUserId();
|
||||
$cacheKey = "daily_notifications_user_{$userId}";
|
||||
|
||||
$notificationIds = $this->dailyNotificationsCache->get($cacheKey, []);
|
||||
|
||||
if (empty($notificationIds)) {
|
||||
$this->logger->info('[SendDailyDigestHandler] No notifications found for user', [
|
||||
'user_id' => $userId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->userRepository->find($userId);
|
||||
$notifications = $this->notificationRepository->findBy(['id' => $notificationIds]);
|
||||
|
||||
if ($user && !empty($notifications)) {
|
||||
$this->notificationMailer->sendDailyDigest($user, $notifications);
|
||||
|
||||
// Clear the cache after sending
|
||||
$this->dailyNotificationsCache->delete($cacheKey);
|
||||
|
||||
$this->logger->info('[SendDailyDigestHandler] Sent daily digest', [
|
||||
'user_id' => $userId,
|
||||
'notification_count' => count($notifications),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailHandlers;
|
||||
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage;
|
||||
use Chill\MainBundle\Notification\Email\NotificationMailer;
|
||||
use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler]
|
||||
class SendImmediateNotificationEmailHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationRepository $notificationRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly NotificationMailer $notificationMailer,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __invoke(SendImmediateNotificationEmailMessage $message): void
|
||||
{
|
||||
$notification = $this->notificationRepository->find($message->getNotificationId());
|
||||
$addressee = $this->userRepository->find($message->getAddresseeId());
|
||||
|
||||
if (null === $notification) {
|
||||
$this->logger->error('[SendImmediateNotificationEmailHandler] Notification not found', [
|
||||
'notification_id' => $message->getNotificationId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $addressee) {
|
||||
$this->logger->error('[SendImmediateNotificationEmailHandler] Addressee not found', [
|
||||
'addressee_id' => $message->getAddresseeId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->notificationMailer->sendEmailToAddressee($notification, $addressee);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('[SendImmediateNotificationEmailHandler] Failed to send email', [
|
||||
'notification_id' => $message->getNotificationId(),
|
||||
'addressee_id' => $message->getAddresseeId(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailMessages;
|
||||
|
||||
readonly class ScheduleDailyNotificationEmailMessage
|
||||
{
|
||||
public function __construct(
|
||||
private int $notificationId,
|
||||
private int $addresseeId,
|
||||
) {}
|
||||
|
||||
public function getNotificationId(): int
|
||||
{
|
||||
return $this->notificationId;
|
||||
}
|
||||
|
||||
public function getAddresseeId(): int
|
||||
{
|
||||
return $this->addresseeId;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailMessages;
|
||||
|
||||
class SendDailyDigestMessage
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $userId,
|
||||
) {}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?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\MainBundle\Notification\Email\NotificationEmailMessages;
|
||||
|
||||
readonly class SendImmediateNotificationEmailMessage
|
||||
{
|
||||
public function __construct(
|
||||
private int $notificationId,
|
||||
private int $addresseeId,
|
||||
) {}
|
||||
|
||||
public function getNotificationId(): int
|
||||
{
|
||||
return $this->notificationId;
|
||||
}
|
||||
|
||||
public function getAddresseeId(): int
|
||||
{
|
||||
return $this->addresseeId;
|
||||
}
|
||||
}
|
@@ -13,6 +13,8 @@ namespace Chill\MainBundle\Notification\Email;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\NotificationComment;
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationEmailMessage;
|
||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage;
|
||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -20,11 +22,12 @@ use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class NotificationMailer
|
||||
readonly class NotificationMailer
|
||||
{
|
||||
public function __construct(private readonly MailerInterface $mailer, private readonly LoggerInterface $logger, private readonly TranslatorInterface $translator) {}
|
||||
public function __construct(private MailerInterface $mailer, private LoggerInterface $logger, private MessageBusInterface $messageBus, private readonly TranslatorInterface $translator) {}
|
||||
|
||||
public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void
|
||||
{
|
||||
@@ -80,38 +83,152 @@ class NotificationMailer
|
||||
|
||||
private function sendNotificationEmailsToAddresses(Notification $notification): void
|
||||
{
|
||||
if (null === $notification->getType()) {
|
||||
$this->logger->warning('[NotificationMailer] Notification has no type, skipping email processing', [
|
||||
'notification_id' => $notification->getId(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($notification->getAddressees() as $addressee) {
|
||||
if (null === $addressee->getEmail()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($notification->isSystem()) {
|
||||
$email = new Email();
|
||||
$email
|
||||
->text($notification->getMessage());
|
||||
} else {
|
||||
$email = new TemplatedEmail();
|
||||
$email
|
||||
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig')
|
||||
->context([
|
||||
'notification' => $notification,
|
||||
'dest' => $addressee,
|
||||
]);
|
||||
}
|
||||
$this->processNotificationForAddressee($notification, $addressee);
|
||||
}
|
||||
}
|
||||
|
||||
private function processNotificationForAddressee(Notification $notification, $addressee): void
|
||||
{
|
||||
$notificationFlags = $addressee->getNotificationFlags();
|
||||
$notificationType = $notification->getType();
|
||||
|
||||
$emailPreference = $notificationFlags[$notificationType->value] ?? null;
|
||||
|
||||
match ($emailPreference) {
|
||||
'immediate-email' => $this->scheduleImmediateEmail($notification, $addressee),
|
||||
'daily-email' => $this->scheduleDailyEmail($notification, $addressee),
|
||||
default => $this->logger->debug('[NotificationMailer] No email preference set for notification type', [
|
||||
'notification_id' => $notification->getId(),
|
||||
'addressee_email' => $addressee->getEmail(),
|
||||
'notification_type' => $notificationType->value,
|
||||
'preference' => $emailPreference,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
private function scheduleImmediateEmail(Notification $notification, $addressee): void
|
||||
{
|
||||
$message = new SendImmediateNotificationEmailMessage(
|
||||
$notification->getId(),
|
||||
$addressee->getId()
|
||||
);
|
||||
|
||||
$this->messageBus->dispatch($message);
|
||||
|
||||
$this->logger->info('[NotificationMailer] Scheduled immediate email', [
|
||||
'notification_id' => $notification->getId(),
|
||||
'addressee_email' => $addressee->getEmail(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function scheduleDailyEmail(Notification $notification, $addressee): void
|
||||
{
|
||||
$message = new ScheduleDailyNotificationEmailMessage(
|
||||
$notification->getId(),
|
||||
$addressee->getId()
|
||||
);
|
||||
|
||||
$this->messageBus->dispatch($message);
|
||||
|
||||
$this->logger->info('[NotificationMailer] Scheduled daily email', [
|
||||
'notification_id' => $notification->getId(),
|
||||
'addressee_email' => $addressee->getEmail(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sends the email but is now called by the immediate notification email message handler.
|
||||
*
|
||||
* @throws TransportExceptionInterface
|
||||
*/
|
||||
public function sendEmailToAddressee(Notification $notification, $addressee): void
|
||||
{
|
||||
if (null === $addressee->getEmail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($notification->isSystem()) {
|
||||
$email = new Email();
|
||||
$email->text($notification->getMessage());
|
||||
} else {
|
||||
$email = new TemplatedEmail();
|
||||
$email
|
||||
->subject($notification->getTitle())
|
||||
->to($addressee->getEmail());
|
||||
|
||||
try {
|
||||
$this->mailer->send($email);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->warning('[NotificationMailer] could not send an email notification', [
|
||||
'to' => $addressee->getEmail(),
|
||||
'error_message' => $e->getMessage(),
|
||||
'error_trace' => $e->getTraceAsString(),
|
||||
->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig')
|
||||
->context([
|
||||
'notification' => $notification,
|
||||
'dest' => $addressee,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$email
|
||||
->subject($notification->getTitle())
|
||||
->to($addressee->getEmail());
|
||||
|
||||
try {
|
||||
$this->mailer->send($email);
|
||||
$this->logger->info('[NotificationMailer] Email sent successfully', [
|
||||
'notification_id' => $notification->getId(),
|
||||
'addressee_email' => $addressee->getEmail(),
|
||||
]);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->warning('[NotificationMailer] Could not send an email notification', [
|
||||
'to' => $addressee->getEmail(),
|
||||
'notification_id' => $notification->getId(),
|
||||
'error_message' => $e->getMessage(),
|
||||
'error_trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send daily digest email with multiple notifications to a user.
|
||||
* @throws TransportExceptionInterface
|
||||
*/
|
||||
public function sendDailyDigest($user, array $notifications): void
|
||||
{
|
||||
if (null === $user->getEmail() || empty($notifications)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email = new TemplatedEmail();
|
||||
$email
|
||||
->textTemplate('@ChillMain/Notification/email_daily_digest.fr.md.twig')
|
||||
->context([
|
||||
'user' => $user,
|
||||
'notifications' => $notifications,
|
||||
'notification_count' => count($notifications),
|
||||
])
|
||||
->subject($this->translator->trans('notification.Daily Notification Digest'))
|
||||
->to($user->getEmail());
|
||||
|
||||
try {
|
||||
$this->mailer->send($email);
|
||||
$this->logger->info('[NotificationMailer] Daily digest email sent successfully', [
|
||||
'user_email' => $user->getEmail(),
|
||||
'notification_count' => count($notifications),
|
||||
]);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->warning('[NotificationMailer] Could not send daily digest email', [
|
||||
'to' => $user->getEmail(),
|
||||
'notification_count' => count($notifications),
|
||||
'error_message' => $e->getMessage(),
|
||||
'error_trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
throw $e; // Re-throw so the message handler can handle the failure
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class AccompanyingCourseNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::ACC_COURSE->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.acc-course');
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class AccompanyingCourseWorkEvalDocNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::ACC_COURSE_WORK_EVAL_DOC->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.acc-course-work-eval-doc');
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class AccompanyingCourseWorkNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::ACC_COURSE_WORK->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.acc-course-work');
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class ActivityNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::ACTIVITY->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.activity');
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class DesignatedReferrerNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::REFERRER_ACC_COURSE->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.referrer-acc-course');
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?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\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
#[AutoconfigureTag('chill_main.notification_flag_provider')]
|
||||
interface NotificationFlagProviderInterface
|
||||
{
|
||||
public function getFlag(): string;
|
||||
|
||||
public function getLabel(): TranslatableInterface;
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class PersonAddressMoveNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::PERSON_MOVE->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.person-address-move');
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Notification\FlagProviders;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
class WorkflowTransitionNotificationFlagProvider implements NotificationFlagProviderInterface
|
||||
{
|
||||
|
||||
public function getFlag(): string
|
||||
{
|
||||
return NotificationFlagEnum::WORKFLOW_TRANS->value;
|
||||
}
|
||||
|
||||
public function getLabel(): TranslatableInterface
|
||||
{
|
||||
return new TranslatableMessage('notification.flags.workflow-trans');
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?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\MainBundle\Notification;
|
||||
|
||||
use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface;
|
||||
|
||||
final readonly class NotificationFlagManager
|
||||
{
|
||||
/**
|
||||
* @var array<NotificationFlagProviderInterface>
|
||||
*/
|
||||
private array $notificationFlagProviders;
|
||||
|
||||
public function __construct(
|
||||
iterable $notificationFlagProviders,
|
||||
) {
|
||||
$this->notificationFlagProviders = iterator_to_array($notificationFlagProviders);
|
||||
}
|
||||
|
||||
public function getAllNotificationFlagProviders(): array
|
||||
{
|
||||
return $this->notificationFlagProviders;
|
||||
}
|
||||
|
||||
public function getNotificationFlagProviderByLabel(string $label): ?NotificationFlagProviderInterface
|
||||
{
|
||||
foreach ($this->notificationFlagProviders as $provider) {
|
||||
if ($provider->getLabel() == $label) {
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
# Résumé quotidien des notifications
|
||||
|
||||
Bonjour {{ user.name ?? user.email }},
|
||||
|
||||
Voici vos {{ notification_count }} notification{% if notification_count > 1 %}s{% endif %} du jour :
|
||||
|
||||
{% for notification in notifications %}
|
||||
## {{ notification.title }}
|
||||
|
||||
{{ notification.message }}
|
||||
|
||||
Vous pouvez visualiser la notification et y répondre ici:
|
||||
|
||||
{{ absolute_url(path('chill_main_notification_show', {'_locale': 'fr', 'id': notification.id }, false)) }}
|
||||
{% endfor %}
|
||||
|
||||
--
|
||||
Le logiciel Chill
|
@@ -45,6 +45,32 @@
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.phonenumber) }}
|
||||
|
||||
<h2 class="mb-4">{{ 'user.profile.notification_preferences'|trans }}</h2>
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'notification.flags.type'|trans }}</th>
|
||||
<th>{{ 'notification.flags.preferences.immediate_email'|trans }}</th>
|
||||
<th>{{ 'notification.flags.preferences.daily_email'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for flag in form.notificationFlags %}
|
||||
<tr>
|
||||
<td class="col-sm-6">
|
||||
<label>{{ form_label(flag) }}</label>
|
||||
</td>
|
||||
<td>
|
||||
{{ form_widget(flag.immediate_email) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ form_widget(flag.daily_email) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'attr': { 'class': 'btn btn-save' } } ) }}
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Workflow\EventSubscriber;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
@@ -125,7 +126,8 @@ class NotificationOnTransition implements EventSubscriberInterface
|
||||
->setRelatedEntityClass(EntityWorkflow::class)
|
||||
->setTitle($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context))
|
||||
->setMessage($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig', $context))
|
||||
->addAddressee($subscriber);
|
||||
->addAddressee($subscriber)
|
||||
->setType(NotificationFlagEnum::WORKFLOW_TRANS);
|
||||
$this->entityManager->persist($notification);
|
||||
}
|
||||
}
|
||||
|
@@ -139,6 +139,11 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Form\UserProfileType: ~
|
||||
Chill\MainBundle\Form\AbsenceType: ~
|
||||
Chill\MainBundle\Form\DataMapper\RegroupmentDataMapper: ~
|
||||
Chill\MainBundle\Form\RegroupmentType: ~
|
||||
|
@@ -12,6 +12,10 @@ services:
|
||||
arguments:
|
||||
$routeParameters: '%chill_main.notifications%'
|
||||
|
||||
Chill\MainBundle\Notification\NotificationFlagManager:
|
||||
arguments:
|
||||
$notificationFlagProviders: !tagged_iterator chill_main.notification_flag_provider
|
||||
|
||||
Chill\MainBundle\Notification\NotificationHandlerManager:
|
||||
arguments:
|
||||
$handlers: !tagged_iterator chill_main.notification_handler
|
||||
|
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
final class Version20250610102953 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add notification flags property to User';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE users ADD notificationFlags JSONB DEFAULT '[]' NOT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE users DROP notificationFlags
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250618115938 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add type property to notifications';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_main_notification ADD type VARCHAR(255)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_main_notification DROP type
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -51,9 +51,10 @@ Label: Nom
|
||||
user:
|
||||
profile:
|
||||
title: Mon profil
|
||||
Phonenumber successfully updated!: Numéro de téléphone mis à jour!
|
||||
Profile successfully updated!: Votre profil a été mis à jour!
|
||||
no job: Pas de métier assigné
|
||||
no scope: Pas de cercle assigné
|
||||
notification_preferences: Préférences pour mes notifications
|
||||
|
||||
user_group:
|
||||
inactive: Inactif
|
||||
@@ -715,6 +716,21 @@ notification:
|
||||
mark_as_read: Marquer comme lu
|
||||
mark_as_unread: Marquer comme non-lu
|
||||
|
||||
flags:
|
||||
type: Type de notification
|
||||
referrer-acc-course: Notification lors de la désignation comme référent
|
||||
acc-course-work-eval-doc: Notification sur un document d'évaluation
|
||||
acc-course-work: Notification sur un action d'accompagnement
|
||||
activity: Notification sur un échange
|
||||
acc-course: Notification sur un parcours d'accompagnement
|
||||
person-address-move: Notification lors que l'usager qui localise un parcours a déménagé
|
||||
person: Notification sur un usager
|
||||
workflow-trans: Notification sur une transition d'un workflow
|
||||
preferences:
|
||||
immediate_email: Recevoir un email immédiatement
|
||||
daily_email: Recevoir un récapitulatif quotidien
|
||||
|
||||
|
||||
|
||||
export:
|
||||
address_helper:
|
||||
|
@@ -13,6 +13,7 @@ namespace Chill\PersonBundle\AccompanyingPeriod\Events;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Chill\MainBundle\Notification\NotificationPersisterInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
|
||||
@@ -65,7 +66,8 @@ class PersonAddressMoveEventSubscriber implements EventSubscriberInterface
|
||||
->setMessage($this->engine->render('@ChillPerson/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig', [
|
||||
'oldPersonLocation' => $person,
|
||||
'period' => $period,
|
||||
]));
|
||||
]))
|
||||
->setType(NotificationFlagEnum::PERSON_MOVE);
|
||||
|
||||
$this->notificationPersister->persist($notification);
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\AccompanyingPeriod\Events;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\NotificationFlagEnum;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Notification\NotificationPersisterInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
@@ -73,7 +74,8 @@ class UserRefEventSubscriber implements EventSubscriberInterface
|
||||
'accompanyingCourse' => $period,
|
||||
]
|
||||
))
|
||||
->addAddressee($period->getUser());
|
||||
->addAddressee($period->getUser())
|
||||
->setType(NotificationFlagEnum::REFERRER_ACC_COURSE);
|
||||
|
||||
$this->notificationPersister->persist($notification);
|
||||
}
|
||||
|
@@ -14,6 +14,15 @@
|
||||
"config/routes/annotations.yaml"
|
||||
]
|
||||
},
|
||||
"doctrine/deprecations": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.13",
|
||||
"recipe": {
|
||||
|
Reference in New Issue
Block a user