Skip to content

Commit

Permalink
[VotingPlatform] add announcement email notification (#10890)
Browse files Browse the repository at this point in the history
  • Loading branch information
ottaviano authored Oct 18, 2024
1 parent c88045c commit 52ce4a8
Show file tree
Hide file tree
Showing 25 changed files with 402 additions and 141 deletions.
2 changes: 1 addition & 1 deletion features/api/adherents.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ Feature:
"""
{
"adherent": 6,
"sympathizer": 1
"sympathizer": 2
}
"""
When I send a "POST" request to "/api/v3/adherents/count?scope=president_departmental_assembly" with body:
Expand Down
11 changes: 5 additions & 6 deletions src/Admin/VotingPlatform/Designation/DesignationAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Admin\VotingPlatform\Designation;

use App\Adherent\Tag\TagEnum;
use App\Admin\AbstractAdmin;
use App\Entity\Geo\Zone;
use App\Entity\VotingPlatform\Designation\CandidacyPool\CandidacyPool;
Expand Down Expand Up @@ -86,10 +85,10 @@ protected function configureFormFields(FormMapper $form): void
'help' => 'Obligatoire pour les élections départementales',
'btn_add' => false,
])
->add('target', ChoiceType::class, [
'label' => 'Personnes concernées',
'choices' => array_combine($tags = TagEnum::getAdherentTags(true), $tags),
'multiple' => true,
->add('targetYear', ChoiceType::class, [
'required' => false,
'label' => 'Collège électoral : Adhérent à jour à partir de :',
'choices' => array_combine($years = range(2022, date('Y')), $years),
])
->end()
->with('Candidature 🎎', ['class' => 'col-md-6', 'box_class' => 'box box-solid box-default'])
Expand Down Expand Up @@ -139,7 +138,7 @@ protected function configureFormFields(FormMapper $form): void
->end()
->tab('Notifications 📯')
->with('Envoi d\'email')
->add('notifications', DesignationNotificationType::class, ['required' => false])
->add('notifications', DesignationNotificationType::class, ['label' => false, 'required' => false])
->end()
->end()
->tab('Questionnaire ❓')
Expand Down
2 changes: 1 addition & 1 deletion src/Command/VotingPlatform/ConfigureCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->configureLocalElection($designation);
} elseif ($designation->isLocalPollType()) {
$this->configureLocalPoll($designation);
} elseif ($designation->isConsultationType()) {
} elseif ($designation->isConsultationType() || $designation->isVoteType()) {
$this->configureConsultation($designation);
} elseif ($designation->isTerritorialAssemblyType()) {
$this->configureTerritorialAssembly($designation);
Expand Down
26 changes: 11 additions & 15 deletions src/Command/VotingPlatform/NotifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

#[AsCommand(
Expand All @@ -24,8 +23,6 @@
)]
class NotifyCommand extends Command
{
private SymfonyStyle $io;

public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly DesignationRepository $designationRepository,
Expand All @@ -38,16 +35,12 @@ public function __construct(
parent::__construct();
}

protected function initialize(InputInterface $input, OutputInterface $output): void
{
$this->io = new SymfonyStyle($input, $output);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$date = new \DateTime();

$this->notifyForEndForCandidacy($date);
$this->notifyBeforeVote($date);
$this->notifyWithReminderToVote($date, Designation::NOTIFICATION_VOTE_REMINDER_1D);
$this->notifyWithReminderToVote($date, Designation::NOTIFICATION_VOTE_REMINDER_1H);
$this->notifyForForElectionResults($date);
Expand All @@ -59,15 +52,11 @@ private function notifyForEndForCandidacy(\DateTimeInterface $date): void
{
$designations = $this->designationRepository->getWithFinishCandidacyPeriod($date, [DesignationTypeEnum::COMMITTEE_ADHERENT]);

$this->io->progressStart();

foreach ($designations as $designation) {
if (DesignationTypeEnum::COMMITTEE_ADHERENT === $designation->getType()) {
$this->notifyCommitteeElections($designation);
}
}

$this->io->progressFinish();
}

public function notifyCommitteeElections(Designation $designation): void
Expand All @@ -87,8 +76,6 @@ public function notifyCommitteeElections(Designation $designation): void
$committeeElection->setAdherentNotified(true);

$this->entityManager->flush();

$this->io->progressAdvance();
}

$this->entityManager->clear();
Expand All @@ -100,7 +87,16 @@ private function notifyWithReminderToVote(\DateTimeInterface $date, int $notific
$elections = $this->electionRepository->getElectionsToClose($date, $notification);

foreach ($elections as $election) {
$this->electionNotifier->notifyVotingPlatformVoteReminder($election, $notification);
$this->electionNotifier->notifyVoteReminder($election, $notification);
}
}

private function notifyBeforeVote(\DateTimeInterface $date): void
{
$elections = $this->electionRepository->findIncomingElections($date);

foreach ($elections as $election) {
$this->electionNotifier->notifyVoteAnnouncement($election);
}
}

Expand Down
31 changes: 29 additions & 2 deletions src/Entity/VotingPlatform/Designation/Designation.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@ class Designation implements EntityAdministratorBlameableInterface, EntityAdhere
'Ouverture du vote' => self::NOTIFICATION_VOTE_OPENED,
'Fermeture du vote' => self::NOTIFICATION_VOTE_CLOSED,
'Résultats disponible' => self::NOTIFICATION_RESULT_READY,
'Rappel de vote' => self::NOTIFICATION_VOTE_REMINDER_1D,
'Rappel de vote J-1' => self::NOTIFICATION_VOTE_REMINDER_1D,
'Rappel de vote H-1' => self::NOTIFICATION_VOTE_REMINDER_1H,
'Ouverture du tour bis' => self::NOTIFICATION_SECOND_ROUND,
'Annonce du vote J-2' => self::NOTIFICATION_VOTE_ANNOUNCEMENT,
];

