WIP add user group as possible addressee to notification

This commit is contained in:
Julie Lenaerts 2025-06-23 15:29:03 +02:00
parent f88f1f1859
commit ac16060441
6 changed files with 135 additions and 45 deletions

View File

@ -82,9 +82,9 @@ framework:
'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority 'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async 'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async 'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
'Chill\MainBundle\Notification\Email\SendImmediateNotificationEmailMessage': immediate_email 'Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage': immediate_email
'Chill\MainBundle\Notification\Email\ScheduleDailyNotificationEmailMessage': daily_email 'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationEmailMessage': daily_email
'Chill\MainBundle\Notification\Email\SendDailyDigestMessage': daily_email 'Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendDailyDigestMessage': daily_email
# end of routes added by chill-bundles recipes # end of routes added by chill-bundles recipes
# Route your messages to the transports # Route your messages to the transports
# 'App\Message\YourMessage': async # 'App\Message\YourMessage': async

View File

@ -22,7 +22,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[ORM\Entity] #[ORM\Entity]
#[ORM\HasLifecycleCallbacks] #[ORM\HasLifecycleCallbacks]
#[ORM\Table(name: 'chill_main_notification')] #[ORM\Table(name: 'chill_main_notification')]
#[ORM\Index(name: 'chill_main_notification_related_entity_idx', columns: ['relatedentityclass', 'relatedentityid'])] #[ORM\Index(columns: ['relatedentityclass', 'relatedentityid'], name: 'chill_main_notification_related_entity_idx')]
class Notification implements TrackUpdateInterface class Notification implements TrackUpdateInterface
{ {
#[ORM\Column(type: Types::TEXT, nullable: false)] #[ORM\Column(type: Types::TEXT, nullable: false)]
@ -38,7 +38,15 @@ class Notification implements TrackUpdateInterface
private Collection $addressees; private Collection $addressees;
/** /**
* a list of destinee which will receive notifications. * @var Collection<int, User>
*/
#[ORM\ManyToMany(targetEntity: UserGroup::class)]
#[ORM\JoinTable(name: 'chill_main_notification_addressee_user_group')]
private Collection $addresseeUserGroups;
/**
* @deprecated
* a list of destinee which will receive notifications
* *
* @var array|string[] * @var array|string[]
*/ */
@ -46,7 +54,8 @@ class Notification implements TrackUpdateInterface
private array $addressesEmails = []; private array $addressesEmails = [];
/** /**
* a list of emails adresses which were added to the notification. * @deprecated
* a list of emails adresses which were added to the notification
* *
* @var array|string[] * @var array|string[]
*/ */
@ -107,22 +116,31 @@ class Notification implements TrackUpdateInterface
public function __construct() public function __construct()
{ {
$this->addressees = new ArrayCollection(); $this->addressees = new ArrayCollection();
$this->addresseeUserGroups = new ArrayCollection();
$this->unreadBy = new ArrayCollection(); $this->unreadBy = new ArrayCollection();
$this->comments = new ArrayCollection(); $this->comments = new ArrayCollection();
$this->setDate(new \DateTimeImmutable()); $this->setDate(new \DateTimeImmutable());
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24));
} }
public function addAddressee(User $addressee): self public function addAddressee(User|UserGroup $addressee): self
{ {
if (!$this->addressees->contains($addressee)) { if ($addressee instanceof User) {
$this->addressees[] = $addressee; if (!$this->addressees->contains($addressee)) {
$this->addedAddresses[] = $addressee; $this->addressees->add($addressee);
return $this;
}
} }
$this->addresseeUserGroups->add($addressee);
return $this; return $this;
} }
/**
* @deprecated
*/
public function addAddressesEmail(string $email) public function addAddressesEmail(string $email)
{ {
if (!\in_array($email, $this->addressesEmails, true)) { if (!\in_array($email, $this->addressesEmails, true)) {
@ -156,13 +174,26 @@ class Notification implements TrackUpdateInterface
#[Assert\Callback] #[Assert\Callback]
public function assertCountAddresses(ExecutionContextInterface $context, $payload): void public function assertCountAddresses(ExecutionContextInterface $context, $payload): void
{ {
if (0 === (\count($this->getAddressesEmails()) + \count($this->getAddressees()))) { if (0 === (\count($this->getAddresseeUserGroups()) + \count($this->getAddressees()))) {
$context->buildViolation('notification.At least one addressee') $context->buildViolation('notification.At least one addressee')
->atPath('addressees') ->atPath('addressees')
->addViolation(); ->addViolation();
} }
} }
/**
* @return Collection<UserGroup>
*/
public function getAddresseeUserGroups(): Collection
{
return $this->addresseeUserGroups;
}
public function setAddresseeUserGroups(Collection $addresseeUserGroups): void
{
$this->addresseeUserGroups = $addresseeUserGroups;
}
public function getAccessKey(): string public function getAccessKey(): string
{ {
return $this->accessKey; return $this->accessKey;
@ -186,8 +217,27 @@ class Notification implements TrackUpdateInterface
return $this->addressees; return $this->addressees;
} }
public function getAllAddressees(): array
{
$allUsers = [];
foreach ($this->getAddressees() as $user) {
$allUsers[$user->getId()] = $user;
}
foreach ($this->getAddresseeUserGroups() as $userGroup) {
foreach ($userGroup->getUsers() as $user) {
$allUsers[$user->getId()] = $user;
}
}
return array_values($allUsers);
}
/** /**
* @return array|string[] * @return array|string[]
*
* @deprecated
*/ */
public function getAddressesEmails(): array public function getAddressesEmails(): array
{ {
@ -196,6 +246,8 @@ class Notification implements TrackUpdateInterface
/** /**
* @return array|string[] * @return array|string[]
*
* @deprecated
*/ */
public function getAddressesEmailsAdded(): array public function getAddressesEmailsAdded(): array
{ {
@ -307,15 +359,24 @@ class Notification implements TrackUpdateInterface
$this->addressesOnLoad = null; $this->addressesOnLoad = null;
} }
public function removeAddressee(User $addressee): self public function removeAddressee(User|UserGroup $addressee): self
{ {
if ($this->addressees->removeElement($addressee)) { if ($addressee instanceof User) {
$this->removedAddresses[] = $addressee; if ($this->addressees->contains($addressee)) {
$this->addressees->removeElement($addressee);
return $this;
}
} }
$this->addresseeUserGroups->removeElement($addressee);
return $this; return $this;
} }
/**
* @deprecated
*/
public function removeAddressesEmail(string $email) public function removeAddressesEmail(string $email)
{ {
if (\in_array($email, $this->addressesEmails, true)) { if (\in_array($email, $this->addressesEmails, true)) {

View File

@ -12,17 +12,13 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form; namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Form\Type\PickUserGroupOrUserDynamicType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
class NotificationType extends AbstractType class NotificationType extends AbstractType
{ {
@ -33,29 +29,13 @@ class NotificationType extends AbstractType
'label' => 'Title', 'label' => 'Title',
'required' => true, 'required' => true,
]) ])
->add('addressees', PickUserDynamicType::class, [ ->add('addressees', PickUserGroupOrUserDynamicType::class, [
'multiple' => true, 'multiple' => true,
'required' => false, 'empty_data' => '[]',
'required' => true,
]) ])
->add('message', ChillTextareaType::class, [ ->add('message', ChillTextareaType::class, [
'required' => false, 'required' => false,
])
->add('addressesEmails', ChillCollectionType::class, [
'label' => 'notification.dest by email',
'help' => 'notification.dest by email help',
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'entry_type' => EmailType::class,
'button_add_label' => 'notification.Add an email',
'button_remove_label' => 'notification.Remove an email',
'empty_collection_explain' => 'notification.Any email',
'entry_options' => [
'constraints' => [
new NotNull(), new NotBlank(), new Email(),
],
'label' => 'Email',
],
]); ]);
} }

View File

@ -72,16 +72,16 @@ readonly class NotificationMailer
*/ */
public function postPersistNotification(Notification $notification, PostPersistEventArgs $eventArgs): void public function postPersistNotification(Notification $notification, PostPersistEventArgs $eventArgs): void
{ {
$this->sendNotificationEmailsToAddresses($notification); $this->sendNotificationEmailsToAddressees($notification);
$this->sendNotificationEmailsToAddressesEmails($notification); // $this->sendNotificationEmailsToAddressesEmails($notification);
} }
public function postUpdateNotification(Notification $notification, PostUpdateEventArgs $eventArgs): void public function postUpdateNotification(Notification $notification, PostUpdateEventArgs $eventArgs): void
{ {
$this->sendNotificationEmailsToAddressesEmails($notification); $this->sendNotificationEmailsToAddressees($notification);
} }
private function sendNotificationEmailsToAddresses(Notification $notification): void private function sendNotificationEmailsToAddressees(Notification $notification): void
{ {
if (null === $notification->getType()) { if (null === $notification->getType()) {
$this->logger->warning('[NotificationMailer] Notification has no type, skipping email processing', [ $this->logger->warning('[NotificationMailer] Notification has no type, skipping email processing', [
@ -91,7 +91,7 @@ readonly class NotificationMailer
return; return;
} }
foreach ($notification->getAddressees() as $addressee) { foreach ($notification->getAllAddressees() as $addressee) {
if (null === $addressee->getEmail()) { if (null === $addressee->getEmail()) {
continue; continue;
} }
@ -232,6 +232,9 @@ readonly class NotificationMailer
} }
} }
/**
* @deprecated
*/
private function sendNotificationEmailsToAddressesEmails(Notification $notification): void private function sendNotificationEmailsToAddressesEmails(Notification $notification): void
{ {
foreach ($notification->getAddressesEmailsAdded() as $emailAddress) { foreach ($notification->getAddressesEmailsAdded() as $emailAddress) {

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 }) }}
{{ form_row(form.addressesEmails) }}
{% include handler.template(notification) with handler.templateData(notification) %} {% include handler.template(notification) with handler.templateData(notification) %}
<div class="mb-3 row"> <div class="mb-3 row">

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250623120824 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add addressee user groups to notifications';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE chill_main_notification_addressee_user_group (notification_id INT NOT NULL, usergroup_id INT NOT NULL, PRIMARY KEY(notification_id, usergroup_id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_ECF81C07EF1A9D84 ON chill_main_notification_addressee_user_group (notification_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_ECF81C07D2112630 ON chill_main_notification_addressee_user_group (usergroup_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_main_notification_addressee_user_group ADD CONSTRAINT FK_ECF81C07EF1A9D84 FOREIGN KEY (notification_id) REFERENCES chill_main_notification (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_main_notification_addressee_user_group ADD CONSTRAINT FK_ECF81C07D2112630 FOREIGN KEY (usergroup_id) REFERENCES chill_main_user_group (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE chill_main_notification_addressee_user_group DROP CONSTRAINT FK_ECF81C07EF1A9D84
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_main_notification_addressee_user_group DROP CONSTRAINT FK_ECF81C07D2112630
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_main_notification_addressee_user_group
SQL);
}
}