src/AtechNet/CoreBundle/Controller/CartController.php line 56

Open in your IDE?
  1. <?php
  2. namespace App\AtechNet\CoreBundle\Controller;
  3. use App\AtechNet\CoreBundle\Entity\Attribute;
  4. use App\AtechNet\CoreBundle\Entity\AttributeOption;
  5. use App\AtechNet\CoreBundle\Entity\AttributeOptionMeta;
  6. use App\AtechNet\CoreBundle\Entity\Basket;
  7. use App\AtechNet\CoreBundle\Entity\BasketItem;
  8. use App\AtechNet\CoreBundle\Entity\BasketItemAttributeOption;
  9. use App\AtechNet\CoreBundle\Entity\Coupon;
  10. use App\AtechNet\CoreBundle\Entity\Customer;
  11. use App\AtechNet\CoreBundle\Entity\Domain;
  12. use App\AtechNet\CoreBundle\Entity\DomainPaymentType;
  13. use App\AtechNet\CoreBundle\Entity\DomainShippingType;
  14. use App\AtechNet\CoreBundle\Entity\Product;
  15. use App\AtechNet\CoreBundle\Entity\ProductType;
  16. use App\AtechNet\CoreBundle\Entity\Vendor;
  17. use App\AtechNet\CoreBundle\Enum\PaymentType;
  18. use App\AtechNet\CoreBundle\Helper\Furbify\CartHelper;
  19. use App\AtechNet\CoreBundle\Helper\Furbify\Image;
  20. use App\AtechNet\CoreBundle\Helper\Furbify\MultiStore;
  21. use App\AtechNet\CoreBundle\Helper\Packeta;
  22. use Doctrine\Common\Collections\ArrayCollection;
  23. use Symfony\Component\HttpFoundation\JsonResponse;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use Symfony\Component\HttpFoundation\Response;
  26. use Symfony\Component\Routing\Annotation\Route;
  27. class CartController extends AbstractController
  28. {
  29.     public const TIMEOUT = 2; // hours
  30.     private $unWantedAttributes = [
  31.         237, 239, 247, 241, 387, 388, 405, 409, 415, 419, 529, 854, 940, 1522, 1523, 1536, 1537,
  32.     ];
  33.     /**
  34.      * @Route("/cart", name="cart_show", defaults={"_locale":""})
  35.      * @Route("/{_locale<%supported_locales%>?}/cart", name="cart_show_locale", defaults={"_locale":""})
  36.      * @param Request $request
  37.      * @return Response
  38.      * @throws \Exception
  39.      */
  40.     public function showAction(Request $request)
  41.     {
  42.         $request->getSession()->remove('abandonedCart');
  43.         $data = [];
  44.         $domain = $this->getMultiStoreHelper($request)->getDomain();
  45.         $data['phone_support'] = $this->getContactInfos()["phone.webshop.{$domain->getLanguage()->getCode()}.{$domain->getType()}"];
  46.         $lastRemovedCartItem = $request->getSession()->get('lastRemovedCartItem');
  47.         if (!empty($lastRemovedCartItem)) {
  48.             $data['removedItem'] = $lastRemovedCartItem;
  49.             $data['removedItem']['href'] = $this->url($request)->link('product', [
  50.                 'product_id' => $lastRemovedCartItem['productId'],
  51.             ]);
  52.         }
  53.         $data['domainId'] = 0;
  54.         $data['header'] = $this->getHeader($request);
  55.         $data['footer'] = $this->getFooter($request);
  56.         $data['contentBottom'] = $this->getContentBottom($request);
  57.         $data['stepsOptions'] = $this->checkoutSteps(1);
  58.         $data['hideChat'] = true;
  59.         $cartId = $request->getSession()->get('cartId');
  60.         $isAbandoned = false;
  61.         $hasLoginError = null;
  62.         $entityManager = $this->getEntityManager();
  63.         if ($request->get('pi') && $request->get('pi') != '') {
  64.             $abandonedCartData = json_decode(base64_decode($request->get('pi')));
  65.             $cartId = $abandonedCartData->id;
  66.             $isAbandoned = true;
  67.             $request->getSession()->set('abandonedCart', $abandonedCartData);
  68.             // Check if customer has to be logged in
  69.             if (property_exists($abandonedCartData, "customerId")) {
  70.                 $hasLoginError = $this->checkCustomerLoginStatusForAbandonedCart($request, $abandonedCartData);
  71.             }
  72.         }
  73.         if (!is_null($hasLoginError)) {
  74.             $data['loginError'] = $hasLoginError;
  75.             if ($hasLoginError == 'redirectToLogin') {
  76.                 return $this->redirectToRoute('customer_login', [
  77.                     'pi' => $request->get('pi'),
  78.                 ]);
  79.             }
  80.             return $this->render('AtechNetCoreBundle/Checkout/cart.html.twig', $data);
  81.         }
  82.         /**
  83.          * @var Basket $cart
  84.          */
  85.         $cart = $isAbandoned ? $this->getAbandonedCart($cartId) : $this->getCart($cartId);
  86.         if (!$cart) {
  87.             return $this->render('AtechNetCoreBundle/Checkout/cart.html.twig', $data);
  88.         }
  89.         $request->getSession()->set('cartId', $cartId);
  90.         if ($isAbandoned) {
  91.             $cart->setUpdated((new \DateTime('NOW')));
  92.             $cart->setActive(true);
  93.             $entityManager->flush();
  94.         }
  95.         // Basket expires in...
  96.         $expire = $cart->getUpdated()->add(new \DateInterval("PT" . self::TIMEOUT . "H"));
  97.         // Empty Basket
  98.         // if (!$isAbandoned && $expire <= (new \DateTime('NOW'))) {
  99.         //     $entityManager->remove($cart);
  100.         //     $entityManager->flush();
  101.         //     return $this->redirectToRoute('cart_show');
  102.         // }
  103.         $multiStore = $this->getMultiStoreHelper($request);
  104.         $domainId = $multiStore->getDomainId();
  105.         $data['currency'] = $domain->getCurrency();
  106.         $data['precision'] = $domain->getPricePrecision();
  107.         $placeholder = $this->getImage('placeholder.jpg', '')->resize(Image::PRODUCT_CART_THUMB_WIDTH, Image::PRODUCT_CART_THUMB_HEIGHT);
  108.         $data['stock'] = true;
  109.         $data['total'] = 0;
  110.         $data['subTotal'] = 0;
  111.         $data['items'] = [];
  112.         $data['marketingProductsGA4'] = '';
  113.         $data['cartBackToProductsLink'] = $request->getSession()->get('cartBackToProductsLink');
  114.         $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  115.         $useZeroVat = $this->useZeroVat($customer, $request);
  116.         $productHelper = $this->getProductHelper($request);
  117.         $databaseTranslator = $this->getDatabaseTranslator($request);
  118.         $enableFreeShipping = false;
  119.         /**
  120.          * @var BasketItem $item
  121.          */
  122.         foreach ($cart->getBasketItems() as $index => $item) {
  123.             $itemProduct = $item->getProduct();
  124.             $product = $productHelper->getStructuredDataForProduct($itemProduct, 'cart');
  125.             if ($product) {
  126.                 if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  127.                     $product['vatPrice'] = $domain->createVatPrice($product['rawPrice'], 1.27);
  128.                     if ($product['isDiscounted']) {
  129.                         $product['discountVatPrice'] = $domain->createVatPrice($product['rawDiscountPrice'], 1.27);
  130.                     }
  131.                 }
  132.                 $stock = true;
  133.                 $image = $placeholder;
  134.                 if (is_object($product['images'])) {
  135.                     $image = $this->getImage($product['images'], 'product/' . $product['code'])->resize(Image::PRODUCT_CART_THUMB_WIDTH, Image::PRODUCT_CART_THUMB_HEIGHT);
  136.                 }
  137.                 // Cross-sell Attribute Options
  138.                 $crossSellAttributeOptions = CartHelper::getCrossSellAttributeOptionsByProductTypeId($product['productTypeId'], $domain->getId());
  139.                 $options = [];
  140.                 $optionPrice = $optionPriceValue = $optionSpecialValue = 0;
  141.                 if (in_array($domainId, MultiStore::getWholesaleDomainIDs())){
  142.                     $norton = $item->getItemAttributes()->filter(function (BasketItemAttributeOption $attribute) {
  143.                         return $attribute->getAttributeOption()->getId() == AttributeOption::ATTRIBUTE_OPTION_ID_NORTON_ANTIVIRUS_FREE;
  144.                     })->first();
  145.                     if ($norton){
  146.                         $item->getItemAttributes()->removeElement($norton);
  147.                         $entityManager->persist($item);
  148.                         $entityManager->flush();
  149.                     }
  150.                 }
  151.                 foreach ($item->getItemAttributes() as $attribute) {
  152.                     /** @var AttributeOption $option */
  153.                     $option = $attribute->getAttributeOption();
  154.                     /** @var AttributeOptionMeta $optionMeta */
  155.                     $optionMeta = $option->getDomainMeta($domain);
  156.                     if ($optionMeta) {
  157.                         $optionValue = $option->getLongTitle($databaseTranslator);
  158.                         if (!$optionValue) {
  159.                             $optionValue = $option->getTitle($databaseTranslator);
  160.                         }
  161.                         $optionTitle = $option->getAttribute()->getTitle($databaseTranslator);
  162.                         // Option Price excluding VAT
  163.                         if ($useZeroVat) {
  164.                             if ($optionMeta->isDiscounted()) {
  165.                                 $price = $optionMeta->getDomainDiscount();
  166.                                 $optionSpecialValue += $optionMeta->getDomainDiscount();
  167.                             } else {
  168.                                 $price = $optionMeta->getDomainPrice();
  169.                             }
  170.                             $optionPriceValue += $optionMeta->getDomainPrice();
  171.                         } // Option Price including VAT
  172.                         else {
  173.                             if ($optionMeta->isDiscounted()) {
  174.                                 if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  175.                                     $price = $domain->createVatPrice($optionMeta->getDiscount(), 1.27);
  176.                                     $optionSpecialValue += $domain->createVatPrice($optionMeta->getDiscount(), 1.27);
  177.                                 } else {
  178.                                     $price = $optionMeta->getDomainVatDiscount();
  179.                                     $optionSpecialValue += $optionMeta->getDomainVatDiscount();
  180.                                 }
  181.                             } else {
  182.                                 if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  183.                                     $price = $domain->createVatPrice($optionMeta->getPrice(), 1.27);
  184.                                 } else {
  185.                                     $price = $optionMeta->getDomainVatPrice();
  186.                                 }
  187.                             }
  188.                             if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  189.                                 $optionPriceValue += $domain->createVatPrice($optionMeta->getPrice(), 1.27);
  190.                             } else {
  191.                                 $optionPriceValue += $optionMeta->getDomainVatPrice();
  192.                             }
  193.                         }
  194.                         $optionModel = $optionHref = '';
  195.                         $optionProduct = $option->getProduct();
  196.                         if ($optionProduct) {
  197.                             $optionModel = $optionProduct->getCode();
  198.                             $optionHref = $this->url($request)->link('product', [
  199.                                 'product_id' => $optionProduct->getId(), 
  200.                                 'isBazaar'   => $optionProduct->isBazaar()
  201.                             ]);
  202.                         }
  203.                         // If Attribute Option is for Cross-sell
  204.                         if (isset($crossSellAttributeOptions[$option->getId()])) {
  205.                             if (isset($crossSellAttributeOptions[$option->getId()]['percentPrice'])) {
  206.                                 $price = $domain->createVatPrice((($item->getPriceWithAttributesForDomainWithException($domain, $option) * $crossSellAttributeOptions[$option->getId()]['percentPrice']) / 100), 1.);
  207.                             }
  208.                         }
  209.                         $configurationProduct = $optionProduct ? $this->getProductHelper($request)->getStructuredDataByProductId($optionProduct->getId(), 'configurator') : null;
  210.                         $options[] = [
  211.                             'id' => $attribute->getId(),
  212.                             'title' => $optionTitle,
  213.                             'value' => $optionValue,
  214.                             'price' => $price * $item->getQuantity(),
  215.                             'model' => $optionModel,
  216.                             'href' => $optionHref,
  217.                             'image' => $configurationProduct ? $this->getImage($configurationProduct['images'], 'product/' . $optionProduct->getId())->resize(Image::PRODUCT_CART_THUMB_WIDTH, Image::PRODUCT_CART_THUMB_HEIGHT) : $this->getImage('placeholder.png', '')->resize(Image::PRODUCT_CART_THUMB_WIDTH, Image::PRODUCT_CART_THUMB_WIDTH),
  218.                         ];
  219.                         $optionPrice += $price;
  220.                     }
  221.                 }
  222.                 // Price
  223.                 $priceValue = $specialValue = $originalPrice = 0;
  224.                 if ($useZeroVat) {
  225.                     if ($product['isDiscounted']) {
  226.                         $price = $specialValue = $product['rawDiscountPrice'];
  227.                         $originalPrice = $product['price'];
  228.                     } else {
  229.                         $price = $product['price'];
  230.                     }
  231.                     $priceValue = $product['price'];
  232.                 } else {
  233.                     if ($product['isDiscounted']) {
  234.                         if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  235.                             $price = $specialValue = $domain->createVatPrice($product['discountPrice'], 1.27);
  236.                             $originalPrice = $domain->createVatPrice($product['rawPrice'], 1.27);
  237.                         } else {
  238.                             $price = $specialValue = $product['discountVatPrice'];
  239.                             $originalPrice = $product['vatPrice'];
  240.                         }
  241.                     } else {
  242.                         if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  243.                             $price = $domain->createVatPrice($product['rawPrice'], 1.27);
  244.                         } else {
  245.                             $price = $product['vatPrice'];
  246.                         }
  247.                     }
  248.                     if (isset($customer) && $customer->getId() == Customer::MAXMEDIA_KFT_ID) {
  249.                         $priceValue = $domain->createVatPrice($product['rawPrice'], 1.27);
  250.                     } else {
  251.                         $priceValue = $product['vatPrice'];
  252.                     }
  253.                 }
  254.                 // Stock
  255.                 if ($itemProduct->isContainer()) {
  256.                     foreach ($itemProduct->getProductContainers() as $container) {
  257.                         $child = $container->getChild();
  258.                         if (is_null($child)) {
  259.                             continue;
  260.                         }
  261.                         if ($child) {
  262.                             if (!$child->isAvailable()) {
  263.                                 $stock = false;
  264.                                 break;
  265.                             }
  266.                             if ($child->getAvailable() < ($container->getMultiplier() * $item->getQuantity())) {
  267.                                 $stock = false;
  268.                                 break;
  269.                             }
  270.                         }
  271.                     }
  272.                 } else { // Simple product
  273.                     if (!$itemProduct->isAvailable()) {
  274.                         $stock = false;
  275.                     }
  276.                     if ($itemProduct->getAvailable() < $item->getQuantity()) {
  277.                         $stock = false;
  278.                     }
  279.                     if ($product['isReservable']) {
  280.                         $stock = true;
  281.                     }
  282.                 }
  283.                 $data['items'][] = [
  284.                     'id' => $item->getId(),
  285.                     'productId' => $product['id'],
  286.                     'typeId' => $product['productTypeId'],
  287.                     'model' => $product['code'],
  288.                     'name' => ($product['vendor'] !== '' ? $product['vendor'] . ' ' : '') . $product['title'],
  289.                     'description' => $product['shortDescription'],
  290.                     'image' => $image,
  291.                     'quantity' => $item->getQuantity(),
  292.                     'originalPrice' => $product['isDiscounted'] ? ($originalPrice * $item->getQuantity()) : false,
  293.                     'price' => $price * $item->getQuantity(),
  294.                     'options' => $options,
  295.                     'stock' => $stock,
  296.                     'href' => $this->url($request)->link('product', [
  297.                         'product_id' => $product['id'], 
  298.                         'isBazaar'   => $product['isBazaar'] ?? 0
  299.                     ]),
  300.                 ];
  301.                 $data['subTotal'] += ($price + $optionPrice) * $item->getQuantity();
  302.                 if (!$stock) {
  303.                     $data['stock'] = false;
  304.                 }
  305.                 $data['marketingProductsGA4'] .= ($data['marketingProductsGA4'] !== '' ? ',' : '') . '{ item_id: \'' . $product['code'] . '\', item_name: \'' . ($product['vendor'] !== '' ? $product['vendor'] . ' ' : '') . $product['title'] . '\', item_brand: \'' . $product['vendor'] . '\', item_category: \'' . $product['productType'] . '\', quantity: ' . $item->getQuantity() . ', discount: \'' . (round(($specialValue + $optionSpecialValue), $data['precision']) > 0 ? (round(($priceValue + $optionPriceValue), $data['precision']) - round(($specialValue + $optionSpecialValue), $data['precision'])) : 0) . '\', price: \'' . round(($priceValue + $optionPriceValue), $data['precision']) . '\', index: ' . $index . '}';
  306.             }
  307.             if (!$enableFreeShipping && $cart->getDomain()->getLanguage()->getCode() != 'en') {
  308.                 /**
  309.                  * CHECK IF EXIST A SHIPPING METHOD WITH FREE SHIPPING
  310.                  */
  311.                 $shippingTypes = $entityManager->createQueryBuilder()
  312.                     ->select('dst')
  313.                     ->from('AtechNetCoreBundle:DomainShippingType', 'dst')
  314.                     ->where('dst.domain = :domain')
  315.                     ->andWhere('dst.freeShippingStartDate IS NOT NULL AND dst.freeShippingEndDate IS NOT NULL')
  316.                     ->andWhere('dst.freeShippingStartDate <= :now AND dst.freeShippingEndDate >= :now')
  317.                     ->setParameters([
  318.                         'domain' => $domain->getId(),
  319.                         'now' => (new \DateTime())->format('Y-m-d'),
  320.                     ])
  321.                     ->getQuery()
  322.                     ->getResult();
  323.                 $filteredShippingTypes = array_filter($shippingTypes, function ($shippingType) use ($product) {
  324.                     /** @var DomainShippingType $shippingType */
  325.                     return in_array($product['productTypeId'], $shippingType->getFreeShippingProductTypes(), true);
  326.                 });
  327.                 $enableFreeShipping = count($filteredShippingTypes) > 0;
  328.             }
  329.         }
  330.         $data['expire'] = $expire->format('Y-m-d H:i:s');
  331.         $data['estimation'] = true;
  332.         $data['coupon'] = $data['shippingTitle'] = '';
  333.         $data['paymentCost'] = $data['shippingCost'] = $data['discount'] = 0;
  334.         $data['shippingIsSelected'] = false;
  335.         $data['domainId'] = $domainId;
  336.         $dataForLowestShippingPriceFunction = [
  337.             'domain' => $domain,
  338.             'cart' => $cart,
  339.             'subTotal' => $data['subTotal'],
  340.             'packetaCodLimit' => Packeta::getCodLimitByDomainMarketCode(strtoupper($domain->getLanguage()->getCode())),
  341.         ];
  342.         $translator = $this->getTranslator();
  343.         if ($request->getSession()->has('checkout')) {
  344.             $checkout = $request->getSession()->get('checkout');
  345.             // Selected Shipping Method
  346.             if (isset($checkout['shippingMethodId']) && (int)$checkout['shippingMethodId'] > 0) {
  347.                 $shippingMethod = $entityManager->getRepository(DomainShippingType::class)->findOneBy([
  348.                     'id' => $checkout['shippingMethodId'],
  349.                     'domain' => $domainId,
  350.                 ]);
  351.                 if ($shippingMethod) {
  352.                     // Shipping Cost
  353.                     $data['shippingCost'] = $shippingMethod->getPrice();
  354.                     // Free Shipping
  355.                     if ((float)$shippingMethod->getFreeShipLimit() > 0 && (float)$shippingMethod->getFreeShipLimit() < $data['subTotal']) {
  356.                         $data['shippingCost'] = 0;
  357.                     }
  358.                     if ($enableFreeShipping) {
  359.                         $data['shippingCost'] = 0;
  360.                     }
  361.                     $data['estimation'] = false;
  362.                     $data['shippingIsSelected'] = true;
  363.                 }
  364.             } // Default Shipping Method
  365.             else {
  366.                 $shippingMethodWithLowestPrice = CartHelper::getShippingMethodWithLowestPriceForDomain($dataForLowestShippingPriceFunction, $entityManager);
  367.                 if ($shippingMethodWithLowestPrice && !$enableFreeShipping) {
  368.                     // Lowest shipping Cost
  369.                     $data['shippingCost'] = $shippingMethodWithLowestPrice->getPrice();
  370.                     // Lowest shipping Name
  371.                     $data['shippingTitle'] = str_replace("(%address%)", "", $translator->trans("shipping.method.type." . $shippingMethodWithLowestPrice->getType()));
  372.                     // Free Shipping
  373.                     if ((float)$shippingMethodWithLowestPrice->getFreeShipLimit() > 0 && (float)$shippingMethodWithLowestPrice->getFreeShipLimit() < $data['subTotal']) {
  374.                         $data['shippingCost'] = 0;
  375.                     }
  376.                     $data['estimation'] = false;
  377.                 }
  378.             }
  379.             $data['freeShipping'] = $enableFreeShipping;
  380.             if (isset($checkout['paymentMethodId']) && (int)$checkout['paymentMethodId'] > 0) {
  381.                 $paymentMethod = $entityManager->getRepository(DomainPaymentType::class)->findOneBy([
  382.                     'id' => $checkout['paymentMethodId'],
  383.                     'domain' => $domainId,
  384.                 ]);
  385.                 if ($paymentMethod) {
  386.                     $data['paymentCost'] = $paymentMethod->getPrice();
  387.                 }
  388.             }
  389.             if (isset($checkout['paymentMethodTypeId']) && (int)$checkout['paymentMethodTypeId'] == PaymentType::CashOnDelivery->value) {
  390.                 $domainPaymentType = $entityManager->getRepository(DomainPaymentType::class)->findOneBy(
  391.                     [
  392.                         'domain' => $multiStore->getDomainId(),
  393.                         'type' => $checkout['paymentMethodTypeId'],
  394.                     ]
  395.                 );
  396.                 if ($domainPaymentType) {
  397.                     $data['cashOnDeliveryProductItem'] = [
  398.                         'title' => $translator->trans('Cash on delivery'),
  399.                         'price' => $domainPaymentType->getPrice(),
  400.                     ];
  401.                 }
  402.             }
  403.         } else {
  404.             $shippingMethodWithLowestPrice = CartHelper::getShippingMethodWithLowestPriceForDomain($dataForLowestShippingPriceFunction, $entityManager);
  405.             if ($shippingMethodWithLowestPrice) {
  406.                 // Shipping Cost
  407.                 $data['shippingCost'] = $shippingMethodWithLowestPrice->getPrice();
  408.                 // Lowest shipping Name
  409.                 $data['shippingTitle'] = str_replace("(%address%)", "", $translator->trans("shipping.method.type." . $shippingMethodWithLowestPrice->getType()));
  410.                 // Free Shipping
  411.                 if ((float)$shippingMethodWithLowestPrice->getFreeShipLimit() > 0 && (float)$shippingMethodWithLowestPrice->getFreeShipLimit() < $data['subTotal']) {
  412.                     $data['shippingCost'] = 0;
  413.                 }
  414.                 $data['estimation'] = false;
  415.             }
  416.         }
  417.         // Coupon
  418.         if (isset($checkout['coupon']['code']) && $checkout['coupon']['code'] != '') {
  419.             $data['coupon'] = $checkout['coupon']['code'];
  420.             $coupon = $entityManager->getRepository(Coupon::class)->findOneBy([
  421.                 'code' => $checkout['coupon']['code'],
  422.                 'domain' => $domain,
  423.             ]);
  424.             if ($coupon) {
  425.                 // Set Email
  426.                 if (!empty($checkout['email'])) {
  427.                     $cart->setEmail($checkout['email']);
  428.                 } else {
  429.                     $cart->setEmail(null);
  430.                 }
  431.                 // Coupon Free Shipping
  432.                 $isCouponApplicableForFreeShipping = CartHelper::isCouponApplicableForFreeShipping($coupon, $cart, $entityManager);
  433.                 // Coupon Discount
  434.                 $isCouponValidForBasket = CartHelper::isCouponValidForBasket($coupon, $cart, $entityManager);
  435.                 if ($isCouponValidForBasket !== false || $isCouponApplicableForFreeShipping) {
  436.                     $data['discount'] = (float)$isCouponValidForBasket;
  437.                 } else {
  438.                     $data['coupon'] = '';
  439.                 }
  440.                 // Free Shipping
  441.                 if ($isCouponApplicableForFreeShipping) {
  442.                     $data['shippingCost'] = 0;
  443.                 }
  444.             } else {
  445.                 $data['coupon'] = '';
  446.             }
  447.         }
  448.         if (isset($checkout['3_percent_discount']) && $checkout['3_percent_discount'] && $data['discount'] == 0) {
  449.             if ($cart) {
  450.                 $data['discount'] = CartHelper::getPercentDiscountForBasket($cart, 3);
  451.                 $data['three_percent_partner_discount'] = true;
  452.             }
  453.         }
  454.         $shippingCost = $data['shippingIsSelected'] ? $data['shippingCost'] : 0;
  455.         $data['total'] = $data['subTotal'] + $shippingCost + $data['paymentCost'] - $data['discount'];
  456.         $data['currency_ISO'] = $this->getCurrencyISOForm($domain->getCurrency());
  457.         return $this->render('AtechNetCoreBundle/Checkout/cart.html.twig', $data);
  458.     }
  459.     /**
  460.      * @var Request $request
  461.      * @var int|null $customerId
  462.      */
  463.     private function checkCustomerLoginStatusForAbandonedCart(Request $request, ?object $abandonedCartObject)
  464.     {
  465.         $translator = $this->getTranslator();
  466.         /**
  467.          * @var Customer $loggedInCustomer
  468.          */
  469.         $loggedInCustomer = $this->getLoginHelper($request)->getLoggedInCustomer();
  470.         if (!$abandonedCartObject->customerId) {
  471.             if ($loggedInCustomer) {
  472.                 return [
  473.                     'alertText' => $translator->trans('Another profile (with another email) is signed in. You have to log out first to continue abandoned cart ordering.'),
  474.                 ];
  475.             }
  476.             return null;
  477.         }
  478.         $customerId = $abandonedCartObject->customerId;
  479.         $entityManager = $this->getEntityManager();
  480.         /**
  481.          * @var Customer $customer
  482.          */
  483.         $customer = $entityManager->getRepository(Customer::class)->findOneById($customerId);
  484.         if ($customer->isGuest()) {
  485.             if ($loggedInCustomer) {
  486.                 return [
  487.                     'alertText' => $translator->trans('Another profile (with another email) is signed in. You have to log out first to continue abandoned cart ordering.'),
  488.                 ];
  489.             }
  490.             return null;
  491.         }
  492.         if (!$loggedInCustomer) {
  493.             return 'redirectToLogin';
  494.         }
  495.         if ($loggedInCustomer->getId() != $customer->getId()) {
  496.             return [
  497.                 'signInRequired' => true,
  498.                 'alertText' => $translator->trans('Another profile (with another email) is signed in. You have to log out AND log in with your <b>{emailToLogIn}</b> email to continue abandoned cart ordering.', [
  499.                     '{emailToLogIn}' => $abandonedCartObject->email,
  500.                 ]),
  501.             ];
  502.         }
  503.     }
  504.     /**
  505.      * @Route("/cart/add", name="cart_add", defaults={"_locale":""})
  506.      * @Route("/{_locale<%supported_locales%>?}/cart/add", name="cart_add_locale", defaults={"_locale":""})
  507.      * @param Request $request
  508.      * @return JsonResponse
  509.      */
  510.     public function addAction(Request $request)
  511.     {
  512.         $json = [];
  513.         $translator = $this->getTranslator();
  514.         $databaseTranslator = $this->getDatabaseTranslator($request);
  515.         $entityManager = $this->getEntityManager();
  516.         $productId = $request->request->get('product_id');
  517.         /**
  518.          * @var Product $product
  519.          */
  520.         $product = $entityManager->getRepository(Product::class)->findOneById($productId);
  521.         if (!$product) {
  522.             $json['error'] = $this->renderView(
  523.                 'AtechNetCoreBundle/Common/modal.html.twig',
  524.                 [
  525.                     'type'    => 'error',
  526.                     'message' => $translator->trans('Product not found'),
  527.                 ]
  528.             );
  529.             return new JsonResponse($json);
  530.         }
  531.         $multiStore = $this->getMultiStoreHelper($request);
  532.         $productHelper = $this->getProductHelper($request);
  533.         $domain = $multiStore->getDomain();
  534.         $currency = $domain->getCurrency();
  535.         $precision = $domain->getPricePrecision();
  536.         $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  537.         $useZeroVat = $this->useZeroVat($customer, $request);
  538.         $cart = $this->getCart($request->getSession()->get('cartId'));
  539.         if (!$cart) {
  540.             $cart = new Basket();
  541.             $cart->setDomain($domain);
  542.             $cart->setPaymentMethod(0);
  543.             $cart->setShippingMethod(0);
  544.             $cart->setUseBillingAddressAsShippingAddress(false);
  545.             $cart->setMessage('');
  546.             $entityManager->persist($cart);
  547.             $entityManager->flush();
  548.             $request->getSession()->set('cartId', $cart->getId());
  549.         }
  550.         if (is_null($cart->getCustomer())) {
  551.             $loggedInCustomer = $this->getLoginHelper($request)->getLoggedInCustomer();
  552.             if ($loggedInCustomer) {
  553.                 $cart->setCustomer($loggedInCustomer);
  554.             }
  555.         }
  556.         // Stock items
  557.         $stockItems = $request->request->get('stockItems');
  558.         // Quantity
  559.         $quantity = $request->request->get('quantity') ?? 1;
  560.         if ($stockItems) {
  561.             $quantity = count($stockItems);
  562.             $stockItemCodes = $request->getSession()->get('stockItemCodes');
  563.             $stockItemCodes[$productId] = $stockItems;
  564.             $request->getSession()->set('stockItemCodes', $stockItemCodes);
  565.         }
  566.         $options = [];
  567.         if (!is_null($request->request->get('option'))) {
  568.             // Options
  569.             $options = $request->request->get('option');
  570.             // Remove unwanted options
  571.             foreach ($options as $key => $optionId) {
  572.                 if (in_array($optionId, $this->unWantedAttributes)) {
  573.                     unset($options[$key]);
  574.                 }
  575.             }
  576.         }
  577.         if (in_array($product->getType()->getId(), [
  578.             ProductType::PRODUCT_TYPE_ID_PC,
  579.             ProductType::PRODUCT_TYPE_ID_NOTEBOOK,
  580.             ProductType::PRODUCT_TYPE_ID_ALL_IN_ONE,
  581.             ProductType::PRODUCT_TYPE_ID_PHONE,
  582.             ProductType::PRODUCT_TYPE_ID_SMARTPHONE,
  583.             ProductType::PRODUCT_TYPE_ID_TABLET,
  584.             ProductType::PRODUCT_TYPE_ID_PC_SET,
  585.         ]) && in_array($domain->getId(), MultiStore::getRetailDomainIDs()))
  586.         {
  587.             if (!isset($options[Attribute::ATTRIBUTE_ID_ACCESSORY_ANTIVIRUS_SOFTWARE]) || $options[Attribute::ATTRIBUTE_ID_ACCESSORY_ANTIVIRUS_SOFTWARE] == "")
  588.             {
  589.                 $options[Attribute::ATTRIBUTE_ID_ACCESSORY_ANTIVIRUS_SOFTWARE] = AttributeOption::ATTRIBUTE_OPTION_ID_NORTON_ANTIVIRUS_FREE;
  590.             }
  591.         }
  592.         $noWebcam = (new ArrayCollection($product->getSpecificationAttributeOptions(false)))->filter(function (AttributeOption $ao) {
  593.             return $ao->getId() == AttributeOption::ATTRIBUTE_OPTION_NO_WEBCAM;
  594.         })->first();
  595.         if ($product->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK && $noWebcam) {
  596.             $freeWebcamAcc = $entityManager->getRepository(AttributeOption::class)->findOneBy(['id' => AttributeOption::ATTRIBUTE_OPTION_ID_NO_WEBCAM_FREE_WEBCAM]);
  597.             if (!$freeWebcamAcc->getProduct()?->isAvailable() && $freeWebcamAcc->getProduct()?->getCode() == 2040025 /*ez az olcsobbik*/) {
  598.                 $newFreeWebcam = $entityManager->getRepository(Product::class)->findOneBy(['code' => 2040005]);
  599.                 $freeWebcamAcc->setProduct($newFreeWebcam);
  600.                 $entityManager->flush();
  601.                 $entityManager->refresh($freeWebcamAcc);
  602.             }
  603.             if (!isset($options[Attribute::ATTRIBUTE_ID_ACCESSORY_FREE_WEBCAM]) || $options[Attribute::ATTRIBUTE_ID_ACCESSORY_FREE_WEBCAM] == "")
  604.             {
  605.                 $options[Attribute::ATTRIBUTE_ID_ACCESSORY_FREE_WEBCAM] = AttributeOption::ATTRIBUTE_OPTION_ID_NO_WEBCAM_FREE_WEBCAM;
  606.             }
  607.         }
  608.         // Check if item is already in cart
  609.         $exists = false;
  610.         $reachedLimit = false;
  611.         /** @var BasketItem $item */
  612.         foreach ($cart->getBasketItems() as $item) {
  613.             if ($product->getId() == $item->getProduct()->getId()) {
  614.                 $exists = $item->getId();
  615.                 $quantity = $stockItems ? count($stockItems) : $item->getQuantity() + 1;
  616.                 if (!is_null($item->getProduct()->getQuantityLimit()) && $quantity > $item->getProduct()->getQuantityLimit()) {
  617.                     $reachedLimit = true;
  618.                 }
  619.                 break;
  620.             }
  621.         }
  622.         $stock = true;
  623.         $available = true;
  624.         if (!$product->isAvailable()) {
  625.             $stock = false;
  626.         }
  627.         if ($product->getAvailable() < $quantity && $product->getOutOfStockStatus() !== Product::OutOfStockStatus_VisibleShadow) {
  628.             $stock = false;
  629.         }
  630.         if ($product->isReservable()) {
  631.             $stock = true;
  632.         }
  633.         $meta = $product->getDomainMeta($domain);
  634.         if ($meta) {
  635.             if (!$meta->isOrderable()) {
  636.                 $available = false;
  637.             }
  638.             if ($product->isReservable() && $meta->isReady() && $meta->isActive()) {
  639.                 $available = true;
  640.             }
  641.         } else {
  642.             $available = false;
  643.         }
  644.         if (!$available) {
  645.             $json['error'] = $this->renderView(
  646.                 'AtechNetCoreBundle/Common/modal.html.twig',
  647.                 [
  648.                     'type'    => 'add-to-cart-warning',
  649.                     'message' => $translator->trans('Unfortunately, the product {product} is currently unavailable', [
  650.                         'product' => $product->getName(),
  651.                     ]),
  652.                 ]
  653.             );
  654.         } elseif (!$stock) {
  655.             $json['error'] = $this->renderView(
  656.                 'AtechNetCoreBundle/Common/modal.html.twig',
  657.                 [
  658.                     'type'    => 'add-to-cart-warning',
  659.                     'message' => $translator->trans('Unfortunately, the product {product} is not available in the desired quantity or not in stock', [
  660.                         'product' => $product->getName(),
  661.                     ]),
  662.                 ]
  663.             );
  664.         } elseif ($reachedLimit) {
  665.             $json['error'] = $this->renderView(
  666.                 'AtechNetCoreBundle/Common/modal.html.twig',
  667.                 [
  668.                     'type'    => 'add-to-cart-warning',
  669.                     'message' => $translator->trans('Unfortunately, the product {product} has an order quantity limit', [
  670.                         'product' => $product->getName(),
  671.                     ]),
  672.                 ]
  673.             );
  674.         } else {
  675.             if ($exists === false) {
  676.                 $item = new BasketItem();
  677.                 $item->setBasket($cart);
  678.                 $item->setProduct($product);
  679.                 $item->setQuantity($quantity);
  680.                 $cart->addBasketItem($item);
  681.                 $cart->setUpdated(new \DateTime());
  682.                 $entityManager->flush();
  683.                 foreach ($options as $optionId) {
  684.                     $option = $entityManager->getRepository(AttributeOption::class)->findOneById($optionId);
  685.                     if ($option) {
  686.                         $attributeOption = new BasketItemAttributeOption();
  687.                         $attributeOption->setBasketItem($item);
  688.                         $attributeOption->setAttributeOption($option);
  689.                         $entityManager->persist($attributeOption);
  690.                         $entityManager->flush();
  691.                         $entityManager->refresh($item);
  692.                     }
  693.                 }
  694.             } else {
  695.                 $item = $entityManager->getRepository(BasketItem::class)->findOneById($exists);
  696.                 $item->setQuantity($quantity);
  697.                 $cart->setUpdated(new \DateTime());
  698.                 $entityManager->flush();
  699.             }
  700.             $itemProduct = $item->getProduct();
  701.             $productData = $productHelper->getStructuredDataForProduct($itemProduct, 'cart');
  702.             // Image
  703.             $image = $this->getImage('placeholder.jpg', '')->resize(Image::PRODUCT_VIEW_ADDITIONAL_WIDTH, Image::PRODUCT_VIEW_ADDITIONAL_HEIGHT);
  704.             if (is_object($productData['images'])) {
  705.                 $image = $this->getImage($productData['images'], 'product/' . $productData['code'])->resize(Image::PRODUCT_VIEW_ADDITIONAL_WIDTH, Image::PRODUCT_VIEW_ADDITIONAL_HEIGHT);
  706.             }
  707.             // Price
  708.             $specialValue = 0;
  709.             if ($useZeroVat) {
  710.                 if ($productData['isDiscounted']) {
  711.                     $price = $specialValue = $productData['rawDiscountPrice'];
  712.                 } else {
  713.                     $price = $productData['price'];
  714.                 }
  715.                 $priceValue = $productData['price'];
  716.             } else {
  717.                 if ($productData['isDiscounted']) {
  718.                     $price = $specialValue = $productData['discountVatPrice'];
  719.                 } else {
  720.                     $price = $productData['vatPrice'];
  721.                 }
  722.                 $priceValue = $productData['vatPrice'];
  723.             }
  724.             // Option Price
  725.             $optionPrice = $optionPriceValue = $optionSpecialValue = 0;
  726.             $attributeOptions = [];
  727.             if ($item->getItemAttributes()) {
  728.                 foreach ($item->getItemAttributes() as $attribute) {
  729.                     $option = $attribute->getAttributeOption();
  730.                     $optionMeta = $option->getDomainMeta($domain);
  731.                     if ($optionMeta) {
  732.                         // Option Price excluding VAT
  733.                         if ($useZeroVat) {
  734.                             if ($optionMeta->isDiscounted()) {
  735.                                 $optionSpecialValue += $optionMeta->getDomainDiscount();
  736.                             }
  737.                             $optionPriceValue += $optionMeta->getDomainPrice();
  738.                         } // Option Price including VAT
  739.                         else {
  740.                             if ($optionMeta->isDiscounted()) {
  741.                                 $optionSpecialValue += $optionMeta->getDomainVatDiscount();
  742.                             }
  743.                             $optionPriceValue += $optionMeta->getDomainVatPrice();
  744.                         }
  745.                     }
  746.                     $attributeOptions[] = $option->getId();
  747.                 }
  748.             }
  749.             // Cross-sell Attribute Options
  750.             $selectedCrossSellAttributeIds = [];
  751.             $crossSellAttributeOptionsData = [];
  752.             $crossSellAttributeOptions = CartHelper::getCrossSellAttributeOptionsByProductTypeId($productData['productTypeId'], $domain->getId());
  753.             if (!empty($crossSellAttributeOptions)) {
  754.                 $crossSellAttributeOptionsData = [];
  755.                 foreach ($crossSellAttributeOptions as $attributeOptionId => $optionData) {
  756.                     $attributeOption = $entityManager->getRepository(AttributeOption::class)->findOneById($attributeOptionId);
  757.                     if ($attributeOption) {
  758.                         $attributeOptionPrice = false;
  759.                         if (isset($optionData['percentPrice'])) {
  760.                             $attributeOptionPrice = $domain->createVatPrice((($item->getPriceWithAttributesForDomainWithException($domain, $attributeOption) * $optionData['percentPrice']) / 100), 1.);
  761.                         } else {
  762.                             $attributeOptionMeta = $attributeOption->getDomainMeta($domain);
  763.                             if ($attributeOptionMeta) {
  764.                                 // Option Price excluding VAT
  765.                                 if ($useZeroVat) {
  766.                                     if ($attributeOptionMeta->isDiscounted()) {
  767.                                         $attributeOptionPrice = $attributeOptionMeta->getDomainDiscount();
  768.                                     } else {
  769.                                         $attributeOptionPrice = $attributeOptionMeta->getDomainPrice();
  770.                                     }
  771.                                 } // Option Price including VAT
  772.                                 else {
  773.                                     if ($attributeOptionMeta->isDiscounted()) {
  774.                                         $attributeOptionPrice = $attributeOptionMeta->getDomainVatDiscount();
  775.                                     } else {
  776.                                         $attributeOptionPrice = $attributeOptionMeta->getDomainVatPrice();
  777.                                     }
  778.                                 }
  779.                             }
  780.                         }
  781.                         $crossSellAttributeOptionImage = false;
  782.                         if (isset($optionData['image'])) {
  783.                             $crossSellAttributeOptionImage = $optionData['image'];
  784.                         } else {
  785.                             $attributeOptionProduct = $attributeOption->getProduct();
  786.                             if ($attributeOptionProduct) {
  787.                                 if ($attributeOptionProduct->getAlbum()->getPhotos()) {
  788.                                     $crossSellAttributeOptionImage = $this->getImage($attributeOptionProduct->getAlbum()->getPhotos()->first(), 'product/' . $attributeOptionProduct->getCode())->resize(Image::PRODUCT_CART_THUMB_WIDTH, Image::PRODUCT_CART_THUMB_HEIGHT);
  789.                                 }
  790.                             }
  791.                         }
  792.                         $crossSellAttributeOptionsData[] = [
  793.                             'attributeOptionId' => $attributeOptionId,
  794.                             'attributeId'       => $attributeOption->getAttribute()->getId(),
  795.                             'image'             => $crossSellAttributeOptionImage,
  796.                             'name'              => $databaseTranslator->trans($attributeOption->getTitle()),
  797.                             'additionalInfo'    => isset($optionData['additionalInfo']) ? $translator->trans($optionData['additionalInfo']) : false,
  798.                             'price'             => $attributeOptionPrice ? number_format($attributeOptionPrice, $precision, ',', ' ') . ' ' . $currency : false,
  799.                         ];
  800.                         if (in_array($attributeOption->getId(), $attributeOptions)) {
  801.                             $selectedCrossSellAttributeIds[$attributeOption->getAttribute()->getId()] = $attributeOption->getId();
  802.                         }
  803.                     }
  804.                 }
  805.             }
  806.             $json['crossSellAttributeOptionsData'] = $crossSellAttributeOptionsData;
  807.             $currency = $domain->getCurrency();
  808.             $currency_ISO = $this->getCurrencyISOForm($currency);
  809.             $json['success'] = [
  810.                 'data' => [
  811.                     'value'       => [
  812.                         'price'   => round(($priceValue + ($optionPriceValue + $optionSpecialValue)), $precision),
  813.                         'special' => round(($specialValue + ($optionPriceValue + $optionSpecialValue)), $precision),
  814.                     ],
  815.                     'quantity'                      => $quantity,
  816.                     'code'                          => $productData['code'],
  817.                     'name'                          => $productData['title'],
  818.                     'brand'                         => $productData['vendor'],
  819.                     'productType'                   => $productData['productType'],
  820.                 ],
  821.                 'html' => $this->renderView(
  822.                     'AtechNetCoreBundle/Common/modal.html.twig',
  823.                     [
  824.                         'type'                          => 'add-to-cart-success',
  825.                         'productId'                     => $itemProduct->getId(),
  826.                         'quantity'                      => $quantity,
  827.                         'code'                          => $productData['code'],
  828.                         'name'                          => $productData['title'],
  829.                         'brand'                         => $productData['vendor'],
  830.                         'image'                         => $image,
  831.                         'currency_ISO'                  => $currency_ISO,
  832.                         'productType'                   => $productData['productType'],
  833.                         'price'                         => number_format((($price + $optionPriceValue) * $quantity), $precision, ',', ' ') . ' ' . $currency,
  834.                         'value'                         => [
  835.                             'price'   => round(($priceValue + $optionPriceValue), $precision),
  836.                             'special' => round(($specialValue + $optionSpecialValue), $precision),
  837.                         ],
  838.                         'zeroVat'                       => $useZeroVat,
  839.                         'selectedCrossSellAttributeIds' => $selectedCrossSellAttributeIds,
  840.                         'crossSellOptions'              => $crossSellAttributeOptionsData,
  841.                         'qualityStatusId'               => $productData['qualityStatusId'],
  842.                     ]
  843.                 ),
  844.             ];
  845.             // Gift - Pexeso
  846.             if ( 
  847.                 Product::PEXESO_SCHOOL_GIFT_ITEM_ID > 0 
  848.                 && in_array($domain->getId(), [ Domain::DOMAIN_ID_RT_SK, Domain::DOMAIN_ID_RT_CZ, Domain::DOMAIN_ID_RT_HU, Domain::DOMAIN_ID_RT_EU ]) 
  849.                 && $product->getType()?->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK 
  850.                 && $meta->isDiscounted() && $meta->isDiscountValid() 
  851.             ) {
  852.                 $giftBasketItem = null;
  853.                 /** @var BasketItem $basketItem */
  854.                 foreach ( $cart->getBasketItems() as $basketItem ) {
  855.                     if ( $basketItem->getProduct()->getId() == Product::PEXESO_SCHOOL_GIFT_ITEM_ID ) {
  856.                         $giftBasketItem = $basketItem;
  857.                         break;
  858.                     }
  859.                 }
  860.                 if ( is_null($giftBasketItem) ) {
  861.                     $giftProduct = $entityManager->getRepository(Product::class)->find(Product::PEXESO_SCHOOL_GIFT_ITEM_ID);
  862.                     if ( $giftProduct && $giftProduct->getAvailable() > 0 ) {
  863.                         $basketItem = new BasketItem();
  864.                         $basketItem->setBasket($cart);
  865.                         $basketItem->setProduct($giftProduct);
  866.                         $basketItem->setQuantity(1);
  867.                         $entityManager->persist($basketItem);
  868.                         $entityManager->flush();
  869.                     }
  870.                 }
  871.             }
  872.         }
  873.         return new JsonResponse($json);
  874.     }
  875.     /**
  876.      * @Route("/cart/update", name="cart_update", defaults={"_locale":""})
  877.      * @Route("/{_locale<%supported_locales%>?}/cart/update", name="cart_update_locale", defaults={"_locale":""})
  878.      * @param Request $request
  879.      * @return JsonResponse
  880.      * @throws \Exception
  881.      */
  882.     public function updateAction(Request $request)
  883.     {
  884.         $json = [];
  885.         $cart = $this->getCart($request->getSession()->get('cartId'));
  886.         if ($cart) {
  887.             $entityManager = $this->getEntityManager();
  888.             $multiStore = $this->getMultiStoreHelper($request);
  889.             $productHelper = $this->getProductHelper($request);
  890.             $domain = $multiStore->getDomain();
  891.             $currency = $domain->getCurrency();
  892.             $precision = $domain->getPricePrecision();
  893.             $itemId = (int)$request->request->get('item');
  894.             $itemAttributeId = (int)$request->request->get('option');
  895.             $quantity = (int)$request->request->get('quantity');
  896.             $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  897.             $useZeroVat = $this->useZeroVat($customer, $request);
  898.             if ($quantity > 0) {
  899.                 $json['product'] = [
  900.                     'id' => $itemId,
  901.                     'quantity' => 0,
  902.                     'price' => 0,
  903.                     'originalPrice' => 0,
  904.                     'options' => [],
  905.                 ];
  906.                 $json['stock'] = [];
  907.                 $json['total'] = 0;
  908.                 $json['subTotal'] = 0;
  909.                 foreach ($cart->getBasketItems() as $item) {
  910.                     $product = $productHelper->getStructuredDataForProduct($item->getProduct(), 'cart');
  911.                     if ($product) {
  912.                         // Update quantity
  913.                         if ((int)$item->getId() == $itemId) {
  914.                             if (!is_null($item->getProduct()->getQuantityLimit()) && $quantity > $item->getProduct()->getQuantityLimit()) {
  915.                                 $json['error'][] = $item->getId();
  916.                                 return new JsonResponse($json);
  917.                             }
  918.                             $item->setQuantity($quantity);
  919.                             $originalPrice = 0;
  920.                             if ($useZeroVat) {
  921.                                 if ($product['isDiscounted']) {
  922.                                     $price = $product['discountPrice'];
  923.                                     $originalPrice = $product['price'];
  924.                                 } else {
  925.                                     $price = $product['price'];
  926.                                 }
  927.                             } else {
  928.                                 if ($product['isDiscounted']) {
  929.                                     $price = $product['discountVatPrice'];
  930.                                     $originalPrice = $product['vatPrice'];
  931.                                 } else {
  932.                                     $price = $product['vatPrice'];
  933.                                 }
  934.                             }
  935.                             $json['product']['quantity'] = $item->getQuantity();
  936.                             $json['product']['price'] = number_format(($price * $item->getQuantity()), $precision, ',', ' ') . ' ' . $currency;
  937.                             if ($originalPrice > 0) {
  938.                                 $json['product']['originalPrice'] = number_format(($originalPrice * $item->getQuantity()), $precision, ',', ' ') . ' ' . $currency;
  939.                             }
  940.                         }
  941.                         // Cross-sell Attribute Options
  942.                         $crossSellAttributeOptions = CartHelper::getCrossSellAttributeOptionsByProductTypeId($product['productTypeId'], $domain->getId());
  943.                         $optionPrice = 0;
  944.                         foreach ($item->getItemAttributes() as $attribute) {
  945.                             $option = $attribute->getAttributeOption();
  946.                             $optionMeta = $option->getDomainMeta($domain);
  947.                             if ($optionMeta) {
  948.                                 // Option Price excluding VAT
  949.                                 if ($useZeroVat) {
  950.                                     if ($optionMeta->isDiscounted()) {
  951.                                         $price = $optionMeta->getDomainDiscount();
  952.                                     } else {
  953.                                         $price = $optionMeta->getDomainPrice();
  954.                                     }
  955.                                 } // Option Price including VAT
  956.                                 else {
  957.                                     if ($optionMeta->isDiscounted()) {
  958.                                         $price = $optionMeta->getDomainVatDiscount();
  959.                                     } else {
  960.                                         $price = $optionMeta->getDomainVatPrice();
  961.                                     }
  962.                                 }
  963.                                 // Cross-sell Attribute Options Calculated Price
  964.                                 if (isset($crossSellAttributeOptions[$option->getId()])) {
  965.                                     if (isset($crossSellAttributeOptions[$option->getId()]['percentPrice'])) {
  966.                                         $price = $domain->createVatPrice((($item->getPriceWithAttributesForDomainWithException($domain, $option) * $crossSellAttributeOptions[$option->getId()]['percentPrice']) / 100), 1.);
  967.                                     }
  968.                                 }
  969.                                 $json['product']['options'][] = [
  970.                                     'id' => $attribute->getId(),
  971.                                     'price' => ($price > 0 ? number_format(($price * $item->getQuantity()), $precision, ',', ' ') . ' ' . $currency : ''),
  972.                                 ];
  973.                                 $optionPrice += $price;
  974.                             }
  975.                         }
  976.                         if ($useZeroVat) {
  977.                             $price = $product['isDiscounted'] ? $product['discountPrice'] : $product['price'];
  978.                         } else {
  979.                             $price = $product['isDiscounted'] ? $product['discountVatPrice'] : $product['vatPrice'];
  980.                         }
  981.                         $json['subTotal'] += ($price + $optionPrice) * $item->getQuantity();
  982.                     }
  983.                 }
  984.                 $paymentCost = $shippingCost = $json['discount'] = 0;
  985.                 if ($request->getSession()->has('checkout')) {
  986.                     $checkout = $request->getSession()->get('checkout');
  987.                     if (isset($checkout['shippingMethodId']) && (int)$checkout['shippingMethodId'] > 0) {
  988.                         $shippingMethod = $entityManager->getRepository(DomainShippingType::class)->findOneBy([
  989.                             'id' => $checkout['shippingMethodId'],
  990.                             'domain' => $multiStore->getDomainId(),
  991.                         ]);
  992.                         if ($shippingMethod) {
  993.                             // Shipping Cost
  994.                             $shippingCost = $shippingMethod->getPrice();
  995.                             // Free Shipping
  996.                             if ((float)$shippingMethod->getFreeShipLimit() > 0 && (float)$shippingMethod->getFreeShipLimit() < $json['subTotal']) {
  997.                                 $shippingCost = 0;
  998.                             }
  999.                         }
  1000.                     }
  1001.                     if (isset($checkout['paymentMethodId']) && (int)$checkout['paymentMethodId'] > 0) {
  1002.                         $paymentMethod = $entityManager->getRepository(DomainPaymentType::class)->findOneBy([
  1003.                             'id' => $checkout['paymentMethodId'],
  1004.                             'domain' => $multiStore->getDomainId(),
  1005.                         ]);
  1006.                         if ($paymentMethod) {
  1007.                             $paymentCost = $paymentMethod->getPrice();
  1008.                         }
  1009.                     }
  1010.                     if (isset($checkout['coupon']['code']) && $checkout['coupon']['code'] != '') {
  1011.                         $coupon = $paymentMethod = $entityManager->getRepository(Coupon::class)->findOneBy([
  1012.                             'code' => $checkout['coupon']['code'],
  1013.                             'domain' => $domain,
  1014.                         ]);
  1015.                         if ($coupon) {
  1016.                             // Set Email
  1017.                             if (!empty($checkout['email'])) {
  1018.                                 $cart->setEmail($checkout['email']);
  1019.                             } else {
  1020.                                 $cart->setEmail(null);
  1021.                             }
  1022.                             $isCouponValidForBasket = CartHelper::isCouponValidForBasket($coupon, $cart, $entityManager);
  1023.                             if ($isCouponValidForBasket) {
  1024.                                 $json['discount'] = $isCouponValidForBasket;
  1025.                             }
  1026.                             // Coupon Free Shipping
  1027.                             $isCouponApplicableForFreeShipping = CartHelper::isCouponApplicableForFreeShipping($coupon, $cart, $entityManager);
  1028.                             if ($isCouponApplicableForFreeShipping) {
  1029.                                 $shippingCost = 0;
  1030.                             }
  1031.                         }
  1032.                     }
  1033.                 }
  1034.                 $json['total'] = number_format(($json['subTotal'] + $shippingCost + $paymentCost - $json['discount']), $precision, ',', ' ') . ' ' . $currency;
  1035.                 $json['subTotal'] = number_format($json['subTotal'], $precision, ',', ' ') . ' ' . $currency;
  1036.                 if ((float)$json['discount'] > 0) {
  1037.                     $json['discount'] = number_format($json['discount'], $precision, ',', ' ') . ' ' . $currency;
  1038.                 } else {
  1039.                     $json['discount'] = '';
  1040.                 }
  1041.                 $entityManager->refresh($cart);
  1042.                 $entityManager->flush();
  1043.                 // // Validate product availability
  1044.                 foreach ($cart->getBasketItems() as $item) {
  1045.                     $product = $item->getProduct();
  1046.                     if ($product) {
  1047.                         if ($product->isContainer()) { // Container
  1048.                             foreach ($product->getProductContainers() as $container) {
  1049.                                 $child = $container->getChild();
  1050.                                 if (is_null($child)) {
  1051.                                     continue;
  1052.                                 }
  1053.                                 if ($child) {
  1054.                                     if (!$child->isAvailable()) {
  1055.                                         $json['stock'][] = $item->getId();
  1056.                                         break;
  1057.                                     }
  1058.                                     if ($child->getAvailable() < ($container->getMultiplier() * $item->getQuantity()) && $child->getOutOfStockStatus() !== Product::OutOfStockStatus_VisibleShadow) {
  1059.                                         $json['stock'][] = $item->getId();
  1060.                                         break;
  1061.                                     }
  1062.                                 }
  1063.                             }
  1064.                         } else { // Simple product
  1065.                             if (!$product->isAvailable()) {
  1066.                                 $json['stock'][] = $item->getId();
  1067.                             }
  1068.                             if ($product->getAvailable() < $item->getQuantity() && $product->getOutOfStockStatus() !== Product::OutOfStockStatus_VisibleShadow) {
  1069.                                 $json['stock'][] = $item->getId();
  1070.                             }
  1071.                         }
  1072.                     }
  1073.                 }
  1074.             } else {
  1075.                 $json['redirect'] = $this->generateUrl('cart_show');
  1076.                 if ($itemAttributeId > 0) {
  1077.                     foreach ($cart->getBasketItems() as $item) {
  1078.                         if ((int)$item->getId() == $itemId) {
  1079.                             foreach ($item->getItemAttributes() as $attribute) {
  1080.                                 if ((int)$attribute->getId() == $itemAttributeId) {
  1081.                                     $item->getItemAttributes()->removeElement($attribute);
  1082.                                     break;
  1083.                                 }
  1084.                             }
  1085.                             break;
  1086.                         }
  1087.                     }
  1088.                 } else {
  1089.                     foreach ($cart->getBasketItems() as $item) {
  1090.                         if ((int)$item->getId() == $itemId) {
  1091.                             $cart->getBasketItems()->removeElement($item);
  1092.                             break;
  1093.                         }
  1094.                     }
  1095.                 }
  1096.                 $entityManager->flush();
  1097.                 // Gift - Pexeso
  1098.                 if ( $this->removeGift(Product::PEXESO_SCHOOL_GIFT_ITEM_ID, $cart, $domain) ) {
  1099.                     $entityManager->refresh($cart);
  1100.                 }
  1101.             }
  1102.             $cart->setUpdated((new \DateTime('NOW')));
  1103.             $entityManager->flush();
  1104.         } else {
  1105.             $json['redirect'] = $this->generateUrl('homepage');
  1106.         }
  1107.         return new JsonResponse($json);
  1108.     }
  1109.     /**
  1110.      * @Route("/cart/remove", name="cart_remove", defaults={"_locale":""})
  1111.      * @Route("/{_locale<%supported_locales%>?}/cart/remove", name="cart_remove_locale", defaults={"_locale":""})
  1112.      * @param Request $request
  1113.      * @return JsonResponse
  1114.      */
  1115.     public function removeAction(Request $request)
  1116.     {
  1117.         $json = [];
  1118.         $entityManager = $this->getEntityManager();
  1119.         $productHelper = $this->getProductHelper($request);
  1120.         $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  1121.         $useZeroVat = $this->useZeroVat($customer, $request);
  1122.         $domain = $this->getMultiStoreHelper($request)->getDomain();
  1123.         $cart = $this->getCart($request->getSession()->get('cartId'));
  1124.         $stockItemCodes = $request->getSession()->get('stockItemCodes');
  1125.         $productId = (int)$request->request->get('productId');
  1126.         if ($cart) {
  1127.             $itemId = (int)$request->request->get('item');
  1128.             $itemAttributeId = (int)$request->request->get('option');
  1129.             if ($itemAttributeId > 0) {
  1130.                 foreach ($cart->getBasketItems() as $item) {
  1131.                     if ((int)$item->getId() == $itemId) {
  1132.                         foreach ($item->getItemAttributes() as $attribute) {
  1133.                             if ((int)$attribute->getId() == $itemAttributeId) {
  1134.                                 $entityManager->remove($attribute);
  1135.                                 $item->getItemAttributes()->removeElement($attribute);
  1136.                                 break;
  1137.                             }
  1138.                         }
  1139.                         break;
  1140.                     }
  1141.                 }
  1142.             } else {
  1143.                 $json['product'] = [];
  1144.                 foreach ($cart->getBasketItems() as $index => $item) {
  1145.                     if ((int)$item->getId() == $itemId) {
  1146.                         $itemProduct = $item->getProduct();
  1147.                         $productData = $productHelper->getStructuredDataForProduct($itemProduct, 'cart');
  1148.                         // Price
  1149.                         $specialValue = 0;
  1150.                         if ($useZeroVat) {
  1151.                             $priceValue = $productData['price'];
  1152.                         } else {
  1153.                             $priceValue = $productData['vatPrice'];
  1154.                         }
  1155.                         // Option Price
  1156.                         $optionPriceValue = $optionSpecialValue = 0;
  1157.                         if ($item->getItemAttributes()) {
  1158.                             foreach ($item->getItemAttributes() as $attribute) {
  1159.                                 $option = $attribute->getAttributeOption();
  1160.                                 $optionMeta = $option->getDomainMeta($domain);
  1161.                                 if ($optionMeta) {
  1162.                                     // Option Price excluding VAT
  1163.                                     if ($useZeroVat) {
  1164.                                         if ($optionMeta->isDiscounted()) {
  1165.                                             $optionSpecialValue += $optionMeta->getDomainDiscount();
  1166.                                         }
  1167.                                         $optionPriceValue += $optionMeta->getDomainPrice();
  1168.                                     } // Option Price including VAT
  1169.                                     else {
  1170.                                         if ($optionMeta->isDiscounted()) {
  1171.                                             $optionSpecialValue += $optionMeta->getDomainVatDiscount();
  1172.                                         }
  1173.                                         $optionPriceValue += $optionMeta->getDomainVatPrice();
  1174.                                     }
  1175.                                 }
  1176.                             }
  1177.                         }
  1178.                         $currency = $cart->getDomain()->getCurrency();
  1179.                         $currency_ISO = $this->getCurrencyISOForm($currency);
  1180.                         $precision = $cart->getDomain()->getPricePrecision();
  1181.                         $json['product'] = [
  1182.                             'currency' => $currency_ISO,
  1183.                             'code' => $productData['code'],
  1184.                             'title' => ($productData['vendor'] != '' ? $productData['vendor'] . ' ' : '') . $productData['title'],
  1185.                             'brand' => $productData['vendor'],
  1186.                             'quantity' => $item->getQuantity(),
  1187.                             'productType' => $productData['productType'],
  1188.                             'index' => $index,
  1189.                             'price' => round(($priceValue + $optionPriceValue), $precision),
  1190.                             'special' => round(($specialValue + $optionSpecialValue), $precision),
  1191.                         ];
  1192.                         $entityManager->remove($item);
  1193.                         break;
  1194.                     }
  1195.                 }
  1196.                 if ($this->getEnvironment() != 'prod') {
  1197.                     unset($json['product']);
  1198.                 }
  1199.             }
  1200.             $cart->setUpdated((new \DateTime('NOW')));
  1201.         } else {
  1202.             $json['redirect'] = '';
  1203.         }
  1204.         $entityManager->flush();
  1205.         unset($stockItemCodes[$productId]);
  1206.         $request->getSession()->set('stockItemCodes', $stockItemCodes);
  1207.         // Gift - Pexeso
  1208.         if ( $this->removeGift(Product::PEXESO_SCHOOL_GIFT_ITEM_ID, $cart, $domain) ) {
  1209.             $entityManager->refresh($cart);
  1210.         }
  1211.         if ($cart && $cart->getBasketItems()->count() == 1) {
  1212.             /** @var BasketItem $basketItem */
  1213.             $basketItem = $cart->getBasketItems()->first();
  1214.             if ( $basketItem->getProduct()->getId() == Product::SUMMER_2026_COUPON_GIFT_ITEM_ID ) {
  1215.                 $entityManager->remove($basketItem);
  1216.                 $entityManager->flush();
  1217.             }
  1218.         }
  1219.         if ($cart && count($cart->getBasketItems()) == 0) {
  1220.             if ($cart->getCustomer()) {
  1221.                 $this->getKlaviyoConnect($request)->createBasketIsEmptiedEvent($cart->getCustomer());
  1222.             }
  1223.             else if ($request->getSession()->has('checkout') && isset($request->getSession()->get('checkout')['email'])) {
  1224.                 $checkout = $request->getSession()->get('checkout');
  1225.                 if (isset($checkout['email'])) {
  1226.                     $this->getKlaviyoConnect($request)->createBasketIsEmptiedEvent($checkout['email']);
  1227.                     $request->getSession()->set('customerEmailForKlaviyo', $checkout['email']);
  1228.                     $request->getSession()->remove('checkout');
  1229.                 }
  1230.             }
  1231.             else if ($request->getSession()->has('customerEmailForKlaviyo')) {
  1232.                 $this->getKlaviyoConnect($request)->createBasketIsEmptiedEvent($request->getSession()->get('customerEmailForKlaviyo'));
  1233.             }
  1234.             $request->getSession()->remove('checkout');
  1235.             $request->getSession()->remove('stockItemCodes');
  1236.         }
  1237.         return new JsonResponse($json);
  1238.     }
  1239.     /**
  1240.      * @Route("/cart/stock-item-remove", name="product_stock_item_remove", defaults={"_locale":""})
  1241.      * @Route("/{_locale<%supported_locales%>?}/cart/stock-item-remove", name="product_stock_item_remove_locale", defaults={"_locale":""})
  1242.      * @param Request $request
  1243.      * @return JsonResponse
  1244.      */
  1245.     public function stockItemRemoveAjaxAction(Request $request): JsonResponse
  1246.     {
  1247.         $productId = $request->get('productId');
  1248.         $stockItemCode = $request->get('stockItemCode');
  1249.         $stockItemCodes = $request->getSession()->get('stockItemCodes');
  1250.         foreach ($stockItemCodes[$productId] as $key => $aikenCode) {
  1251.             if ($stockItemCode == $aikenCode) {
  1252.                 unset($stockItemCodes[$productId][$key]);
  1253.                 if (count($stockItemCodes[$productId]) == 0) {
  1254.                     unset($stockItemCodes[$productId]);
  1255.                 }
  1256.                 if (count($stockItemCodes) == 0) {
  1257.                     $request->getSession()->remove('stockItemCodes');
  1258.                 }
  1259.                 break;
  1260.             }
  1261.         }
  1262.         $entityManager = $this->getEntityManager();
  1263.         $basketItemId = $request->get('basketItemId');
  1264.         $basketItem = $entityManager->getRepository(BasketItem::class)->findOneById($basketItemId);
  1265.         if ($basketItem->getQuantity() > 1) {
  1266.             $basketItem->setQuantity($basketItem->getQuantity() - 1);
  1267.         } else {
  1268.             $basketItem->getBasket()->getBasketItems()->removeElement($basketItem);
  1269.         }
  1270.         $entityManager->flush();
  1271.         if (count($stockItemCodes) > 0) {
  1272.             $request->getSession()->set('stockItemCodes', $stockItemCodes);
  1273.         }
  1274.         return new JsonResponse([
  1275.             'status' => 'succes',
  1276.         ]);
  1277.     }
  1278.     /**
  1279.      * @Route("/cart/undo_removed", name="cart_remove_last_removed", defaults={"_locale":""})
  1280.      * @Route("/{_locale<%supported_locales%>?}/cart/undo_removed", name="cart_remove_last_removed_locale", defaults={"_locale":""})
  1281.      * @param Request $request
  1282.      * @return JsonResponse
  1283.      */
  1284.     public function emptyLastRemovedArrayAction(Request $request)
  1285.     {
  1286.         $productId = $request->request->get('product_id');
  1287.         $product = $this->getEntityManager()->getRepository(Product::class)->findOneById($productId);
  1288.         $productHelper = $this->getProductHelper($request);
  1289.         $productData = $productHelper->getStructuredDataForProduct($product, 'cart');
  1290.         $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  1291.         $useZeroVat = $this->useZeroVat($customer, $request);
  1292.         if ($useZeroVat) {
  1293.             $price = $productData['isDiscounted'] ? $productData['discountPrice'] : $productData['price'];
  1294.         } else {
  1295.             $price = $productData['isDiscounted'] ? $productData['discountVatPrice'] : $productData['vatPrice'];
  1296.         }
  1297.         $dataForGtagAddItems = [
  1298.             'brand' => $productData['vendor'],
  1299.             'title' => $product->getName(),
  1300.             'type' => $product->getType()->getTitle(),
  1301.             'price' => $price,
  1302.         ];
  1303.         $translator = $this->getTranslator();
  1304.         return new JsonResponse([
  1305.             'success' => $request->getSession()->remove('lastRemovedCartItem'),
  1306.             'gtag' => $dataForGtagAddItems,
  1307.             'error' => $this->renderView(
  1308.                 'AtechNetCoreBundle/Common/modal.html.twig',
  1309.                 [
  1310.                     'type' => 'error',
  1311.                     'message' => $translator->trans('Product not found'),
  1312.                 ]
  1313.             ),
  1314.         ]);
  1315.     }
  1316.     /**
  1317.      * @Route("/cart/clear-removed", name="cart_clear_removed_array", defaults={"_locale":""})
  1318.      * @Route("/{_locale<%supported_locales%>?}/cart/clear-removed", name="cart_clear_removed_array_locale", defaults={"_locale":""})
  1319.      * @param Request $request
  1320.      * @return JsonResponse
  1321.      */
  1322.     public function clearRemovedArrayAction(Request $request)
  1323.     {
  1324.         $request->getSession()->remove('lastRemovedCartItem');
  1325.         return new JsonResponse([]);
  1326.     }
  1327.     /**
  1328.      * @Route("/cart/coupon", name="cart_coupon", defaults={"_locale":""})
  1329.      * @Route("/{_locale<%supported_locales%>?}/cart/coupon", name="cart_coupon_locale", defaults={"_locale":""})
  1330.      * @param Request $request
  1331.      * @return JsonResponse
  1332.      */
  1333.     public function couponAction(Request $request): JsonResponse
  1334.     {
  1335.         $json = [];
  1336.         $translator = $this->getTranslator();
  1337.         if (empty($request->request->get('coupon'))) {
  1338.             $json['error'] = $translator->trans('Please enter a coupon code');
  1339.             return new JsonResponse($json);
  1340.         }
  1341.         $entityManager = $this->getEntityManager();
  1342.         $basket = null;
  1343.         if ($request->getSession()->has('cartId')) {
  1344.             $basketId = $request->getSession()->get('cartId');
  1345.             $basket = $this->getCart($basketId);
  1346.         }
  1347.         $couponCode = $request->request->get('coupon');
  1348.         $multiStore = $this->getMultiStoreHelper($request);
  1349.         $domain = $multiStore->getDomain();
  1350.         $coupon = $entityManager->getRepository(Coupon::class)->createQueryBuilder('c')
  1351.             ->where('c.code = :code')
  1352.             ->andWhere('c.domain = :domain')
  1353.             ->andWhere('c.description NOT LIKE :description')
  1354.             ->setParameters([
  1355.                 'code' => $couponCode,
  1356.                 'domain' => $domain,
  1357.                 'description' => '%Unas%'
  1358.             ])->getQuery()->getOneOrNullResult();
  1359.         $checkout = $request->getSession()->get('checkout') ?? [];
  1360.         if (!$coupon || !$basket) {
  1361.             if (in_array($couponCode, $this->getWhiteListCouponCodes()) && $basket) {
  1362.                 // Apply Coupon
  1363.                 $checkout['coupon'] = [
  1364.                     'code' => $couponCode,
  1365.                     'type' => 0,
  1366.                     'discount' => 0,
  1367.                 ];
  1368.                 if ($basket) {
  1369.                     $basket->setCouponCode($couponCode);
  1370.                     $entityManager->flush();
  1371.                 }
  1372.                 // Save
  1373.                 $request->getSession()->set('checkout', $checkout);
  1374.                 // Alert
  1375.                 $json['error'] = $translator->trans('Your coupon discount has been applied');
  1376.             } else {
  1377.                 $json['error'] = $translator->trans('Coupon is invalid');
  1378.             }
  1379.             return new JsonResponse($json);
  1380.         }
  1381.         // Validate Date
  1382.         $now = (new \DateTime('now'))->getTimestamp();
  1383.         if (!is_null($coupon->getValidFrom()) && $coupon->getValidFrom()->getTimestamp() > $now) {
  1384.             $json['error'] = $translator->trans('Coupon is expired');
  1385.             return new JsonResponse($json);
  1386.         }
  1387.         if (!is_null($coupon->getValidTo()) && $coupon->getValidTo()->getTimestamp() < $now) {
  1388.             $json['error'] = $translator->trans('Coupon is expired');
  1389.             return new JsonResponse($json);
  1390.         }
  1391.         $basket->setEmail(!empty($checkout['email']) ? $checkout['email'] : null);
  1392.         // Coupon Free Shipping
  1393.         $isCouponApplicableForFreeShipping = CartHelper::isCouponApplicableForFreeShipping($coupon, $basket, $entityManager);
  1394.         // Coupon Discount
  1395.         $isCouponValidForBasket = CartHelper::isCouponValidForBasket($coupon, $basket, $entityManager);
  1396.         if ($isCouponValidForBasket !== false || $isCouponApplicableForFreeShipping) {
  1397.             // Apply Coupon
  1398.             $checkout['coupon'] = [
  1399.                 'code' => $coupon->getCode(),
  1400.                 'type' => $coupon->getDiscountType(),
  1401.                 'discount' => (float)$isCouponValidForBasket,
  1402.                 'freeShipping' => $isCouponApplicableForFreeShipping,
  1403.             ];
  1404.             //mac + mouse coupon
  1405.             if ($checkout['coupon']['code'] == Coupon::MAC_MOUSE_COUPON_CODE) {
  1406.                 $basketMacItems = $basket->getBasketItems()->filter(function (BasketItem $item) {
  1407.                     if ($item->getProduct()->getVendor()->getId() == Vendor::VENDOR_ID_APPLE && $item->getProduct()->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK) {
  1408.                         return true;
  1409.                     }
  1410.                 });
  1411.                 $mouseAttribute = $entityManager->getRepository(AttributeOption::class)->findOneById(AttributeOption::ATTRIBUTE_OPTION_ID_MACBOOK_FREE_MOUSE);
  1412.                 /** @var BasketItem $item */
  1413.                 foreach ($basketMacItems as $item) {
  1414.                     $basketAttributeOption = new BasketItemAttributeOption();
  1415.                     $basketAttributeOption->setBasketItem($item);
  1416.                     $basketAttributeOption->setAttributeOption($mouseAttribute);
  1417.                     $entityManager->persist($basketAttributeOption);
  1418.                 }
  1419.             }
  1420.             if ($checkout['coupon']['code'] == Coupon::SUMMER_2026_COUPON_CODE) {
  1421.                 $productRepo = $entityManager->getRepository(Product::class);
  1422.                 $giftProduct = $productRepo->find(Product::SUMMER_2026_COUPON_GIFT_ITEM_ID);
  1423.                 if ($giftProduct && $giftProduct->getAvailable() > 0) {
  1424.                     $basketItem = new BasketItem();
  1425.                     $basketItem->setBasket($basket);
  1426.                     $basketItem->setProduct($giftProduct);
  1427.                     $basketItem->setQuantity(1);
  1428.                     $entityManager->persist($basketItem);
  1429.                 }
  1430.             }
  1431.             $basket->setCouponCode($couponCode);
  1432.             $entityManager->flush();
  1433.             // Save
  1434.             $request->getSession()->set('checkout', $checkout);
  1435.             // Success
  1436.             $json['success'] = $this->getTranslator()->trans('Your coupon discount has been applied');
  1437.         } else {
  1438.             if (($coupon->isOnlyOnceUse() && CartHelper::isCouponAlreadyUsed($entityManager, $basket, $couponCode))
  1439.                 || ($coupon->isOnlyOnceCustomerUse() && CartHelper::isCouponAlreadyUsedThisUser($entityManager, $basket, $couponCode))) {
  1440.                 $json['error'] = $translator->trans('Coupon has already been used');
  1441.             }
  1442.             if (empty($json['error']) && !$coupon->isAvailableForDiscountedProducts()) {
  1443.                 $json['error'] = $translator->trans('Coupon does not apply to discounted products');
  1444.             }
  1445.             if (empty($json['error'])) {
  1446.                 $json['error'] = $translator->trans('Coupon only applies to certain products');
  1447.             }
  1448.         }
  1449.         return new JsonResponse($json);
  1450.     }
  1451.     /**
  1452.      * @Route("/cart/coupon_remove", name="cart_coupon_remove", defaults={"_locale":""})
  1453.      * @Route("/{_locale<%supported_locales%>?}/cart/coupon_remove", name="cart_coupon_remove_locale", defaults={"_locale":""})
  1454.      * @param Request $request
  1455.      * @return JsonResponse
  1456.      */
  1457.     public function couponRemoveAction(Request $request)
  1458.     {
  1459.         $translator = $this->getTranslator();
  1460.         $checkout = $request->getSession()->get('checkout');
  1461.         //mac + mouse coupon
  1462.         if ($checkout['coupon']['code'] == Coupon::MAC_MOUSE_COUPON_CODE) {
  1463.             $entityManager = $this->getEntityManager();
  1464.             $basket = $this->getCart($request->getSession()->get('cartId'));
  1465.             /** @var BasketItem $item */
  1466.             foreach ($basket->getBasketItems() as $item) {
  1467.                 if ($item->getProduct()->getVendor()->getId() == Vendor::VENDOR_ID_APPLE && $item->getProduct()->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK) {
  1468.                     /** @var BasketItemAttributeOption $attributeOption */
  1469.                     foreach ($item->getItemAttributes() as $attribute) {
  1470.                         if ($attribute->getAttributeOption()->getId() == AttributeOption::ATTRIBUTE_OPTION_ID_MACBOOK_FREE_MOUSE) {
  1471.                             $entityManager->remove($attribute);
  1472.                         }
  1473.                     }
  1474.                 }
  1475.             }
  1476.             $entityManager->flush();
  1477.         }
  1478.         if ($checkout['coupon']['code'] == Coupon::SUMMER_2026_COUPON_CODE) {
  1479.             $entityManager = $this->getEntityManager();
  1480.             $basket = $this->getCart($request->getSession()->get('cartId'));
  1481.             foreach ($basket->getBasketItems() as $item) {
  1482.                 if ($item->getProduct()->getId() == Product::SUMMER_2026_COUPON_GIFT_ITEM_ID) {
  1483.                     $entityManager->remove($item);
  1484.                     $entityManager->flush();
  1485.                 }
  1486.             }
  1487.         }
  1488.         unset($checkout['coupon']);
  1489.         $request->getSession()->set('checkout', $checkout);
  1490.         $json['error'] = $translator->trans('Your coupon discount has been deleted');
  1491.         return new JsonResponse($json);
  1492.     }
  1493.     /**
  1494.      * @Route("/cart/addOption", name="cart_add_option", defaults={"_locale":""})
  1495.      * @Route("/{_locale<%supported_locales%>?}/cart/addOption", name="cart_add_option_locale", defaults={"_locale":""})
  1496.      * @param Request $request
  1497.      * @return JsonResponse
  1498.      */
  1499.     public function addOptionAction(Request $request)
  1500.     {
  1501.         $json = [];
  1502.         // Entity Manager
  1503.         $entityManager = $this->getEntityManager();
  1504.         // Product Helper
  1505.         $productHelper = $this->getProductHelper($request);
  1506.         // Checkout Data
  1507.         if ($request->getSession()->has('checkout')) {
  1508.             $checkout = $request->getSession()->get('checkout');
  1509.         } else {
  1510.             $checkout = [];
  1511.         }
  1512.         // Customer
  1513.         $customer = $this->getLoginHelper($request)->getLoggedInCustomer();
  1514.         // Product ID
  1515.         $productId = $request->request->get('productId');
  1516.         // Attribute Option ID
  1517.         $attributeOptionId = $request->request->get('attributeOptionId');
  1518.         // Product
  1519.         $product = $entityManager->getRepository(Product::class)->findOneById($productId);
  1520.         // Attribute Option
  1521.         $attributeOption = $entityManager->getRepository(AttributeOption::class)->findOneById($attributeOptionId);
  1522.         if ($product && $attributeOption) {
  1523.             // Structured Data
  1524.             $productData = $productHelper->getStructuredDataForProduct($product, 'cart');
  1525.             // Multi-Store Helper
  1526.             $multiStore = $this->getMultiStoreHelper($request);
  1527.             // Domain
  1528.             $domain = $multiStore->getDomain();
  1529.             // Cross-sell Attribute Options
  1530.             $crossSellAttributeOptions = CartHelper::getCrossSellAttributeOptionsByProductTypeId($productData['productTypeId'], $domain->getId());
  1531.             // Available
  1532.             if (isset($crossSellAttributeOptions[$attributeOptionId])) {
  1533.                 // Cart
  1534.                 $cart = $this->getCart($request->getSession()->get('cartId'));
  1535.                 if ($cart) {
  1536.                     // Find Item in Cart by Product Id
  1537.                     $found = false;
  1538.                     foreach ($cart->getBasketItems() as $item) {
  1539.                         if ($product->getId() == $item->getProduct()->getId()) {
  1540.                             $found = $item;
  1541.                             if ($item->getItemAttributes()) {
  1542.                                 foreach ($item->getItemAttributes() as $attribute) {
  1543.                                     $option = $attribute->getAttributeOption();
  1544.                                     if (isset($crossSellAttributeOptions[$option->getId()]) && $attributeOption->getAttribute()->getId() == $option->getAttribute()->getId()) {
  1545.                                         $found = false;
  1546.                                         $json['added'] = $option->getId();
  1547.                                         break;
  1548.                                     }
  1549.                                 }
  1550.                             }
  1551.                             break;
  1552.                         }
  1553.                     }
  1554.                     // Add Attribute Option to Product
  1555.                     if ($found) {
  1556.                         $basketItemAttributeOption = new BasketItemAttributeOption();
  1557.                         $basketItemAttributeOption->setBasketItem($found);
  1558.                         $basketItemAttributeOption->setAttributeOption($attributeOption);
  1559.                         $entityManager->persist($basketItemAttributeOption);
  1560.                         $entityManager->flush();
  1561.                         // Success
  1562.                         $json['success'] = true;
  1563.                     }
  1564.                 }
  1565.             }
  1566.         }
  1567.         return new JsonResponse($json);
  1568.     }
  1569.     /**
  1570.      * @Route("/cart/widget", name="cart_widget", defaults={"_locale":""})
  1571.      * @Route("/{_locale<%supported_locales%>?}/cart/widget", name="cart_widget_locale", defaults={"_locale":""})
  1572.      * @param Request $request
  1573.      * @return Response
  1574.      * @throws \Exception
  1575.      */
  1576.     public function widgetAction(Request $request)
  1577.     {
  1578.         $module = $this->getModuleHelper($request);
  1579.         return new Response($module->widgetCart());
  1580.     }
  1581.     /**
  1582.      * @param int $giftId
  1583.      * @param Basket $cart
  1584.      * @param Domain $domain
  1585.      * 
  1586.      * @return bool
  1587.      */
  1588.     private function removeGift(int $productId, Basket $cart, Domain $domain): bool {
  1589.         if ( $productId > 0 && in_array($domain->getId(), [ Domain::DOMAIN_ID_RT_SK, Domain::DOMAIN_ID_RT_CZ, Domain::DOMAIN_ID_RT_HU, Domain::DOMAIN_ID_RT_EU ]) ) {
  1590.             if ( $cart && $cart->getBasketItems()->count() > 0 ) {
  1591.                 $giftBasketItem = null;
  1592.                 $removeGift = true;
  1593.                 /** @var BasketItem $basketItem */
  1594.                 foreach ( $cart->getBasketItems() as $basketItem ) {
  1595.                     if ( $basketItem->getProduct()->getId() == $productId ) {
  1596.                         $giftBasketItem = $basketItem;
  1597.                     }
  1598.                     if ( $basketItem->getProduct()?->getType()?->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK ) {
  1599.                         $productDomainMeta = $basketItem->getProduct()->getDomainMeta($domain);
  1600.                         if ( $productDomainMeta !== false && $productDomainMeta->isDiscounted() && $productDomainMeta->isDiscountValid() ) {
  1601.                             $removeGift = false;
  1602.                         }
  1603.                     }
  1604.                     if ( !is_null($giftBasketItem) && $removeGift === false ) {
  1605.                         break;
  1606.                     }
  1607.                 }
  1608.                 if ( !is_null($giftBasketItem) && $removeGift === true ) {
  1609.                     $this->getEntityManager()->remove($giftBasketItem);
  1610.                     $this->getEntityManager()->flush();
  1611.                     return true;
  1612.                 }
  1613.             }
  1614.         }
  1615.         return false;
  1616.     }
  1617. }