public const NOTIFICATION_VOTE_OPENED = 1;
Expand All @@ -96,6 +98,7 @@ class Designation implements EntityAdministratorBlameableInterface, EntityAdhere
public const NOTIFICATION_SECOND_ROUND = 8;
public const NOTIFICATION_RESULT_READY = 16;
public const NOTIFICATION_VOTE_REMINDER_1H = 32;
public const NOTIFICATION_VOTE_ANNOUNCEMENT = 64;

/**
* @var string|null
Expand Down Expand Up @@ -223,7 +226,12 @@ class Designation implements EntityAdministratorBlameableInterface, EntityAdhere
private ?string $description = null;

#[ORM\Column(type: 'integer', nullable: true)]
private $notifications = self::NOTIFICATION_VOTE_OPENED + self::NOTIFICATION_RESULT_READY + self::NOTIFICATION_VOTE_REMINDER_1D + self::NOTIFICATION_VOTE_REMINDER_1H;
private ?int $notifications =
self::NOTIFICATION_VOTE_OPENED +
self::NOTIFICATION_RESULT_READY +
self::NOTIFICATION_VOTE_REMINDER_1D +
self::NOTIFICATION_VOTE_REMINDER_1H +
self::NOTIFICATION_VOTE_ANNOUNCEMENT;

#[ORM\Column(type: 'boolean')]
private bool $isBlankVoteEnabled = true;
Expand Down Expand Up @@ -487,6 +495,7 @@ public function hasValidZone(): bool
DesignationTypeEnum::POLL,
DesignationTypeEnum::LOCAL_POLL,
DesignationTypeEnum::CONSULTATION,
DesignationTypeEnum::VOTE,
DesignationTypeEnum::TERRITORIAL_ASSEMBLY,
], true)) {
return true;
Expand Down Expand Up @@ -595,6 +604,7 @@ public function isLocalElectionTypes(): bool
DesignationTypeEnum::LOCAL_POLL,
DesignationTypeEnum::LOCAL_ELECTION,
DesignationTypeEnum::CONSULTATION,
DesignationTypeEnum::VOTE,
]);
}

Expand All @@ -608,6 +618,11 @@ public function isConsultationType(): bool
return DesignationTypeEnum::CONSULTATION === $this->type;
}

public function isVoteType(): bool
{
return DesignationTypeEnum::VOTE === $this->type;
}

public function isTerritorialAssemblyType(): bool
{
return DesignationTypeEnum::TERRITORIAL_ASSEMBLY === $this->type;
Expand Down Expand Up @@ -840,6 +855,18 @@ public function isLimitedResultsView(): bool
DesignationTypeEnum::TERRITORIAL_ASSEMBLY,
DesignationTypeEnum::COMMITTEE_SUPERVISOR,
DesignationTypeEnum::CONSULTATION,
DesignationTypeEnum::VOTE,
], true);
}

public function initCreationDate(): void
{
if ($this->getVoteStartDate()) {
$electionCreationDate = (clone $this->getVoteStartDate())->modify(\sprintf('-%d days', $this->isCommitteeSupervisorType() ? 15 : 2));

if ($this->electionCreationDate !== $electionCreationDate) {
$this->electionCreationDate = $electionCreationDate;
}
}
}
}
2 changes: 1 addition & 1 deletion src/JeMengage/Alert/Provider/ElectionAlertProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function getAlert(Adherent $adherent): ?Alert
{
$designations = $this->electionManager->findActiveDesignations(
$adherent,
[DesignationTypeEnum::LOCAL_POLL, DesignationTypeEnum::CONSULTATION],
[DesignationTypeEnum::LOCAL_POLL, DesignationTypeEnum::CONSULTATION, DesignationTypeEnum::VOTE],
);

if (!$designations) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Mailer\Message\Renaissance\VotingPlatform;

use App\Entity\Adherent;
use App\Entity\VotingPlatform\Election;
use App\Mailer\Message\Renaissance\AbstractRenaissanceMessage;
use Ramsey\Uuid\Uuid;

abstract class AbstractAnnouncementMessage extends AbstractRenaissanceMessage
{
/**
* @param Adherent[] $adherents
*/
public static function create(Election $election, array $adherents, string $url): self
{
$first = array_shift($adherents);

$message = new static(
Uuid::uuid4(),
$first->getEmailAddress(),
$first->getFullName(),
$election->getTitle(),
[
'vote_title' => $election->getTitle(),
'vote_start_date' => static::formatDate($election->getVoteStartDate(), 'd MMMM y'),
'vote_start_hour' => static::formatDate($election->getVoteStartDate(), 'HH\'h\'mm'),
'vote_end_date' => static::formatDate($election->getVoteEndDate(), 'd MMMM y'),
'vote_end_hour' => static::formatDate($election->getVoteEndDate(), 'HH\'h\'mm'),
'year' => $election->getDesignation()->targetYear,
'description' => nl2br($election->getDesignation()->getDescription() ?? ''),
'primary_link' => $url,
],
[
'first_name' => $first->getFirstName(),
]
);

foreach ($adherents as $adherent) {
$message->addRecipient($adherent->getEmailAddress(), $adherent->getFullName(), [
'first_name' => $adherent->getFirstName(),
]);
}

return $message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App\Mailer\Message\Renaissance\VotingPlatform;

class ConsultationAnnouncementMessage extends AbstractAnnouncementMessage
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App\Mailer\Message\Renaissance\VotingPlatform;

class VoteAnnouncementMessage extends AbstractAnnouncementMessage
{
}
10 changes: 2 additions & 8 deletions src/Normalizer/DesignationDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,13 @@ public function denormalize($data, string $class, ?string $format = null, array
if ($designation->getCandidacyEndDate() !== $voteDate = $designation->getVoteStartDate()) {
$designation->setCandidacyEndDate($voteDate);
}

if ($designation->getVoteStartDate()) {
$electionCreationDate = (clone $designation->getVoteStartDate())->modify('-15 days')->setTime(0, 0, 0);

if ($designation->electionCreationDate !== $electionCreationDate) {
$designation->electionCreationDate = $electionCreationDate;
}
}
} elseif ($designation->isConsultationType()) {
$designation->alertTitle = $designation->alertTitle ?: $designation->getTitle();
$designation->alertCtaLabel = $designation->alertCtaLabel ?: 'Voir';
$designation->alertDescription = $designation->alertDescription ?: (new UnicodeString($designation->getDescription() ?? ''))->truncate(200, '', false);
}

$designation->initCreationDate();
}

