Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/packages/backoffice_menu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ parameters:
url: '/admin/event/special_price'
extra_routes:
- admin_event_special_price
admin_event_billeterie_privee:
nom: 'Billeteries privées'
niveau: 'ROLE_ADMIN'
url: '/admin/event/billeterie-privee'
extra_routes:
- admin_event_billeterie_privee
- admin_event_billeterie_privee_edit
- admin_event_billeterie_privee_delete
admin_event_prices:
nom: 'Tarifications'
niveau: 'ROLE_FORUM'
Expand Down
4 changes: 4 additions & 0 deletions config/packages/rate_limiter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ framework:
policy: sliding_window
limit: 10
interval: '1 hour'
billeterie_privee:
policy: sliding_window
limit: 10
interval: '1 hour'
18 changes: 18 additions & 0 deletions config/routing/admin_event.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ admin_event_special_price_extend:
requirements:
id: \d+

admin_event_billeterie_privee:
path: /billeterie-privee
defaults: {_controller: AppBundle\Controller\Admin\Event\BilleteriePriveeAction}

admin_event_billeterie_privee_edit:
path: /billeterie-privee/edit/{event}/{id}
defaults: {_controller: AppBundle\Controller\Admin\Event\EditBilleteriePriveeAction}
requirements:
event: \d+
id: \d+

admin_event_billeterie_privee_delete:
path: /billeterie-privee/delete/{event}/{id}
defaults: {_controller: AppBundle\Controller\Admin\Event\DeleteBilleteriePriveeAction}
requirements:
event: \d+
id: \d+

admin_event_prices:
path: /prices
defaults: {_controller: AppBundle\Controller\Admin\Event\PricesAction }
Expand Down
4 changes: 4 additions & 0 deletions config/routing/event.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ sponsor_ticket_form:
path: /{eventSlug}/tickets/sponsor-form
defaults: { _controller: AppBundle\Controller\Event\Ticket\SponsorTicketFormAction }

billeterie_privee_home:
path: /{eventSlug}/tickets/billeterie-privee/{token}
defaults: { _controller: AppBundle\Controller\Event\Ticket\BilleteriePriveeAction }

ticket:
path: /{eventSlug}/tickets
defaults: { _controller: AppBundle\Controller\Event\Ticket\TicketAction }
Expand Down
28 changes: 28 additions & 0 deletions db/migrations/20260911090000_add_billeterie_privee.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddBilleteriePrivee extends AbstractMigration
{
public function change(): void
{
$this
->table('afup_forum_billeterie_privee')
->addColumn('id_forum', 'integer', ['null' => false, 'signed' => false])
->addColumn('nom', 'string', ['limit' => 255, 'null' => false])
->addColumn('token', 'string', ['limit' => 64, 'null' => false])
->addColumn('mot_de_passe', 'string', ['limit' => 255, 'null' => false])
->addColumn('id_tarif', 'integer', ['null' => false, 'signed' => false])
->addColumn('prix', 'float', ['null' => false])
->addColumn('max_places', 'integer', ['null' => false, 'signed' => false])
->addColumn('date_debut', 'datetime', ['null' => false])
->addColumn('date_fin', 'datetime', ['null' => false])
->addColumn('created_on', 'datetime', ['null' => false])
->addIndex(['token'], ['unique' => true])
->addIndex(['id_forum'])
->save()
;
}
}
35 changes: 35 additions & 0 deletions db/seeds/BilleteriesPrivees.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

use Phinx\Seed\AbstractSeed;

