2026-01-19 21:08:04 +01:00
< ? php
namespace App\Controller ;
2026-01-23 09:25:11 +01:00
use App\Entity\Customer ;
2026-01-27 20:24:02 +01:00
use App\Entity\CustomerTracking ;
2026-02-09 14:06:26 +01:00
use App\Entity\Formules ;
2026-01-20 14:31:12 +01:00
use App\Entity\Product ;
2026-01-30 17:58:12 +01:00
use App\Entity\ProductReserve ;
2026-02-09 11:26:52 +01:00
use App\Entity\Promotion ;
2026-01-27 23:36:11 +01:00
use App\Entity\SitePerformance ;
2026-01-23 09:20:53 +01:00
use App\Repository\CustomerRepository ;
2026-01-27 20:24:02 +01:00
use App\Repository\CustomerTrackingRepository ;
2026-01-28 10:00:58 +01:00
use App\Repository\FormulesRepository ;
2026-01-31 13:49:25 +01:00
use App\Repository\OrderSessionRepository ;
2026-02-05 16:04:13 +01:00
use App\Repository\OptionsRepository ;
2026-01-20 13:22:01 +01:00
use App\Repository\ProductRepository ;
2026-01-30 17:58:12 +01:00
use App\Repository\ProductReserveRepository ;
2026-02-09 11:26:52 +01:00
use App\Repository\PromotionRepository ;
2026-01-20 13:22:01 +01:00
use App\Service\Mailer\Mailer ;
2026-01-23 08:43:47 +01:00
use App\Service\Search\Client ;
2026-01-19 21:08:04 +01:00
use Doctrine\ORM\EntityManagerInterface ;
2026-01-20 11:20:28 +01:00
use Fkrzski\RobotsTxt\RobotsTxt ;
2026-01-19 21:08:04 +01:00
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ;
2026-01-20 13:22:01 +01:00
use Symfony\Component\Form\Extension\Core\Type\EmailType ;
use Symfony\Component\Form\Extension\Core\Type\TextareaType ;
use Symfony\Component\Form\Extension\Core\Type\TextType ;
2026-01-19 21:08:04 +01:00
use Symfony\Component\HttpFoundation\JsonResponse ;
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpFoundation\Response ;
2026-02-02 12:14:07 +01:00
use Symfony\Component\HttpKernel\KernelInterface ;
2026-01-19 21:08:04 +01:00
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface ;
use Symfony\Component\Routing\Attribute\Route ;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils ;
2026-01-23 08:43:47 +01:00
use Vich\UploaderBundle\Templating\Helper\UploaderHelper ;
2026-02-02 12:14:07 +01:00
use App\Service\Pdf\DevisPdfService ;
use App\Entity\Devis ;
use App\Entity\DevisLine ;
use App\Entity\CustomerAddress ;
2026-02-04 11:58:07 +01:00
use Symfony\Contracts\HttpClient\HttpClientInterface ;
2026-01-19 21:08:04 +01:00
class ReserverController extends AbstractController
{
2026-02-01 10:28:09 +01:00
private KernelInterface $kernel ;
private ? array $simplifiedCommunes = null ;
public function __construct ( KernelInterface $kernel )
{
$this -> kernel = $kernel ;
}
2026-02-02 12:14:07 +01:00
#[Route('/flow/{sessionId}/devis', name: 'reservation_generate_devis', methods: ['GET'])]
public function generateDevis (
string $sessionId ,
OrderSessionRepository $repository ,
ProductRepository $productRepository ,
2026-02-05 16:04:13 +01:00
OptionsRepository $optionsRepository
2026-02-02 12:14:07 +01:00
) : Response {
$session = $repository -> findOneBy ([ 'uuid' => $sessionId ]);
if ( ! $session ) {
return $this -> redirectToRoute ( 'reservation' );
}
2026-02-05 08:18:29 +01:00
if ( $session -> getState () === 'send' ) {
return $this -> redirectToRoute ( 'reservation_flow_success' , [ 'sessionId' => $sessionId ]);
}
2026-02-02 12:14:07 +01:00
$sessionData = $session -> getProducts ();
$ids = $sessionData [ 'ids' ] ? ? [];
$startStr = $sessionData [ 'start' ] ? ? null ;
$endStr = $sessionData [ 'end' ] ? ? null ;
2026-02-05 16:04:13 +01:00
$duration = $this -> calculateDuration ( $startStr , $endStr );
$dates = $this -> getDatesFromStrings ( $startStr , $endStr );
2026-02-02 12:14:07 +01:00
// Création des objets temporaires pour le PDF
$customer = new Customer ();
$customer -> setName ( $session -> getCustomer () ? $session -> getCustomer () -> getName () : 'Client' );
$customer -> setSurname ( $session -> getCustomer () ? $session -> getCustomer () -> getSurname () : 'Temporaire' );
$customer -> setEmail ( $session -> getCustomer () ? $session -> getCustomer () -> getEmail () : '' );
$customer -> setPhone ( $session -> getCustomer () ? $session -> getCustomer () -> getPhone () : '' );
// Adresse de facturation
$billAddress = new CustomerAddress ();
$billAddress -> setAddress ( $session -> getBillingAddress () ? ? '' );
$billAddress -> setZipcode ( $session -> getBillingZipCode () ? ? '' );
$billAddress -> setCity ( $session -> getBillingTown () ? ? '' );
// Adresse de prestation
$shipAddress = new CustomerAddress ();
$shipAddress -> setAddress ( $session -> getAdressEvent () ? ? '' );
$shipAddress -> setAddress2 ( $session -> getAdress2Event () ? ? '' );
$shipAddress -> setZipcode ( $session -> getZipCodeEvent () ? ? '' );
$shipAddress -> setCity ( $session -> getTownEvent () ? ? '' );
$devis = new Devis ();
$devis -> setCustomer ( $customer );
$devis -> setBillAddress ( $billAddress );
$devis -> setAddressShip ( $shipAddress );
$devis -> setNum ( 'PROVISOIRE' );
2026-02-05 16:04:13 +01:00
$devis -> setStartAt ( $dates [ 'start' ]);
$devis -> setEndAt ( $dates [ 'end' ]);
2026-02-02 12:14:07 +01:00
2026-02-05 08:18:29 +01:00
$selectedOptionsMap = $sessionData [ 'options' ] ? ? [];
2026-02-09 11:48:43 +01:00
$runningTotalHT = 0 ;
2026-02-05 08:18:29 +01:00
2026-02-09 14:06:26 +01:00
$formule = $session -> getFormule ();
$formuleConfig = [];
if ( $formule ) {
$restriction = $formule -> getFormulesRestriction ();
if ( $restriction ) {
$formuleConfig = $restriction -> getRestrictionConfig () ? ? [];
}
$formulaBasePrice = 0 ;
if ( $duration <= 1 ) $formulaBasePrice = $formule -> getPrice1j ();
elseif ( $duration <= 2 ) $formulaBasePrice = $formule -> getPrice2j ();
else $formulaBasePrice = $formule -> getPrice5j ();
$line = new DevisLine ();
$line -> setProduct ( " Formule : " . $formule -> getName ());
$line -> setPriceHt ( $formulaBasePrice );
$line -> setPriceHtSup ( 0 );
$line -> setDay ( 1 );
$devis -> addDevisLine ( $line );
$runningTotalHT += $formulaBasePrice ;
}
2026-02-02 12:14:07 +01:00
if ( ! empty ( $ids )) {
$products = $productRepository -> findBy ([ 'id' => $ids ]);
2026-02-05 08:18:29 +01:00
$processedProductIds = [];
2026-02-02 12:14:07 +01:00
foreach ( $products as $product ) {
2026-02-05 08:18:29 +01:00
$processedProductIds [] = $product -> getId ();
2026-02-09 14:06:26 +01:00
$isInFormule = false ;
if ( $formule ) {
foreach ( $formuleConfig as $c ) {
if (( $c [ 'product' ] ? ? '' ) === $product -> getName ()) {
$isInFormule = true ;
break ;
}
}
}
2026-02-02 12:14:07 +01:00
$line = new DevisLine ();
$line -> setProduct ( $product -> getName ());
2026-02-09 14:06:26 +01:00
if ( $formule && $isInFormule ) {
$line -> setPriceHt ( 0 );
$line -> setPriceHtSup ( 0 );
} else {
$line -> setPriceHt ( $product -> getPriceDay ());
$line -> setPriceHtSup ( $product -> getPriceSup ());
$runningTotalHT += $product -> getPriceDay () + ( $product -> getPriceSup () * max ( 0 , $duration - 1 ));
}
2026-02-02 12:14:07 +01:00
$line -> setDay ( $duration );
$devis -> addDevisLine ( $line );
2026-02-05 08:18:29 +01:00
if ( isset ( $selectedOptionsMap [ $product -> getId ()])) {
$optionIds = $selectedOptionsMap [ $product -> getId ()];
if ( ! empty ( $optionIds )) {
$options = $optionsRepository -> findBy ([ 'id' => $optionIds ]);
foreach ( $options as $option ) {
$lineOpt = new DevisLine ();
$lineOpt -> setProduct ( " Option : " . $option -> getName ());
$lineOpt -> setPriceHt ( $option -> getPriceHt ());
$lineOpt -> setPriceHtSup ( 0 );
$lineOpt -> setDay ( $duration );
$devis -> addDevisLine ( $lineOpt );
2026-02-09 11:48:43 +01:00
$runningTotalHT += $option -> getPriceHt ();
2026-02-05 08:18:29 +01:00
}
}
}
}
foreach ( $selectedOptionsMap as $prodId => $optIds ) {
if ( ! in_array ( $prodId , $processedProductIds ) && ! empty ( $optIds )) {
$options = $optionsRepository -> findBy ([ 'id' => $optIds ]);
foreach ( $options as $option ) {
$lineOpt = new DevisLine ();
$lineOpt -> setProduct ( " Option : " . $option -> getName ());
$lineOpt -> setPriceHt ( $option -> getPriceHt ());
$lineOpt -> setPriceHtSup ( 0 );
$lineOpt -> setDay ( $duration );
$devis -> addDevisLine ( $lineOpt );
2026-02-09 11:48:43 +01:00
$runningTotalHT += $option -> getPriceHt ();
2026-02-05 08:18:29 +01:00
}
}
2026-02-02 12:14:07 +01:00
}
}
2026-02-09 11:48:43 +01:00
$promoData = $session -> getPromotion ();
if ( $promoData && $runningTotalHT > 0 ) {
$discount = $runningTotalHT * ( $promoData [ 'percentage' ] / 100 );
$promoLine = new DevisLine ();
$promoLine -> setProduct ( " Promotion : " . $promoData [ 'name' ] . " (- " . $promoData [ 'percentage' ] . " %) " );
$promoLine -> setPriceHt ( - $discount );
$promoLine -> setPriceHtSup ( 0 );
$promoLine -> setDay ( 1 );
$devis -> addDevisLine ( $promoLine );
}
2026-02-05 16:04:13 +01:00
$pdfService = new DevisPdfService ( $this -> kernel , $devis , $productRepository );
2026-02-02 12:14:07 +01:00
$content = $pdfService -> generate ();
return new Response ( $content , 200 , [
'Content-Type' => 'application/pdf' ,
'Content-Disposition' => 'attachment; filename="devis_provisoire.pdf"'
]);
}
2026-02-01 10:28:09 +01:00
private function loadSimplifiedCommunes () : array
{
if ( $this -> simplifiedCommunes !== null ) {
return $this -> simplifiedCommunes ;
}
$filePath = $this -> kernel -> getProjectDir () . '/public/simplified_communes_by_zip.json' ;
if ( ! file_exists ( $filePath )) {
return [];
}
$content = file_get_contents ( $filePath );
if ( $content === false ) {
return [];
}
$this -> simplifiedCommunes = json_decode ( $content , true );
if ( $this -> simplifiedCommunes === null && json_last_error () !== JSON_ERROR_NONE ) {
return [];
}
return $this -> simplifiedCommunes ;
}
2026-02-05 16:04:13 +01:00
2026-01-20 11:20:28 +01:00
#[Route('/robots.txt', name: 'robots_txt', defaults: ['_format' => 'txt'])]
public function index ( Request $request ) : Response
{
$robots = new RobotsTxt ();
2026-02-06 17:46:30 +01:00
if ( ! file_exists ( $this -> kernel -> getProjectDir () . '/var/.online' )) {
$robots -> disallow ( '/' );
} else {
$robots -> disallow ( '/signature' );
$robots -> disallow ( '/payment' );
$robots -> disallow ( '/paiment' );
$robots -> disallow ( '/intranet' );
$robots -> disallow ( '/crm' );
$robots -> disallow ( '/etl' );
$robots -> crawlDelay ( 60 );
$robots -> allow ( '/reservation' );
$robots -> sitemap ( $request -> getSchemeAndHttpHost () . '/seo/sitemap.xml' );
}
2026-01-20 11:20:28 +01:00
2026-01-30 18:22:52 +01:00
return new Response ( $robots -> toString (), Response :: HTTP_OK , [
2026-01-20 11:20:28 +01:00
'Content-Type' => 'text/plain'
]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/', name: 'reservation')]
2026-01-30 18:22:52 +01:00
public function revervation ( FormulesRepository $formulesRepository , ProductRepository $productRepository ) : Response
2026-01-19 21:08:04 +01:00
{
2026-02-03 14:53:11 +01:00
$products = $productRepository -> findBy ([ 'category' => '3-15 ans' , 'isPublish' => true ], [ 'updatedAt' => 'DESC' ], 3 );
2026-01-30 18:22:52 +01:00
$formules = $formulesRepository -> findBy ([ 'isPublish' => true ], [ 'pos' => 'ASC' ], 3 );
return $this -> render ( 'revervation/home.twig' , [
2026-01-28 10:00:58 +01:00
'products' => $products ,
'formules' => $formules ,
2026-01-20 13:22:01 +01:00
]);
2026-01-20 13:51:23 +01:00
}
2026-01-30 17:58:12 +01:00
#[Route('/produit/check', name: 'produit_check', methods: ['GET', 'POST'])]
public function productCheck ( Request $request , ProductReserveRepository $productReserveRepository , ProductRepository $productRepository ) : Response
{
2026-01-31 13:49:25 +01:00
$productId = $request -> query -> get ( 'id' );
$startStr = $request -> query -> get ( 'start' );
$endStr = $request -> query -> get ( 'end' );
2026-01-30 17:58:12 +01:00
if ( ! $productId && $request -> isMethod ( 'POST' )) {
$payload = $request -> getPayload ();
$productId = $payload -> get ( 'id' );
$startStr = $payload -> get ( 'start' );
$endStr = $payload -> get ( 'end' );
}
if ( ! $productId || ! $startStr || ! $endStr ) {
return new JsonResponse ([ 'error' => 'Missing parameters' ], Response :: HTTP_BAD_REQUEST );
}
$product = $productRepository -> find ( $productId );
if ( ! $product ) {
return new JsonResponse ([ 'error' => 'Product not found' ], Response :: HTTP_NOT_FOUND );
}
try {
$start = new \DateTimeImmutable ( $startStr );
$end = new \DateTimeImmutable ( $endStr );
} catch ( \Exception $e ) {
return new JsonResponse ([ 'error' => 'Invalid date format' ], Response :: HTTP_BAD_REQUEST );
}
$reserve = new ProductReserve ();
$reserve -> setProduct ( $product );
$reserve -> setStartAt ( $start );
$reserve -> setEndAt ( $end );
$isAvailable = $productReserveRepository -> checkAvailability ( $reserve );
return new JsonResponse ([ 'dispo' => $isAvailable ]);
}
2026-02-01 10:28:09 +01:00
#[Route('/produit/check/basket', name: 'produit_check_basket', methods: ['POST'])]
public function productCheckBasket ( Request $request , ProductReserveRepository $productReserveRepository , ProductRepository $productRepository ) : JsonResponse
{
$data = json_decode ( $request -> getContent (), true );
$ids = $data [ 'ids' ] ? ? [];
$startStr = $data [ 'start' ] ? ? null ;
$endStr = $data [ 'end' ] ? ? null ;
if ( ! is_array ( $ids ) || empty ( $ids ) || ! $startStr || ! $endStr ) {
return new JsonResponse ([ 'available' => false , 'message' => 'Missing or invalid parameters' ], Response :: HTTP_BAD_REQUEST );
}
$availability = $this -> _checkProductsAvailability ( $ids , $startStr , $endStr , $productRepository , $productReserveRepository );
if ( ! $availability [ 'allProductsAvailable' ]) {
return new JsonResponse ([
'available' => false ,
'message' => 'Certains produits de votre panier ne sont pas disponibles aux dates sélectionnées.' ,
'unavailable_products_ids' => $availability [ 'unavailableProductIds' ]
]);
}
return new JsonResponse ([ 'available' => true , 'message' => 'Tous les produits sont disponibles.' ]);
}
private function _checkProductsAvailability (
array $ids ,
? string $startStr ,
? string $endStr ,
ProductRepository $productRepository ,
ProductReserveRepository $productReserveRepository
) : array {
$allProductsAvailable = true ;
$unavailableProductIds = [];
if ( empty ( $ids ) || ! $startStr || ! $endStr ) {
return [ 'allProductsAvailable' => false , 'unavailableProductIds' => $ids ];
}
try {
$start = new \DateTimeImmutable ( $startStr );
$end = new \DateTimeImmutable ( $endStr );
} catch ( \Exception $e ) {
return [ 'allProductsAvailable' => false , 'unavailableProductIds' => $ids ];
}
foreach ( $ids as $productId ) {
$product = $productRepository -> find ( $productId );
if ( ! $product ) {
$allProductsAvailable = false ;
$unavailableProductIds [] = $productId ;
continue ;
}
$reserve = new ProductReserve ();
$reserve -> setProduct ( $product );
$reserve -> setStartAt ( $start );
$reserve -> setEndAt ( $end );
$isAvailable = $productReserveRepository -> checkAvailability ( $reserve );
if ( ! $isAvailable ) {
$allProductsAvailable = false ;
$unavailableProductIds [] = $productId ;
}
}
return [ 'allProductsAvailable' => $allProductsAvailable , 'unavailableProductIds' => $unavailableProductIds ];
}
2026-01-28 13:41:31 +01:00
#[Route('/web-vitals', name: 'reservation_web-vitals', methods: ['POST'])]
2026-01-27 23:36:11 +01:00
public function webVitals ( Request $request , EntityManagerInterface $em ) : Response
{
$data = json_decode ( $request -> getContent (), true );
if ( ! $data || ! isset ( $data [ 'name' ], $data [ 'value' ])) {
return new Response ( 'Invalid data' , Response :: HTTP_BAD_REQUEST );
}
$existing = $em -> getRepository ( SitePerformance :: class ) -> findOneBy ([ 'metricId' => $data [ 'id' ]]);
$perf = $existing ? ? new SitePerformance ();
$perf -> setName ( $data [ 'name' ]);
$perf -> setValue (( float ) $data [ 'value' ]);
$perf -> setPath ( $data [ 'path' ] ? ? '/' );
$perf -> setMetricId ( $data [ 'id' ] ? ? null );
2026-01-28 09:20:51 +01:00
$perf -> setCreatedAt ( new \DateTimeImmutable ());
2026-01-27 23:36:11 +01:00
if ( ! $existing ) {
$em -> persist ( $perf );
}
$em -> flush ();
return new Response ( '' , Response :: HTTP_NO_CONTENT );
}
2026-01-27 20:24:02 +01:00
2026-01-30 18:22:52 +01:00
#[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])]
2026-02-05 16:04:13 +01:00
public function basketJson (
Request $request ,
ProductRepository $productRepository ,
OptionsRepository $optionsRepository ,
2026-02-09 11:26:52 +01:00
UploaderHelper $uploaderHelper ,
2026-02-09 14:06:26 +01:00
PromotionRepository $promotionRepository ,
FormulesRepository $formulesRepository
2026-02-05 16:04:13 +01:00
) : Response {
2026-01-30 18:22:52 +01:00
$data = json_decode ( $request -> getContent (), true );
$ids = $data [ 'ids' ] ? ? [];
2026-02-04 11:58:07 +01:00
$selectedOptionsMap = $data [ 'options' ] ? ? [];
2026-01-30 18:22:52 +01:00
$startStr = $data [ 'start' ] ? ? null ;
$endStr = $data [ 'end' ] ? ? null ;
2026-02-05 16:04:13 +01:00
$duration = $this -> calculateDuration ( $startStr , $endStr );
2026-01-30 18:18:49 +01:00
2026-01-30 18:22:52 +01:00
$products = [];
if ( ! empty ( $ids )) {
$products = $productRepository -> findBy ([ 'id' => $ids ]);
}
2026-01-30 18:18:49 +01:00
2026-02-03 14:53:11 +01:00
$foundIds = array_map ( fn ( $p ) => $p -> getId (), $products );
$removedIds = array_values ( array_diff ( $ids , $foundIds ));
2026-02-09 11:26:52 +01:00
$promotions = $promotionRepository -> findActivePromotions ( new \DateTime ());
$promotion = $promotions [ 0 ] ? ? null ;
2026-02-09 14:06:26 +01:00
$formuleId = $data [ 'formule' ] ? ? null ;
$formule = null ;
if ( $formuleId ) {
$formule = $formulesRepository -> find ( $formuleId );
}
$cartData = $this -> buildCartData ( $products , $selectedOptionsMap , $duration , $optionsRepository , $uploaderHelper , $promotion , $formule );
2026-02-04 11:58:07 +01:00
2026-02-05 16:04:13 +01:00
return new JsonResponse ([
'start_date' => $startStr ,
'end_date' => $endStr ,
'products' => $cartData [ 'items' ],
'options' => $cartData [ 'rootOptions' ],
'unavailable_products_ids' => $removedIds ,
2026-02-09 11:26:52 +01:00
'total' => $cartData [ 'total' ],
2026-02-09 14:06:26 +01:00
'promotion' => $promotion ? [ 'name' => $promotion -> getName (), 'percentage' => $promotion -> getPercentage ()] : null ,
'formule' => $formule ? [ 'name' => $formule -> getName ()] : null
2026-02-05 16:04:13 +01:00
]);
}
2026-01-30 18:22:52 +01:00
2026-01-31 14:17:34 +01:00
#[Route('/session', name: 'reservation_session_create', methods: ['POST'])]
2026-02-09 14:06:26 +01:00
public function createSession ( Request $request , EntityManagerInterface $em , OrderSessionRepository $sessionRepository , PromotionRepository $promotionRepository , FormulesRepository $formulesRepository , ProductRepository $productRepository ) : Response
2026-01-31 13:49:25 +01:00
{
$data = json_decode ( $request -> getContent (), true );
$existingUuid = $request -> getSession () -> get ( 'order_session_uuid' );
$session = null ;
if ( $existingUuid ) {
$session = $sessionRepository -> findOneBy ([ 'uuid' => $existingUuid ]);
}
if ( ! $session ) {
$session = new \App\Entity\OrderSession ();
$session -> setUuid ( \Symfony\Component\Uid\Uuid :: v4 () -> toRfc4122 ());
$session -> setState ( 'created' );
}
2026-02-09 07:57:43 +01:00
// Save promos along with other data
$sessionData = $data ? ? [];
// Ensure promos key exists if sent (it should be in $data if frontend sends it)
$session -> setProducts ( $sessionData );
2026-01-31 13:49:25 +01:00
2026-02-09 11:26:52 +01:00
$promotions = $promotionRepository -> findActivePromotions ( new \DateTime ());
$promotion = $promotions [ 0 ] ? ? null ;
if ( $promotion ) {
$session -> setPromotion ([ 'name' => $promotion -> getName (), 'percentage' => $promotion -> getPercentage ()]);
} else {
$session -> setPromotion ( null );
}
2026-02-09 14:06:26 +01:00
$formuleId = $data [ 'formule' ] ? ? null ;
if ( $formuleId ) {
$formule = $formulesRepository -> find ( $formuleId );
if ( $formule ) {
// --- DURATION CHECK ---
$startStr = $data [ 'start' ] ? ? null ;
$endStr = $data [ 'end' ] ? ? null ;
$duration = $this -> calculateDuration ( $startStr , $endStr );
if ( $duration > 5 ) {
return new JsonResponse ([ 'error' => " Impossible d'ajouter cette formule : la durée de réservation excède 5 jours. " ], Response :: HTTP_BAD_REQUEST );
}
// --- SECURITY CHECK ---
$restriction = $formule -> getFormulesRestriction ();
if ( $restriction ) {
$ids = $data [ 'ids' ] ? ? [];
if ( ! empty ( $ids )) {
$products = $productRepository -> findBy ([ 'id' => $ids ]);
$config = $restriction -> getRestrictionConfig () ? ? [];
$counts = [ 'structure' => 0 , 'alimentaire' => 0 , 'barhnums' => 0 ];
foreach ( $products as $product ) {
$pName = $product -> getName ();
$type = null ;
foreach ( $config as $c ) {
if (( $c [ 'product' ] ? ? '' ) === $pName ) {
$type = $c [ 'type' ] ? ? null ;
break ;
}
}
if ( $type === 'structure' ) {
$counts [ 'structure' ] ++ ;
} elseif ( $type === 'alimentaire' ) {
$counts [ 'alimentaire' ] ++ ;
} elseif ( $type ) {
// Only count if type is defined in config (meaning it's part of the formula)
$counts [ 'barhnums' ] ++ ;
}
// Products not in the formula config are considered "extras" and allowed.
}
if ( $counts [ 'structure' ] > $restriction -> getNbStructureMax ()) {
return new JsonResponse ([ 'error' => " Le nombre maximum de structures est dépassé ( { $restriction -> getNbStructureMax () } ). " ], Response :: HTTP_BAD_REQUEST );
}
if ( $counts [ 'alimentaire' ] > $restriction -> getNbAlimentaireMax ()) {
return new JsonResponse ([ 'error' => " Le nombre maximum d'éléments alimentaires est dépassé ( { $restriction -> getNbAlimentaireMax () } ). " ], Response :: HTTP_BAD_REQUEST );
}
if ( $counts [ 'barhnums' ] > $restriction -> getNbBarhumsMax ()) {
return new JsonResponse ([ 'error' => " Le nombre maximum de barnums/mobilier est dépassé ( { $restriction -> getNbBarhumsMax () } ). " ], Response :: HTTP_BAD_REQUEST );
}
}
}
$session -> setFormule ( $formule );
}
}
2026-01-31 13:49:25 +01:00
$user = $this -> getUser ();
if ( $user instanceof Customer ) {
$session -> setCustomer ( $user );
}
$em -> persist ( $session );
$em -> flush ();
$request -> getSession () -> set ( 'order_session_uuid' , $session -> getUuid ());
return new JsonResponse ([
'flowUrl' => $this -> generateUrl ( 'reservation_flow' , [ 'sessionId' => $session -> getUuid ()])
]);
}
2026-01-31 15:36:53 +01:00
#[Route('/flow/{sessionId}/confirmed', name: 'reservation_flow_confirmed', methods: ['GET', 'POST'])]
public function flowConfirmed (
string $sessionId ,
OrderSessionRepository $repository ,
ProductRepository $productRepository ,
2026-02-01 10:28:09 +01:00
UploaderHelper $uploaderHelper ,
2026-02-03 14:53:11 +01:00
ProductReserveRepository $productReserveRepository ,
2026-02-04 11:58:07 +01:00
EntityManagerInterface $em ,
2026-02-05 16:04:13 +01:00
OptionsRepository $optionsRepository ,
2026-02-05 08:18:29 +01:00
HttpClientInterface $client ,
Request $request ,
Mailer $mailer
2026-01-31 15:36:53 +01:00
) : Response {
$session = $repository -> findOneBy ([ 'uuid' => $sessionId ]);
if ( ! $session ) {
return $this -> render ( 'revervation/session_lost.twig' );
}
2026-02-05 08:18:29 +01:00
if ( $session -> getState () === 'send' ) {
return $this -> redirectToRoute ( 'reservation_flow_success' , [ 'sessionId' => $sessionId ]);
}
if ( $request -> isMethod ( 'POST' )) {
$mailer -> send (
'contact@ludikevent.fr' ,
" Ludikevent " ,
" [Ludikevent] - Nouvelle demande de réservation " ,
" mails/reserve/confirmation.twig " ,
[ 'session' => $session ]
);
$session -> setState ( 'send' );
$em -> flush ();
$request -> getSession () -> remove ( 'order_session_uuid' );
return $this -> redirectToRoute ( 'reservation_flow_success' , [ 'sessionId' => $sessionId ]);
}
2026-02-01 10:28:09 +01:00
$sessionData = $session -> getProducts ();
$ids = $sessionData [ 'ids' ] ? ? [];
2026-02-04 11:58:07 +01:00
$selectedOptionsMap = $sessionData [ 'options' ] ? ? [];
2026-02-01 10:28:09 +01:00
$startStr = $sessionData [ 'start' ] ? ? null ;
$endStr = $sessionData [ 'end' ] ? ? null ;
// Check product availability
$availability = $this -> _checkProductsAvailability ( $ids , $startStr , $endStr , $productRepository , $productReserveRepository );
if ( ! $availability [ 'allProductsAvailable' ]) {
$this -> addFlash ( 'danger' , 'Certains produits de votre panier ne sont plus disponibles. Veuillez vérifier votre réservation.' );
return $this -> redirectToRoute ( 'reservation_flow' , [ 'sessionId' => $sessionId ]);
}
2026-02-05 16:04:13 +01:00
$duration = $this -> calculateDuration ( $startStr , $endStr );
2026-02-01 10:28:09 +01:00
$products = [];
if ( ! empty ( $ids )) {
$products = $productRepository -> findBy ([ 'id' => $ids ]);
}
2026-02-03 14:53:11 +01:00
// Cleanup missing products from session
$foundIds = array_map ( fn ( $p ) => $p -> getId (), $products );
if ( count ( $foundIds ) !== count ( $ids )) {
$sessionData [ 'ids' ] = $foundIds ;
$session -> setProducts ( $sessionData );
$em -> flush ();
}
2026-02-09 11:26:52 +01:00
$promoData = $session -> getPromotion ();
$promotion = null ;
if ( $promoData ) {
$promotion = new Promotion ();
$promotion -> setName ( $promoData [ 'name' ]);
$promotion -> setPercentage ( $promoData [ 'percentage' ]);
}
2026-02-09 14:06:26 +01:00
$formule = $session -> getFormule ();
$cartData = $this -> buildCartData ( $products , $selectedOptionsMap , $duration , $optionsRepository , $uploaderHelper , $promotion , $formule );
2026-02-01 10:28:09 +01:00
2026-02-05 08:18:29 +01:00
// --- Calcul Frais de Livraison ---
2026-02-05 16:04:13 +01:00
$deliveryData = $this -> calculateDelivery (
$session -> getAdressEvent (),
$session -> getZipCodeEvent (),
$session -> getTownEvent (),
$client
);
2026-02-05 08:18:29 +01:00
2026-02-01 10:28:09 +01:00
return $this -> render ( 'revervation/flow_confirmed.twig' , [
'session' => $session ,
'cart' => [
2026-02-05 16:04:13 +01:00
'items' => $cartData [ 'items' ],
'options' => $cartData [ 'rootOptions' ],
2026-02-01 10:28:09 +01:00
'startDate' => $startStr ? new \DateTimeImmutable ( $startStr ) : null ,
'endDate' => $endStr ? new \DateTimeImmutable ( $endStr ) : null ,
'duration' => $duration ,
2026-02-05 16:04:13 +01:00
'totalHT' => $cartData [ 'total' ][ 'totalHT' ],
'totalTva' => $cartData [ 'total' ][ 'totalTva' ],
'totalTTC' => $cartData [ 'total' ][ 'totalTTC' ],
2026-02-09 11:26:52 +01:00
'discount' => $cartData [ 'total' ][ 'discount' ],
'promotion' => $cartData [ 'total' ][ 'promotion' ],
2026-02-09 14:06:26 +01:00
'formule' => $cartData [ 'total' ][ 'formule' ],
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $cartData [ 'tvaEnabled' ],
2026-02-05 08:18:29 +01:00
],
2026-02-05 16:04:13 +01:00
'delivery' => $deliveryData
2026-02-01 10:28:09 +01:00
]);
2026-01-31 15:36:53 +01:00
}
2026-02-05 08:18:29 +01:00
#[Route('/flow/{sessionId}/success', name: 'reservation_flow_success', methods: ['GET'])]
public function flowSuccess ( string $sessionId , OrderSessionRepository $repository ) : Response
{
$session = $repository -> findOneBy ([ 'uuid' => $sessionId ]);
if ( ! $session ) {
return $this -> redirectToRoute ( 'reservation' );
}
if ( $session -> getState () !== 'send' ) {
return $this -> redirectToRoute ( 'reservation_flow' , [ 'sessionId' => $sessionId ]);
}
return $this -> render ( 'revervation/success.twig' );
}
2026-01-31 13:49:25 +01:00
#[Route('/flow/{sessionId}', name: 'reservation_flow', methods: ['GET', 'POST'])]
public function flowLogin (
string $sessionId ,
AuthenticationUtils $authenticationUtils ,
OrderSessionRepository $repository ,
ProductRepository $productRepository ,
2026-02-01 10:28:09 +01:00
UploaderHelper $uploaderHelper ,
2026-02-05 08:18:29 +01:00
ProductReserveRepository $productReserveRepository ,
2026-02-05 16:04:13 +01:00
OptionsRepository $optionsRepository
2026-01-31 13:49:25 +01:00
) : Response {
$session = $repository -> findOneBy ([ 'uuid' => $sessionId ]);
if ( ! $session ) {
return $this -> render ( 'revervation/session_lost.twig' );
}
2026-02-05 08:18:29 +01:00
if ( $session -> getState () === 'send' ) {
return $this -> redirectToRoute ( 'reservation_flow_success' , [ 'sessionId' => $sessionId ]);
}
2026-01-31 13:49:25 +01:00
$sessionData = $session -> getProducts ();
$ids = $sessionData [ 'ids' ] ? ? [];
2026-02-05 08:18:29 +01:00
$selectedOptionsMap = $sessionData [ 'options' ] ? ? [];
2026-01-31 13:49:25 +01:00
$startStr = $sessionData [ 'start' ] ? ? null ;
$endStr = $sessionData [ 'end' ] ? ? null ;
2026-02-01 10:28:09 +01:00
// Check product availability
$availability = $this -> _checkProductsAvailability ( $ids , $startStr , $endStr , $productRepository , $productReserveRepository );
if ( ! $availability [ 'allProductsAvailable' ]) {
$this -> addFlash ( 'danger' , 'Certains produits de votre panier ne sont plus disponibles. Veuillez vérifier votre sélection.' );
return $this -> redirectToRoute ( 'reservation' );
}
2026-02-05 16:04:13 +01:00
$duration = $this -> calculateDuration ( $startStr , $endStr );
2026-01-31 13:49:25 +01:00
$products = [];
if ( ! empty ( $ids )) {
$products = $productRepository -> findBy ([ 'id' => $ids ]);
}
2026-02-03 14:53:11 +01:00
// Cleanup missing products from session
$foundIds = array_map ( fn ( $p ) => $p -> getId (), $products );
if ( count ( $foundIds ) !== count ( $ids )) {
$sessionData [ 'ids' ] = $foundIds ;
$session -> setProducts ( $sessionData );
2026-02-05 16:04:13 +01:00
// We should probably flush, but no EntityManager injected here in original code for GET/POST mix.
// Added simple cleanup.
2026-02-03 14:53:11 +01:00
}
2026-02-09 11:26:52 +01:00
$promoData = $session -> getPromotion ();
$promotion = null ;
if ( $promoData ) {
$promotion = new Promotion ();
$promotion -> setName ( $promoData [ 'name' ]);
$promotion -> setPercentage ( $promoData [ 'percentage' ]);
}
2026-02-09 14:06:26 +01:00
$formule = $session -> getFormule ();
$cartData = $this -> buildCartData ( $products , $selectedOptionsMap , $duration , $optionsRepository , $uploaderHelper , $promotion , $formule );
2026-01-31 13:49:25 +01:00
return $this -> render ( 'revervation/flow.twig' , [
'session' => $session ,
'last_username' => $authenticationUtils -> getLastUsername (),
'error' => $authenticationUtils -> getLastAuthenticationError (),
'cart' => [
2026-02-05 16:04:13 +01:00
'items' => $cartData [ 'items' ],
'options' => $cartData [ 'rootOptions' ],
2026-01-31 13:49:25 +01:00
'startDate' => $startStr ? new \DateTimeImmutable ( $startStr ) : null ,
'endDate' => $endStr ? new \DateTimeImmutable ( $endStr ) : null ,
'duration' => $duration ,
2026-02-05 16:04:13 +01:00
'totalHT' => $cartData [ 'total' ][ 'totalHT' ],
'totalTva' => $cartData [ 'total' ][ 'totalTva' ],
'totalTTC' => $cartData [ 'total' ][ 'totalTTC' ],
2026-02-09 11:26:52 +01:00
'discount' => $cartData [ 'total' ][ 'discount' ],
'promotion' => $cartData [ 'total' ][ 'promotion' ],
2026-02-09 14:06:26 +01:00
'formule' => $cartData [ 'total' ][ 'formule' ],
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $cartData [ 'tvaEnabled' ],
2026-01-31 13:49:25 +01:00
]
]);
}
#[Route('/flow/{sessionId}/update', name: 'reservation_flow_update', methods: ['POST'])]
public function flowUpdate (
string $sessionId ,
Request $request ,
OrderSessionRepository $repository ,
EntityManagerInterface $em
) : Response {
$session = $repository -> findOneBy ([ 'uuid' => $sessionId ]);
if ( ! $session ) {
return $this -> redirectToRoute ( 'reservation' );
}
2026-02-05 08:18:29 +01:00
if ( $session -> getState () === 'send' ) {
return $this -> redirectToRoute ( 'reservation_flow_success' , [ 'sessionId' => $sessionId ]);
}
2026-01-31 13:49:25 +01:00
$session -> setBillingAddress ( $request -> request -> get ( 'billingAddress' ));
$session -> setBillingZipCode ( $request -> request -> get ( 'billingZipCode' ));
$session -> setBillingTown ( $request -> request -> get ( 'billingTown' ));
$session -> setAdressEvent ( $request -> request -> get ( 'adressEvent' ));
$session -> setAdress2Event ( $request -> request -> get ( 'adress2Event' ));
$session -> setZipCodeEvent ( $request -> request -> get ( 'zipCodeEvent' ));
$session -> setTownEvent ( $request -> request -> get ( 'townEvent' ));
$session -> setType ( $request -> request -> get ( 'type' ));
$session -> setDetails ( $request -> request -> get ( 'details' ));
$session -> setTypeSol ( $request -> request -> get ( 'typeSol' ));
$session -> setPente ( $request -> request -> get ( 'pente' ));
$session -> setAccess ( $request -> request -> get ( 'access' ));
$distance = $request -> request -> get ( 'distancePower' );
if ( $distance !== null && $distance !== '' ) {
$session -> setDistancePower (( float ) $distance );
}
$em -> flush ();
2026-01-31 15:36:53 +01:00
return $this -> redirectToRoute ( 'reservation_flow_confirmed' , [ 'sessionId' => $sessionId ]);
2026-01-31 13:49:25 +01:00
}
2026-01-28 13:41:31 +01:00
#[Route('/umami', name: 'reservation_umami', methods: ['POST'])]
2026-01-27 20:24:02 +01:00
public function umami (
Request $request ,
CustomerTrackingRepository $customerTrackingRepository ,
EntityManagerInterface $em
) : Response {
/** @var Customer $user */
$user = $this -> getUser ();
if ( ! $user ) {
return new JsonResponse ([ 'error' => 'User not found' ], Response :: HTTP_UNAUTHORIZED );
}
$data = json_decode ( $request -> getContent (), true );
$umamiSessionId = $data [ 'umami_session' ] ? ? null ;
if ( ! $umamiSessionId ) {
return new JsonResponse ([ 'error' => 'No session provided' ], Response :: HTTP_BAD_REQUEST );
}
$track = $customerTrackingRepository -> findOneBy ([ 'trackId' => $umamiSessionId ]);
if ( ! $track ) {
$track = new CustomerTracking ();
$track -> setTrackId ( $umamiSessionId );
2026-01-30 18:22:52 +01:00
$track -> setCreateAT ( new \DateTime ());
2026-01-27 20:24:02 +01:00
$track -> setCustomer ( $user );
$em -> persist ( $track );
} else {
if ( $track -> getCustomer () !== $user ) {
$track -> setCustomer ( $user );
}
}
$em -> flush ();
return new JsonResponse ([ 'status' => 'success' ]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/catalogue', name: 'reservation_catalogue')]
2026-01-20 13:51:23 +01:00
public function revervationCatalogue ( ProductRepository $productRepository ) : Response
{
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/catalogue.twig' , [
2026-02-03 14:53:11 +01:00
'products' => $productRepository -> findBy ([ 'isPublish' => true ]),
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $this -> isTvaEnabled (),
2026-01-20 13:51:23 +01:00
]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/formules', name: 'reservation_formules')]
2026-01-28 11:55:38 +01:00
public function revervationFormules ( FormulesRepository $formulesRepository ) : Response
2026-01-27 22:43:36 +01:00
{
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/formules.twig' , [
'formules' => $formulesRepository -> findBy ([ 'isPublish' => true ], [ 'pos' => 'ASC' ]),
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $this -> isTvaEnabled (),
2026-01-27 22:43:36 +01:00
]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/formules/{slug}', name: 'reservation_formule_show')]
2026-01-30 18:22:52 +01:00
public function revervationView ( string $slug , FormulesRepository $formulesRepository ) : Response
2026-01-28 10:00:58 +01:00
{
2026-01-28 11:55:38 +01:00
$parts = explode ( '-' , $slug );
2026-01-30 18:22:52 +01:00
$realId = $parts [ 0 ];
2026-01-28 10:00:58 +01:00
2026-01-28 11:55:38 +01:00
$formule = $formulesRepository -> find ( $realId );
if ( ! $formule ) {
throw $this -> createNotFoundException ( 'Formules introuvable' );
}
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/formule/show.twig' , [
2026-01-30 09:13:01 +01:00
'formule' => $formule ,
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $this -> isTvaEnabled (),
2026-01-28 10:00:58 +01:00
]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/comment-reserver', name: 'reservation_workflow')]
2026-01-21 13:54:50 +01:00
public function revervationWorkfkow () : Response
{
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/workflow.twig' );
2026-01-21 13:54:50 +01:00
}
2026-01-22 09:27:22 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/options/{id}', name: 'reservation_options_show')]
2026-01-22 09:27:22 +01:00
public function revervationShowOpitons ( string $id , ProductRepository $productRepository ) : Response
{
2026-01-30 18:22:52 +01:00
// TODO: Implement logic
return new Response ( 'Not implemented' );
2026-01-22 09:27:22 +01:00
}
2026-01-28 13:41:31 +01:00
#[Route('/produit/{id}', name: 'reservation_product_show')]
2026-02-09 11:26:52 +01:00
public function revervationShowProduct ( string $id , ProductRepository $productRepository , PromotionRepository $promotionRepository ) : Response
2026-01-20 13:51:23 +01:00
{
2026-01-20 14:31:12 +01:00
$parts = explode ( '-' , $id );
2026-01-30 18:22:52 +01:00
$realId = $parts [ 0 ];
2026-01-20 13:51:23 +01:00
2026-01-20 14:31:12 +01:00
$product = $productRepository -> find ( $realId );
2026-01-20 13:51:23 +01:00
2026-01-20 14:31:12 +01:00
if ( ! $product ) {
throw $this -> createNotFoundException ( 'Produit introuvable' );
}
$allInCat = $productRepository -> findBy ([ 'category' => $product -> getCategory ()], [], 5 );
2026-01-30 18:22:52 +01:00
$otherProducts = array_filter ( $allInCat , function ( $p ) use ( $product ) {
2026-01-20 14:31:12 +01:00
return $p -> getId () !== $product -> getId ();
});
2026-02-09 11:26:52 +01:00
$promotions = $promotionRepository -> findActivePromotions ( new \DateTime ());
$promotion = $promotions [ 0 ] ? ? null ;
2026-01-20 14:31:12 +01:00
return $this -> render ( 'revervation/produit.twig' , [
'product' => $product ,
2026-02-05 16:04:13 +01:00
'tvaEnabled' => $this -> isTvaEnabled (),
2026-02-09 11:26:52 +01:00
'otherProducts' => array_slice ( $otherProducts , 0 , 4 ),
'promotion' => $promotion
2026-01-20 14:31:12 +01:00
]);
2026-01-19 21:08:04 +01:00
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/connexion', name: 'reservation_login')]
2026-01-23 08:43:47 +01:00
public function revervationLogin ( AuthenticationUtils $authenticationUtils ) : Response
2026-01-22 21:16:29 +01:00
{
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/login.twig' , [
2026-01-23 08:43:47 +01:00
'last_username' => $authenticationUtils -> getLastUsername (),
'error' => $authenticationUtils -> getLastAuthenticationError ()
]);
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/logout', name: 'reservation_logout')]
2026-01-23 09:15:15 +01:00
public function revervationLogout () : Response
{
return $this -> redirectToRoute ( 'reservation' );
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/creation-compte', name: 'reservation_register')]
2026-01-23 09:25:11 +01:00
public function revervationRegister (
Request $request ,
Mailer $mailer ,
EntityManagerInterface $em ,
UserPasswordHasherInterface $hasher
) : Response {
if ( $request -> isMethod ( 'POST' )) {
$payload = $request -> getPayload ();
2026-01-23 08:43:47 +01:00
2026-01-23 09:25:11 +01:00
$customer = new Customer ();
$customer -> setEmail ( $payload -> getString ( 'email' ));
$customer -> setName ( $payload -> getString ( 'name' ));
$customer -> setSurname ( $payload -> getString ( 'surname' ));
$customer -> setPhone ( $payload -> getString ( 'phone' ));
$customer -> setCiv ( $payload -> getString ( 'civ' ));
2026-01-30 18:22:52 +01:00
$customer -> setType ( $payload -> getString ( 'type' ));
2026-01-23 09:25:11 +01:00
if ( $customer -> getType () === 'buisness' ) {
$customer -> setSiret ( $payload -> getString ( 'siret' ));
2026-02-05 08:18:29 +01:00
$customer -> setRaisonSocial ( $payload -> getString ( 'raisonSocial' ));
$customer -> setTypCompany ( $payload -> getString ( 'typCompany' ));
2026-01-23 09:25:11 +01:00
}
$hashedPassword = $hasher -> hashPassword ( $customer , $payload -> getString ( 'password' ));
$customer -> setPassword ( $hashedPassword );
$customer -> setRoles ([ 'ROLE_USER' ]);
2026-01-30 18:22:52 +01:00
$mailer -> send (
$customer -> getEmail (),
$customer -> getName () . " " . $customer -> getSurname (),
2026-01-23 09:25:11 +01:00
" [Ludikevent] - Code de récupération " ,
2026-01-30 18:22:52 +01:00
" mails/welcome.twig " ,
2026-02-03 14:53:11 +01:00
[ 'customer' => $customer ]
2026-01-30 18:22:52 +01:00
);
2026-01-23 09:25:11 +01:00
$em -> persist ( $customer );
$em -> flush ();
$this -> addFlash ( 'success' , 'Votre compte a été créé avec succès ! Connectez-vous.' );
return $this -> redirectToRoute ( 'reservation_login' );
}
return $this -> render ( 'revervation/register.twig' );
2026-01-23 08:43:47 +01:00
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/mot-de-passe', name: 'reservation_password')]
2026-01-23 09:20:53 +01:00
public function forgotPassword (
Request $request ,
CustomerRepository $repository ,
EntityManagerInterface $em ,
2026-01-30 18:22:52 +01:00
Mailer $mailer ,
2026-01-23 09:20:53 +01:00
UserPasswordHasherInterface $hasher
) : Response {
$session = $request -> getSession ();
$step = $request -> query -> get ( 'step' , 'request' );
if ( $request -> isMethod ( 'POST' )) {
$payload = $request -> getPayload ();
// ÉTAPE 1 : Générer le code et l'envoyer
if ( $payload -> has ( 'email_request' )) {
$email = $payload -> getString ( 'email_request' );
$customer = $repository -> findOneBy ([ 'email' => $email ]);
if ( $customer ) {
$code = str_pad (( string ) random_int ( 0 , 999999 ), 6 , '0' , STR_PAD_LEFT );
$session -> set ( 'reset_password' , [
'email' => $email ,
'code' => $code ,
'expires' => time () + 900 // Valable 15 minutes
]);
2026-01-30 18:22:52 +01:00
$mailer -> send (
$customer -> getEmail (),
$customer -> getName () . " " . $customer -> getSurname (),
" [Ludikevent] - Code de récupération " ,
" mails/code_password.twig " ,
[ 'code' => $code ]
);
2026-01-23 09:20:53 +01:00
return $this -> redirectToRoute ( 'reservation_password' , [ 'step' => 'verify' ]);
}
$this -> addFlash ( 'danger' , 'Email inconnu.' );
}
// ÉTAPE 2 : Vérifier le code en session
if ( $payload -> has ( 'code_verify' )) {
$data = $session -> get ( 'reset_password' );
$inputCode = $payload -> getString ( 'code_verify' );
if ( $data && $data [ 'code' ] === $inputCode && time () < $data [ 'expires' ]) {
return $this -> redirectToRoute ( 'reservation_password' , [ 'step' => 'reset' ]);
}
$this -> addFlash ( 'danger' , 'Code invalide ou expiré.' );
}
// ÉTAPE 3 : Changer le mot de passe
if ( $payload -> has ( 'new_password' )) {
$data = $session -> get ( 'reset_password' );
if ( $data ) {
$customer = $repository -> findOneBy ([ 'email' => $data [ 'email' ]]);
if ( $customer ) {
$newEncoded = $hasher -> hashPassword ( $customer , $payload -> getString ( 'new_password' ));
$customer -> setPassword ( $newEncoded );
$em -> flush ();
2026-01-30 18:22:52 +01:00
$session -> remove ( 'reset_password' );
2026-01-23 09:20:53 +01:00
$this -> addFlash ( 'success' , 'Mot de passe mis à jour !' );
return $this -> redirectToRoute ( 'reservation_login' );
}
}
}
}
2026-01-22 23:25:35 +01:00
2026-01-23 09:20:53 +01:00
return $this -> render ( 'reservation/password.twig' , [
'step' => $step ,
'email' => $session -> get ( 'reset_password' )[ 'email' ] ? ? null
2026-01-22 23:25:35 +01:00
]);
2026-01-22 21:16:29 +01:00
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/contact', name: 'reservation_contact')]
2026-01-20 13:22:01 +01:00
public function revervationContact ( Request $request , Mailer $mailer ) : Response
2026-01-19 21:08:04 +01:00
{
2026-01-20 13:22:01 +01:00
$form = $this -> createFormBuilder ()
2026-01-30 18:22:52 +01:00
-> 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 ]);
2026-01-20 13:22:01 +01:00
$formObject = $form -> getForm ();
$formObject -> handleRequest ( $request );
if ( $formObject -> isSubmitted () && $formObject -> isValid ()) {
$data = $formObject -> getData ();
$mailer -> send (
'lilian@ludikevent.fr' ,
" Ludikevent " ,
" [Ludikevent] - Demande de contact via la plateforme de reservation " ,
" mails/reserve/contact.twig " ,
$data
);
$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' );
}
return $this -> render ( 'revervation/contact.twig' , [
'form' => $formObject -> createView ()
]);
2026-01-19 21:08:04 +01:00
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/recherche', name: 'reservation_search')]
2026-01-30 18:22:52 +01:00
public function recherche ( UploaderHelper $uploaderHelper , Client $client , Request $request , ProductRepository $productRepository ) : Response
2026-01-19 21:08:04 +01:00
{
2026-01-30 18:22:52 +01:00
$results = $client -> search ( 'product' , $request -> query -> get ( 'q' , '' ));
2026-01-23 08:43:47 +01:00
$items = [];
2026-01-30 18:22:52 +01:00
2026-01-23 08:43:47 +01:00
foreach ( $results [ 'hits' ] as $result ) {
$p = $productRepository -> find ( $result [ 'id' ]);
2026-02-03 14:53:11 +01:00
if ( $p instanceof Product && $p -> isPublish ()) {
2026-01-23 08:43:47 +01:00
$items [] = [
'image' => $uploaderHelper -> asset ( $p , 'imageFile' ) ? : " /provider/images/favicon.png " ,
" name " => $p -> getName (),
" price " => $p -> getPriceDay (),
" price1day " => $p -> getPriceDay (),
" caution " => $p -> getCaution (),
" priceSup " => $p -> getPriceSup (),
2026-01-30 18:22:52 +01:00
'link' => $this -> generateUrl ( 'reservation_product_show' , [ 'id' => $p -> slug ()]),
2026-01-23 08:43:47 +01:00
];
}
}
2026-01-30 18:22:52 +01:00
return $this -> render ( 'revervation/search.twig' , [
2026-01-23 08:43:47 +01:00
'products' => $items
]);
2026-01-19 21:08:04 +01:00
}
2026-01-28 13:41:31 +01:00
#[Route('/mentions-legales', name: 'reservation_mentions-legal')]
2026-01-30 18:22:52 +01:00
public function revervationLegal () : Response
2026-01-19 21:08:04 +01:00
{
return $this -> render ( 'revervation/legal.twig' );
}
2026-01-30 18:22:52 +01:00
2026-02-01 10:28:09 +01:00
#[Route('/cities/lookup', name: 'api_cities_lookup', methods: ['POST'])]
public function getCityByZipCode ( Request $request ) : JsonResponse
{
$data = json_decode ( $request -> getContent (), true );
$zipCode = $data [ 'zipCode' ] ? ? null ;
if ( ! $zipCode ) {
return new JsonResponse ([ 'error' => 'Missing zipCode parameter' ], Response :: HTTP_BAD_REQUEST );
}
$simplifiedCommunes = $this -> loadSimplifiedCommunes ();
$cities = $simplifiedCommunes [ $zipCode ] ? ? [];
if ( ! empty ( $cities )) {
return new JsonResponse ([ 'cities' => $cities ]);
}
return new JsonResponse ([ 'cities' => [], 'message' => 'City not found for this zip code' ], Response :: HTTP_NOT_FOUND );
}
2026-01-28 13:41:31 +01:00
#[Route('/rgpd', name: 'reservation_rgpd')]
2026-01-30 18:22:52 +01:00
public function revervationRgpd () : Response
2026-01-19 21:08:04 +01:00
{
return $this -> render ( 'revervation/rgpd.twig' );
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/cookies', name: 'reservation_cookies')]
2026-01-30 18:22:52 +01:00
public function revervationCookies () : Response
2026-01-19 21:08:04 +01:00
{
return $this -> render ( 'revervation/cookies.twig' );
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/cgv', name: 'reservation_cgv')]
2026-01-30 18:22:52 +01:00
public function revervationCgv () : Response
2026-01-19 21:08:04 +01:00
{
return $this -> render ( 'revervation/cgv.twig' );
}
2026-01-30 18:22:52 +01:00
2026-01-28 13:41:31 +01:00
#[Route('/hosting', name: 'reservation_hosting')]
2026-01-30 18:22:52 +01:00
public function revervationHosting () : Response
2026-01-19 21:08:04 +01:00
{
return $this -> render ( 'revervation/hosting.twig' );
}
2026-02-04 11:58:07 +01:00
#[Route('/estimer-la-livraison', name: 'reservation_estimate_delivery')]
public function estimateDelivery ( Request $request , HttpClientInterface $client ) : Response
{
$form = $this -> createFormBuilder ()
-> add ( 'address' , TextType :: class , [ 'required' => true ])
-> add ( 'zipCode' , TextType :: class , [ 'required' => true ])
-> add ( 'city' , TextType :: class , [ 'required' => true ])
-> getForm ();
$form -> handleRequest ( $request );
$estimation = null ;
2026-02-05 16:04:13 +01:00
$details = null ;
$geometry = null ;
2026-02-04 11:58:07 +01:00
if ( $form -> isSubmitted () && $form -> isValid ()) {
$data = $form -> getData ();
2026-02-05 16:04:13 +01:00
$deliveryData = $this -> calculateDelivery ( $data [ 'address' ], $data [ 'zipCode' ], $data [ 'city' ], $client );
if ( $deliveryData [ 'details' ] !== null ) {
$estimation = $deliveryData [ 'estimation' ];
$details = $deliveryData [ 'details' ];
$geometry = $deliveryData [ 'geometry' ];
} else {
$this -> addFlash ( 'warning' , 'Adresse introuvable ou erreur lors du calcul.' );
}
}
2026-02-04 11:58:07 +01:00
2026-02-05 16:04:13 +01:00
return $this -> render ( 'revervation/estimate_delivery.twig' , [
'form' => $form -> createView (),
'estimation' => $estimation ,
'details' => $details ,
'geometry' => $geometry
]);
}
// --- Private Helper Methods ---
private function calculateDuration ( ? string $startStr , ? string $endStr ) : int
{
if ( ! $startStr || ! $endStr ) {
return 1 ;
}
try {
$start = new \DateTimeImmutable ( $startStr );
$end = new \DateTimeImmutable ( $endStr );
if ( $end >= $start ) {
return $start -> diff ( $end ) -> days + 1 ;
}
} catch ( \Exception $e ) {
// Log error if necessary
}
return 1 ;
}
private function getDatesFromStrings ( ? string $startStr , ? string $endStr ) : array
{
$start = null ;
$end = null ;
if ( $startStr && $endStr ) {
2026-02-04 11:58:07 +01:00
try {
2026-02-05 16:04:13 +01:00
$start = new \DateTimeImmutable ( $startStr );
$end = new \DateTimeImmutable ( $endStr );
} catch ( \Exception $e ) {
// Ignore invalid dates
}
}
return [ 'start' => $start , 'end' => $end ];
}
private function isTvaEnabled () : bool
{
return isset ( $_ENV [ 'TVA_ENABLED' ]) && $_ENV [ 'TVA_ENABLED' ] === " true " ;
}
private function calculateDelivery ( ? string $address , ? string $zipCode , ? string $town , HttpClientInterface $client ) : array
{
$result = [
'estimation' => null ,
'details' => null ,
'geometry' => null
];
if ( ! $address || ! $zipCode || ! $town ) {
return $result ;
}
$query = sprintf ( '%s %s %s' , $address , $zipCode , $town );
try {
$response = $client -> request ( 'GET' , 'https://api-adresse.data.gouv.fr/search/' , [
'query' => [
'q' => $query ,
'limit' => 1
]
]);
$content = $response -> toArray ();
if ( ! empty ( $content [ 'features' ])) {
$coords = $content [ 'features' ][ 0 ][ 'geometry' ][ 'coordinates' ];
$lon = $coords [ 0 ];
$lat = $coords [ 1 ];
// Point de départ (LudikEvent)
$startLat = 49.849 ;
$startLon = 3.286 ;
// Calcul itinéraire via API Geoplateforme
$itineraireResponse = $client -> request ( 'GET' , 'https://data.geopf.fr/navigation/itineraire' , [
2026-02-04 11:58:07 +01:00
'query' => [
2026-02-05 16:04:13 +01:00
'resource' => 'bdtopo-osrm' ,
'start' => $startLon . ',' . $startLat ,
'end' => $lon . ',' . $lat ,
'profile' => 'car' ,
'optimization' => 'fastest' ,
'distanceUnit' => 'kilometer' ,
'geometryFormat' => 'geojson'
2026-02-04 11:58:07 +01:00
]
]);
2026-02-05 16:04:13 +01:00
$itineraire = $itineraireResponse -> toArray ();
$distance = $itineraire [ 'distance' ];
$result [ 'geometry' ] = $itineraire [ 'geometry' ] ? ? null ;
2026-02-04 11:58:07 +01:00
2026-02-05 16:04:13 +01:00
$rate = 0.50 ;
$trips = 4 ;
if ( $distance <= 10 ) {
$result [ 'estimation' ] = 0.0 ;
$chargedDistance = 0.0 ;
} else {
$chargedDistance = $distance - 10 ;
$result [ 'estimation' ] = ( $chargedDistance * $trips ) * $rate ;
}
$result [ 'details' ] = [
'distance' => $distance ,
'chargedDistance' => $chargedDistance ,
'trips' => $trips ,
'rate' => $rate ,
'isFree' => ( $distance <= 10 )
];
}
} catch ( \Exception $e ) {
// Return default nulls on error
}
return $result ;
}
2026-02-09 14:06:26 +01:00
private function buildCartData ( array $products , array $selectedOptionsMap , int $duration , OptionsRepository $optionsRepository , UploaderHelper $uploaderHelper , ? Promotion $promotion = null , ? Formules $formule = null ) : array
2026-02-05 16:04:13 +01:00
{
$items = [];
$rootOptions = [];
$totalHT = 0 ;
2026-02-09 14:06:26 +01:00
$formulaExtras = 0 ;
2026-02-05 16:04:13 +01:00
$tvaEnabled = $this -> isTvaEnabled ();
$tvaRate = $tvaEnabled ? 0.20 : 0 ;
$processedProductIds = [];
2026-02-09 14:06:26 +01:00
$formuleConfig = [];
if ( $formule ) {
$restriction = $formule -> getFormulesRestriction ();
if ( $restriction ) {
$formuleConfig = $restriction -> getRestrictionConfig () ? ? [];
}
}
2026-02-05 16:04:13 +01:00
foreach ( $products as $product ) {
$processedProductIds [] = $product -> getId ();
$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 ));
// Traitement des options
$productOptions = [];
$optionsTotalHT = 0 ;
if ( isset ( $selectedOptionsMap [ $product -> getId ()])) {
$optionIds = $selectedOptionsMap [ $product -> getId ()];
if ( ! empty ( $optionIds )) {
$optionsEntities = $optionsRepository -> findBy ([ 'id' => $optionIds ]);
foreach ( $optionsEntities as $option ) {
$optPrice = $option -> getPriceHt ();
$optData = [
'id' => $option -> getId (),
'name' => $option -> getName (),
'price' => $optPrice
];
if ( $product -> getOptions () -> contains ( $option )) {
$productOptions [] = $optData ;
$optionsTotalHT += $optPrice ;
} else {
$rootOptions [] = $optData ;
2026-02-09 14:06:26 +01:00
if ( $formule ) {
$formulaExtras += $optPrice ;
} else {
$totalHT += $optPrice ;
}
2026-02-05 16:04:13 +01:00
}
2026-02-04 11:58:07 +01:00
}
2026-02-05 16:04:13 +01:00
}
}
2026-02-04 11:58:07 +01:00
2026-02-05 16:04:13 +01:00
$productTotalHT += $optionsTotalHT ;
$productTotalTTC = $productTotalHT * ( 1 + $tvaRate );
2026-02-09 14:06:26 +01:00
$isInFormule = false ;
if ( $formule ) {
foreach ( $formuleConfig as $c ) {
if (( $c [ 'product' ] ? ? '' ) === $product -> getName ()) {
$isInFormule = true ;
break ;
}
}
}
if ( $formule ) {
if ( $isInFormule ) {
$formulaExtras += $optionsTotalHT ;
} else {
$formulaExtras += $productTotalHT ;
}
} else {
$totalHT += $productTotalHT ;
}
2026-02-05 16:04:13 +01:00
$items [] = [
2026-02-09 14:06:26 +01:00
'id' => $product -> getId (),
2026-02-05 16:04:13 +01:00
'name' => $product -> getName (),
2026-02-09 14:06:26 +01:00
'product' => $product ,
2026-02-05 16:04:13 +01:00
'image' => $uploaderHelper -> asset ( $product , 'imageFile' ),
2026-02-09 14:06:26 +01:00
'priceHt1Day' => $price1Day ,
'priceHTSupDay' => $priceSup ,
2026-02-05 16:04:13 +01:00
'priceTTC1Day' => $price1Day * ( 1 + $tvaRate ),
2026-02-09 14:06:26 +01:00
'price1Day' => $price1Day ,
'priceSup' => $priceSup ,
2026-02-05 16:04:13 +01:00
'totalPriceHT' => $productTotalHT ,
'totalPriceTTC' => $productTotalTTC ,
2026-02-09 14:06:26 +01:00
'options' => $productOptions ,
'in_formule' => $isInFormule
2026-02-05 16:04:13 +01:00
];
}
// Traiter les options orphelines
foreach ( $selectedOptionsMap as $prodId => $optIds ) {
if ( ! in_array ( $prodId , $processedProductIds ) && ! empty ( $optIds )) {
$optionsEntities = $optionsRepository -> findBy ([ 'id' => $optIds ]);
foreach ( $optionsEntities as $option ) {
$optPrice = $option -> getPriceHt ();
$rootOptions [] = [
'id' => $option -> getId (),
'name' => $option -> getName (),
'price' => $optPrice ,
'orphan_product_id' => $prodId
2026-02-04 11:58:07 +01:00
];
2026-02-09 14:06:26 +01:00
if ( $formule ) {
$formulaExtras += $optPrice ;
} else {
$totalHT += $optPrice ;
}
2026-02-04 11:58:07 +01:00
}
}
}
2026-02-09 14:06:26 +01:00
if ( $formule ) {
$formulaBasePrice = 0 ;
if ( $duration <= 1 ) {
$formulaBasePrice = $formule -> getPrice1j ();
} elseif ( $duration <= 2 ) {
$formulaBasePrice = $formule -> getPrice2j ();
} else {
$formulaBasePrice = $formule -> getPrice5j ();
}
$totalHT = $formulaBasePrice + $formulaExtras ;
}
2026-02-09 11:26:52 +01:00
$discountAmount = 0 ;
if ( $promotion ) {
$discountAmount = $totalHT * ( $promotion -> getPercentage () / 100 );
$totalHT -= $discountAmount ;
}
2026-02-05 16:04:13 +01:00
$totalTva = $totalHT * $tvaRate ;
$totalTTC = $totalHT + $totalTva ;
return [
'items' => $items ,
'rootOptions' => $rootOptions ,
'total' => [
'totalHT' => $totalHT ,
'totalTva' => $totalTva ,
2026-02-09 11:26:52 +01:00
'totalTTC' => $totalTTC ,
'discount' => $discountAmount ,
2026-02-09 14:06:26 +01:00
'promotion' => $promotion ? $promotion -> getName () : null ,
'formule' => $formule ? $formule -> getName () : null
2026-02-05 16:04:13 +01:00
],
'tvaEnabled' => $tvaEnabled
];
2026-02-04 11:58:07 +01:00
}
2026-01-19 21:08:04 +01:00
}