return $designation;
Expand Down
52 changes: 48 additions & 4 deletions src/Repository/AdherentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
use App\Entity\Phoning\Campaign;
use App\Entity\Phoning\CampaignHistory;
use App\Entity\VotingPlatform\Designation\Designation;
use App\Entity\VotingPlatform\Election;
use App\Entity\VotingPlatform\ElectionRound;
use App\Entity\VotingPlatform\Vote;
use App\Entity\VotingPlatform\Voter;
use App\Entity\VotingPlatform\VotersList;
use App\Membership\MembershipSourceEnum;
use App\Pap\CampaignHistoryStatusEnum as PapCampaignHistoryStatusEnum;
Expand Down Expand Up @@ -937,15 +941,57 @@ public function countInZones(array $zones, bool $adherentRenaissance, bool $symp
;
}

public function getAllInZones(array $zones, bool $adherentRenaissance, bool $sympathizerRenaissance): array
public function getAllInZones(array $zones, bool $adherentRenaissance, bool $sympathizerRenaissance, ?int $offset = null, ?int $limit = null): array
{
if (!$zones) {
return [];
}

return $this
$qb = $this
->createQueryBuilderForZones($zones, $adherentRenaissance, $sympathizerRenaissance)
->select('PARTIAL adherent.{id, uuid, emailAddress, source, firstName, lastName, lastMembershipDonation}')
;

if (null !== $offset && null !== $limit) {
$qb
->setMaxResults($limit)
->setFirstResult($offset)
;
}

return $qb
->getQuery()
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->getResult()
;
}

