Skip to content

Commit

Permalink
[Admin] allow send national event tickets (#10233)
Browse files Browse the repository at this point in the history
* [Admin] allow send national event tickets

* Add search
  • Loading branch information
ottaviano authored May 17, 2024
1 parent 3f655b6 commit 21490e7
Show file tree
Hide file tree
Showing 19 changed files with 575 additions and 37 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ENABLE_CANARY=1
ENABLE_ASSESSOR_SPACE=1

GCLOUD_BUCKET=
GCLOUD_NATIONAL_EVENT_BUCKET=

PAYBOX_SITE=
PAYBOX_RANK=
Expand Down
6 changes: 6 additions & 0 deletions config/packages/prod/flysystem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ flysystem:
options:
client: 'Google\Cloud\Storage\StorageClient'
bucket: '%env(GCLOUD_BUCKET)%'

national_event.storage:
adapter: 'gcloud'
options:
client: 'Google\Cloud\Storage\StorageClient'
bucket: '%env(GCLOUD_NATIONAL_EVENT_BUCKET)%'
2 changes: 1 addition & 1 deletion config/services/admin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@

<argument />
<argument>App\Entity\NationalEvent\NationalEvent</argument>
<argument />
<argument>App\Controller\Admin\AdminNationalEventCRUDController</argument>
</service>

<service id="app.admin.national_event_inscriptions" class="App\Admin\NationalEvent\NationalEventInscriptionsAdmin">
Expand Down
26 changes: 26 additions & 0 deletions migrations/Version20240517080738.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20240517080738 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE
national_event
ADD
subject_ticket_email VARCHAR(255) DEFAULT NULL,
ADD
image_ticket_email LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE national_event_inscription ADD ticket_qrcode_file VARCHAR(255) DEFAULT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE national_event DROP subject_ticket_email, DROP image_ticket_email');
$this->addSql('ALTER TABLE national_event_inscription DROP ticket_qrcode_file');
}
}
23 changes: 21 additions & 2 deletions src/Admin/NationalEvent/NationalEventAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@
use League\Flysystem\FilesystemOperator;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Route\RouteCollectionInterface;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Contracts\Service\Attribute\Required;

class NationalEventAdmin extends AbstractAdmin
{
private FilesystemOperator $storage;

protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection
->add('inscriptions', $this->getRouterIdParameter().'/inscriptions')
->add('sendTickets', $this->getRouterIdParameter().'/send-tickets')
->add('generateTicketQRCodes', $this->getRouterIdParameter().'/generate-ticket-qrcodes')
;
}

protected function configureListFields(ListMapper $list): void
{
$list
Expand All @@ -24,7 +36,12 @@ protected function configureListFields(ListMapper $list): void
->add('intoImagePath', null, ['label' => 'Image', 'template' => 'admin/national_event/list_image.html.twig'])
->add('startDate', null, ['label' => 'Date de début'])
->add('endDate', null, ['label' => 'Date de fin'])
->add(ListMapper::NAME_ACTIONS, null, ['actions' => ['edit' => []]])
->add(ListMapper::NAME_ACTIONS, null, ['actions' => [
'edit' => [],
'inscriptions' => [
'template' => 'admin/national_event/list_action_inscriptions.html.twig',
],
]])
;
}

Expand Down Expand Up @@ -52,7 +69,9 @@ protected function configureFormFields(FormMapper $form): void
->add('textConfirmation', CKEditorType::class, ['label' => false, 'required' => true])
->end()
->with('Contenu du mail de billet', ['class' => 'col-md-6'])
->add('textTicketEmail', CKEditorType::class, ['label' => false, 'required' => true])
->add('subjectTicketEmail', TextType::class, ['label' => 'Objet', 'required' => true])
->add('imageTicketEmail', UrlType::class, ['label' => 'URL de l\'image', 'required' => true])
->add('textTicketEmail', CKEditorType::class, ['label' => 'Détail', 'required' => true])
->end()
;
}
Expand Down
44 changes: 13 additions & 31 deletions src/Command/SendNationalEventTicketsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@
namespace App\Command;

use App\Entity\NationalEvent\EventInscription;
use App\Mailer\MailerService;
use App\Mailer\Message\BesoinDEurope\NationalEventTicketMessage;
use App\NationalEvent\Command\SendTicketCommand;
use App\NationalEvent\InscriptionStatusEnum;
use App\Repository\NationalEvent\EventInscriptionRepository;
use Doctrine\ORM\EntityManagerInterface as ObjectManager;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Endroid\QrCode\Builder\BuilderInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Messenger\MessageBusInterface;

