```
✨ feat(ReserverController): Refactorise le contrôleur de réservation.
```
This commit is contained in:
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user