public function getAllInZonesAndNotVoted(Election $election, array $zones, ?int $offset = null, ?int $limit = null): array
{
if (!$zones) {
return [];
}

$qb = $this
->createQueryBuilderForZones($zones, true, false)
->select('PARTIAL adherent.{id, uuid, emailAddress, source, firstName, lastName, lastMembershipDonation}')
->leftJoin(Voter::class, 'voter', Join::WITH, 'voter.adherent = adherent')
->leftJoin('voter.votersLists', 'voters_lists', Join::WITH, 'voters_lists.election = :election')
->leftJoin(ElectionRound::class, 'election_round', Join::WITH, 'election_round.election = :election')
->leftJoin(Vote::class, 'vote', Join::WITH, 'vote.voter = voter AND vote.electionRound = election_round')
->andWhere('vote.id IS NULL')
->groupBy('adherent.id')
->setParameter('election', $election)
;

if (null !== $offset && null !== $limit) {
$qb
->setMaxResults($limit)
->setFirstResult($offset)
;
}

return $qb
->getQuery()
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->getResult()
Expand Down Expand Up @@ -977,10 +1023,8 @@ private function createQueryBuilderForZones(
bool $sympathizerRenaissance,
): QueryBuilder {
$qb = $this->createQueryBuilder('adherent')
->where('adherent.source = :source')
->andWhere('adherent.status = :status')
->setParameters([
'source' => MembershipSourceEnum::RENAISSANCE,
'status' => Adherent::ENABLED,
])
;
Expand Down
Loading

0 comments on commit 52ce4a8

Please sign in to comment.