<?php
namespace App\Services;
use App\Entity\Cart;
use App\Entity\CartProduct;
use App\Entity\CartProductEquipment;
use App\Entity\CartProductParameterValue;
use App\Entity\Currency;
use App\Entity\Language;
use App\Entity\Product;
use App\Entity\ProductEquipment;
use App\Entity\ProductLangParam;
use App\Entity\ProductParameterValue;
use App\Entity\ProductPriceVariants;
use App\Entity\RebateCode;
use App\Entity\SubProduct;
use App\Repository\CartRepository;
use App\Repository\DeliveryCountryRepository;
use App\Repository\DeliveryMethodRepository;
use App\Repository\PaymentMethodRepository;
use App\Exception\CartException;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class CartService
{
private $em;
private $translator;
private $cartRepository;
private $tokenStorage;
private $session;
private $deliveryCountryRepository;
private $productManager;
/**
* CartService constructor.
* @param EntityManagerInterface $em
* @param TranslatorInterface $translator
* @param CartRepository $cartRepository
*/
public function __construct(PaymentMethodRepository $paymentMethodRepository, DeliveryMethodRepository $deliveryMethodRepository, DeliveryCountryRepository $deliveryCountryRepository, EntityManagerInterface $em, TranslatorInterface $translator, CartRepository $cartRepository, TokenStorageInterface $tokenStorage, SessionInterface $session, ProductManager $productManager) {
$this->em = $em;
$this->translator = $translator;
$this->deliveryCountryRepository = $deliveryCountryRepository;
$this->cartRepository = $cartRepository;
$this->tokenStorage = $tokenStorage;
$this->session = $session;
$this->deliveryMethodRepository = $deliveryMethodRepository;
$this->paymentMethodRepository = $paymentMethodRepository;
$this->productManager = $productManager;
}
/**
* Get cart session
* @return mixed
*/
public function getCartSession() {
return $this->session->get('cart_session');
}
/**
* @return string locale
*/
public function getLocale() {
return $this->session->get('_locale');
}
/**
* @return Currency
*/
public function getCurrency() {
return $this->session->get('currency');
}
/**
* @return Cart
* @throws CartException
*/
public function getCart() {
$cart = $this->em->getRepository(Cart::class)->findOneBy(['session'=>$this->session->get('cart_session')]);
if ($cart instanceof Cart) {
return $cart;
}
$create = $this->getOrCreateCart();
if ($create instanceof Cart) {
return $create;
}
throw new CartException("Cart did not found");
}
/**
* * Get Sum of products in the cart
* @param bool $withRebate - whether with rebate code (RebateCode entity) or not
* @return array
*/
public function getSum($withRebate = false, $withVat = true) {
$sum = 0;
$rebate = ($withRebate && is_object($this->getCart()->getRebateCode()) && $this->getCart()->getRebateCode()->getCodeType() == RebateCode::CODE_TYPE_PERCENT) ? true : false;
/** @var $cart CartProduct */
foreach ($this->getCartProducts() as $cart) {
$sum = $sum + ($this->calculateProductSinglePrice($cart, $rebate, $withVat) * $cart->getQuantity());
}
if ($withRebate and is_object($this->getCart()->getRebateCode()) and $this->getCart()->getRebateCode()->getCodeType() == RebateCode::CODE_TYPE_AMOUNT and $this->getCart()->isCartApplicableForAmountDiscount()) {
$sum = $sum - $this->getCart()->getRebateCode()->getPrice();
}
if ($sum < 0) {
$sum = 0;
}
return ['sum'=>$sum, 'currency'=>$this->getCurrency()];
}
public function getSumWithVat(Cart $cart) {
$sum = 0;
/** @var $cartProduct CartProduct */
foreach ($cart->getProducts() as $cartProduct) {
$price = $cartProduct->getProduct()->getPrice($cartProduct->getCurrency(), $cartProduct->getVat());
$sum = $sum + ($cartProduct->getQuantity() * $price['price']);
}
return $sum;
}
/**
* Calculate price of single record product from cart
*
* @param CartProduct $cartProduct
* @param bool $withRebate
* @return mixed
* @throws CartException
*/
public function calculateProductSinglePrice(CartProduct $cartProduct, $withRebate = false, $withVat = true) {
$currencyDefault = $this->em->getRepository(Currency::class)->findOneBy(['isDefault'=>true, 'deletedBy'=>false]);
$currency = $this->session->get('currency', $currencyDefault);
//brutto!
if ($withVat) {
if ($cartProduct->getCart()->getLanguage()->getLocaleShort() == 'ro') {
$params = $cartProduct->getProduct()->getLangParams();
foreach ($params as $param) {
if ($param->getLanguage()->getLocaleShort() == 'ro') {
$vat = $param->getVat();
$vatRate = ($vat->getValue()+100)/100;
if ($withRebate === false) {
return $cartProduct->getPriceNet() * $vatRate;
} else {
$rebateCode = $this->getCart()->getRebateCode();
if ($rebateCode && $rebateCode->getCodeType() == RebateCode::CODE_TYPE_PERCENT && $this->getCart()->isProductApplicableForRebate($cartProduct->getProduct())) {
$discount = $cartProduct->getPriceNet() * $vatRate * ($rebateCode->getPercent() / 100);
return $cartProduct->getPriceNet() * $vatRate - $discount;
} else {
return $cartProduct->getPriceNet() * $vatRate;
}
}
}
}
}
$price['price'] = $cartProduct->getPrice();
}
if (!isset($price['price'])) {
throw new CartException('Price of product not found');
}
if (is_object($this->getCart()->getRebateCode()) and $cartProduct->getSubProduct() == null and $this->getCart()->isProductApplicableForRebate($cartProduct->getProduct()) and $withRebate) {
/** @var $rebateCode RebateCode */
$rebateCode = $this->getCart()->getRebateCode();
if ($rebateCode->getCodeType() == RebateCode::CODE_TYPE_PERCENT) {
$priceForDiscount = $cartProduct->getProduct()->getPrice($currency, $cartProduct->getVat());
$priceForDiscount['price'] = $cartProduct->getPrice();
$discount = $priceForDiscount['price'] * ($rebateCode->getPercent() / 100);
$newVatPrice = $priceForDiscount['price'] - $discount;
return $newVatPrice;
//$vatRate = ($cartProduct->getVat()->getValue()+100)/100;
//return round($newVatPrice/$vatRate,2);
} else {
return $price['price'];
}
} else {
return $price['price'];
}
}
/**
* Dynamically calculate total cost with shipping when user change delivery and payment method
*
* @param null $deliveryMethodId
* @param null $paymentMethodId
* @return array
*/
public function ajaxCalculateSum($deliveryMethodId = null, $paymentMethodId = null) {
$calculate = $this->getSum(false, true);
$calculateWithRebate = $this->getSum($withRebate = true, true);
//dump($calculateWithRebate);
//dump($calculate);
//exit();
$cartSum = $calculateWithRebate['sum'];
$sum = $calculate['sum'];
$sumWithRebate = $calculateWithRebate['sum'];
if ($deliveryMethodId) {
$deliveryPrice = $this->deliveryMethodRepository->getPrice($deliveryMethodId, $this->getCurrency());
$sum = $sum + $deliveryPrice;
$sumWithRebate = $sumWithRebate + $deliveryPrice;
}
if ($paymentMethodId) {
$paymentPrice = $this->paymentMethodRepository->getPrice($paymentMethodId, $this->getCurrency());
$sum = $sum + $paymentPrice;
$sumWithRebate = $sumWithRebate + $paymentPrice;
}
$shippingCost = $sumWithRebate - $cartSum;
if ($this->getCurrency()->getId() == 4) {
//return ['savings'=>$sumWithRebate-$sum, 'sum'=>(int)$sum, 'sumWithRebate'=>(int)$sumWithRebate, 'shippingCost'=>(int)$shippingCost];
}
return ['savings'=>$sumWithRebate-$sum, 'sum'=>$sum, 'sumWithRebate'=>$sumWithRebate, 'shippingCost'=>$shippingCost];
}
/**
* Todo - dorobić tutaj sprawdzanie czy produkt jest widoczny dla danej wersji językowej
* @return ArrayCollection
* @throws CartException
*/
public function getCartProducts() {
$collection = new ArrayCollection();
/** @var $cart CartProduct */
foreach ($this->getCart()->getProducts() as $cart) {
/** @var $langParamEntity ProductLangParam */
$langParamEntity = $this->em->getRepository(ProductLangParam::class)->findOneBy(['language'=>$cart->getCart()->getLanguage(), 'product'=>$cart->getProduct()]);
if ($langParamEntity->getVisible()) {
$collection->add($cart);
}
}
return $collection;
}
/**
* Simple counter of products in cart
*
* @return int
* @throws CartException
*/
public function countProducts() {
$count = 0;
foreach ($this->getCartProducts() as $cartProduct) {
$count = $count+$cartProduct->getQuantity();
}
return $count;
}
/**
* Recalculate cart (when currency changed for example)
*/
public function cartRecalculate(Language $language) {
/** @var $cart CartProduct */
foreach ($this->getCartProducts() as $cart) {
$priceData = ['price'=>$this->productManager->getPrice($cart->getProduct(), $language, $cart->getVariant(), $cart), 'currency'=>$language->getCurrency()];
if ($cart->getSubProduct()) {
$priceData['price'] = $cart->getSubProduct()->getPrice();
}
if (isset($priceData['price'])) {
$cart->setPrice($priceData['price']);
$cart->setCurrency($priceData['currency']);
}
$this->em->persist($cart);
}
$this->em->flush();
}
/**
* * Check if record with given parameter, product and session exists
* @param Request $request
* @return \App\Entity\CartProduct|bool|null|object
*/
public function checkDoubleRecord(Request $request) {
$record = $this->em->getRepository(CartProduct::class)->findOneBy([
'session' => $this->session->get('cart_session'),
'product' => $this->getProductEntity($request->request->get('product_id')),
'subProduct' => $request->request->get('sub'),
'code' => $this->recordCode($request->request->get('parameters'), $request->request->get('product_id'), $request->request->get('equipments')),
]);
if (is_object($record)) {
return $record;
}
return false;
}
/**
* @param $variant
* @return null|ProductPriceVariants
*/
public function getVariantEntity($variant) {
if ($variant) {
return $this->em->getRepository(ProductPriceVariants::class)->find($variant);
}
return null;
}
/**
* @return \App\Entity\Currency|null|object
*/
public function getDefaultCurrency() {
return $this->em->getRepository(Currency::class)->findOneBy(['isDefault'=>true, 'deletedBy'=>null]);
}
/**
* @param $id
* @return \App\Entity\Product|null
*/
public function getProductEntity($id) {
return $this->em->getRepository(Product::class)->findOneBy(['id'=>$id, 'deletedBy'=>null]);
}
public function removeInvisible(Cart $cart) {
/** @var $cartProduct CartProduct */
foreach ($cart->getProducts() as $cartProduct) {
$langParamEntity = $this->em->getRepository(ProductLangParam::class)->findOneBy(['language'=>$cart->getLanguage(), 'product'=>$cartProduct->getProduct()]);
if (!$langParamEntity->getVisible()) {
foreach ($cartProduct->getEquipments() as $equipment) {
$this->em->remove($equipment);
}
foreach ($cartProduct->getParameterValues() as $parameterValue) {
$this->em->remove($parameterValue);
}
$this->em->remove($cartProduct);
}
}
if ($cart->getRebateCode() && $cart->getRebateCode()->getValidTo()) {
$now = new \DateTime();
$validTo = $cart->getRebateCode()->getValidTo();
if ($validTo->getTimestamp() > $now->getTimestamp()) {
#OK!
} else {
$cart->setRebateCode(null);
$cart->setRebateCodeContent(null);
$this->em->persist($cart);
$this->em->flush();
}
}
$this->em->flush();
}
/**
* @return Cart
*/
public function getOrCreateCart() {
$cart = $this->em->getRepository(Cart::class)->findOneBy(['session'=>$this->session->get('cart_session')]);
if (is_object($cart)) {
return $cart;
}
return $this->create();
}
public function recordCode($parameters, $productId, $equipments = null) {
if (!is_array($parameters) && $equipments === null) {
return md5($productId);
}
$code = $productId;
if (!$parameters && !$equipments) {
$code = md5($productId);
} else {
$string = '';
if (is_array($parameters)) {
foreach ($parameters as $parameter) {
$string .= $string.$parameter['value'];
}
$code = md5($string);
}
}
if ($equipments && is_array($equipments)) {
foreach ($equipments as $equipment) {
$code = 'eq'.$code.$equipment;
}
}
return $code;
}
public function updateQuantityCart(Request $request) {
$product = $this->getProductEntity($request->request->get('product_id'));
/** @var $record CartProduct */
$record = $this->em->getRepository(CartProduct::class)->findOneBy(
[
'id'=>$request->request->get('record_id'),
'product'=>$product,
'session' => $this->session->get('cart_session'),
]
);
if ($record) {
$record->setQuantity($request->request->get('quantity'));
$this->em->persist($record);
$this->em->flush();
}
return new JsonResponse(['message'=>'success']);
}
public function isSubProductInCart(Request $request) {
$product = $this->getProductEntity($request->request->get('product_id'));
/** @var $record CartProduct */
$record = $this->em->getRepository(CartProduct::class)->findOneBy(
[
'id'=>$request->request->get('record_id'),
'product'=>$product,
'session' => $this->session->get('cart_session'),
]
);
if ($record->getSubProduct()) {
return true;
}
return false;
}
public function removeFromCart(Request $request) {
$product = $this->getProductEntity($request->request->get('product_id'));
$record = $this->em->getRepository(CartProduct::class)->findOneBy(
[
'id'=>$request->request->get('record_id'),
'product'=>$product,
'session' => $this->session->get('cart_session'),
]
);
if ($record) {
$this->em->remove($record);
$this->em->flush();
}
return new JsonResponse(['message'=>'success']);
}
public function isMaxQuantityExceeded(Product $product, $quantity) {
if (!$product->getMaxQuantity()) {
return false;
}
return $quantity > $product->getMaxQuantity();
}
private function subProductAddedNotification(SubProduct $product) {
$html = 'Dodano do koszyka produkt "Kup taniej": '.$product->getName();
$content = [
'fromField'=>['fromFieldId'=>'f'],
'subject'=>'Dodano do koszyka produkt z sekcji "Kup taniej"',
'content'=>['html'=>$html, 'plain'=>$html],
'recipients'=>['to'=>['email'=>'marketing@centrumkrzesel.pl']],
];
$body = json_encode($content);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api3.getresponse360.pl/v3/transactional-emails");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = array();
$headers[] = "Content-Type: application/json";
$headers[] = "X-Auth-Token: api-key gs478s9uv59n5ekulmpmgn5p0uqpepbn";
$headers[] = "X-Domain: centrumkrzesel.com";
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
curl_close ($ch);
return $result;
}
private function getUserDetails(Request $request): array
{
// Get the user's IP address
$userIp = $request->getClientIp();
// Get the user's browser information
$browserInfo = $request->headers->get('User-Agent');
// Return the details as an array
return [
'ip' => $userIp,
'browser' => $browserInfo,
];
}
/**
* @param Request $request
* @return CartProduct|bool|null|object
* @throws CartException
* @throws \Doctrine\ORM\ORMException
*/
public function addToCart(Request $request) {
$product = $this->getProductEntity($request->request->get('product_id'));
$variant = $request->request->get('variant');
$code = $this->recordCode($request->request->get('parameters'), $request->request->get('product_id'), $request->request->get('equipments'));
/** @var $language Language */
$language = $this->em->getRepository(Language::class)->findOneBy(['deletedBy'=>null, 'locale'=>$request->getLocale()]);
/** @var $langParam ProductLangParam */
$langParam = $this->em->getRepository(ProductLangParam::class)->findOneBy(['product'=>$product, 'language'=>$language]);
$cart = $this->getOrCreateCart();
$this->em->persist($cart);
$sub = null;
if ($request->request->get('sub')) {
/** @var $sub SubProduct */
$sub = $this->em->getRepository(SubProduct::class)->find($request->request->get('sub'));
$sub->setActive(false);
$sub->setAddToCartTime(new \DateTime());
$this->em->persist($sub);
$this->subProductAddedNotification($sub);
$priceData['price'] = $sub->getPrice();
$priceData['currency'] = $language->getCurrency();
} else {
$priceData = ['price'=>$this->productManager->getPrice($product, $language, $variant), 'currency'=>$language->getCurrency()];
}
if (isset($priceData['price'])) {
$productPrice = $priceData['price'];
$double = $this->checkDoubleRecord($request);
$quantity = $request->request->get('quantity');
if (!is_numeric($quantity)) {
$quantity = 1;
}
if (!$this->session->get('cart_session')) {
$data = serialize($request->request->all());
throw new CartException("Brak sesji - odśwież przeglądarkę: ".$data.' DATA: '.serialize($this->getUserDetails($request)));
}
$cartRecord = (is_object($double)) ? $double : new CartProduct();
$cartRecord->setProduct($product);
if ($variant) {
$cartRecord->setVariant($this->getVariantEntity($variant));
$net = $cartRecord->getVariant()->getProductPrice()->getPrice();
if ($request->request->get('equipments')) {
foreach ($request->request->get('equipments') as $equipmentId) {
/** @var $equipmentEntity ProductEquipment */
$equipmentEntity = $this->em->getReference(ProductEquipment::class, $equipmentId);
$net = $net + $equipmentEntity->getPriceNet();
}
}
$cartRecord->setPriceNet($net);
} else {
$net = $this->productManager->getPriceNet($product, $language);
if ($request->request->get('equipments')) {
foreach ($request->request->get('equipments') as $equipmentId) {
/** @var $equipmentEntity ProductEquipment */
$equipmentEntity = $this->em->getReference(ProductEquipment::class, $equipmentId);
$net = $net + $equipmentEntity->getPriceNet();
}
}
$cartRecord->setPriceNet($net);
}
$cartRecord->setCart($cart);
$cartRecord->setCode($code);
$cartRecord->setSubProduct($sub);
$cartRecord->setVat($langParam->getVat());
$cartRecord->setSession($this->session->get('cart_session'));
$cartRecord->setPrice($productPrice);
$cartRecord->setCurrency($priceData['currency']);
if ($sub) {
$cartRecord->setQuantity(1);
} else {
if (!$this->isMaxQuantityExceeded($product, $cartRecord->getQuantity() + $quantity)) {
$cartRecord->setQuantity($cartRecord->getQuantity() + $quantity);
}
}
$cartRecord->setPriceInitial($productPrice);
if (!is_object($double) and $request->request->get('parameters') and $sub === null) {
foreach ($request->request->get('parameters') as $parameter) {
$valueEntity = $this->em->getReference(ProductParameterValue::class, $parameter['value']);
$parameterEntity = $valueEntity->getParameter();
$value = new CartProductParameterValue();
$checkDouble = $this->em->getRepository(CartProductParameterValue::class)->findOneBy(['cartProduct'=>$cartRecord, 'parameter'=>$parameterEntity, 'parameterValue'=>$valueEntity]);
if (!is_object($checkDouble)) {
$value->setCartProduct($cartRecord);
$value->setParameterValue($valueEntity);
$value->setParameter($parameterEntity);
$this->em->persist($value);
}
}
}
if (!is_object($double) and $request->request->get('equipments')) {
foreach ($request->request->get('equipments') as $equipmentId) {
$equipmentEntity = $this->em->getReference(ProductEquipment::class, $equipmentId);
$newEquipment = new CartProductEquipment();
$newEquipment->setQuantity(1);
$newEquipment->setEquipment($equipmentEntity);
$newEquipment->setProduct($cartRecord->getProduct());
$newEquipment->setCartProduct($cartRecord);
$newEquipment->setSession($this->session->get('cart_session'));
$newEquipment->setPrice($equipmentEntity->getPrice());
$this->em->persist($newEquipment);
}
}
$this->em->persist($cartRecord);
$this->em->flush();
$this->cartRecalculate($language);
return $cartRecord;
} else {
throw new CartException("Product price not found");
}
}
/**
* @return Cart
* @throws CartException
*/
public function create() {
$cart = new Cart();
$cart->setSession($this->session->get('cart_session'));
/** @var $language Language */
$language = $this->em->getRepository(Language::class)->findOneBy(['locale'=>$this->session->get('_locale'), 'deletedBy'=>null]);
if (!is_object($language)) {
throw new CartException("Language session not found in cart");
}
$cart->setIp($_SERVER['REMOTE_ADDR']);
$cart->setLanguage($language);
$cart->setUserAgent($_SERVER['HTTP_USER_AGENT']);
return $cart;
}
/**
* @param $session
*/
public function delete($session) {
$cart = $this->getRepository()->find($session);
$this->em->remove($cart);
$this->em->flush();
}
/**
* @return CartRepository
*/
public function getRepository() {
return $this->cartRepository;
}
/**
* Discard rebate code - set to null in cart
*/
public function discardRebateCode() {
$cart = $this->getCart();
$cart->setRebateCodeContent('');
$rebateCode = $this->getCart()->getRebateCode();
if (!is_object($rebateCode)) {
return;
}
if ($rebateCode->getMultiple() !== true) {
$rebateCode->setUsed(false);
$this->em->persist($rebateCode);
}
$cart->setRebateCode(null);
$this->em->persist($cart);
$this->em->flush();
}
/**
* @param $countryId
* @param $deliveryId
* @param $paymentId
* @return Cart
* @throws CartException
*/
public function prepareToCheckout($deliveryId, $paymentId) {
$deliveryMethod = $this->deliveryMethodRepository->getRepository()->find($deliveryId);
$paymentMethod = $this->paymentMethodRepository->getRepository()->find($paymentId);
if (!is_object($deliveryMethod) or !is_object($paymentMethod)) {
throw new CartException("Delivery, Payment or Country not an object");
}
$cart = $this->getCart();
$cart->setDeliveryMethod($deliveryMethod);
$cart->setPaymentMethod($paymentMethod);
$this->em->persist($cart);
$this->em->flush();
return $cart;
}
public function getSpecialDeliveryMethodsIds(Cart $cart) {
$special = [];
/** @var $cartProduct CartProduct */
foreach ($cart->getProducts() as $cartProduct) {
$product = $cartProduct->getProduct();
foreach ($product->getSpecialDeliveryMethods() as $deliveryMethod) {
if (!in_array($deliveryMethod->getId(), $special)) {
$special[] = $deliveryMethod->getId();
}
}
}
return $special;
}
public function getCrossSellingProducts(Cart $cart) {
$crossSell = new ArrayCollection();
/** @var $cartProduct CartProduct */
foreach ($this->getCartProducts() as $cartProduct) {
$product = $cartProduct->getProduct();
$crossSelling = $product->getCrossSelling();
foreach ($crossSelling as $crossSellingRow) {
if ($crossSellingRow->getDeletedBy()) {
continue;
}
if ($crossSellingRow->getCrossSellingProduct()->getDeletedBy()) {
continue;
}
$langParamEntity = $this->em->getRepository(ProductLangParam::class)->findOneBy(['language'=>$cart->getLanguage(), 'product'=>$crossSellingRow->getCrossSellingProduct()]);
if ($langParamEntity and $langParamEntity->getVisible()) {
$crossSell->add($crossSellingRow->getCrossSellingProduct());
}
}
}
return $crossSell;
}
}