1480 lines
57 KiB
PHP
1480 lines
57 KiB
PHP
<?php
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Entity\Customer;
|
|
use App\Entity\CustomerTracking;
|
|
use App\Entity\Formules;
|
|
use App\Entity\Product;
|
|
use App\Entity\ProductReserve;
|
|
use App\Entity\Promotion;
|
|
use App\Entity\SitePerformance;
|
|
use App\Repository\CustomerRepository;
|
|
use App\Repository\CustomerTrackingRepository;
|
|
use App\Repository\FormulesRepository;
|
|
use App\Repository\OrderSessionRepository;
|
|
use App\Repository\OptionsRepository;
|
|
use App\Repository\ProductRepository;
|
|
use App\Repository\ProductReserveRepository;
|
|
use App\Repository\PromotionRepository;
|
|
use App\Service\Mailer\Mailer;
|
|
use App\Service\Search\Client;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Fkrzski\RobotsTxt\RobotsTxt;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
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\HttpKernel\KernelInterface;
|
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
|
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
|
use App\Service\Pdf\DevisPdfService;
|
|
use App\Entity\Devis;
|
|
use App\Entity\DevisLine;
|
|
use App\Entity\CustomerAddress;
|
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
|
|
class ReserverController extends AbstractController
|
|
{
|
|
private KernelInterface $kernel;
|
|
private ?array $simplifiedCommunes = null;
|
|
|
|
public function __construct(KernelInterface $kernel)
|
|
{
|
|
$this->kernel = $kernel;
|
|
}
|
|
|
|
#[Route('/flow/{sessionId}/devis', name: 'reservation_generate_devis', methods: ['GET'])]
|
|
public function generateDevis(
|
|
string $sessionId,
|
|
OrderSessionRepository $repository,
|
|
ProductRepository $productRepository,
|
|
OptionsRepository $optionsRepository
|
|
): Response {
|
|
$session = $repository->findOneBy(['uuid' => $sessionId]);
|
|
if (!$session) {
|
|
return $this->redirectToRoute('reservation');
|
|
}
|
|
|
|
if ($session->getState() === 'send') {
|
|
return $this->redirectToRoute('reservation_flow_success', ['sessionId' => $sessionId]);
|
|
}
|
|
|
|
$sessionData = $session->getProducts();
|
|
$ids = $sessionData['ids'] ?? [];
|
|
$startStr = $sessionData['start'] ?? null;
|
|
$endStr = $sessionData['end'] ?? null;
|
|
|
|
$duration = $this->calculateDuration($startStr, $endStr);
|
|
$dates = $this->getDatesFromStrings($startStr, $endStr);
|
|
|
|
// 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');
|
|
$devis->setStartAt($dates['start']);
|
|
$devis->setEndAt($dates['end']);
|
|
|
|
$selectedOptionsMap = $sessionData['options'] ?? [];
|
|
$runningTotalHT = 0;
|
|
|
|
$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;
|
|
}
|
|
|
|
if (!empty($ids)) {
|
|
$products = $productRepository->findBy(['id' => $ids]);
|
|
$processedProductIds = [];
|
|
|
|
foreach ($products as $product) {
|
|
$processedProductIds[] = $product->getId();
|
|
|
|
$isInFormule = false;
|
|
if ($formule) {
|
|
foreach ($formuleConfig as $c) {
|
|
if (($c['product'] ?? '') === $product->getName()) {
|
|
$isInFormule = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$line = new DevisLine();
|
|
$line->setProduct($product->getName());
|
|
|
|
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));
|
|
}
|
|
|
|
$line->setDay($duration);
|
|
$devis->addDevisLine($line);
|
|
|
|
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);
|
|
|
|
$runningTotalHT += $option->getPriceHt();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
$runningTotalHT += $option->getPriceHt();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$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);
|
|
}
|
|
|
|
$pdfService = new DevisPdfService($this->kernel, $devis, $productRepository);
|
|
$content = $pdfService->generate();
|
|
|
|
return new Response($content, 200, [
|
|
'Content-Type' => 'application/pdf',
|
|
'Content-Disposition' => 'attachment; filename="devis_provisoire.pdf"'
|
|
]);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#[Route('/robots.txt', name: 'robots_txt', defaults: ['_format' => 'txt'])]
|
|
public function index(Request $request): Response
|
|
{
|
|
$robots = new RobotsTxt();
|
|
|
|
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');
|
|
}
|
|
|
|
return new Response($robots->toString(), Response::HTTP_OK, [
|
|
'Content-Type' => 'text/plain'
|
|
]);
|
|
}
|
|
|
|
#[Route('/', name: 'reservation')]
|
|
public function revervation(FormulesRepository $formulesRepository, ProductRepository $productRepository): Response
|
|
{
|
|
$products = $productRepository->findBy(['category' => '3-15 ans', 'isPublish' => true], ['updatedAt' => 'DESC'], 3);
|
|
$formules = $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC'], 3);
|
|
|
|
return $this->render('revervation/home.twig', [
|
|
'products' => $products,
|
|
'formules' => $formules,
|
|
]);
|
|
}
|
|
|
|
#[Route('/produit/check', name: 'produit_check', methods: ['GET', 'POST'])]
|
|
public function productCheck(Request $request, ProductReserveRepository $productReserveRepository, ProductRepository $productRepository): Response
|
|
{
|
|
$productId = $request->query->get('id');
|
|
$startStr = $request->query->get('start');
|
|
$endStr = $request->query->get('end');
|
|
|
|
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]);
|
|
}
|
|
|
|
#[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];
|
|
}
|
|
|
|
#[Route('/web-vitals', name: 'reservation_web-vitals', methods: ['POST'])]
|
|
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);
|
|
$perf->setCreatedAt(new \DateTimeImmutable());
|
|
|
|
if (!$existing) {
|
|
$em->persist($perf);
|
|
}
|
|
|
|
$em->flush();
|
|
|
|
return new Response('', Response::HTTP_NO_CONTENT);
|
|
}
|
|
|
|
#[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])]
|
|
public function basketJson(
|
|
Request $request,
|
|
ProductRepository $productRepository,
|
|
OptionsRepository $optionsRepository,
|
|
UploaderHelper $uploaderHelper,
|
|
PromotionRepository $promotionRepository,
|
|
FormulesRepository $formulesRepository
|
|
): Response {
|
|
$data = json_decode($request->getContent(), true);
|
|
$ids = $data['ids'] ?? [];
|
|
$selectedOptionsMap = $data['options'] ?? [];
|
|
$startStr = $data['start'] ?? null;
|
|
$endStr = $data['end'] ?? null;
|
|
|
|
$duration = $this->calculateDuration($startStr, $endStr);
|
|
|
|
$products = [];
|
|
if (!empty($ids)) {
|
|
$products = $productRepository->findBy(['id' => $ids]);
|
|
}
|
|
|
|
$foundIds = array_map(fn($p) => $p->getId(), $products);
|
|
$removedIds = array_values(array_diff($ids, $foundIds));
|
|
|
|
$promotions = $promotionRepository->findActivePromotions(new \DateTime());
|
|
$promotion = $promotions[0] ?? null;
|
|
|
|
$formuleId = $data['formule'] ?? null;
|
|
$formule = null;
|
|
if ($formuleId) {
|
|
$formule = $formulesRepository->find($formuleId);
|
|
}
|
|
|
|
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper, $promotion, $formule);
|
|
|
|
return new JsonResponse([
|
|
'start_date' => $startStr,
|
|
'end_date' => $endStr,
|
|
'products' => $cartData['items'],
|
|
'options' => $cartData['rootOptions'],
|
|
'unavailable_products_ids' => $removedIds,
|
|
'total' => $cartData['total'],
|
|
'promotion' => $promotion ? ['name' => $promotion->getName(), 'percentage' => $promotion->getPercentage()] : null,
|
|
'formule' => $formule ? ['name' => $formule->getName()] : null
|
|
]);
|
|
}
|
|
|
|
#[Route('/session', name: 'reservation_session_create', methods: ['POST'])]
|
|
public function createSession(Request $request, EntityManagerInterface $em, OrderSessionRepository $sessionRepository, PromotionRepository $promotionRepository, FormulesRepository $formulesRepository, ProductRepository $productRepository): Response
|
|
{
|
|
$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');
|
|
}
|
|
|
|
// 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);
|
|
|
|
$promotions = $promotionRepository->findActivePromotions(new \DateTime());
|
|
$promotion = $promotions[0] ?? null;
|
|
|
|
if ($promotion) {
|
|
$session->setPromotion(['name' => $promotion->getName(), 'percentage' => $promotion->getPercentage()]);
|
|
} else {
|
|
$session->setPromotion(null);
|
|
}
|
|
|
|
$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);
|
|
}
|
|
}
|
|
|
|
$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()])
|
|
]);
|
|
}
|
|
|
|
#[Route('/flow/{sessionId}/confirmed', name: 'reservation_flow_confirmed', methods: ['GET', 'POST'])]
|
|
public function flowConfirmed(
|
|
string $sessionId,
|
|
OrderSessionRepository $repository,
|
|
ProductRepository $productRepository,
|
|
UploaderHelper $uploaderHelper,
|
|
ProductReserveRepository $productReserveRepository,
|
|
EntityManagerInterface $em,
|
|
OptionsRepository $optionsRepository,
|
|
HttpClientInterface $client,
|
|
Request $request,
|
|
Mailer $mailer
|
|
): Response {
|
|
$session = $repository->findOneBy(['uuid' => $sessionId]);
|
|
if (!$session) {
|
|
return $this->render('revervation/session_lost.twig');
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
$sessionData = $session->getProducts();
|
|
$ids = $sessionData['ids'] ?? [];
|
|
$selectedOptionsMap = $sessionData['options'] ?? [];
|
|
$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]);
|
|
}
|
|
|
|
$duration = $this->calculateDuration($startStr, $endStr);
|
|
|
|
$products = [];
|
|
if (!empty($ids)) {
|
|
$products = $productRepository->findBy(['id' => $ids]);
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
$promoData = $session->getPromotion();
|
|
$promotion = null;
|
|
if ($promoData) {
|
|
$promotion = new Promotion();
|
|
$promotion->setName($promoData['name']);
|
|
$promotion->setPercentage($promoData['percentage']);
|
|
}
|
|
|
|
$formule = $session->getFormule();
|
|
|
|
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper, $promotion, $formule);
|
|
|
|
// --- Calcul Frais de Livraison ---
|
|
$deliveryData = $this->calculateDelivery(
|
|
$session->getAdressEvent(),
|
|
$session->getZipCodeEvent(),
|
|
$session->getTownEvent(),
|
|
$client
|
|
);
|
|
|
|
return $this->render('revervation/flow_confirmed.twig', [
|
|
'session' => $session,
|
|
'cart' => [
|
|
'items' => $cartData['items'],
|
|
'options' => $cartData['rootOptions'],
|
|
'startDate' => $startStr ? new \DateTimeImmutable($startStr) : null,
|
|
'endDate' => $endStr ? new \DateTimeImmutable($endStr) : null,
|
|
'duration' => $duration,
|
|
'totalHT' => $cartData['total']['totalHT'],
|
|
'totalTva' => $cartData['total']['totalTva'],
|
|
'totalTTC' => $cartData['total']['totalTTC'],
|
|
'discount' => $cartData['total']['discount'],
|
|
'promotion' => $cartData['total']['promotion'],
|
|
'formule' => $cartData['total']['formule'],
|
|
'tvaEnabled' => $cartData['tvaEnabled'],
|
|
],
|
|
'delivery' => $deliveryData
|
|
]);
|
|
}
|
|
|
|
#[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');
|
|
}
|
|
|
|
#[Route('/flow/{sessionId}', name: 'reservation_flow', methods: ['GET', 'POST'])]
|
|
public function flowLogin(
|
|
string $sessionId,
|
|
AuthenticationUtils $authenticationUtils,
|
|
OrderSessionRepository $repository,
|
|
ProductRepository $productRepository,
|
|
UploaderHelper $uploaderHelper,
|
|
ProductReserveRepository $productReserveRepository,
|
|
OptionsRepository $optionsRepository
|
|
): Response {
|
|
$session = $repository->findOneBy(['uuid' => $sessionId]);
|
|
if (!$session) {
|
|
return $this->render('revervation/session_lost.twig');
|
|
}
|
|
|
|
if ($session->getState() === 'send') {
|
|
return $this->redirectToRoute('reservation_flow_success', ['sessionId' => $sessionId]);
|
|
}
|
|
|
|
$sessionData = $session->getProducts();
|
|
$ids = $sessionData['ids'] ?? [];
|
|
$selectedOptionsMap = $sessionData['options'] ?? [];
|
|
$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 sélection.');
|
|
return $this->redirectToRoute('reservation');
|
|
}
|
|
|
|
$duration = $this->calculateDuration($startStr, $endStr);
|
|
|
|
$products = [];
|
|
if (!empty($ids)) {
|
|
$products = $productRepository->findBy(['id' => $ids]);
|
|
}
|
|
|
|
// Cleanup missing products from session
|
|
$foundIds = array_map(fn($p) => $p->getId(), $products);
|
|
if (count($foundIds) !== count($ids)) {
|
|
$sessionData['ids'] = $foundIds;
|
|
$session->setProducts($sessionData);
|
|
// We should probably flush, but no EntityManager injected here in original code for GET/POST mix.
|
|
// Added simple cleanup.
|
|
}
|
|
|
|
$promoData = $session->getPromotion();
|
|
$promotion = null;
|
|
if ($promoData) {
|
|
$promotion = new Promotion();
|
|
$promotion->setName($promoData['name']);
|
|
$promotion->setPercentage($promoData['percentage']);
|
|
}
|
|
|
|
$formule = $session->getFormule();
|
|
|
|
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper, $promotion, $formule);
|
|
|
|
return $this->render('revervation/flow.twig', [
|
|
'session' => $session,
|
|
'last_username' => $authenticationUtils->getLastUsername(),
|
|
'error' => $authenticationUtils->getLastAuthenticationError(),
|
|
'cart' => [
|
|
'items' => $cartData['items'],
|
|
'options' => $cartData['rootOptions'],
|
|
'startDate' => $startStr ? new \DateTimeImmutable($startStr) : null,
|
|
'endDate' => $endStr ? new \DateTimeImmutable($endStr) : null,
|
|
'duration' => $duration,
|
|
'totalHT' => $cartData['total']['totalHT'],
|
|
'totalTva' => $cartData['total']['totalTva'],
|
|
'totalTTC' => $cartData['total']['totalTTC'],
|
|
'discount' => $cartData['total']['discount'],
|
|
'promotion' => $cartData['total']['promotion'],
|
|
'formule' => $cartData['total']['formule'],
|
|
'tvaEnabled' => $cartData['tvaEnabled'],
|
|
]
|
|
]);
|
|
}
|
|
|
|
#[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');
|
|
}
|
|
|
|
if ($session->getState() === 'send') {
|
|
return $this->redirectToRoute('reservation_flow_success', ['sessionId' => $sessionId]);
|
|
}
|
|
|
|
$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();
|
|
|
|
return $this->redirectToRoute('reservation_flow_confirmed', ['sessionId' => $sessionId]);
|
|
}
|
|
|
|
#[Route('/umami', name: 'reservation_umami', methods: ['POST'])]
|
|
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);
|
|
$track->setCreateAT(new \DateTime());
|
|
$track->setCustomer($user);
|
|
|
|
$em->persist($track);
|
|
} else {
|
|
if ($track->getCustomer() !== $user) {
|
|
$track->setCustomer($user);
|
|
}
|
|
}
|
|
|
|
$em->flush();
|
|
|
|
return new JsonResponse(['status' => 'success']);
|
|
}
|
|
|
|
#[Route('/catalogue', name: 'reservation_catalogue')]
|
|
public function revervationCatalogue(ProductRepository $productRepository): Response
|
|
{
|
|
return $this->render('revervation/catalogue.twig', [
|
|
'products' => $productRepository->findBy(['isPublish' => true]),
|
|
'tvaEnabled' => $this->isTvaEnabled(),
|
|
]);
|
|
}
|
|
|
|
#[Route('/formules', name: 'reservation_formules')]
|
|
public function revervationFormules(FormulesRepository $formulesRepository): Response
|
|
{
|
|
return $this->render('revervation/formules.twig', [
|
|
'formules' => $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC']),
|
|
'tvaEnabled' => $this->isTvaEnabled(),
|
|
]);
|
|
}
|
|
|
|
#[Route('/formules/{slug}', name: 'reservation_formule_show')]
|
|
public function revervationView(string $slug, FormulesRepository $formulesRepository): Response
|
|
{
|
|
$parts = explode('-', $slug);
|
|
$realId = $parts[0];
|
|
|
|
$formule = $formulesRepository->find($realId);
|
|
|
|
if (!$formule) {
|
|
throw $this->createNotFoundException('Formules introuvable');
|
|
}
|
|
|
|
return $this->render('revervation/formule/show.twig', [
|
|
'formule' => $formule,
|
|
'tvaEnabled' => $this->isTvaEnabled(),
|
|
]);
|
|
}
|
|
|
|
#[Route('/comment-reserver', name: 'reservation_workflow')]
|
|
public function revervationWorkfkow(): Response
|
|
{
|
|
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, PromotionRepository $promotionRepository): Response
|
|
{
|
|
$parts = explode('-', $id);
|
|
$realId = $parts[0];
|
|
|
|
$product = $productRepository->find($realId);
|
|
|
|
if (!$product) {
|
|
throw $this->createNotFoundException('Produit introuvable');
|
|
}
|
|
|
|
$allInCat = $productRepository->findBy(['category' => $product->getCategory()], [], 5);
|
|
|
|
$otherProducts = array_filter($allInCat, function ($p) use ($product) {
|
|
return $p->getId() !== $product->getId();
|
|
});
|
|
|
|
$promotions = $promotionRepository->findActivePromotions(new \DateTime());
|
|
$promotion = $promotions[0] ?? null;
|
|
|
|
return $this->render('revervation/produit.twig', [
|
|
'product' => $product,
|
|
'tvaEnabled' => $this->isTvaEnabled(),
|
|
'otherProducts' => array_slice($otherProducts, 0, 4),
|
|
'promotion' => $promotion
|
|
]);
|
|
}
|
|
|
|
#[Route('/connexion', name: 'reservation_login')]
|
|
public function revervationLogin(AuthenticationUtils $authenticationUtils): Response
|
|
{
|
|
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,
|
|
Mailer $mailer,
|
|
EntityManagerInterface $em,
|
|
UserPasswordHasherInterface $hasher
|
|
): Response {
|
|
if ($request->isMethod('POST')) {
|
|
$payload = $request->getPayload();
|
|
|
|
$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'));
|
|
$customer->setType($payload->getString('type'));
|
|
|
|
if ($customer->getType() === 'buisness') {
|
|
$customer->setSiret($payload->getString('siret'));
|
|
$customer->setRaisonSocial($payload->getString('raisonSocial'));
|
|
$customer->setTypCompany($payload->getString('typCompany'));
|
|
}
|
|
|
|
$hashedPassword = $hasher->hashPassword($customer, $payload->getString('password'));
|
|
$customer->setPassword($hashedPassword);
|
|
$customer->setRoles(['ROLE_USER']);
|
|
|
|
$mailer->send(
|
|
$customer->getEmail(),
|
|
$customer->getName() . " " . $customer->getSurname(),
|
|
"[Ludikevent] - Code de récupération",
|
|
"mails/welcome.twig",
|
|
['customer' => $customer]
|
|
);
|
|
|
|
$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');
|
|
}
|
|
|
|
#[Route('/mot-de-passe', name: 'reservation_password')]
|
|
public function forgotPassword(
|
|
Request $request,
|
|
CustomerRepository $repository,
|
|
EntityManagerInterface $em,
|
|
Mailer $mailer,
|
|
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
|
|
]);
|
|
|
|
$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']);
|
|
}
|
|
$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();
|
|
|
|
$session->remove('reset_password');
|
|
$this->addFlash('success', 'Mot de passe mis à jour !');
|
|
return $this->redirectToRoute('reservation_login');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->render('reservation/password.twig', [
|
|
'step' => $step,
|
|
'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]);
|
|
|
|
$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()
|
|
]);
|
|
}
|
|
|
|
#[Route('/recherche', name: 'reservation_search')]
|
|
public function recherche(UploaderHelper $uploaderHelper, Client $client, Request $request, ProductRepository $productRepository): Response
|
|
{
|
|
$results = $client->search('product', $request->query->get('q', ''));
|
|
$items = [];
|
|
|
|
foreach ($results['hits'] as $result) {
|
|
$p = $productRepository->find($result['id']);
|
|
if ($p instanceof Product && $p->isPublish()) {
|
|
$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(),
|
|
'link' => $this->generateUrl('reservation_product_show', ['id' => $p->slug()]),
|
|
];
|
|
}
|
|
}
|
|
|
|
return $this->render('revervation/search.twig', [
|
|
'products' => $items
|
|
]);
|
|
}
|
|
|
|
#[Route('/mentions-legales', name: 'reservation_mentions-legal')]
|
|
public function revervationLegal(): Response
|
|
{
|
|
return $this->render('revervation/legal.twig');
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
|
|
#[Route('/rgpd', name: 'reservation_rgpd')]
|
|
public function revervationRgpd(): Response
|
|
{
|
|
return $this->render('revervation/rgpd.twig');
|
|
}
|
|
|
|
#[Route('/cookies', name: 'reservation_cookies')]
|
|
public function revervationCookies(): Response
|
|
{
|
|
return $this->render('revervation/cookies.twig');
|
|
}
|
|
|
|
#[Route('/cgv', name: 'reservation_cgv')]
|
|
public function revervationCgv(): Response
|
|
{
|
|
return $this->render('revervation/cgv.twig');
|
|
}
|
|
|
|
#[Route('/hosting', name: 'reservation_hosting')]
|
|
public function revervationHosting(): Response
|
|
{
|
|
return $this->render('revervation/hosting.twig');
|
|
}
|
|
|
|
#[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;
|
|
$details = null;
|
|
$geometry = null;
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
|
$data = $form->getData();
|
|
$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.');
|
|
}
|
|
}
|
|
|
|
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) {
|
|
try {
|
|
$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', [
|
|
'query' => [
|
|
'resource' => 'bdtopo-osrm',
|
|
'start' => $startLon . ',' . $startLat,
|
|
'end' => $lon . ',' . $lat,
|
|
'profile' => 'car',
|
|
'optimization' => 'fastest',
|
|
'distanceUnit' => 'kilometer',
|
|
'geometryFormat' => 'geojson'
|
|
]
|
|
]);
|
|
|
|
$itineraire = $itineraireResponse->toArray();
|
|
$distance = $itineraire['distance'];
|
|
$result['geometry'] = $itineraire['geometry'] ?? null;
|
|
|
|
$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;
|
|
}
|
|
|
|
private function buildCartData(array $products, array $selectedOptionsMap, int $duration, OptionsRepository $optionsRepository, UploaderHelper $uploaderHelper, ?Promotion $promotion = null, ?Formules $formule = null): array
|
|
{
|
|
$items = [];
|
|
$rootOptions = [];
|
|
$totalHT = 0;
|
|
$formulaExtras = 0;
|
|
$tvaEnabled = $this->isTvaEnabled();
|
|
$tvaRate = $tvaEnabled ? 0.20 : 0;
|
|
$processedProductIds = [];
|
|
|
|
$formuleConfig = [];
|
|
if ($formule) {
|
|
$restriction = $formule->getFormulesRestriction();
|
|
if ($restriction) {
|
|
$formuleConfig = $restriction->getRestrictionConfig() ?? [];
|
|
}
|
|
}
|
|
|
|
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;
|
|
if ($formule) {
|
|
$formulaExtras += $optPrice;
|
|
} else {
|
|
$totalHT += $optPrice;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$productTotalHT += $optionsTotalHT;
|
|
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
|
|
|
|
$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;
|
|
}
|
|
|
|
$items[] = [
|
|
'id' => $product->getId(),
|
|
'name' => $product->getName(),
|
|
'product' => $product,
|
|
'image' => $uploaderHelper->asset($product, 'imageFile'),
|
|
'priceHt1Day' => $price1Day,
|
|
'priceHTSupDay' => $priceSup,
|
|
'priceTTC1Day' => $price1Day * (1 + $tvaRate),
|
|
'price1Day' => $price1Day,
|
|
'priceSup' => $priceSup,
|
|
'totalPriceHT' => $productTotalHT,
|
|
'totalPriceTTC' => $productTotalTTC,
|
|
'options' => $productOptions,
|
|
'in_formule' => $isInFormule
|
|
];
|
|
}
|
|
|
|
// 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
|
|
];
|
|
if ($formule) {
|
|
$formulaExtras += $optPrice;
|
|
} else {
|
|
$totalHT += $optPrice;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($formule) {
|
|
$formulaBasePrice = 0;
|
|
if ($duration <= 1) {
|
|
$formulaBasePrice = $formule->getPrice1j();
|
|
} elseif ($duration <= 2) {
|
|
$formulaBasePrice = $formule->getPrice2j();
|
|
} else {
|
|
$formulaBasePrice = $formule->getPrice5j();
|
|
}
|
|
|
|
$totalHT = $formulaBasePrice + $formulaExtras;
|
|
}
|
|
|
|
$discountAmount = 0;
|
|
if ($promotion) {
|
|
$discountAmount = $totalHT * ($promotion->getPercentage() / 100);
|
|
$totalHT -= $discountAmount;
|
|
}
|
|
|
|
$totalTva = $totalHT * $tvaRate;
|
|
$totalTTC = $totalHT + $totalTva;
|
|
|
|
return [
|
|
'items' => $items,
|
|
'rootOptions' => $rootOptions,
|
|
'total' => [
|
|
'totalHT' => $totalHT,
|
|
'totalTva' => $totalTva,
|
|
'totalTTC' => $totalTTC,
|
|
'discount' => $discountAmount,
|
|
'promotion' => $promotion ? $promotion->getName() : null,
|
|
'formule' => $formule ? $formule->getName() : null
|
|
],
|
|
'tvaEnabled' => $tvaEnabled
|
|
];
|
|
}
|
|
}
|