feat(ReserverController): Améliore et restructure le contrôleur de réservation.
```
This commit is contained in:
Serreau Jovann
2026-01-30 18:22:52 +01:00
parent 3226b81bfb
commit 8b7a740cba

View File

@@ -2,48 +2,34 @@
namespace App\Controller;
use App\Entity\Account;
use App\Entity\AccountResetPasswordRequest;
use App\Entity\Customer;
use App\Entity\CustomerTracking;
use App\Entity\Product;
use App\Entity\ProductReserve;
use App\Entity\SitePerformance;
use App\Form\RequestPasswordConfirmType;
use App\Form\RequestPasswordRequestType;
use App\Logger\AppLogger;
use App\Repository\CustomerRepository;
use App\Repository\CustomerTrackingRepository;
use App\Repository\FormulesRepository;
use App\Repository\ProductRepository;
use App\Repository\ProductReserveRepository;
use App\Service\Mailer\Mailer;
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
use App\Service\ResetPassword\Event\ResetPasswordEvent;
use App\Service\Search\Client;
use Doctrine\ORM\EntityManagerInterface;
use Fkrzski\RobotsTxt\RobotsTxt;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
use Vich\UploaderBundle\Templating\Helper\UploaderHelperInterface;
class ReserverController extends AbstractController
{
#[Route('/robots.txt', name: 'robots_txt', defaults: ['_format' => 'txt'])]
public function index(Request $request): Response
{
@@ -54,16 +40,18 @@ class ReserverController extends AbstractController
$robots->allow('/reservation');
$robots->sitemap($request->getSchemeAndHttpHost().'/seo/sitemap.xml');
return new Response($robots->toString(),Response::HTTP_OK,[
return new Response($robots->toString(), Response::HTTP_OK, [
'Content-Type' => 'text/plain'
]);
}
#[Route('/', name: 'reservation')]
public function revervation(FormulesRepository $formulesRepository,ProductRepository $productRepository): Response
public function revervation(FormulesRepository $formulesRepository, ProductRepository $productRepository): Response
{
$products =$productRepository->findBy(['category'=>'3-15 ans'], ['updatedAt' => 'DESC'],3);
$formules =$formulesRepository->findBy(['isPublish'=>true], ['pos' => 'ASC'],3);
return $this->render('revervation/home.twig',[
$products = $productRepository->findBy(['category' => '3-15 ans'], ['updatedAt' => 'DESC'], 3);
$formules = $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC'], 3);
return $this->render('revervation/home.twig', [
'products' => $products,
'formules' => $formules,
]);
@@ -118,10 +106,7 @@ class ReserverController extends AbstractController
return new Response('Invalid data', Response::HTTP_BAD_REQUEST);
}
// On vérifie si cet ID de métrique existe déjà pour éviter les doublons
// (web-vitals peut renvoyer plusieurs fois la même métrique si elle s'affine)
$existing = $em->getRepository(SitePerformance::class)->findOneBy(['metricId' => $data['id']]);
$perf = $existing ?? new SitePerformance();
$perf->setName($data['name']);
@@ -139,192 +124,79 @@ class ReserverController extends AbstractController
return new Response('', Response::HTTP_NO_CONTENT);
}
#[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])]
public function basketJson(Request $request, ProductRepository $productRepository, UploaderHelper $uploaderHelper): Response
{
$data = json_decode($request->getContent(), true);
$ids = $data['ids'] ?? [];
$startStr = $data['start'] ?? null;
$endStr = $data['end'] ?? null;
// Calcul de la durée
$duration = 1;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
// Différence en jours + 1 car inclusif (ex: du 7 au 7 = 1 jour)
// On s'assure que end >= start
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
#[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])]
public function basketJson(Request $request, ProductRepository $productRepository, UploaderHelper $uploaderHelper): Response
{
$data = json_decode($request->getContent(), true);
$ids = $data['ids'] ?? [];
$startStr = $data['start'] ?? null;
$endStr = $data['end'] ?? null;
// Calcul de la durée
$duration = 1;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
}
// Protection contre les données invalides
if (!is_array($ids)) {
$ids = [];
}
// Récupération des produits
$products = [];
if (!empty($ids)) {
$products = $productRepository->findBy(['id' => $ids]);
}
$items = [];
$totalHT = 0;
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
$tvaRate = $tvaEnabled ? 0.20 : 0;
foreach ($products as $product) {
$price1Day = $product->getPriceDay();
$priceSup = $product->getPriceSup() ?? 0.0;
// Calcul du coût total pour ce produit selon la durée
// 1er jour @ price1Day, jours suivants @ priceSup
$productTotalHT = $price1Day + ($priceSup * max(0, $duration - 1));
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'id' => $product->getId(),
'name' => $product->getName(),
'image' => $uploaderHelper->asset($product, 'imageFile'),
'priceHt1Day' => $price1Day,
'priceHTSupDay' => $priceSup,
'priceTTC1Day' => $price1Day * (1 + $tvaRate),
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
];
$totalHT += $productTotalHT;
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
return new JsonResponse([
'start_date' => $startStr,
'end_date' => $endStr,
'products' => $items,
'total' => [
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC
]
]);
}
if (!is_array($ids)) {
$ids = [];
}
$products = [];
if (!empty($ids)) {
$products = $productRepository->findBy(['id' => $ids]);
}
$items = [];
$totalHT = 0;
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
$tvaRate = $tvaEnabled ? 0.20 : 0;
foreach ($products as $product) {
$price1Day = $product->getPriceDay();
$priceSup = $product->getPriceSup() ?? 0.0;
// Calcul du coût total pour ce produit selon la durée
$productTotalHT = $price1Day + ($priceSup * max(0, $duration - 1));
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'id' => $product->getId(),
'name' => $product->getName(),
'image' => $uploaderHelper->asset($product, 'imageFile'),
'priceHt1Day' => $price1Day,
'priceHTSupDay' => $priceSup,
'priceTTC1Day' => $price1Day * (1 + $tvaRate),
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
];
$totalHT += $productTotalHT;
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
return new JsonResponse([
'start_date' => $startStr,
'end_date' => $endStr,
'products' => $items,
'total' => [
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC
]
]);
}
#[Route('/umami', name: 'reservation_umami', methods: ['POST'])]
public function umami(
Request $request,
@@ -344,18 +216,16 @@ class ReserverController extends AbstractController
return new JsonResponse(['error' => 'No session provided'], Response::HTTP_BAD_REQUEST);
}
// On cherche si un tracking existe déjà pour cet ID Umami
$track = $customerTrackingRepository->findOneBy(['trackId' => $umamiSessionId]);
if (!$track) {
$track = new CustomerTracking();
$track->setTrackId($umamiSessionId);
$track->setCreateAT(new \DateTime()); // Utilise Immutable si possible
$track->setCreateAT(new \DateTime());
$track->setCustomer($user);
$em->persist($track);
} else {
// Si le track existe déjà mais n'était pas lié à l'utilisateur
if ($track->getCustomer() !== $user) {
$track->setCustomer($user);
}
@@ -365,78 +235,71 @@ class ReserverController extends AbstractController
return new JsonResponse(['status' => 'success']);
}
#[Route('/catalogue', name: 'reservation_catalogue')]
public function revervationCatalogue(ProductRepository $productRepository): Response
{
return $this->render('revervation/catalogue.twig',[
return $this->render('revervation/catalogue.twig', [
'products' => $productRepository->findAll(),
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
]);
}
#[Route('/formules', name: 'reservation_formules')]
public function revervationFormules(FormulesRepository $formulesRepository): Response
{
return $this->render('revervation/formules.twig',[
'formules' => $formulesRepository->findBy(['isPublish'=>true],['pos' => 'ASC']),
return $this->render('revervation/formules.twig', [
'formules' => $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC']),
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
]);
}
#[Route('/formules/{slug}', name: 'reservation_formule_show')]
public function revervationView(string $slug,FormulesRepository $formulesRepository): Response
public function revervationView(string $slug, FormulesRepository $formulesRepository): Response
{
$parts = explode('-', $slug);
$realId = $parts[0]; // Récupère le tout premier élément (l'index 0)
$realId = $parts[0];
// 2. Récupération du produit par son ID numérique
$formule = $formulesRepository->find($realId);
if (!$formule) {
throw $this->createNotFoundException('Formules introuvable');
}
return $this->render('revervation/formule/show.twig',[
return $this->render('revervation/formule/show.twig', [
'formule' => $formule,
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
]);
}
#[Route('/comment-reserver', name: 'reservation_workflow')]
public function revervationWorkfkow(): Response
{
return $this->render('revervation/workflow.twig',[
]);
return $this->render('revervation/workflow.twig');
}
#[Route('/options/{id}', name: 'reservation_options_show')]
public function revervationShowOpitons(string $id, ProductRepository $productRepository): Response
{
// TODO: Implement logic
return new Response('Not implemented');
}
#[Route('/produit/{id}', name: 'reservation_product_show')]
public function revervationShowProduct(string $id, ProductRepository $productRepository): Response
{
// 1. Extraction de l'ID (ex: "15-chateau-fort" -> 15)
$parts = explode('-', $id);
$realId = $parts[0]; // Récupère le tout premier élément (l'index 0)
$realId = $parts[0];
// 2. Récupération du produit par son ID numérique
$product = $productRepository->find($realId);
if (!$product) {
throw $this->createNotFoundException('Produit introuvable');
}
// 3. Logique des suggestions (inchangée)
$allInCat = $productRepository->findBy(['category' => $product->getCategory()], [], 5);
$otherProducts = array_filter($allInCat, function($p) use ($product) {
$otherProducts = array_filter($allInCat, function ($p) use ($product) {
return $p->getId() !== $product->getId();
});
@@ -446,20 +309,22 @@ class ReserverController extends AbstractController
'otherProducts' => array_slice($otherProducts, 0, 4)
]);
}
#[Route('/connexion', name: 'reservation_login')]
public function revervationLogin(AuthenticationUtils $authenticationUtils): Response
{
return $this->render('revervation/login.twig',[
return $this->render('revervation/login.twig', [
'last_username' => $authenticationUtils->getLastUsername(),
'error' => $authenticationUtils->getLastAuthenticationError()
]);
}
#[Route('/logout', name: 'reservation_logout')]
public function revervationLogout(): Response
{
return $this->redirectToRoute('reservation');
}
#[Route('/creation-compte', name: 'reservation_register')]
public function revervationRegister(
Request $request,
@@ -467,7 +332,6 @@ class ReserverController extends AbstractController
EntityManagerInterface $em,
UserPasswordHasherInterface $hasher
): Response {
if ($request->isMethod('POST')) {
$payload = $request->getPayload();
@@ -477,22 +341,24 @@ class ReserverController extends AbstractController
$customer->setSurname($payload->getString('surname'));
$customer->setPhone($payload->getString('phone'));
$customer->setCiv($payload->getString('civ'));
$customer->setType($payload->getString('type')); // 'particular' ou 'buisness'
$customer->setType($payload->getString('type'));
if ($customer->getType() === 'buisness') {
$customer->setSiret($payload->getString('siret'));
}
// Hachage du mot de passe
$hashedPassword = $hasher->hashPassword($customer, $payload->getString('password'));
$customer->setPassword($hashedPassword);
$customer->setRoles(['ROLE_USER']);
$mailer->send($customer->getEmail(),
$customer->getName()." ".$customer->getSurname(),
$mailer->send(
$customer->getEmail(),
$customer->getName() . " " . $customer->getSurname(),
"[Ludikevent] - Code de récupération",
"mails/welcome.twig",[
'account' => $customer,
]);
"mails/welcome.twig",
['account' => $customer]
);
$em->persist($customer);
$em->flush();
@@ -502,12 +368,13 @@ class ReserverController extends AbstractController
return $this->render('revervation/register.twig');
}
#[Route('/mot-de-passe', name: 'reservation_password')]
public function forgotPassword(
Request $request,
CustomerRepository $repository,
EntityManagerInterface $em,
Mailer $mailer,
Mailer $mailer,
UserPasswordHasherInterface $hasher
): Response {
$session = $request->getSession();
@@ -524,19 +391,19 @@ class ReserverController extends AbstractController
if ($customer) {
$code = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// On stocke en session : email + code
$session->set('reset_password', [
'email' => $email,
'code' => $code,
'expires' => time() + 900 // Valable 15 minutes
]);
$mailer->send($customer->getEmail(),
$customer->getName()." ".$customer->getSurname(),
"[Ludikevent] - Code de récupération",
"mails/code_password.twig",[
'code' => $code
]);
$mailer->send(
$customer->getEmail(),
$customer->getName() . " " . $customer->getSurname(),
"[Ludikevent] - Code de récupération",
"mails/code_password.twig",
['code' => $code]
);
return $this->redirectToRoute('reservation_password', ['step' => 'verify']);
}
@@ -565,7 +432,7 @@ class ReserverController extends AbstractController
$customer->setPassword($newEncoded);
$em->flush();
$session->remove('reset_password'); // On nettoie la session
$session->remove('reset_password');
$this->addFlash('success', 'Mot de passe mis à jour !');
return $this->redirectToRoute('reservation_login');
}
@@ -578,30 +445,16 @@ class ReserverController extends AbstractController
'email' => $session->get('reset_password')['email'] ?? null
]);
}
#[Route('/contact', name: 'reservation_contact')]
public function revervationContact(Request $request, Mailer $mailer): Response
{
$form = $this->createFormBuilder()
->add('name', TextType::class, [
'label' => 'Nom',
'required' => true,
])
->add('surname', TextType::class, [
'label' => 'Prenom',
'required' => true,
])
->add('email', EmailType::class, [
'label' => 'Email',
'required' => true,
])
->add('phone', TextType::class, [
'label' => 'Telephone',
'required' => true,
])
->add('message', TextareaType::class, [
'label' => 'Message',
'required' => true,
]);
->add('name', TextType::class, ['label' => 'Nom', 'required' => true])
->add('surname', TextType::class, ['label' => 'Prenom', 'required' => true])
->add('email', EmailType::class, ['label' => 'Email', 'required' => true])
->add('phone', TextType::class, ['label' => 'Telephone', 'required' => true])
->add('message', TextareaType::class, ['label' => 'Message', 'required' => true]);
$formObject = $form->getForm();
$formObject->handleRequest($request);
@@ -617,7 +470,6 @@ class ReserverController extends AbstractController
$data
);
// Ajout du message flash de succès
$this->addFlash('success', 'Votre message a bien été envoyé ! Notre équipe vous répondra dans les plus brefs délais.');
return $this->redirectToRoute('reservation_contact');
@@ -627,14 +479,16 @@ class ReserverController extends AbstractController
'form' => $formObject->createView()
]);
}
#[Route('/recherche', name: 'reservation_search')]
public function recherche(UploaderHelper $uploaderHelper,Client $client,Request $request,ProductRepository $productRepository): Response
public function recherche(UploaderHelper $uploaderHelper, Client $client, Request $request, ProductRepository $productRepository): Response
{
$results = $client->search('product',$request->query->get('q',''));
$results = $client->search('product', $request->query->get('q', ''));
$items = [];
foreach ($results['hits'] as $result) {
$p = $productRepository->find($result['id']);
if($p instanceof Product) {
if ($p instanceof Product) {
$items[] = [
'image' => $uploaderHelper->asset($p, 'imageFile') ?: "/provider/images/favicon.png",
"name" => $p->getName(),
@@ -642,38 +496,42 @@ class ReserverController extends AbstractController
"price1day" => $p->getPriceDay(),
"caution" => $p->getCaution(),
"priceSup" => $p->getPriceSup(),
'link' => $this->generateUrl('reservation_product_show',['id'=>$p->slug()]),
'link' => $this->generateUrl('reservation_product_show', ['id' => $p->slug()]),
];
}
}
return $this->render('revervation/search.twig',[
return $this->render('revervation/search.twig', [
'products' => $items
]);
}
#[Route('/mentions-legales', name: 'reservation_mentions-legal')]
public function revervationLegal()
public function revervationLegal(): Response
{
return $this->render('revervation/legal.twig');
}
#[Route('/rgpd', name: 'reservation_rgpd')]
public function revervationRgpd()
public function revervationRgpd(): Response
{
return $this->render('revervation/rgpd.twig');
}
#[Route('/cookies', name: 'reservation_cookies')]
public function revervationCookies()
public function revervationCookies(): Response
{
return $this->render('revervation/cookies.twig');
}
#[Route('/cgv', name: 'reservation_cgv')]
public function revervationCgv()
public function revervationCgv(): Response
{
return $this->render('revervation/cgv.twig');
}
#[Route('/hosting', name: 'reservation_hosting')]
public function revervationHosting()
public function revervationHosting(): Response
{
return $this->render('revervation/hosting.twig');
}