#[AsCommand('app:national-event:send-tickets')]
class SendNationalEventTicketsCommand extends Command
Expand All @@ -25,9 +22,7 @@ class SendNationalEventTicketsCommand extends Command

public function __construct(
private readonly EventInscriptionRepository $eventInscriptionRepository,
private readonly ObjectManager $entityManager,
private readonly MailerService $transactionalMailer,
private readonly BuilderInterface $builder,
private readonly MessageBusInterface $messageBus,
) {
parent::__construct();
}
Expand All @@ -44,9 +39,9 @@ protected function initialize(InputInterface $input, OutputInterface $output): v

public function execute(InputInterface $input, OutputInterface $output): int
{
$paginator = $this->getQueryBuilder($emails = $input->getOption('email'));
$inscriptions = $this->getQueryBuilder($input->getOption('email'));

if (0 === $total = $paginator->count()) {
if (0 === $total = \count($inscriptions)) {
$this->io->success('No tickets to send.');

return self::SUCCESS;
Expand All @@ -58,37 +53,25 @@ public function execute(InputInterface $input, OutputInterface $output): int

$this->io->progressStart($total);

do {
foreach ($paginator as $eventInscription) {
$this->transactionalMailer->sendMessage(NationalEventTicketMessage::create(
$eventInscription,
$this->builder->data($eventInscription->getUuid()->toString())->build()->getDataUri()
));
foreach ($inscriptions as $eventInscription) {
$this->messageBus->dispatch(new SendTicketCommand($eventInscription->getUuid()));

$eventInscription->ticketSentAt = new \DateTime();

$this->entityManager->flush();

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

$this->entityManager->clear();
} while (empty($emails) && iterator_count($paginator->getIterator()));
$this->io->progressAdvance();
}

$this->io->progressFinish();

return self::SUCCESS;
}

/**
* @return Paginator|EventInscription[]
* @return EventInscription[]
*/
private function getQueryBuilder(array $emails): Paginator
private function getQueryBuilder(array $emails): array
{
$queryBuilder = $this->eventInscriptionRepository
->createQueryBuilder('event_inscription')
->innerJoin('event_inscription.event', 'event')
->addSelect('event')
->select('PARTIAL event_inscription.{id, uuid}')
;

if ($emails) {
Expand All @@ -101,10 +84,9 @@ private function getQueryBuilder(array $emails): Paginator
->andWhere('event_inscription.status IN (:status)')
->andWhere('event_inscription.ticketSentAt IS NULL')
->setParameter('status', [InscriptionStatusEnum::ACCEPTED, InscriptionStatusEnum::INCONCLUSIVE])
->setMaxResults(500)
;
}

return new Paginator($queryBuilder->getQuery());
return $queryBuilder->getQuery()->getResult();
}
}
71 changes: 71 additions & 0 deletions src/Controller/Admin/AdminNationalEventCRUDController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Controller\Admin;

use App\Entity\NationalEvent\NationalEvent;
use App\NationalEvent\Command\GenerateTicketQRCodeCommand;
use App\NationalEvent\Command\SendTicketCommand;
use App\NationalEvent\InscriptionStatusEnum;
use App\Repository\NationalEvent\EventInscriptionRepository;
use Sonata\AdminBundle\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;

class AdminNationalEventCRUDController extends CRUDController
{
public function inscriptionsAction(Request $request, NationalEvent $event, EventInscriptionRepository $eventInscriptionRepository): Response
{
return $this->renderWithExtraParams('admin/national_event/inscriptions.html.twig', [
'national_event' => $event,
'action' => 'inscriptions',
'event_inscriptions' => $eventInscriptionRepository->findAllForEventPaginated(
$event,
$request->query->get('q'),
$statuses = [InscriptionStatusEnum::ACCEPTED, InscriptionStatusEnum::INCONCLUSIVE],
$request->query->getInt('page', 1),
2
),
'count_without_qrcodes' => $eventInscriptionRepository->countWithoutTicketQRCodes($event),
'count_without_ticket' => $eventInscriptionRepository->countTickets($event, true, $statuses),
'count_with_ticket' => $eventInscriptionRepository->countTickets($event, false, $statuses),
'count_by_status' => $eventInscriptionRepository->countByStatus($event),
]);
}

public function sendTicketsAction(Request $request, NationalEvent $event, EventInscriptionRepository $eventInscriptionRepository, MessageBusInterface $messageBus): Response
{
$inscriptions = [];

if ($request->query->has('all')) {
$inscriptions = $eventInscriptionRepository->findAllPartialForEvent($event, [InscriptionStatusEnum::ACCEPTED, InscriptionStatusEnum::INCONCLUSIVE]);
} elseif ($request->query->has('only_missing')) {
$inscriptions = $eventInscriptionRepository->findAllPartialForEvent($event, [InscriptionStatusEnum::ACCEPTED, InscriptionStatusEnum::INCONCLUSIVE], true);
}

if ($inscriptions) {
foreach ($inscriptions as $inscription) {
$messageBus->dispatch(new SendTicketCommand($inscription->getUuid()));
}

$this->addFlash('sonata_flash_success', 'Les billets sont en cours d\'envoi.');
} else {
$this->addFlash('sonata_flash_error', 'Aucun billet à envoyer.');
}

return $this->redirect($this->admin->generateObjectUrl('inscriptions', $event));
}

public function generateTicketQRCodesAction(NationalEvent $event, EventInscriptionRepository $eventInscriptionRepository, MessageBusInterface $messageBus): Response
{
$inscriptions = $eventInscriptionRepository->findAllWithoutTickets($event);

foreach ($inscriptions as $inscription) {
$messageBus->dispatch(new GenerateTicketQRCodeCommand($inscription->getUuid()));
}

$this->addFlash('sonata_flash_success', 'Les codes QR des billets sont en cours de génération.');

return $this->redirect($this->admin->generateObjectUrl('inscriptions', $event));
}
}
5 changes: 5 additions & 0 deletions src/Entity/NationalEvent/EventInscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ class EventInscription
*/
public ?string $ticketCustomDetail = null;

/**
* @ORM\Column(nullable=true)
*/
public ?string $ticketQRCodeFile = null;

public function __construct(NationalEvent $event, ?UuidInterface $uuid = null)
{
$this->uuid = $uuid ?? Uuid::uuid4();
Expand Down
16 changes: 16 additions & 0 deletions src/Entity/NationalEvent/NationalEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,25 @@ class NationalEvent

/**
* @ORM\Column(type="text", nullable=true)
*
* @Assert\NotBlank(groups={"Admin"})
*/
public ?string $textTicketEmail = null;

/**
* @ORM\Column(nullable=true)
*
* @Assert\NotBlank(groups={"Admin"})
*/
public ?string $subjectTicketEmail = null;

/**
* @ORM\Column(type="text", nullable=true)
*
* @Assert\NotBlank(groups={"Admin"})
*/
public ?string $imageTicketEmail = null;

/**
* @ORM\Column(nullable=true)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@

class NationalEventTicketMessage extends AbstractBesoinDEuropeMessage
{
public static function create(EventInscription $eventInscription, string $qrCodeData): self
public static function create(EventInscription $eventInscription): self
{
$event = $eventInscription->event;

return new self(
Uuid::uuid4(),
$eventInscription->addressEmail,
$eventInscription->getFullName(),
'',
$event->subjectTicketEmail,
[
'first_name' => $eventInscription->firstName,
'last_name' => $eventInscription->lastName,
'qr_code_img' => $qrCodeData,
'qr_code_img' => $eventInscription->ticketQRCodeFile,
'header_ticket_image' => $event->imageTicketEmail,
'event_mail_details' => $eventInscription->event->textTicketEmail,
],
);
Expand Down
9 changes: 9 additions & 0 deletions src/NationalEvent/Command/GenerateTicketQRCodeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\NationalEvent\Command;

use App\Messenger\Message\UuidDefaultAsyncMessage;

class GenerateTicketQRCodeCommand extends UuidDefaultAsyncMessage
{
}
9 changes: 9 additions & 0 deletions src/NationalEvent/Command/SendTicketCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\NationalEvent\Command;

use App\Messenger\Message\UuidDefaultAsyncMessage;

class SendTicketCommand extends UuidDefaultAsyncMessage
{
}
Loading

0 comments on commit 21490e7

Please sign in to comment.