class BilleteriesPrivees extends AbstractSeed
{
public const string TOKEN = 'token-billetterie-privee-test';

public function run(): void
{
$now = new DateTime('now');

// Seed idempotent : suppression de la donnee de test si elle existe deja
$this->execute(sprintf("DELETE FROM afup_forum_billeterie_privee WHERE token = '%s'", self::TOKEN));

$data = [
[
'id_forum' => Event::ID_FORUM,
'nom' => 'Billetterie privée de test - école XYZ',
'token' => self::TOKEN,
'mot_de_passe' => password_hash('password', PASSWORD_DEFAULT),
'id_tarif' => Tarif::TYPE_2_DAYS['id'],
'prix' => 80.0,
'max_places' => 50,
'date_debut' => $now->format('Y-m-d H:i:s'),
'date_fin' => $now->modify('+1 month')->format('Y-m-d H:i:s'),
'created_on' => $now->format('Y-m-d H:i:s'),
],
];

$this->table('afup_forum_billeterie_privee')->insert($data)->saveData();
}
}
18 changes: 0 additions & 18 deletions phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -5587,18 +5587,6 @@
'count' => 1,
'path' => __DIR__ . '/sources/AppBundle/Event/Form/TicketType.php',
];
$ignoreErrors[] = [
'message' => '#^Parameter \\#1 \\$dateEnd of method AppBundle\\\\Event\\\\Model\\\\TicketEventType\\:\\:setDateEnd\\(\\) expects DateTime, DateTime\\|null given\\.$#',
'identifier' => 'argument.type',
'count' => 1,
'path' => __DIR__ . '/sources/AppBundle/Event/Form/TicketType.php',
];
$ignoreErrors[] = [
'message' => '#^Parameter \\#1 \\$dateStart of method AppBundle\\\\Event\\\\Model\\\\TicketEventType\\:\\:setDateStart\\(\\) expects DateTime, DateTime\\|null given\\.$#',
'identifier' => 'argument.type',
'count' => 1,
'path' => __DIR__ . '/sources/AppBundle/Event/Form/TicketType.php',
];
$ignoreErrors[] = [
'message' => '#^Parameter \\#1 \\$event of method AppBundle\\\\Event\\\\Model\\\\Repository\\\\TicketEventTypeRepository\\:\\:getTicketsByEvent\\(\\) expects AppBundle\\\\Event\\\\Model\\\\Event, AppBundle\\\\Event\\\\Model\\\\Event\\|null given\\.$#',
'identifier' => 'argument.type',
Expand All @@ -5611,12 +5599,6 @@
'count' => 1,
'path' => __DIR__ . '/sources/AppBundle/Event/Form/TicketType.php',
];
$ignoreErrors[] = [
'message' => '#^Parameter \\#1 \\$ticketType of method AppBundle\\\\Event\\\\Model\\\\TicketEventType\\:\\:setTicketType\\(\\) expects AppBundle\\\\Event\\\\Model\\\\TicketType, AppBundle\\\\Event\\\\Model\\\\TicketType\\|null given\\.$#',
'identifier' => 'argument.type',
'count' => 1,
'path' => __DIR__ . '/sources/AppBundle/Event/Form/TicketType.php',
];
$ignoreErrors[] = [
'message' => '#^Parameter \\#2 \\$event of method AppBundle\\\\Event\\\\Ticket\\\\TicketTypeAvailability\\:\\:getStock\\(\\) expects AppBundle\\\\Event\\\\Model\\\\Event, AppBundle\\\\Event\\\\Model\\\\Event\\|null given\\.$#',
'identifier' => 'argument.type',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace AppBundle\Controller\Admin\Event;

use AppBundle\Event\AdminEventSelection;
use AppBundle\Event\Entity\BilleteriePrivee;
use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository;
use AppBundle\Event\Form\BilleteriePriveeType;
use AppBundle\Event\Model\Repository\TicketTypeRepository;
use DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class BilleteriePriveeAction extends AbstractController
{
public function __construct(
private readonly BilleteriePriveeRepository $billeteriePriveeRepository,
private readonly TicketTypeRepository $ticketTypeRepository,
) {}

public function __invoke(Request $request, AdminEventSelection $eventSelection): Response
{
$event = $eventSelection->event;

$billeteriePrivee = new BilleteriePrivee();
$billeteriePrivee->token = base64_encode(random_bytes(30));
$billeteriePrivee->eventId = (int) $event->getId();
$billeteriePrivee->dateDebut = new DateTimeImmutable();
$billeteriePrivee->dateFin = DateTimeImmutable::createFromMutable($event->getDateEndSales());
$billeteriePrivee->createdOn = new DateTimeImmutable();

$form = $this->createForm(BilleteriePriveeType::class, $billeteriePrivee, [
'ticketTypes' => $this->ticketTypeRepository->getAll(),
]);

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$motDePasse = $form->get('plainMotDePasse')->getData();
if (is_string($motDePasse) && $motDePasse !== '') {
$billeteriePrivee->motDePasse = password_hash($motDePasse, PASSWORD_DEFAULT);
}

$this->billeteriePriveeRepository->save($billeteriePrivee);

$this->addFlash('notice', 'La billeterie privée a été enregistrée');

return $this->redirectToRoute('admin_event_billeterie_privee', [
'id' => (int) $event->getId(),
]);
}

$billeteries = [];
foreach ($this->billeteriePriveeRepository->findByEvent((int) $event->getId()) as $billeterie) {
$ticketType = $this->ticketTypeRepository->get($billeterie->ticketTypeId);
$billeteries[] = [
'billeterie_privee' => $billeterie,
'ticket_type' => $ticketType !== null ? $ticketType->getPrettyName() : '',
'places_prises' => $this->billeteriePriveeRepository->countPlacesPrisesParToken($billeterie->token),
];
}

return $this->render('admin/event/billeterie_privee.html.twig', [
'billeteries_privees' => $billeteries,
'event' => $event,
'title' => 'Billeteries privées',
'form' => $form->createView(),
'event_select_form' => $eventSelection->selectForm(),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace AppBundle\Controller\Admin\Event;

use AppBundle\Controller\Event\EventActionHelper;
use AppBundle\Event\Entity\BilleteriePrivee;
use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class DeleteBilleteriePriveeAction extends AbstractController
{
public function __construct(
private readonly EventActionHelper $eventActionHelper,
private readonly BilleteriePriveeRepository $billeteriePriveeRepository,
) {}

public function __invoke(Request $request, int $event, int $id): Response
{
$event = $this->eventActionHelper->getEventById($event);

$billeteriePrivee = $this->billeteriePriveeRepository->find($id);
if (!$billeteriePrivee instanceof BilleteriePrivee || $billeteriePrivee->eventId !== $event->getId()) {
throw $this->createNotFoundException(sprintf('Impossible de trouver la billeterie privée ayant l\'identifiant : %s', $id));
}

if ($this->billeteriePriveeRepository->countPlacesPrisesParToken($billeteriePrivee->token) > 0) {
$this->addFlash('error', 'La billeterie privée ne peut pas être supprimée car des places ont déjà été prises.');
return $this->redirectToRoute('admin_event_billeterie_privee', ['id' => $event->getId()]);
}

$this->billeteriePriveeRepository->delete($billeteriePrivee);

$this->addFlash('notice', 'La billeterie privée a été supprimée');
return $this->redirectToRoute('admin_event_billeterie_privee', ['id' => $event->getId()]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace AppBundle\Controller\Admin\Event;

use AppBundle\Controller\Event\EventActionHelper;
use AppBundle\Event\Entity\BilleteriePrivee;
use AppBundle\Event\Entity\Repository\BilleteriePriveeRepository;
use AppBundle\Event\Form\BilleteriePriveeType;
use AppBundle\Event\Model\Repository\TicketTypeRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class EditBilleteriePriveeAction extends AbstractController
{
public function __construct(
private readonly EventActionHelper $eventActionHelper,
private readonly BilleteriePriveeRepository $billeteriePriveeRepository,
private readonly TicketTypeRepository $ticketTypeRepository,
) {}

public function __invoke(Request $request, int $event, int $id): Response
{
$event = $this->eventActionHelper->getEventById($event);

$billeteriePrivee = $this->billeteriePriveeRepository->find($id);
if (!$billeteriePrivee instanceof BilleteriePrivee || $billeteriePrivee->eventId !== $event->getId()) {
throw $this->createNotFoundException(sprintf('Impossible de trouver la billeterie privée ayant l\'identifiant : %s', $id));
}

$maxPlacesActuelles = $billeteriePrivee->maxPlaces;

$form = $this->createForm(BilleteriePriveeType::class, $billeteriePrivee, [
'ticketTypes' => $this->ticketTypeRepository->getAll(),
'is_edit' => true,
]);

$form->handleRequest($request);

$placesPrises = null;

if ($form->isSubmitted()) {
if ($form->isValid()) {
$motDePasse = $form->get('plainMotDePasse')->getData();
if (is_string($motDePasse) && $motDePasse !== '') {
$billeteriePrivee->motDePasse = password_hash($motDePasse, PASSWORD_DEFAULT);
}
}

// Protection du quota : impossible de réduire sous le nombre de places déjà prises.
// Le comptage n'est fait que si le champ maxPlaces a été modifié
$maxPlacesSoumises = $form->get('maxPlaces')->getData();
if (is_int($maxPlacesSoumises) && $maxPlacesSoumises < $maxPlacesActuelles) {
$placesPrises = $this->billeteriePriveeRepository->countPlacesPrisesParToken($billeteriePrivee->token);
if ($maxPlacesSoumises < $placesPrises) {
$form->get('maxPlaces')->addError(new FormError(sprintf(
'%d place(s) déjà prise(s) : le nombre de places ne peut pas être réduit en dessous.',
$placesPrises,
)));
}
}

if ($form->isValid()) {
$this->billeteriePriveeRepository->save($billeteriePrivee);

$this->addFlash('notice', 'La billeterie privée a été modifiée');

return $this->redirectToRoute('admin_event_billeterie_privee', [
'id' => $event->getId(),
]);
}
} else {
// Pas de soumission : on affiche le compteur de places prises
$placesPrises = $this->billeteriePriveeRepository->countPlacesPrisesParToken($billeteriePrivee->token);
}

return $this->render('admin/event/billeterie_privee_edit.html.twig', [
'billeterie_privee' => $billeteriePrivee,
'places_prises' => $placesPrises,
'event' => $event,
'title' => 'Billeteries privées - Modifier',
'form' => $form->createView(),
]);
}
}
Loading
Loading