feat(ReserverController): Refactorise le contrôleur de réservation.
```
This commit is contained in:
Serreau Jovann
2026-02-05 16:04:13 +01:00
parent f0fb03e5c7
commit 383ad7d3eb

View File

@@ -11,6 +11,7 @@ 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\Service\Mailer\Mailer;
@@ -50,8 +51,7 @@ class ReserverController extends AbstractController
string $sessionId,
OrderSessionRepository $repository,
ProductRepository $productRepository,
KernelInterface $kernel,
\App\Repository\OptionsRepository $optionsRepository
OptionsRepository $optionsRepository
): Response {
$session = $repository->findOneBy(['uuid' => $sessionId]);
if (!$session) {
@@ -67,21 +67,8 @@ class ReserverController extends AbstractController
$startStr = $sessionData['start'] ?? null;
$endStr = $sessionData['end'] ?? null;
// Calcul de la durée
$duration = 1;
$start = null;
$end = null;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
}
}
$duration = $this->calculateDuration($startStr, $endStr);
$dates = $this->getDatesFromStrings($startStr, $endStr);
// Création des objets temporaires pour le PDF
$customer = new Customer();
@@ -108,8 +95,8 @@ class ReserverController extends AbstractController
$devis->setBillAddress($billAddress);
$devis->setAddressShip($shipAddress);
$devis->setNum('PROVISOIRE');
$devis->setStartAt($start);
$devis->setEndAt($end);
$devis->setStartAt($dates['start']);
$devis->setEndAt($dates['end']);
$selectedOptionsMap = $sessionData['options'] ?? [];
@@ -158,7 +145,7 @@ class ReserverController extends AbstractController
}
}
$pdfService = new DevisPdfService($kernel, $devis, $productRepository);
$pdfService = new DevisPdfService($this->kernel, $devis, $productRepository);
$content = $pdfService->generate();
return new Response($content, 200, [
@@ -175,25 +162,22 @@ class ReserverController extends AbstractController
$filePath = $this->kernel->getProjectDir() . '/public/simplified_communes_by_zip.json';
if (!file_exists($filePath)) {
// Log an error or throw an exception if the file doesn't exist
// For now, return an empty array if file is missing
return [];
}
$content = file_get_contents($filePath);
if ($content === false) {
// Log an error or throw an exception if reading fails
return [];
}
$this->simplifiedCommunes = json_decode($content, true);
if ($this->simplifiedCommunes === null && json_last_error() !== JSON_ERROR_NONE) {
// Log JSON decoding error
return [];
}
return $this->simplifiedCommunes;
}
#[Route('/robots.txt', name: 'robots_txt', defaults: ['_format' => 'txt'])]
public function index(Request $request): Response
{
@@ -359,27 +343,19 @@ class ReserverController extends AbstractController
}
#[Route('/basket/json', name: 'reservation_basket_json', methods: ['POST'])]
public function basketJson(Request $request, ProductRepository $productRepository, \App\Repository\OptionsRepository $optionsRepository, UploaderHelper $uploaderHelper): Response
{
public function basketJson(
Request $request,
ProductRepository $productRepository,
OptionsRepository $optionsRepository,
UploaderHelper $uploaderHelper
): Response {
$data = json_decode($request->getContent(), true);
$ids = $data['ids'] ?? [];
$selectedOptionsMap = $data['options'] ?? [];
$startStr = $data['start'] ?? null;
$endStr = $data['end'] ?? null;
// Calcul de la durée
$duration = 1;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
}
}
$duration = $this->calculateDuration($startStr, $endStr);
$products = [];
if (!empty($ids)) {
@@ -389,108 +365,18 @@ class ReserverController extends AbstractController
$foundIds = array_map(fn($p) => $p->getId(), $products);
$removedIds = array_values(array_diff($ids, $foundIds));
$items = [];
$totalHT = 0;
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
$tvaRate = $tvaEnabled ? 0.20 : 0;
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper);
$rootOptions = [];
$processedProductIds = [];
return new JsonResponse([
'start_date' => $startStr,
'end_date' => $endStr,
'products' => $cartData['items'],
'options' => $cartData['rootOptions'],
'unavailable_products_ids' => $removedIds,
'total' => $cartData['total']
]);
}
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
];
// Vérifier si l'option est réellement liée au produit
if ($product->getOptions()->contains($option)) {
$productOptions[] = $optData;
$optionsTotalHT += $optPrice;
} else {
// Option demandée mais pas liée au produit -> Root options
$rootOptions[] = $optData;
// On ajoute quand même le prix ? Le user a dit "options non lier".
// Généralement si c'est "non lié", c'est une erreur ou une option globale.
// On l'ajoute au root mais pas au total du produit.
// Faut-il l'ajouter au total général ?
// Supposons que oui, c'est un "extra".
$totalHT += $optPrice;
}
}
}
}
$productTotalHT += $optionsTotalHT;
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'id' => $product->getId(),
'name' => $product->getName(),
'image' => $uploaderHelper->asset($product, 'imageFile'),
'priceHt1Day' => $price1Day,
'priceHTSupDay' => $priceSup,
'priceTTC1Day' => $price1Day * (1 + $tvaRate),
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
'options' => $productOptions
];
$totalHT += $productTotalHT;
}
// Traiter les options pour les produits qui ne sont PAS dans le panier (orphelins de produit)
foreach ($selectedOptionsMap as $prodId => $optIds) {
// Si le produit n'a pas été traité (absent de $products car pas dans $ids ou pas trouvé)
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 // Info debug
];
$totalHT += $optPrice;
}
}
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
return new JsonResponse([
'start_date' => $startStr,
'end_date' => $endStr,
'products' => $items,
'options' => $rootOptions,
'unavailable_products_ids' => $removedIds,
'total' => [
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC
]
]);
}
#[Route('/session', name: 'reservation_session_create', methods: ['POST'])]
public function createSession(Request $request, EntityManagerInterface $em, OrderSessionRepository $sessionRepository): Response
{
@@ -528,13 +414,12 @@ class ReserverController extends AbstractController
#[Route('/flow/{sessionId}/confirmed', name: 'reservation_flow_confirmed', methods: ['GET', 'POST'])]
public function flowConfirmed(
string $sessionId,
AuthenticationUtils $authenticationUtils,
OrderSessionRepository $repository,
ProductRepository $productRepository,
UploaderHelper $uploaderHelper,
ProductReserveRepository $productReserveRepository,
EntityManagerInterface $em,
\App\Repository\OptionsRepository $optionsRepository,
OptionsRepository $optionsRepository,
HttpClientInterface $client,
Request $request,
Mailer $mailer
@@ -577,19 +462,7 @@ class ReserverController extends AbstractController
return $this->redirectToRoute('reservation_flow', ['sessionId' => $sessionId]);
}
// Calcul de la durée
$duration = 1;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
}
}
$duration = $this->calculateDuration($startStr, $endStr);
$products = [];
if (!empty($ids)) {
@@ -604,169 +477,30 @@ class ReserverController extends AbstractController
$em->flush();
}
$items = [];
$totalHT = 0;
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
$tvaRate = $tvaEnabled ? 0.20 : 0;
$rootOptions = [];
$processedProductIds = [];
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;
$totalHT += $optPrice;
}
}
}
}
$productTotalHT += $optionsTotalHT;
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'product' => $product,
'image' => $uploaderHelper->asset($product, 'imageFile'),
'price1Day' => $price1Day,
'priceSup' => $priceSup,
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
'options' => $productOptions
];
$totalHT += $productTotalHT;
}
// 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
];
$totalHT += $optPrice;
}
}
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper);
// --- Calcul Frais de Livraison ---
$deliveryEstimation = null;
$deliveryDetails = null;
$deliveryGeometry = null;
if ($session->getAdressEvent() && $session->getZipCodeEvent() && $session->getTownEvent()) {
$query = sprintf('%s %s %s', $session->getAdressEvent(), $session->getZipCodeEvent(), $session->getTownEvent());
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'];
$deliveryGeometry = $itineraire['geometry'] ?? null;
$rate = 0.50;
$trips = 4;
if ($distance <= 10) {
$deliveryEstimation = 0.0;
$chargedDistance = 0.0;
} else {
$chargedDistance = $distance - 10;
$deliveryEstimation = ($chargedDistance * $trips) * $rate;
}
$deliveryDetails = [
'distance' => $distance,
'chargedDistance' => $chargedDistance,
'trips' => $trips,
'rate' => $rate,
'isFree' => ($distance <= 10)
];
}
} catch (\Exception $e) {
// Silent fail for delivery calculation in flow
}
}
$deliveryData = $this->calculateDelivery(
$session->getAdressEvent(),
$session->getZipCodeEvent(),
$session->getTownEvent(),
$client
);
return $this->render('revervation/flow_confirmed.twig', [
'session' => $session,
'cart' => [
'items' => $items,
'options' => $rootOptions,
'items' => $cartData['items'],
'options' => $cartData['rootOptions'],
'startDate' => $startStr ? new \DateTimeImmutable($startStr) : null,
'endDate' => $endStr ? new \DateTimeImmutable($endStr) : null,
'duration' => $duration,
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC,
'tvaEnabled' => $tvaEnabled,
'totalHT' => $cartData['total']['totalHT'],
'totalTva' => $cartData['total']['totalTva'],
'totalTTC' => $cartData['total']['totalTTC'],
'tvaEnabled' => $cartData['tvaEnabled'],
],
'delivery' => [
'estimation' => $deliveryEstimation,
'details' => $deliveryDetails,
'geometry' => $deliveryGeometry
]
'delivery' => $deliveryData
]);
}
@@ -794,11 +528,8 @@ class ReserverController extends AbstractController
ProductRepository $productRepository,
UploaderHelper $uploaderHelper,
ProductReserveRepository $productReserveRepository,
EntityManagerInterface $em,
\App\Repository\OptionsRepository $optionsRepository
OptionsRepository $optionsRepository
): Response {
// This is the POST target for the login form, but also the GET page.
// The authenticator handles the POST. For GET, we just render the page.
$session = $repository->findOneBy(['uuid' => $sessionId]);
if (!$session) {
return $this->render('revervation/session_lost.twig');
@@ -822,19 +553,7 @@ class ReserverController extends AbstractController
return $this->redirectToRoute('reservation');
}
// Calcul de la durée
$duration = 1;
if ($startStr && $endStr) {
try {
$start = new \DateTimeImmutable($startStr);
$end = new \DateTimeImmutable($endStr);
if ($end >= $start) {
$duration = $start->diff($end)->days + 1;
}
} catch (\Exception $e) {
$duration = 1;
}
}
$duration = $this->calculateDuration($startStr, $endStr);
$products = [];
if (!empty($ids)) {
@@ -846,101 +565,26 @@ class ReserverController extends AbstractController
if (count($foundIds) !== count($ids)) {
$sessionData['ids'] = $foundIds;
$session->setProducts($sessionData);
$em->flush();
// We should probably flush, but no EntityManager injected here in original code for GET/POST mix.
// Added simple cleanup.
}
$items = [];
$totalHT = 0;
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
$tvaRate = $tvaEnabled ? 0.20 : 0;
$rootOptions = [];
$processedProductIds = [];
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;
$totalHT += $optPrice;
}
}
}
}
$productTotalHT += $optionsTotalHT;
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'product' => $product,
'image' => $uploaderHelper->asset($product, 'imageFile'),
'price1Day' => $price1Day,
'priceSup' => $priceSup,
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
'options' => $productOptions
];
$totalHT += $productTotalHT;
}
// 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
];
$totalHT += $optPrice;
}
}
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
$cartData = $this->buildCartData($products, $selectedOptionsMap, $duration, $optionsRepository, $uploaderHelper);
return $this->render('revervation/flow.twig', [
'session' => $session,
'last_username' => $authenticationUtils->getLastUsername(),
'error' => $authenticationUtils->getLastAuthenticationError(),
'cart' => [
'items' => $items,
'options' => $rootOptions,
'items' => $cartData['items'],
'options' => $cartData['rootOptions'],
'startDate' => $startStr ? new \DateTimeImmutable($startStr) : null,
'endDate' => $endStr ? new \DateTimeImmutable($endStr) : null,
'duration' => $duration,
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC,
'tvaEnabled' => $tvaEnabled,
'totalHT' => $cartData['total']['totalHT'],
'totalTva' => $cartData['total']['totalTva'],
'totalTTC' => $cartData['total']['totalTTC'],
'tvaEnabled' => $cartData['tvaEnabled'],
]
]);
}
@@ -1029,7 +673,7 @@ class ReserverController extends AbstractController
{
return $this->render('revervation/catalogue.twig', [
'products' => $productRepository->findBy(['isPublish' => true]),
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
'tvaEnabled' => $this->isTvaEnabled(),
]);
}
@@ -1038,7 +682,7 @@ class ReserverController extends AbstractController
{
return $this->render('revervation/formules.twig', [
'formules' => $formulesRepository->findBy(['isPublish' => true], ['pos' => 'ASC']),
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
'tvaEnabled' => $this->isTvaEnabled(),
]);
}
@@ -1056,7 +700,7 @@ class ReserverController extends AbstractController
return $this->render('revervation/formule/show.twig', [
'formule' => $formule,
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
'tvaEnabled' => $this->isTvaEnabled(),
]);
}
@@ -1093,7 +737,7 @@ class ReserverController extends AbstractController
return $this->render('revervation/produit.twig', [
'product' => $product,
'tvaEnabled' => isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true",
'tvaEnabled' => $this->isTvaEnabled(),
'otherProducts' => array_slice($otherProducts, 0, 4)
]);
}
@@ -1357,77 +1001,238 @@ class ReserverController extends AbstractController
$form->handleRequest($request);
$estimation = null;
$details = null;
$geometry = null;
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$query = sprintf('%s %s %s', $data['address'], $data['zipCode'], $data['city']);
$deliveryData = $this->calculateDelivery($data['address'], $data['zipCode'], $data['city'], $client);
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
$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'];
$geometry = $itineraire['geometry'] ?? null;
$rate = 0.50;
$trips = 4;
if ($distance <= 10) {
$estimation = 0.0;
$chargedDistance = 0.0;
} else {
$chargedDistance = $distance - 10;
$estimation = ($chargedDistance * $trips) * $rate;
}
$details = [
'distance' => $distance,
'chargedDistance' => $chargedDistance,
'trips' => $trips,
'rate' => $rate,
'isFree' => ($distance <= 10)
];
} else {
$this->addFlash('warning', 'Adresse introuvable.');
}
} catch (\Exception $e) {
$this->addFlash('error', 'Erreur lors du calcul de l\'itinéraire.');
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 ?? null,
'geometry' => $geometry ?? null
'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): array
{
$items = [];
$rootOptions = [];
$totalHT = 0;
$tvaEnabled = $this->isTvaEnabled();
$tvaRate = $tvaEnabled ? 0.20 : 0;
$processedProductIds = [];
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;
$totalHT += $optPrice;
}
}
}
}
$productTotalHT += $optionsTotalHT;
$productTotalTTC = $productTotalHT * (1 + $tvaRate);
$items[] = [
'id' => $product->getId(), // Ensure ID is present for basketJson
'name' => $product->getName(),
'product' => $product, // Kept for twig compatibility if needed
'image' => $uploaderHelper->asset($product, 'imageFile'),
'priceHt1Day' => $price1Day, // For basketJson
'priceHTSupDay' => $priceSup, // For basketJson
'priceTTC1Day' => $price1Day * (1 + $tvaRate),
'price1Day' => $price1Day, // For flow template
'priceSup' => $priceSup, // For flow template
'totalPriceHT' => $productTotalHT,
'totalPriceTTC' => $productTotalTTC,
'options' => $productOptions
];
$totalHT += $productTotalHT;
}
// 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
];
$totalHT += $optPrice;
}
}
}
$totalTva = $totalHT * $tvaRate;
$totalTTC = $totalHT + $totalTva;
return [
'items' => $items,
'rootOptions' => $rootOptions,
'total' => [
'totalHT' => $totalHT,
'totalTva' => $totalTva,
'totalTTC' => $totalTTC
],
'tvaEnabled' => $tvaEnabled
];
}
}