2469 lines
84 KiB
PHP
2469 lines
84 KiB
PHP
<?php
|
|
|
|
use KupShop\CatalogBundle\ProductList\ProductCollection;
|
|
use KupShop\ContentBundle\Exception\InvalidCartItemException;
|
|
use KupShop\ContentBundle\Util\CartMerge;
|
|
use KupShop\I18nBundle\Translations\ProductsTranslation;
|
|
use KupShop\I18nBundle\Translations\VariationsTranslation;
|
|
use KupShop\KupShopBundle\Context\CountryContext;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\UserContext;
|
|
use KupShop\KupShopBundle\Context\VatContext;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\KupShopBundle\Util\Database\QueryHint;
|
|
use KupShop\KupShopBundle\Util\Event\ErrorIgnoringEventDispatcher;
|
|
use KupShop\KupShopBundle\Util\Mail\EmailCheck;
|
|
use KupShop\KupShopBundle\Util\Price\Price;
|
|
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
|
|
use KupShop\KupShopBundle\Wrapper\PriceWrapper;
|
|
use KupShop\OrderDiscountBundle\Util\DiscountManager;
|
|
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
|
|
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
|
|
use KupShop\OrderingBundle\Event\CartBeforeInsertItemEvent;
|
|
use KupShop\OrderingBundle\Event\CartEvent;
|
|
use KupShop\OrderingBundle\Event\CartItemEvent;
|
|
use KupShop\OrderingBundle\Event\OrderEvent;
|
|
use KupShop\OrderingBundle\Event\PurchaseStateCreatedEvent;
|
|
use KupShop\OrderingBundle\Exception\CartValidationException;
|
|
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
|
|
use KupShop\OrderingBundle\Wrapper\Purchase\ProductPurchaseItemWrapper;
|
|
use Query\Operator;
|
|
use Query\QueryBuilder;
|
|
use Query\Translation;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Component\HttpFoundation\RequestStack;
|
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
/**
|
|
* Class for Order preparation.
|
|
*
|
|
* @author Joe
|
|
*/
|
|
class CartBase implements ArrayAccess
|
|
{
|
|
use DatabaseCommunication;
|
|
|
|
public $products = [];
|
|
/** @var ProductCollection */
|
|
public $productList;
|
|
public $user;
|
|
|
|
/** @var Decimal */
|
|
public $totalPriceNoVat;
|
|
/** @var Decimal */
|
|
public $totalPriceWithVat;
|
|
/** @var Decimal */
|
|
public $totalPricePay;
|
|
/** @var Decimal */
|
|
public $totalPricePayNoVat;
|
|
|
|
/**
|
|
* @var Decimal[]
|
|
*/
|
|
public $priceByVat = [];
|
|
public $totalPieces = 0.;
|
|
|
|
/**
|
|
* @var Decimal
|
|
*/
|
|
public $totalDiscountPrice;
|
|
|
|
/**
|
|
* @var Decimal
|
|
*/
|
|
public $totalDiscountPriceNoVat;
|
|
|
|
/**
|
|
* @var Decimal
|
|
*/
|
|
public $totalChargesPrice;
|
|
|
|
/**
|
|
* @var Decimal
|
|
*/
|
|
public $totalChargesPriceNoVat;
|
|
|
|
public $freeShippingValue = 0.;
|
|
public $freeShipping = false;
|
|
|
|
public $openOrders = [];
|
|
public $vats = [];
|
|
public $orders_charges;
|
|
public $charges;
|
|
|
|
/** @var DeliveryTypeBase[] */
|
|
public $delivery_types = [];
|
|
|
|
public $virtual_products;
|
|
public $only_virtual_products;
|
|
|
|
public $coupons = [];
|
|
|
|
// User info
|
|
public $invoice = [];
|
|
public $delivery = [];
|
|
public $transport;
|
|
public $delivery_id;
|
|
public $payment_id;
|
|
public $note;
|
|
public $newsletter = false;
|
|
public $addressesSet = false;
|
|
public $userOrderNo;
|
|
|
|
public $paymentData = [];
|
|
public $deliveryData = [];
|
|
public $cart_data;
|
|
/**
|
|
* @var DeliveryType
|
|
*/
|
|
protected $deliveryType;
|
|
protected $storeDeliveryData = false;
|
|
public $register = false;
|
|
public $max_step = 0;
|
|
public $max_step_submitted = -1;
|
|
|
|
protected $errors = [];
|
|
|
|
public static $NO_CART_ID = 'no-cartID';
|
|
public static $ERR_NOT_SELECTED_DELIVERY = 5;
|
|
public static $ERR_NOT_SUPPORTED_COUNTRY = 18;
|
|
public static $ERR_EXISTENT_PRODUCT = 18;
|
|
public static $ERR_INVALID_VARIATION = 19;
|
|
public static $ERR_OUT_OF_STOCK = 30;
|
|
public static $ERR_NOT_SELECTED_VARIATION = 31;
|
|
public static $ERR_CANNOT_BE_PURCHASED = 33;
|
|
|
|
private SessionInterface $session;
|
|
/** @var PurchaseUtil */
|
|
protected $purchaseUtil;
|
|
|
|
/** @var PurchaseState */
|
|
protected $purchaseState;
|
|
|
|
protected ?PurchaseState $previousPurchaseState = null;
|
|
|
|
protected ?RequestStack $requestStack = null;
|
|
|
|
public $actualStep;
|
|
|
|
private static $cartID;
|
|
/**
|
|
* @var array
|
|
*/
|
|
public $steps;
|
|
/**
|
|
* @var bool
|
|
*/
|
|
public $selected;
|
|
|
|
private $actionHandlersData = [];
|
|
|
|
private $initialized = false;
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
global $cfg;
|
|
|
|
$this->session = ServiceContainer::getService('session');
|
|
|
|
if (empty($cfg['Order']['Steps'])) {
|
|
// die("Nejsou nadefinovány kroky objenávky");
|
|
|
|
// Kroky objednavkoveho procesu
|
|
$this->steps = [
|
|
'cart' => [
|
|
'title' => translate('step_cart', 'cart'),
|
|
'name' => '',
|
|
'data' => ['items'],
|
|
],
|
|
'delivery' => [
|
|
'title' => translate('step_delivery', 'cart'),
|
|
'name' => translate('url_delivery', 'cart'),
|
|
'data' => ['delivery', 'payment'],
|
|
],
|
|
'user' => [
|
|
'title' => translate('step_user', 'cart'),
|
|
'name' => translate('url_user', 'cart'),
|
|
'data' => ['user'],
|
|
],
|
|
'summary' => [
|
|
'title' => translate('step_summary', 'cart'),
|
|
'name' => translate('url_summary', 'cart'),
|
|
],
|
|
];
|
|
} else {
|
|
$this->steps = $cfg['Order']['Steps'];
|
|
foreach ($this->steps as &$step) {
|
|
if (!empty($step['title_translate'])) {
|
|
$step['title'] = translate($step['title_translate'], 'cart');
|
|
}
|
|
if (!empty($step['name_translate'])) {
|
|
$step['name'] = translate($step['name_translate'], 'cart');
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($this->steps as $stepName => &$step) {
|
|
if (empty($step['template'])) {
|
|
$step['template'] = "ordering.{$stepName}.tpl";
|
|
}
|
|
|
|
if (!isset($step['name'])) {
|
|
$step['name'] = $stepName;
|
|
}
|
|
|
|
if (empty($step['url'])) {
|
|
if (empty($step['name'])) {
|
|
$step['url'] = path('kupshop_content_cart_cart_1');
|
|
} else {
|
|
$step['url'] = path('kupshop_content_cart_cart', ['step' => $step['name']]);
|
|
}
|
|
// backward compatibility
|
|
$step['url'] = ltrim($step['url'], '/');
|
|
}
|
|
}
|
|
|
|
$this->user = new User();
|
|
$this->invoice = &$this->user->invoice;
|
|
$this->delivery = &$this->user->delivery;
|
|
$this->transport = &$this->user->transport;
|
|
|
|
$this->totalDiscountPrice = DecimalConstants::zero();
|
|
$this->totalDiscountPriceNoVat = DecimalConstants::zero();
|
|
$this->totalPricePay = DecimalConstants::zero();
|
|
$this->totalPricePayNoVat = DecimalConstants::zero();
|
|
$this->totalPriceNoVat = DecimalConstants::zero();
|
|
$this->totalPriceWithVat = DecimalConstants::zero();
|
|
$this->totalChargesPrice = DecimalConstants::zero();
|
|
$this->totalChargesPriceNoVat = DecimalConstants::zero();
|
|
|
|
// prepare productList
|
|
$this->productList = new ProductCollection();
|
|
$this->productList->setEntityType(FilterParams::ENTITY_VARIATION);
|
|
}
|
|
|
|
#[Required]
|
|
final public function setPurchaseUtil(PurchaseUtil $purchaseUtil): void
|
|
{
|
|
$this->purchaseUtil = $purchaseUtil;
|
|
}
|
|
|
|
public function hasData($type, $submitted = true)
|
|
{
|
|
$i = 0;
|
|
|
|
$maxStep = $this->max_step_submitted;
|
|
if (!$submitted) {
|
|
$maxStep = $this->max_step;
|
|
}
|
|
|
|
foreach ($this->steps as $step) {
|
|
if ($i > $maxStep) {
|
|
break;
|
|
}
|
|
|
|
if (in_array($type, getVal('data', $step, []))) {
|
|
return true;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function findStep(&$name)
|
|
{
|
|
$step = &$this->steps[$name];
|
|
|
|
$this->actualStep = $name;
|
|
|
|
$step['selected'] = true;
|
|
|
|
return $step;
|
|
}
|
|
|
|
public function findNextStep($name)
|
|
{
|
|
$steps = array_keys($this->steps);
|
|
$index = array_search($name, $steps) + 1;
|
|
|
|
$this->max_step = $index;
|
|
|
|
return $this->findStep($steps[$index]);
|
|
}
|
|
|
|
public function findPreviousStep($name)
|
|
{
|
|
$steps = array_keys($this->steps);
|
|
$index = array_search($name, $steps) - 1;
|
|
|
|
if ($index < 0) {
|
|
return false;
|
|
}
|
|
|
|
return $this->findStep($steps[$index]);
|
|
}
|
|
|
|
public function visitStep(&$name, $submitted, $changed)
|
|
{
|
|
if (empty($this->steps[$name])) {
|
|
foreach ($this->steps as $stepName => &$step) {
|
|
if ($step['name'] == $name) {
|
|
$name = $stepName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$steps = array_keys($this->steps);
|
|
|
|
if (empty($this->steps[$name])) {
|
|
return $steps[0];
|
|
}
|
|
|
|
$index = array_search($name, $steps);
|
|
|
|
if ($index > $this->max_step) {
|
|
return $steps[$this->max_step];
|
|
} elseif ($index < $this->max_step && $changed) {
|
|
$this->max_step = $index;
|
|
$this->max_step_submitted = $index - 1;
|
|
}
|
|
|
|
if ($submitted) {
|
|
$this->max_step_submitted = $this->max_step;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fetches data from database, product ID specified in $pid.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function createFromDB()
|
|
{
|
|
if ($this->initialized) {
|
|
return;
|
|
}
|
|
|
|
$this->initialized = true;
|
|
|
|
// Get all products
|
|
$this->fetchProducts();
|
|
|
|
$this->fetchCharges();
|
|
|
|
// Get all discounts
|
|
$this->fetchDiscounts();
|
|
|
|
// Fetch vats
|
|
$this->fetchVats();
|
|
|
|
// Fetch address from user
|
|
$this->fetchAddresses();
|
|
|
|
// Fetch totals
|
|
$this->fetchTotals();
|
|
|
|
// Fetch delivery types
|
|
$this->fetchDelivery();
|
|
|
|
// cenu dopravy nechceme pricitat do celkove ceny na prvnim kroku kosiku
|
|
if ($this->transport && $this->getActualStepIndex() > 0) {
|
|
$purchaseState = $this->getPurchaseState();
|
|
// nasetovat delivery type do purchase statu, aby se mohla pricist cena dopravy k celkove cene
|
|
$purchaseState->setDeliveryType($this->getDeliveryType());
|
|
|
|
$this->purchaseUtil->recalculateTotalPrices($purchaseState);
|
|
$this->purchaseUtil->ensureTotalIsNotNegative($purchaseState);
|
|
$this->fetchTotalsFromPurchaseState($purchaseState);
|
|
}
|
|
|
|
// Otevrene objednavky uzivatele
|
|
$this->openOrders = Order::getOpenOrders();
|
|
|
|
$this->cart_data = $this->getData();
|
|
}
|
|
|
|
public function setActionHandlersData(array $actionHandlersData)
|
|
{
|
|
$this->actionHandlersData = $actionHandlersData;
|
|
}
|
|
|
|
public function getActionHandlersData(): array
|
|
{
|
|
return $this->actionHandlersData;
|
|
}
|
|
|
|
public function getPurchaseState(): PurchaseState
|
|
{
|
|
if (isset($this->purchaseState)) {
|
|
return $this->purchaseState;
|
|
}
|
|
|
|
if (self::getCartID() === self::$NO_CART_ID) {
|
|
$purchaseState = $this->purchaseUtil->getEmptyPurchaseState();
|
|
} else {
|
|
// get and remove whether current purchase state was invalidated
|
|
$purchaseStateIsInvalid = $this->session->remove('cart.purchase_state.invalid');
|
|
|
|
if (($purchaseState = $this->purchaseUtil->unserializeState($this->session->get('cart.purchase_state'))) && $purchaseStateIsInvalid) {
|
|
$this->previousPurchaseState = $purchaseState;
|
|
$purchaseState = null;
|
|
}
|
|
}
|
|
|
|
if (!$purchaseState) {
|
|
$this->createFromDB();
|
|
$purchaseState = $this->purchaseUtil->createStateFromCart($this);
|
|
|
|
// add order discounts to PurchaseState
|
|
if (findModule(Modules::ORDER_DISCOUNT)) {
|
|
$discountManager = ServiceContainer::getService(DiscountManager::class);
|
|
$discountManager->setPurchaseState($purchaseState);
|
|
|
|
if ($purchaseState->getProducts()) {
|
|
$discountManager->setActionHandlersData($this->actionHandlersData);
|
|
$purchaseState = $discountManager->recalculate();
|
|
if ($this->actualStep == 'cart') {
|
|
$discountManager->addUserMessages();
|
|
}
|
|
if ($data = $purchaseState->getCustomData()) {
|
|
$this->setData('discounts', $data);
|
|
}
|
|
if ($handlers = $discountManager->getActionHandlers()) {
|
|
$purchaseState->setDiscountHandlers($handlers);
|
|
}
|
|
}
|
|
}
|
|
|
|
$eventDispatcher = ServiceContainer::getService('event_dispatcher');
|
|
$purchaseState = $eventDispatcher
|
|
->dispatch(new PurchaseStateCreatedEvent($purchaseState, $this))
|
|
->getPurchaseState();
|
|
|
|
$this->purchaseUtil->ensureTotalIsNotNegative($purchaseState);
|
|
|
|
$this->purchaseUtil->persistPurchaseState($purchaseState);
|
|
}
|
|
|
|
return $this->purchaseState = $purchaseState;
|
|
}
|
|
|
|
public function getPreviousPurchaseState(): ?PurchaseState
|
|
{
|
|
return $this->previousPurchaseState;
|
|
}
|
|
|
|
public function invalidatePurchaseState(): void
|
|
{
|
|
$this->session->set('cart.purchase_state.invalid', true);
|
|
$this->purchaseState = null;
|
|
}
|
|
|
|
public function userLoggedIn($id_user)
|
|
{
|
|
$this->load();
|
|
|
|
$this->addressesSet = false;
|
|
|
|
// ################################
|
|
// PRENESENI OFFLINE KOSIKU DO LOGGED IN STAVU
|
|
|
|
$userKey = $this->getCartID();
|
|
|
|
$cartMerge = ServiceContainer::getService(CartMerge::class);
|
|
|
|
// zjistim, zda je kosik prihlaseneho uzivatele prazdnej nebo ne
|
|
$isLoggedUserCartEmpty = $cartMerge->isUserCartEmpty();
|
|
// zmerguju kosik neprihlaseneho uzivatele do kosiku prihlaseneho uzivatele
|
|
$mergedCount = $cartMerge->merge($userKey);
|
|
sqlQueryBuilder()->delete('cart')->where(Operator::equals(['user_key' => $userKey]))->execute();
|
|
|
|
// pokud kosik prihlaseneho uzivatele nebyl prazdnej a neco jsem do nej namergoval, tak pridam hlasku
|
|
if (!$isLoggedUserCartEmpty && $mergedCount > 0) {
|
|
addUserMessage(translate('cartMergeInfo', 'order'), 'info');
|
|
}
|
|
|
|
$user = User::createFromId($id_user);
|
|
if ($user && $user->user_key) {
|
|
self::setCartID($user->user_key);
|
|
}
|
|
|
|
$this->invalidatePurchaseState();
|
|
}
|
|
|
|
public function userLoggedOut()
|
|
{
|
|
$this->session->remove('cart');
|
|
|
|
$this->requestStack->getMainRequest()?->attributes->set('gtm_logout', true);
|
|
|
|
if (findModule(Modules::JS_SHOP)) {
|
|
$this->session->set(\KupShop\GraphQLBundle\EventListener\JsShopRefreshListener::SESSION_NAME, true);
|
|
}
|
|
$this->invalidatePurchaseState();
|
|
}
|
|
|
|
public function getSelectParams($alias = '')
|
|
{
|
|
$userContext = Contexts::get(UserContext::class);
|
|
|
|
if ($userContext->getActiveId() > 0) {
|
|
return [$alias.'id_user' => $userContext->getActiveId()];
|
|
} else {
|
|
return [$alias.'user_key' => $this->getCartID()];
|
|
}
|
|
}
|
|
|
|
public static function getCartID($allowEmpty = true)
|
|
{
|
|
if (!self::$cartID) {
|
|
$cookie = getVal('cartID', $_COOKIE, '');
|
|
if ($cookie) {
|
|
$userKey = $cookie;
|
|
} elseif ($allowEmpty) {
|
|
return self::$NO_CART_ID;
|
|
} else {
|
|
$userKey = session_id();
|
|
SetCookies('cartID', $userKey, 86400 * 365);
|
|
}
|
|
self::$cartID = $userKey;
|
|
}
|
|
|
|
return self::$cartID;
|
|
}
|
|
|
|
public static function setCartID($userKey = '')
|
|
{
|
|
self::$cartID = $userKey;
|
|
SetCookies('cartID', $userKey, 86400 * 365);
|
|
|
|
return self::$cartID;
|
|
}
|
|
|
|
public function clear()
|
|
{
|
|
$this->session->remove('cart');
|
|
$this->session->remove('cartData');
|
|
$this->invalidatePurchaseState();
|
|
|
|
if (findModule(\Modules::JS_SHOP)) {
|
|
$this->session->set(\KupShop\GraphQLBundle\EventListener\JsShopRefreshListener::SESSION_NAME, true);
|
|
}
|
|
|
|
return $this->deleteSQL('cart', $this->getSelectParams());
|
|
}
|
|
|
|
public function deleteItem($id)
|
|
{
|
|
$this->invalidatePurchaseState();
|
|
|
|
$this->sendCartEvent($this, CartItemEvent::BEFORE_DELETE_ITEM, new CartItemEvent($this, ['id' => $id]));
|
|
|
|
return $this->deleteSQL('cart', array_merge(['id' => $id], $this->getSelectParams()));
|
|
}
|
|
|
|
public function updateItem($id, $data)
|
|
{
|
|
if (isset($data['pieces']) && ($data['pieces'] <= 0 || !is_numeric($data['pieces']))) {
|
|
return $this->deleteItem($id);
|
|
}
|
|
|
|
$cartItemArray = sqlFetchAssoc($this->selectSQL('cart', ['id' => $id], ['id_product', 'id_variation', 'pieces', 'note']));
|
|
$product = null;
|
|
$differences = [];
|
|
if ($cartItemArray) {
|
|
$product = \Variation::createProductOrVariation($cartItemArray['id_product'], $cartItemArray['id_variation'] ?? null);
|
|
$product->createFromDB();
|
|
}
|
|
|
|
if (isset($data['note_json'])) {
|
|
$noteJson = json_decode($data['note_json'] ?: '', true) ?: [];
|
|
$data['note'] = array_replace_recursive($data['note'] ?? [], $noteJson);
|
|
unset($data['note_json']);
|
|
}
|
|
|
|
if (isset($data['note']) && $product) {
|
|
$note = $product->parseNote($cartItemArray['note']);
|
|
$note = array_replace_recursive($note, $data['note']);
|
|
$data['note'] = $product->dumpNote($note);
|
|
}
|
|
|
|
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS_FLOAT)) {
|
|
if (!empty($cartItemArray) && $product) {
|
|
$data['pieces'] = $this->roundPieces($product, $data['pieces']);
|
|
}
|
|
}
|
|
|
|
$affected = $this->updateSQL('cart', $data, array_merge(['id' => $id], $this->getSelectParams()));
|
|
|
|
if ($cartItemArray) {
|
|
$cartItemArrayUpdated = array_merge($cartItemArray, $data);
|
|
$differences = array_diff($cartItemArrayUpdated, $cartItemArray);
|
|
if ($differences['pieces'] ?? false) {
|
|
$differences['pieces_diff'] = $cartItemArrayUpdated['pieces'] - $cartItemArray['pieces'];
|
|
}
|
|
}
|
|
|
|
$this->sendCartEvent($this, CartItemEvent::AFTER_UPDATE_ITEM, new CartItemEvent($this, ['id' => $id] + $data, $product, $differences));
|
|
|
|
return $affected;
|
|
}
|
|
|
|
public function recalculate($items)
|
|
{
|
|
foreach ($items as $id => $data) {
|
|
if (!is_array($data)) {
|
|
$data = ['pieces' => $data];
|
|
}
|
|
|
|
$this->updateItem($id, $data);
|
|
}
|
|
|
|
$this->invalidatePurchaseState();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function addItem(&$params = [])
|
|
{
|
|
/*
|
|
* Tmp fix - kvuli asynchronimu odesilani SS GTM event.
|
|
* Ve chvili kdy se dokonci celej tenhle request, tak se po zaslani response uzivateli, jeste spusti asynchronni volani odeslani do GTM.
|
|
* Jenze uvnitr se vytahuje ID sekce, coz vede k tomu ze se uvnitr MenuSectionTree vola jestli je uzivatel admin nebo ne -> vola se session,
|
|
* ale tu uz to neotevre protoze uz se odeslala response uzivateli a padne to. Divnost symfony.
|
|
* Tim, ze se to zavola tady, se to ulozi do staticky promenny a priste se uz nesaha do session.
|
|
*/
|
|
getAdminUser();
|
|
$ret = null;
|
|
|
|
if (empty($params['id_product'])) {
|
|
return $ret;
|
|
}
|
|
|
|
// get cart with allowEmpty: false parameter, to ensure the cart session and cookie is created
|
|
self::getCartID(false);
|
|
|
|
$product = Variation::createProductOrVariation($params['id_product'], $params['id_variation'] ?? null);
|
|
if (!$product->createFromDB()) {
|
|
throw new InvalidCartItemException(sprintf('Product ID %s does not exists!', $params['id_product']));
|
|
}
|
|
|
|
// Check variation is selected if product has variations
|
|
if (empty($params['id_variation']) && $product->hasVariations()) {
|
|
return -19;
|
|
}
|
|
|
|
ServiceContainer::getService('event_dispatcher')->dispatch(new CartBeforeInsertItemEvent($product, $params['note'] ?? '', $params['pieces'] ?? 0));
|
|
|
|
sqlStartTransaction();
|
|
|
|
// Encode note
|
|
if (!empty($params['note'])) {
|
|
$params['note'] = $product->dumpNote($params['note']);
|
|
}
|
|
|
|
$searchFields = array_merge($this->getSelectParams(), $this->filterFields($params, ['id_product', 'id_variation', 'note']));
|
|
|
|
$item = sqlFetchAssoc($this->selectSQL('cart', $searchFields, ['id', 'pieces']));
|
|
$piecesYet = $this->roundPieces($product, $item['pieces'] ?? 0);
|
|
$params['pieces'] = $this->roundPieces($product, $params['pieces']);
|
|
|
|
if ($piecesYet > 0) {
|
|
$this->updateItem($item['id'], ['pieces' => $item['pieces'] + $params['pieces']]);
|
|
$params['pieces'] = $item['pieces'] + $params['pieces'];
|
|
$ret = $item['id'];
|
|
} elseif ($params['pieces'] > 0) {
|
|
$params['date'] = date('Y-m-d H:i:s');
|
|
$insertFields = array_merge($this->getSelectParams(), $this->filterFields($params, ['id_product', 'id_variation', 'note', 'pieces', 'date']));
|
|
$this->insertSQL('cart', $insertFields);
|
|
$ret = sqlInsertId();
|
|
$item = ['id' => $ret];
|
|
|
|
$differences = ['pieces' => $params['pieces'], 'pieces_diff' => $params['pieces']];
|
|
$this->sendCartEvent($this, CartItemEvent::AFTER_INSERT_ITEM,
|
|
new CartItemEvent($this, $item + $params, $product, $differences)
|
|
);
|
|
}
|
|
|
|
sqlFinishTransaction();
|
|
|
|
if ($ret) {
|
|
$this->invalidatePurchaseState();
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public function addCoupon($coupon)
|
|
{
|
|
$coupon = trim($coupon);
|
|
|
|
if (empty($coupon)) {
|
|
return false;
|
|
}
|
|
|
|
$coupon_already_used = in_array(
|
|
strtolower($coupon),
|
|
array_map(
|
|
'strtolower',
|
|
$this->coupons
|
|
));
|
|
|
|
if ($coupon_already_used) {
|
|
return true;
|
|
}
|
|
|
|
$coupon = strtoupper($coupon);
|
|
$coupon_exists = false;
|
|
|
|
if (findModule(Modules::ORDER_DISCOUNT)) {
|
|
/** @var DiscountManager $discountManager */
|
|
$discountManager = ServiceContainer::getService(DiscountManager::class);
|
|
$order_discounts = [];
|
|
if ($discountManager->couponNumberExists($coupon, $order_discounts)) {
|
|
$this->fetchProducts();
|
|
$purchaseState = $this->purchaseUtil->createStateFromCart($this);
|
|
$discountManager->setPurchaseState($purchaseState);
|
|
$discountManager->recalculate();
|
|
$coupon_exists = $discountManager->isCouponValid($coupon, $order_discounts);
|
|
}
|
|
}
|
|
|
|
if (!$coupon_exists) {
|
|
return $coupon_exists;
|
|
}
|
|
|
|
$this->coupons[$coupon] = $coupon;
|
|
|
|
$this->invalidatePurchaseState();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function deleteCoupon($coupon = null)
|
|
{
|
|
if ($coupon !== null) {
|
|
$coupon = trim($coupon);
|
|
} else {
|
|
$coupon = reset($this->coupons);
|
|
}
|
|
|
|
if ($coupon) {
|
|
foreach ($this->coupons as $key => $cartCoupon) {
|
|
if (strcasecmp($cartCoupon, $coupon) == 0) {
|
|
unset($this->coupons[$key]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->invalidatePurchaseState();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fetching functions.
|
|
*/
|
|
protected function fetchProducts()
|
|
{
|
|
if (!is_null($this->only_virtual_products)) {
|
|
return $this->products;
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$this->totalPriceNoVat = DecimalConstants::zero();
|
|
$this->totalPriceWithVat = DecimalConstants::zero();
|
|
$this->virtual_products = false;
|
|
$this->only_virtual_products = true;
|
|
|
|
$where = queryCreate($this->getSelectParams(), true);
|
|
|
|
$qb = sqlQueryBuilder();
|
|
// TODO prepsat na ProductList a nebude toto potreba
|
|
$inStoreField = \Query\Product::getInStoreField(true, $qb);
|
|
|
|
$qb->addSelect('c.id, c.pieces, c.id_variation, c.note, p.id as id_product, p.title, p.price, p.producer as id_producer, pr.name as producer, p.discount,
|
|
FIND_IN_SET("Z", p.campaign)>0 free_shipping, COALESCE(pv.delivery_time, p.delivery_time) as delivery_time, COALESCE(pv.ean, p.ean) as ean');
|
|
|
|
$qb->addSelect("{$inStoreField} as in_store");
|
|
|
|
// prepare sub query
|
|
$subQuery = sqlQueryBuilder()
|
|
->from('cart', 'sc')
|
|
->addSelect('SUM(pieces)')
|
|
->where('sc.id_product = c.id_product AND
|
|
(sc.id_variation = c.id_variation OR sc.id_variation IS NULL AND c.id_variation IS NULL)')
|
|
->andWhere($where)
|
|
->setMaxResults(1);
|
|
$qb->addSelect("({$subQuery}) as total_pieces");
|
|
|
|
$qb->from('cart', 'c')
|
|
->addSelect(\Query\Product::withVat())
|
|
->addSelect('c.date')
|
|
->leftJoin('c', 'products', 'p', 'c.id_product=p.id')
|
|
->leftJoin('c', 'producers', 'pr', 'pr.id=p.producer')
|
|
->leftJoin('c', 'products_variations', 'pv', 'c.id_variation=pv.id');
|
|
|
|
if ($showMax = findModule('products', 'showMax')) {
|
|
$qb->addSelect('COALESCE(pv.in_store_show_max, p.in_store_show_max,'.$showMax.') as in_store_show_max');
|
|
}
|
|
|
|
if (findModule('products_variations', 'variationCode')) {
|
|
$qb->addSelect('pv.code as variation_code');
|
|
}
|
|
|
|
if (findModule('products', 'weight')) {
|
|
$qb->addSelect('COALESCE(pv.weight, p.weight) weight');
|
|
}
|
|
|
|
$qb->andWhere($where)
|
|
->groupBy('c.id')
|
|
->orderBy('c.id')
|
|
->sendToMaster();
|
|
|
|
foreach ($qb->execute() as $row) {
|
|
$IDInCart = $row['id'];
|
|
|
|
$productListId = $row['id_product'];
|
|
if ($row['id_variation']) {
|
|
$productListId .= '/'.$row['id_variation'];
|
|
}
|
|
|
|
if (!($product = $this->productList->get($productListId))) {
|
|
if ($row['id_variation']) {
|
|
$product = new Variation($row['id_product'], $row['id_variation']);
|
|
} else {
|
|
$product = new Product($row['id_product']);
|
|
}
|
|
|
|
if ($product->createFromDB($product->id) === false) {
|
|
sqlQueryBuilder()->delete('cart')
|
|
->where(\Query\Operator::equals(['id' => $row['id']]))
|
|
->execute();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$dec_Pieces = toDecimal($row['pieces']);
|
|
$Pieces = $row['pieces'];
|
|
|
|
$note = $product->parseNote($row['note']);
|
|
$InStore = max(0, $row['in_store']);
|
|
|
|
$vat = $row['vat'];
|
|
|
|
$title = $product->title;
|
|
if (!is_null($row['id_variation'])) {
|
|
$variationTitle = $product->variationTitle ?? '';
|
|
$title .= " ({$variationTitle})";
|
|
}
|
|
|
|
$price = $this->purchaseUtil->getItemPrice($row, $product);
|
|
$priceArray = PriceWrapper::wrap($price);
|
|
|
|
$price_with_vat_rounded = $priceArray['value_with_vat'];
|
|
$price_without_vat_rounded = $priceArray['value_without_vat'];
|
|
$item_price_with_vat = $price_with_vat_rounded->mul($dec_Pieces);
|
|
$item_price_without_vat = calcPrice($item_price_with_vat, -getVat($row['vat']));
|
|
$totalPriceArray = formatPrice($item_price_without_vat, getVat($row['vat']));
|
|
|
|
// celkova cena bez DPH
|
|
$this->totalPriceNoVat = $this->totalPriceNoVat->add($price_without_vat_rounded->mul($dec_Pieces));
|
|
|
|
// celkova cena s DPH
|
|
$this->totalPriceWithVat = $this->totalPriceWithVat->add($price_with_vat_rounded->mul($dec_Pieces));
|
|
|
|
// ceny podle DPH, vlozit cenu s DPH
|
|
if (!isset($this->priceByVat[$vat])) {
|
|
$this->priceByVat[$vat] = DecimalConstants::zero();
|
|
}
|
|
|
|
$this->priceByVat[$vat] = $this->priceByVat[$vat]->add($price_without_vat_rounded->mul($dec_Pieces));
|
|
|
|
if ($row['free_shipping'] > 0) {
|
|
$this->freeShipping = true;
|
|
}
|
|
|
|
// zapocitat celkovy pocet kusu
|
|
$this->totalPieces += $row['pieces'];
|
|
|
|
// Dostupnost
|
|
$availability = 0; // Vyprodano
|
|
|
|
if (!empty($row['in_store_show_max']) > 0) {
|
|
$InStore = min(intval($row['in_store_show_max']), $InStore);
|
|
}
|
|
|
|
// Simulate variation store and delivery_time
|
|
$product->deliveryTime = $row['delivery_time'];
|
|
$product->inStore = $InStore;
|
|
|
|
if ($dbcfg->prod_subtract_from_store == 'Y') {
|
|
if (max($Pieces, $row['total_pieces']) <= $InStore) {
|
|
$availability = 1;
|
|
} // Skladem
|
|
|
|
if ($availability == 0 && findModule('products_suppliers')) {
|
|
$inSuppliers = $product->getInStoreSuppliers($row['id_variation']);
|
|
$product->in_store_suppliers = intval($inSuppliers);
|
|
|
|
if ($inSuppliers > 0) {
|
|
// Je dostatek u dodavatele
|
|
$InStore += $inSuppliers;
|
|
if ($Pieces <= $InStore) {
|
|
$availability = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$product->prepareDeliveryText();
|
|
} else {
|
|
$product->prepareDeliveryText();
|
|
if ($product->deliveryTime == 0 || $product->deliveryTimeRaw == 0) {
|
|
$availability = 1;
|
|
}
|
|
}
|
|
|
|
$prod = [];
|
|
$prod['id'] = $product->id;
|
|
$prod['id_variation'] = $row['id_variation'];
|
|
$prod['idincart'] = $IDInCart;
|
|
$prod['title'] = $title;
|
|
$prod['note'] = $note;
|
|
$prod['pieces'] = $Pieces;
|
|
$prod['total_pieces'] = $row['total_pieces'];
|
|
$prod['date'] = $row['date'];
|
|
$prod['inStore'] = $InStore;
|
|
$prod['availability'] = $availability;
|
|
$prod['deliveryTime'] = $product->deliveryTime;
|
|
$prod['deliveryTimeText'] = $product->deliveryTimeText;
|
|
$prod['deliveryTimeRaw'] = $product->deliveryTimeRaw;
|
|
$prod['discount'] = $row['discount'];
|
|
$prod['producer'] = $row['producer'];
|
|
$prod['id_producer'] = $row['id_producer'];
|
|
$prod['ean'] = $row['ean'];
|
|
$prod['vat'] = getVat($row['vat']);
|
|
$prod['product'] = $product;
|
|
$prod['virtual'] = $product->isVirtual();
|
|
|
|
$this->virtual_products = $this->virtual_products || $prod['virtual'];
|
|
$this->only_virtual_products = $this->only_virtual_products & $prod['virtual'];
|
|
|
|
$product->fetchSets();
|
|
$prod['sets'] = $product->sets;
|
|
|
|
$prod['price'] = $priceArray;
|
|
$prod['totalPrice'] = $totalPriceArray;
|
|
if (isset($row['variation_code'])) {
|
|
$prod['variation_code'] = $row['variation_code'];
|
|
}
|
|
|
|
if (findModule('products', 'weight')) {
|
|
$prod['weight'] = $row['weight'] ?: $product['weight'];
|
|
}
|
|
|
|
$this->products[$IDInCart] = $prod;
|
|
$this->productList->set($productListId, $this->products[$IDInCart]['product']);
|
|
|
|
// ----------------------------------------------------------------
|
|
// kdyz je u zbozi příplatek, ktery nebyl jeste zapocitan v cene
|
|
if (findModule(Modules::PRODUCTS_CHARGES)) {
|
|
foreach ($product->fetchCharges($note['charges'] ?? []) as $charge) {
|
|
if ($charge['included'] == 'N' && $charge['active']) {
|
|
/** @var Price $price */
|
|
$price = $charge['price'];
|
|
|
|
$chargePieces = $dec_Pieces;
|
|
if (($charge['data']['onetime'] ?? 'N') === 'Y') {
|
|
$chargePieces = DecimalConstants::one();
|
|
}
|
|
|
|
$totalPrice = new Price($price->getPriceWithoutVat()->mul($chargePieces), $price->getCurrency(), $price->getVat());
|
|
|
|
// celkova cena bez DPH
|
|
$this->totalPriceNoVat = $this->totalPriceNoVat->add($totalPrice->getPriceWithoutVat());
|
|
|
|
// celkova cena s DPH
|
|
$this->totalPriceWithVat = $this->totalPriceWithVat->add($totalPrice->getPriceWithVat());
|
|
|
|
// ceny podle DPH, vlozit cenu s DPH
|
|
if (!isset($this->priceByVat[$charge['vat']])) {
|
|
$this->priceByVat[$charge['vat']] = toDecimal(0);
|
|
}
|
|
$this->priceByVat[$charge['vat']] = $this->priceByVat[$charge['vat']]->add($totalPrice->getPriceWithoutVat());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->productList->fetchProducers();
|
|
$this->productList->fetchGifts();
|
|
$this->productList->fetchMainImages(4);
|
|
$this->products = array_map(function ($prod) {
|
|
$prod['image'] = $prod['product']['image'] ?? null;
|
|
|
|
return $prod;
|
|
}, $this->products);
|
|
|
|
if (empty($this->products)) {
|
|
$this->only_virtual_products = false;
|
|
}
|
|
|
|
return $this->products;
|
|
}
|
|
|
|
public function hasOnlyVirtualProducts()
|
|
{
|
|
if (is_null($this->only_virtual_products)) {
|
|
$this->fetchProducts();
|
|
}
|
|
|
|
if ($this->only_virtual_products) {
|
|
// pokud mam slevu, ve ktery je produkt, tak se nemuzu nabizet dopravu pro virtualni produkty
|
|
$productsInDiscounts = array_filter($this->getPurchaseState()->getDiscounts(), fn ($x) => $x instanceof ProductPurchaseItem && !$x->getProduct()->isVirtual());
|
|
if (!empty($productsInDiscounts)) {
|
|
$this->only_virtual_products = false;
|
|
}
|
|
}
|
|
|
|
return $this->only_virtual_products;
|
|
}
|
|
|
|
public function hasVirtualProducts()
|
|
{
|
|
if (is_null($this->virtual_products)) {
|
|
$this->fetchProducts();
|
|
}
|
|
|
|
return $this->virtual_products;
|
|
}
|
|
|
|
public function getAvailability()
|
|
{
|
|
$availability = 1;
|
|
|
|
foreach ($this->products as $product) {
|
|
switch ($product['availability']) {
|
|
case 0:
|
|
return 0;
|
|
|
|
case 2:
|
|
$availability = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $availability;
|
|
}
|
|
|
|
private function fetchDiscounts()
|
|
{
|
|
$purchaseState = $this->getPurchaseState();
|
|
|
|
// add discounts from PurchaseState
|
|
if (findModule(Modules::ORDER_DISCOUNT)) {
|
|
$totalDiscount = $purchaseState->getDiscountsTotalPrice();
|
|
$this->totalDiscountPrice = $totalDiscount->getPriceWithVat()->additiveInverse();
|
|
$this->totalDiscountPriceNoVat = $totalDiscount->getPriceWithoutVat()->additiveInverse();
|
|
}
|
|
}
|
|
|
|
private function fetchVats()
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
// jen pokud je platcem DPH
|
|
if ($dbcfg['shop_vat_payer'] == 'Y') {
|
|
$SQL = sqlQueryBuilder()->select('id, descr, vat')
|
|
->from('vats')
|
|
->orderBy('vat');
|
|
|
|
if (findModule(Modules::OSS_VATS)) {
|
|
$vatContext = Contexts::get(VatContext::class);
|
|
if ($vatContext->isCountryOssActive()) {
|
|
$countryContext = Contexts::get(CountryContext::class);
|
|
$SQL->andWhere(\Query\Operator::equals(['id_country', $countryContext->getActiveId()]));
|
|
} else {
|
|
$SQL->andWhere('id_country IS NULL');
|
|
}
|
|
}
|
|
|
|
foreach ($SQL->execute() as $row) {
|
|
$Vat = $row['vat'];
|
|
$id = $row['id'];
|
|
|
|
if (!isset($this->priceByVat[$id])) {
|
|
$this->priceByVat[$id] = toDecimal(0);
|
|
}
|
|
|
|
// nove promenne s cenou
|
|
$priceVatArray = formatPrice($this->priceByVat[$id], $Vat, false);
|
|
|
|
$this->vats[] = [
|
|
'descr' => $row['descr'],
|
|
'tax' => $priceVatArray,
|
|
];
|
|
}
|
|
sqlFreeResult($SQL);
|
|
}
|
|
}
|
|
|
|
public function fetchCharges()
|
|
{
|
|
$purchaseState = $this->getPurchaseState();
|
|
if ($charges = $purchaseState->getCharges()) {
|
|
// add charges from PurchaseState
|
|
$this->charges = array_column($charges, 'id_charge');
|
|
$this->charges = array_fill_keys($this->charges, ['checked' => 'Y']);
|
|
$totalCharges = $purchaseState->getChargesTotalPrice();
|
|
$this->totalChargesPrice = $totalCharges->getPriceWithVat();
|
|
$this->totalChargesPriceNoVat = $totalCharges->getPriceWithoutVat();
|
|
}
|
|
}
|
|
|
|
public function field_productList(): ProductCollection
|
|
{
|
|
if (findModule(\Modules::ORDERS, \Modules::SUB_ORDERS_FROM_PURCHASE_STATE)) {
|
|
return $this->getPurchaseState()->createProductCollection();
|
|
}
|
|
|
|
return $this->productList;
|
|
}
|
|
|
|
public function field_products(): array
|
|
{
|
|
if (findModule(\Modules::ORDERS, \Modules::SUB_ORDERS_FROM_PURCHASE_STATE)) {
|
|
static $productsCache;
|
|
|
|
if ($productsCache !== null) {
|
|
return $productsCache;
|
|
}
|
|
|
|
$productCollection = $this->getPurchaseState()->createProductCollection();
|
|
$productCollection->fetchMainImages(4);
|
|
|
|
$productPurchaseItemWrapper = ServiceContainer::getService(ProductPurchaseItemWrapper::class);
|
|
|
|
$products = [];
|
|
foreach ($this->getPurchaseState()->getProducts() as $key => $product) {
|
|
$products[$key] = (clone $productPurchaseItemWrapper)->setObject($product);
|
|
}
|
|
|
|
return $productsCache = $products;
|
|
}
|
|
|
|
return $this->products;
|
|
}
|
|
|
|
/**
|
|
* Implements ArrayAccess interface.
|
|
*/
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
$this->{$offset} = $value;
|
|
}
|
|
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return isset($this->{$offset});
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
unset($this->{$offset});
|
|
}
|
|
|
|
public function offsetGet($offset): mixed
|
|
{
|
|
$method = $this->offsetMethod($offset);
|
|
if (method_exists($this, $method)) {
|
|
return call_user_func([$this, $method]);
|
|
}
|
|
|
|
return isset($this->{$offset}) ? $this->{$offset} : null;
|
|
}
|
|
|
|
public function offsetMethod($offset)
|
|
{
|
|
return 'field_'.$offset;
|
|
}
|
|
|
|
private function fetchDelivery()
|
|
{
|
|
if (!findModule('eshop_delivery')) {
|
|
return;
|
|
}
|
|
if ($this->transport > 0) {
|
|
$deliveryType = $this->getDeliveryType();
|
|
|
|
if (!$deliveryType) {
|
|
return;
|
|
}
|
|
|
|
if (empty($this->delivery_id)) {
|
|
$this->delivery_id = $deliveryType->id_delivery;
|
|
}
|
|
|
|
if (empty($this->payment_id)) {
|
|
$this->payment_id = $deliveryType->id_payment;
|
|
}
|
|
|
|
$deliveryType->getDelivery()->applyToCart($this);
|
|
|
|
if ($this->storeDeliveryData && (findModule('payments') || findModule('deliveries'))) {
|
|
$payment = $deliveryType->getPayment();
|
|
if ($payment) {
|
|
$this->paymentData = $payment->storePaymentInfo();
|
|
} else {
|
|
$this->paymentData = [];
|
|
}
|
|
|
|
$delivery = $deliveryType->getDelivery();
|
|
if ($delivery) {
|
|
$this->deliveryData = $delivery->storeDeliveryInfo(getVal($delivery->id, getVal('delivery_data')));
|
|
} else {
|
|
$this->deliveryData = [];
|
|
}
|
|
}
|
|
} else {
|
|
if ($this->delivery_id) {
|
|
$deliveries = Delivery::getAll(false);
|
|
$delivery = getVal($this->delivery_id, $deliveries);
|
|
|
|
$this->deliveryData = $delivery ? $delivery->storeDeliveryInfo(getVal($delivery->id, getVal('delivery_data'))) : [];
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getTotalPriceForDelivery()
|
|
{
|
|
assert($this->initialized, 'Can not call getTotalPriceForDelivery() before Cart initialization - createFromDB');
|
|
|
|
if (findModule(Modules::ORDERS, Modules::SUB_ORDERS_FROM_PURCHASE_STATE)) {
|
|
$totalPriceWithVat = $this->getPurchaseState()->getProductsTotalPrice()->getPriceWithVat();
|
|
} else {
|
|
$totalPriceWithVat = $this->totalPriceWithVat;
|
|
}
|
|
|
|
if (findModule(Modules::DELIVERY_TYPES, Modules::SUB_FREE_DELIVERY_NO_DISCOUNT)) {
|
|
$totalPriceForDelivery = $totalPriceWithVat;
|
|
} else {
|
|
// TotalPrice darkovych poukazu
|
|
$useCouponTotalPrice = $this->purchaseUtil->getUseCouponTotalPrice($this->getPurchaseState());
|
|
// Aplikovaný poukaz nesnižuje hodnotu objednávky pro dopravu zdarma.
|
|
$totalDiscountPrice = $this->totalDiscountPrice->add($useCouponTotalPrice->getPriceWithVat());
|
|
$totalPriceForDelivery = Decimal::max($totalPriceWithVat->sub($totalDiscountPrice), DecimalConstants::zero());
|
|
}
|
|
|
|
if (!findModule(Modules::DELIVERY_TYPES, Modules::SUB_FREE_DELIVERY_NO_CHARGES)) {
|
|
$totalPriceForDelivery = Decimal::max($totalPriceForDelivery->add($this->totalChargesPrice), DecimalConstants::zero());
|
|
}
|
|
|
|
$currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class);
|
|
|
|
return new Price($totalPriceForDelivery, $currencyContext->getActive(), 0);
|
|
}
|
|
|
|
protected function fetchTotalsFromPurchaseState(PurchaseState $purchaseState)
|
|
{
|
|
$total = $purchaseState->getProductsTotalPrice();
|
|
$this->totalPriceWithVat = $total->getPriceWithVat();
|
|
$this->totalPriceNoVat = $total->getPriceWithoutVat();
|
|
|
|
$total = $purchaseState->getDiscountsTotalPrice();
|
|
$this->totalDiscountPrice = $total->getPriceWithVat()->additiveInverse();
|
|
$this->totalDiscountPriceNoVat = $total->getPriceWithoutVat()->additiveInverse();
|
|
|
|
$total = $purchaseState->getChargesTotalPrice();
|
|
$this->totalChargesPrice = $total->getPriceWithVat();
|
|
$this->totalChargesPriceNoVat = $total->getPriceWithoutVat();
|
|
|
|
$total = $purchaseState->getTotalPrice();
|
|
$this->totalPricePay = $total->getPriceWithVat();
|
|
$this->totalPricePayNoVat = $total->getPriceWithoutVat();
|
|
}
|
|
|
|
protected function fetchTotals()
|
|
{
|
|
// celkova cena k zaplaceni
|
|
$this->totalPriceWithVat = toDecimal($this->totalPriceWithVat);
|
|
$this->totalPricePay = Decimal::max($this->totalPriceWithVat->sub($this->totalDiscountPrice)->add($this->totalChargesPrice), DecimalConstants::zero());
|
|
|
|
$this->totalPriceNoVat = toDecimal($this->totalPriceNoVat);
|
|
$this->totalPricePayNoVat = Decimal::max($this->totalPriceNoVat->sub($this->totalDiscountPriceNoVat)->add($this->totalChargesPriceNoVat), DecimalConstants::zero());
|
|
|
|
$currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class);
|
|
|
|
if (findModule('eshop_delivery')) {
|
|
foreach ($this->getDeliveryTypes() as &$delivery) {
|
|
if ($delivery->id == $this->transport) {
|
|
$this->selected = true;
|
|
|
|
$delivery->attachToCart($this);
|
|
}
|
|
}
|
|
|
|
unset($delivery);
|
|
|
|
// Add delivery price
|
|
if ($this->transport && $this->getActualStepIndex() > 0) {
|
|
if (isset($this->delivery_types[$this->transport])) {
|
|
$delivery = $this->delivery_types[$this->transport];
|
|
|
|
// Kdyz uplatnujeme kupon, tak se musi tykat i dopravy. Teoreticky by mohlo jit vsude SUB_APPLY_DISCOUNT_ON_DELIVERY
|
|
// zapnout, ale nechcem nic rozbit
|
|
$isCoupon = false;
|
|
foreach ($this->getPurchaseState()->getDiscounts() as $discount) {
|
|
if (($discount->getNote()['discount_type'] ?? '') == 'use_coupon') {
|
|
$isCoupon = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$apply_discount_on_delivery = findModule(Modules::DELIVERY_TYPES, Modules::SUB_APPLY_DISCOUNT_ON_DELIVERY) || $isCoupon;
|
|
|
|
$totalPricePay = new Price($apply_discount_on_delivery ? $this->totalPriceWithVat : $this->totalPricePay, $currencyContext->getActive(), 0);
|
|
$totalPricePayNoVat = new Price($apply_discount_on_delivery ? $this->totalPriceNoVat : $this->totalPricePayNoVat, $currencyContext->getActive(), $delivery->vat);
|
|
|
|
$this->totalPricePay = PriceCalculator::add($totalPricePay, $delivery->getPrice())->getPriceWithVat(false);
|
|
$this->totalPricePayNoVat = PriceCalculator::add($totalPricePayNoVat, $delivery->getPrice())->getPriceWithoutVat(false);
|
|
|
|
if ($apply_discount_on_delivery) {
|
|
$this->totalPricePay = Decimal::max($this->totalPricePay->sub($this->totalDiscountPrice)->add($this->totalChargesPrice), DecimalConstants::zero());
|
|
$this->totalPricePayNoVat = Decimal::max($this->totalPricePayNoVat->sub($this->totalDiscountPriceNoVat)->add($this->totalChargesPriceNoVat), DecimalConstants::zero());
|
|
}
|
|
} else {
|
|
$this->delivery_id = null;
|
|
$this->setTransport(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Round total price
|
|
$this->totalPricePay = roundPrice($this->totalPricePay, $currencyContext->getActive()->getPriceRoundOrder(), bata: false);
|
|
}
|
|
|
|
public function getActualStepIndex()
|
|
{
|
|
$steps = array_keys($this->steps);
|
|
|
|
return array_search($this->actualStep, $steps);
|
|
}
|
|
|
|
public function save(?array $filter = null)
|
|
{
|
|
$serializeFields = ['coupons', 'invoice', 'delivery', 'transport', 'delivery_id', 'payment_id',
|
|
'note', 'addressesSet', 'paymentData', 'deliveryData', 'newsletter', 'register', 'max_step', 'max_step_submitted',
|
|
'actionHandlersData', 'userOrderNo', ];
|
|
|
|
if ($filter) {
|
|
$serializeFields = array_intersect($serializeFields, $filter);
|
|
}
|
|
|
|
$data = [];
|
|
foreach ($serializeFields as $field) {
|
|
$data[$field] = $this->$field;
|
|
}
|
|
|
|
$this->session->set('cart', serialize($data));
|
|
}
|
|
|
|
public function load(?array $filter = null)
|
|
{
|
|
$data = $this->session->get('cart');
|
|
|
|
if (!$data) {
|
|
return false;
|
|
}
|
|
|
|
$data = unserialize($data);
|
|
foreach ($data as $key => $value) {
|
|
if (!$filter || in_array($key, $filter)) {
|
|
$this->$key = $value;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function setData($key, $value)
|
|
{
|
|
$data = $this->getData();
|
|
|
|
if (!$key) {
|
|
$data = array_merge($data, $value);
|
|
} else {
|
|
$data[$key] = $value;
|
|
}
|
|
|
|
$this->session->set('cartData', $data);
|
|
}
|
|
|
|
/**
|
|
* @return bool|mixed
|
|
*/
|
|
public function getData($key = null)
|
|
{
|
|
$data = $this->session->get('cartData', []);
|
|
if ($key) {
|
|
if (!empty($data[$key])) {
|
|
return $data[$key];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function fetchAddresses()
|
|
{
|
|
if (!$this->addressesSet) {
|
|
$this->user->id = Contexts::get(UserContext::class)->getActiveId();
|
|
if ($this->user->fetchAddresses()) {
|
|
return;
|
|
}
|
|
|
|
// Select first enabled country
|
|
if ($this->getDeliveryType()) {
|
|
$countries = $this->getDeliveryType()->getDelivery()->getSupportedCountries();
|
|
$this->delivery['country'] = $this->invoice['country'] = reset($countries);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getTotalWeight()
|
|
{
|
|
$weight = 0.;
|
|
|
|
foreach ($this->products as $product) {
|
|
if ($product['pieces'] > 0) {
|
|
$weight += ($product['weight'] ?? 0) * $product['pieces'];
|
|
}
|
|
}
|
|
|
|
return $weight;
|
|
}
|
|
|
|
public function getMaxItemWeight()
|
|
{
|
|
$maxItemWeight = 0.;
|
|
|
|
foreach ($this->products as $product) {
|
|
if (!empty($product['sets'])) {
|
|
foreach ($product['sets'] as $set_product) {
|
|
if ($set_product['weight'] > $maxItemWeight) {
|
|
$maxItemWeight = $set_product['weight'];
|
|
}
|
|
}
|
|
} else {
|
|
if ($product['weight'] > $maxItemWeight) {
|
|
$maxItemWeight = $product['weight'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $maxItemWeight;
|
|
}
|
|
|
|
public function updateAddresses($invoice, $delivery)
|
|
{
|
|
$this->addressesSet = true;
|
|
$previousDic = $this->invoice['dic'] ?? null;
|
|
|
|
$requires_delivery = true;
|
|
if ($this->delivery_id) {
|
|
// we need to update address before getDeliveries call
|
|
$this->user->updateAddresses($invoice, $delivery);
|
|
|
|
$deliveries = Delivery::getAll(false);
|
|
if (isset($deliveries[$this->delivery_id])) {
|
|
$requires_delivery = $deliveries[$this->delivery_id]->requiresDeliveryAddress();
|
|
}
|
|
}
|
|
|
|
$error = $this->user->updateAddresses($invoice, $delivery, !$requires_delivery);
|
|
|
|
$emailCheck = ServiceContainer::getService(EmailCheck::class);
|
|
if (!$emailCheck->isEmailDomainValid($invoice['email'])) {
|
|
throw new Exception(sprintf(translate('error', 'user')['invalid_email_domain'], htmlentities($invoice['email'])));
|
|
}
|
|
|
|
// detect dic change
|
|
$reloadCartProducts = false;
|
|
if ($previousDic !== ($invoice['dic'] ?? null)) {
|
|
// save cart to session so UserContext::isVatPayer can load info about set DIC
|
|
$this->save();
|
|
// reload cart products to load products with correct vat
|
|
// PurchaseState is then created from cart products so we need products with correct vats loaded
|
|
$reloadCartProducts = true;
|
|
}
|
|
|
|
// Remember selected country in CountryContext - keeps CountryContext and cart delivery_country in sync
|
|
$deliveryCountry = ($this->delivery['country'] ?? false) ?: $this->invoice['country'] ?? false;
|
|
if ($deliveryCountry) {
|
|
/** @var CountryContext $countryContext */
|
|
$countryContext = Contexts::get(CountryContext::class);
|
|
if ($deliveryCountry != $countryContext->getActiveId()) {
|
|
$countryContext->activate($deliveryCountry);
|
|
$countryContext->remember($deliveryCountry);
|
|
|
|
// Force to recalculate product prices
|
|
// HACK: Asi by to chtělo nějakou metodu na to, ne? Tohleje hnus.
|
|
$reloadCartProducts = true;
|
|
}
|
|
}
|
|
|
|
if ($reloadCartProducts) {
|
|
$vatContext = Contexts::get(VatContext::class);
|
|
$vatContext->clearCache();
|
|
|
|
$this->only_virtual_products = null;
|
|
$this->fetchProducts();
|
|
}
|
|
|
|
return $error;
|
|
}
|
|
|
|
public function registerUser($password)
|
|
{
|
|
$this->register = false;
|
|
|
|
if ($password) {
|
|
$error = $this->user->sanitizeRegistration($password);
|
|
|
|
if (!$error) {
|
|
$this->register = $password;
|
|
}
|
|
} else {
|
|
$error = null;
|
|
}
|
|
|
|
return $error;
|
|
}
|
|
|
|
public function setTransport($transport_id)
|
|
{
|
|
$this->transport = $transport_id;
|
|
|
|
$this->storeDeliveryData = true;
|
|
|
|
$this->invalidatePurchaseState();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function setDeliveryAndPayment($delivery_id, $payment_id)
|
|
{
|
|
if ($delivery_id < 0) {
|
|
$this->resetDeliveryType();
|
|
}
|
|
|
|
if ($payment_id < 0) {
|
|
$this->resetDeliveryType(false);
|
|
}
|
|
|
|
if ($delivery_id > 0) {
|
|
$this->delivery_id = $delivery_id;
|
|
}
|
|
|
|
if ($payment_id) {
|
|
$this->payment_id = $payment_id;
|
|
}
|
|
|
|
$payment_id = explode('-', $this->payment_id)[0];
|
|
|
|
if ($this->hasOnlyVirtualProducts()) {
|
|
foreach ($this->getVirtualDeliveryTypes() as $delivery_type) {
|
|
if ($delivery_type->id_delivery == $this->delivery_id && $delivery_type->id_payment == $payment_id) {
|
|
return $this->setTransport($delivery_type->id);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (DeliveryType::getAll(false) as $delivery_type) {
|
|
if ($delivery_type->id_delivery == $this->delivery_id && $delivery_type->id_payment == $payment_id) {
|
|
return $this->setTransport($delivery_type->id);
|
|
}
|
|
}
|
|
|
|
$this->resetDeliveryType(false);
|
|
|
|
return false;
|
|
}
|
|
|
|
public function resetDeliveryType(bool $resetDelivery = true): void
|
|
{
|
|
if ($resetDelivery) {
|
|
$this->delivery_id = null;
|
|
}
|
|
|
|
$this->transport = null;
|
|
$this->payment_id = null;
|
|
|
|
$this->invalidatePurchaseState();
|
|
}
|
|
|
|
/**
|
|
* @return DeliveryType[]
|
|
*/
|
|
public function getVirtualDeliveryTypes()
|
|
{
|
|
$delivery_types = DeliveryType::getAll(true);
|
|
if ($this->initialized) {
|
|
$totalPrice = $this->getTotalPriceForDelivery();
|
|
} else {
|
|
$totalPrice = new Price($this->totalPriceWithVat, Contexts::get(CurrencyContext::class)->getActive(), 0);
|
|
}
|
|
$delivery_types = array_filter($delivery_types, function ($d) use ($totalPrice) {
|
|
return $d->delivery_class == 'VirtualDelivery' && $d->getPayment()->accept($totalPrice, $this->freeShipping);
|
|
});
|
|
|
|
return $delivery_types;
|
|
}
|
|
|
|
public function getDeliveryTypes(): array
|
|
{
|
|
if (empty($this->delivery_types)) {
|
|
if ($this->hasOnlyVirtualProducts()) {
|
|
$this->delivery_types = $this->getVirtualDeliveryTypes();
|
|
}
|
|
if (empty($this->delivery_types)) {
|
|
if ($this->getPurchaseState()->isFreeDelivery()) {
|
|
$this->freeShipping = true;
|
|
}
|
|
$totalPrice = $this->getTotalPriceForDelivery();
|
|
$this->delivery_types = Order::getDeliveryTypeList($totalPrice, $this->freeShipping, null, null, $this->getPurchaseState());
|
|
}
|
|
}
|
|
|
|
return $this->delivery_types;
|
|
}
|
|
|
|
/**
|
|
* @return Delivery[]
|
|
*/
|
|
public function getDeliveries()
|
|
{
|
|
$deliveries = [];
|
|
foreach ($this->getDeliveryTypes() as $delivery_type) {
|
|
$delivery = $delivery_type->getDelivery();
|
|
$deliveries[$delivery->id] = $delivery;
|
|
}
|
|
|
|
$totalPriceForDelivery = $this->getTotalPriceForDelivery();
|
|
foreach ($deliveries as $delivery) {
|
|
if ($this->delivery_id == $delivery['id']) {
|
|
$delivery['selected'] = true;
|
|
|
|
// Load payment class info
|
|
if (findModule('deliveries') && !empty($delivery['class'])) {
|
|
$delivery->loadDeliveryInfo($this->deliveryData);
|
|
}
|
|
}
|
|
|
|
if (!$delivery->accept($totalPriceForDelivery, $this->freeShipping, $this->getPurchaseState())) {
|
|
unset($deliveries[$delivery['id']]);
|
|
}
|
|
|
|
$delivery->applyToCart($this);
|
|
|
|
try {
|
|
$delivery->check($this);
|
|
} catch (\KupShop\OrderingBundle\Exception\DeliveryException $e) {
|
|
// ignorujeme, check volany kvuli pripadnemu disablovani dopravy pomoci exception
|
|
$delivery->setException($e);
|
|
}
|
|
}
|
|
|
|
return $deliveries;
|
|
}
|
|
|
|
public function getLowestDeliveryPrice()
|
|
{
|
|
$minPrice = Decimal::create(0);
|
|
foreach ($this->getDeliveries() as $delivery) {
|
|
/** @var Decimal $price */
|
|
$price = $delivery['price']['value_with_vat'];
|
|
|
|
if ($minPrice->asInteger() == 0 && $price > $minPrice) {
|
|
$minPrice = $price;
|
|
} else {
|
|
if ($minPrice > $price && $price->asInteger() != 0) {
|
|
$minPrice = $price;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $minPrice;
|
|
}
|
|
|
|
public function getPayments()
|
|
{
|
|
$all_payments = DeliveryType::getPayments($this->hasOnlyVirtualProducts());
|
|
if ($this->hasOnlyVirtualProducts()) {
|
|
$delivery_types = $this->getVirtualDeliveryTypes();
|
|
$payments = [];
|
|
foreach ($delivery_types as $delivery_type) {
|
|
if (array_key_exists($delivery_type->id_payment, $all_payments)) {
|
|
$payments[$delivery_type->id_payment] = $all_payments[$delivery_type->id_payment];
|
|
}
|
|
}
|
|
}
|
|
if (empty($payments)) {
|
|
$payments = $all_payments;
|
|
}
|
|
|
|
$currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class);
|
|
$paymentsHasDelivery = $this->getPaymentHasDelivery();
|
|
foreach ($payments as &$payment) {
|
|
if (!array_key_exists($payment['id'], $paymentsHasDelivery)) {
|
|
unset($payments[$payment['id']]);
|
|
continue;
|
|
}
|
|
|
|
$payment['disabled'] = false;
|
|
|
|
if ($this->payment_id == $payment['id']) {
|
|
$payment['selected'] = true;
|
|
}
|
|
|
|
if ($this->paymentDisabled($payment['id'])) {
|
|
$payment['disabled'] = true;
|
|
}
|
|
|
|
$totalPrice = new Price(toDecimal($this->totalPricePay), $currencyContext->getActive(), 0);
|
|
if ($payment['price_dont_countin_from'] && PriceCalculator::firstLower($payment['price_dont_countin_from'], $totalPrice)) {
|
|
$payment['price'] = formatCustomerPrice(0);
|
|
}
|
|
|
|
if ($payment['class']) {
|
|
try {
|
|
$payment['class']->check($this);
|
|
} catch (\KupShop\OrderingBundle\Exception\PaymentException $e) {
|
|
// ignorujeme, check volany kvuli pripadnemu disablovani platby pomoci exception
|
|
$payment['class']->setException($e);
|
|
$payment['exception'] = $payment['class']->exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $payments;
|
|
}
|
|
|
|
protected function getPaymentHasDelivery()
|
|
{
|
|
$paymentsHasDelivery = [];
|
|
|
|
foreach ($this->delivery_types as $type) {
|
|
$paymentsHasDelivery[$type->id_payment] = true;
|
|
}
|
|
|
|
return $paymentsHasDelivery;
|
|
}
|
|
|
|
public function paymentDisabled($paymentId)
|
|
{
|
|
$disabled = true;
|
|
foreach ($this->delivery_types as $type) {
|
|
if ($this->delivery_id == $type->id_delivery && $paymentId == $type->id_payment) {
|
|
$disabled = false;
|
|
}
|
|
}
|
|
|
|
return $disabled;
|
|
}
|
|
|
|
/**
|
|
* @return DeliveryType
|
|
*/
|
|
public function getDeliveryType()
|
|
{
|
|
return getVal($this->transport, $this->delivery_types);
|
|
}
|
|
|
|
public function setNote($note)
|
|
{
|
|
$this->note = $note;
|
|
}
|
|
|
|
public function setUserOrderNo($userOrderNo)
|
|
{
|
|
$this->userOrderNo = $userOrderNo;
|
|
}
|
|
|
|
// Order Submission
|
|
public function submitOrder($languageID = null)
|
|
{
|
|
QueryHint::routeToMaster();
|
|
|
|
$error = $this->checkCart();
|
|
|
|
$emailCheck = ServiceContainer::getService(EmailCheck::class);
|
|
if (!$emailCheck->isEmailDomainValid($this->invoice['email'] ?? '')) {
|
|
$error = 10;
|
|
}
|
|
if (empty($error)) {
|
|
// Get delivery
|
|
if ($this->hasOnlyVirtualProducts()) {
|
|
$this->deliveryType = ($this->getVirtualDeliveryTypes()[$this->transport] ?? DeliveryType::get($this->transport));
|
|
} else {
|
|
$this->deliveryType = DeliveryType::get($this->transport);
|
|
}
|
|
|
|
// pokud je vyplnena dodaci adresa,
|
|
// tak nebudeme mergovat, jinak muze vzniknout chyba v dodaci adrese
|
|
$filter_delivery = array_filter($this->delivery, function ($value, $key) { return !empty($value) && ($key != 'country') && ($key != 'currency'); }, ARRAY_FILTER_USE_BOTH);
|
|
if (!$filter_delivery) {
|
|
$this->delivery['country'] = '';
|
|
$this->delivery = array_merge($this->invoice, array_filter($this->delivery));
|
|
}
|
|
|
|
// Activate country context from delivery country
|
|
if ($this->delivery['country'] ?? false) {
|
|
Contexts::get(CountryContext::class)->activate($this->delivery['country']);
|
|
}
|
|
|
|
// Nejhorsi - fallback na doplneni v pripade, ze z kosiku prijde prazdno
|
|
if (empty($this->invoice['country'])) {
|
|
$sentry = getRaven();
|
|
$sentry->captureMessage('Order create: missing invoice_country!', [], ['cart' => $this, 'invoice' => $this->invoice, 'delivery' => $this->delivery]);
|
|
|
|
$this->invoice['country'] = Contexts::get(CountryContext::class)->getActiveId();
|
|
}
|
|
|
|
if (sqlGetConnection()->getTransactionNestingLevel() <= 0) {
|
|
sqlQuery('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
|
|
}
|
|
|
|
$order = sqlGetConnection()->transactional(fn () => $this->submitOrderTransactional($languageID));
|
|
|
|
// check if order exists in db
|
|
$id_order = sqlQueryBuilder()->select('id')->from('orders')
|
|
->where(Operator::equals(['id' => $order->id]))
|
|
->execute()->fetchOne();
|
|
if (!$id_order) {
|
|
$sentry = getRaven();
|
|
$sentry->captureMessage("submitOrder error - returned order that doesn't exist!", [], ['order' => $order, 'cart' => $this]);
|
|
|
|
throw new \RuntimeException(translate('order_failed_try_again', 'order_error'));
|
|
}
|
|
|
|
$this->sendErrorIgnoringOrderEvent($order, OrderEvent::ORDER_FINISHED);
|
|
|
|
return $order;
|
|
}
|
|
|
|
return $error;
|
|
}
|
|
|
|
public function submitOrderTransactional($languageID): Order
|
|
{
|
|
global $cfg;
|
|
|
|
// cartParams => ukladame pred vytvorenim uzivatele behem objednavkoveho procesu - aby se nam neztratil kosik
|
|
$cartParams = $this->getSelectParams();
|
|
|
|
// Synchronization point. Lock all current cart items to avoid multiple cart submission
|
|
$SQL = sqlQueryBuilder()->select('c.*')
|
|
->forUpdate()
|
|
->from('cart', 'c')
|
|
->andWhere(queryCreate($cartParams, true))
|
|
->execute();
|
|
|
|
// During locking, all cart items disappeared - probably already ordered and present in another order
|
|
if ($SQL->rowCount() <= 0) {
|
|
// TODO: tmp log kvuli zdvojenym objednavkam
|
|
$this->kibanaLog(
|
|
'Cart::submitOrder - duplicate order submit',
|
|
[
|
|
'cartID' => self::getCartID(),
|
|
]
|
|
);
|
|
|
|
throw new CartValidationException(translate('duplicate_order', 'order_error'));
|
|
}
|
|
|
|
$id_user = $this->submitOrderRegister();
|
|
|
|
if (!isset($languageID)) {
|
|
$languageID = ServiceContainer::getService(
|
|
\KupShop\KupShopBundle\Context\LanguageContext::class
|
|
)->getActiveId();
|
|
}
|
|
$fields = [
|
|
'id_user' => $id_user,
|
|
'order_no' => '_'.rand(0, 999999),
|
|
'status' => 0,
|
|
'invoice_name' => $this->invoice['name'] ?? '',
|
|
'invoice_surname' => $this->invoice['surname'] ?? '',
|
|
'invoice_firm' => $this->invoice['firm'] ?? '',
|
|
'invoice_ico' => $this->invoice['ico'] ?? '',
|
|
'invoice_dic' => $this->invoice['dic'] ?? '',
|
|
'invoice_street' => $this->invoice['street'] ?? '',
|
|
'invoice_city' => $this->invoice['city'] ?? '',
|
|
'invoice_zip' => $this->invoice['zip'] ?? '',
|
|
'invoice_country' => $this->invoice['country'] ?? '',
|
|
'invoice_phone' => $this->invoice['phone'] ?? '',
|
|
'invoice_email' => $this->invoice['email'] ?? '',
|
|
'invoice_copy_email' => $this->invoice['copy_email'] ?? '',
|
|
'invoice_state' => $this->invoice['state'] ?? '',
|
|
'invoice_custom_address' => $this->invoice['custom_address'] ?? '',
|
|
'delivery_name' => $this->delivery['name'] ?? '',
|
|
'delivery_surname' => $this->delivery['surname'] ?? '',
|
|
'delivery_firm' => $this->delivery['firm'] ?? '',
|
|
'delivery_street' => $this->delivery['street'] ?? '',
|
|
'delivery_city' => $this->delivery['city'] ?? '',
|
|
'delivery_zip' => $this->delivery['zip'] ?? '',
|
|
'delivery_country' => $this->delivery['country'] ?? '',
|
|
'delivery_state' => $this->delivery['state'] ?? '',
|
|
'delivery_custom_address' => $this->delivery['custom_address'] ?? '',
|
|
'delivery_phone' => $this->delivery['phone'] ?? '',
|
|
'delivery_email' => $this->delivery['delivery_email'] ?? '',
|
|
'delivery_type' => $this->deliveryType['name'],
|
|
'id_delivery' => $this->deliveryType['id'] > 0 ? $this->deliveryType['id'] : null,
|
|
'delivery_complete' => 0,
|
|
'note_user' => $this->note,
|
|
'user_order_no' => $this->userOrderNo,
|
|
'date_created' => date('Y-m-d H:i:s'),
|
|
'date_updated' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
if (findModule(Modules::CURRENCIES)) {
|
|
$currency = Contexts::get(CurrencyContext::class)->getActive();
|
|
$fields['id_language'] = $languageID;
|
|
$fields['currency'] = $currency->getId();
|
|
$fields['currency_rate'] = $currency->getRate()->asString();
|
|
}
|
|
|
|
if (!empty($this->note) && !empty($cfg['Order']['Flags']) && !empty($cfg['Order']['Flags']['R'])) {
|
|
$fields['flags'] = getVal('flags', $fields).',R';
|
|
}
|
|
|
|
if (findModule(Modules::ORDERS, Modules::SUB_CREATE_ORDER_FROM_PURCHASE_STATE)) {
|
|
$this->purchaseState->setCustomData([
|
|
'order' => $fields,
|
|
'delivery_data' => $this->deliveryData,
|
|
'payment_data' => $this->paymentData,
|
|
]);
|
|
$order = $this->purchaseUtil->createOrderFromPurchaseState($this->purchaseState);
|
|
|
|
$this->submitOrderData($order);
|
|
$this->submitOrderDeliveries($order);
|
|
$this->submitOrderPayments($order);
|
|
|
|
$this->clear();
|
|
|
|
return $order;
|
|
}
|
|
|
|
$this->insertSQL('orders', $fields);
|
|
|
|
// ID nove ulozene objednavky
|
|
$IDorder = sqlInsertId();
|
|
|
|
// Create Order object
|
|
$order = new Order();
|
|
$order->createFromDB($IDorder);
|
|
|
|
// set PurchaseState from cart to order
|
|
$order->setPurchaseState($this->getPurchaseState());
|
|
|
|
// Set status indicating order creation
|
|
$order->status = -1;
|
|
|
|
$this->sendOrderEvent($order, OrderEvent::ORDER_CREATED);
|
|
|
|
// ulozit vybrane zbozi do objednavky
|
|
$where = queryCreate($cartParams, true);
|
|
|
|
$SQL = sqlQueryBuilder()->select("c.id as cart_id, c.pieces, c.id_variation, c.note, p.id as product_id, p.title, p.code, p.price, p.discount,
|
|
FIND_IN_SET('Z', p.campaign)>0 free_delivery")
|
|
->from('cart', 'c')
|
|
->addSelect(\Query\Product::withVat())
|
|
->leftJoin('c', 'products', 'p', 'c.id_product=p.id')
|
|
->andWhere($where)
|
|
->groupBy('c.id')
|
|
->orderBy('c.id'); // order asc by id, so the items will be added in the same order that they were added to the cart
|
|
|
|
// Traverse all ordered products
|
|
if (findModule(\Modules::ORDERS, \Modules::SUB_ORDERS_FROM_PURCHASE_STATE)) {
|
|
$this->insertItemsFromPurchaseState($order);
|
|
} else {
|
|
$purchaseStateProducts = $order->getPurchaseState()->getProducts();
|
|
foreach ($SQL->execute() as $row) {
|
|
$IDInCart = $row['cart_id'];
|
|
$IDprod = $row['product_id'];
|
|
$IDvariation = $row['id_variation'];
|
|
$Pieces = $row['pieces'];
|
|
$Note = $row['note'];
|
|
$productPurchaseItem = null;
|
|
if (array_key_exists($IDInCart, $purchaseStateProducts)) {
|
|
$productPurchaseItem = $purchaseStateProducts[$IDInCart];
|
|
$Pieces = $productPurchaseItem->getPieces();
|
|
// Pieces nemusi sedet v pripade slevy na nejlevnejsi produkt - kdyz toho nejlevnejsiho produktu mam napr. 5 kusu,
|
|
// tak v purchaseState budou rozdeleny na 2 ProductPurchaseItem - 1 ks se slevou + 4 ks bez slevy
|
|
// v tabulce cart 5 ks toho produktu jsou dohromady ($Pieces = 5), ale $productPurchaseItem->getPieces() = 4
|
|
// 1 ks se slevou bude pridan pozdeji (v recalculateFull)
|
|
}
|
|
|
|
$res = $order->insertItem($IDprod, $IDvariation, $Pieces, $Note);
|
|
|
|
// vymazat zbozi z kosiku
|
|
$this->deleteSQL('cart', ['id' => $IDInCart]);
|
|
|
|
if ($productPurchaseItem) {
|
|
$productPurchaseItem->setId($res);
|
|
}
|
|
}
|
|
}
|
|
|
|
$order->recalculateFull($this->deliveryType['id'], null, true, true);
|
|
|
|
try {
|
|
$this->sendErrorIgnoringOrderEvent($order, OrderEvent::ORDER_COMPLETE);
|
|
|
|
$this->submitOrderData($order);
|
|
$this->submitOrderDeliveries($order);
|
|
$this->submitOrderPayments($order);
|
|
|
|
$this->clear();
|
|
} catch (Throwable $e) {
|
|
if (isDevelopment()) {
|
|
throw $e;
|
|
}
|
|
// send to sentry and continue
|
|
$logger = getRaven();
|
|
$logger->captureException($e);
|
|
}
|
|
|
|
return $order;
|
|
}
|
|
|
|
protected function insertItemsFromPurchaseState(Order $order): void
|
|
{
|
|
foreach ($order->getPurchaseState()->getProducts() as $item) {
|
|
$IdInCart = $item->getId();
|
|
|
|
$order->insertProductPurchaseItem($item);
|
|
|
|
// vymazat zbozi z kosiku
|
|
$this->deleteSQL('cart', ['id' => $IdInCart]);
|
|
}
|
|
}
|
|
|
|
protected function checkCart()
|
|
{
|
|
$this->sendCartEvent($this, CartEvent::CHECK_CART);
|
|
|
|
// Check delivery type selected
|
|
if (findModule('eshop_delivery')) {
|
|
$deliveryType = $this->getDeliveryType();
|
|
if (!$deliveryType) {
|
|
return 5;
|
|
}
|
|
|
|
$error = $deliveryType->check($this);
|
|
if ($error) {
|
|
return $error;
|
|
}
|
|
}
|
|
|
|
// Check cart is not empty
|
|
$products = sqlQueryBuilder()->select('COUNT(c.id)')
|
|
->from('cart', 'c')
|
|
->join('c', 'products', 'p', 'c.id_product=p.id')
|
|
->where(\Query\Operator::equals($this->getSelectParams()))
|
|
->andWhere(Translation::joinTranslatedFields(ProductsTranslation::class,
|
|
function ($qb, $columnName, $translatedField) {
|
|
$qb->andWhere(\Query\Operator::coalesce($translatedField, 'p.figure').' = "Y" ');
|
|
}, ['figure']))
|
|
->sendToMaster()
|
|
->execute()->fetchColumn();
|
|
|
|
if (intval($products) == 0) {
|
|
return 12;
|
|
}
|
|
|
|
$dbcfg = Settings::getDefault();
|
|
|
|
$nvProducts = [];
|
|
// check if products in cart can be purchased
|
|
if (($dbcfg->prod_sell_with_zero_price ?? 'N') != 'Y') {
|
|
foreach ($this->fetchProducts() as $item) {
|
|
/** @var Product $product */
|
|
$product = $item['product'];
|
|
|
|
// Pokud se jedná o multiset tak master produkt je za 0kč (a je viditelný) -> skip
|
|
if (array_key_exists('MS', $product->campaign_codes) && $product->visible == 'Y') {
|
|
continue;
|
|
}
|
|
|
|
// beru cenu od produktu, protoze kdybych bral cenu od polozky v kosiku, tak to muze zpusobit akorat
|
|
// problemy na shopech, kde maji nejaky customizace a chteji prodavat za 0 Kc (viz. sartor a vzorky latek)
|
|
if (!$product->getProductPrice()->getPriceWithVat()->isPositive() || $product->visible != 'Y') {
|
|
$nvProducts[$item['id']] = $item['id_variation'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($nvProducts) > 0) {
|
|
$this->errors[self::$ERR_CANNOT_BE_PURCHASED] = $nvProducts;
|
|
$ids = implode(',', $nvProducts);
|
|
logError(__FILE__, __LINE__, "Produkty {$ids} nelze zakoupit.");
|
|
|
|
return 33;
|
|
}
|
|
|
|
// Check all items are in store if required
|
|
return $this->checkCartItems();
|
|
}
|
|
|
|
public function checkCartItems(&$errors = null)
|
|
{
|
|
$event = $this->sendCartEvent($this, CartEvent::CHECK_CART_ITEMS);
|
|
|
|
$where = queryCreate($this->getSelectParams(), true);
|
|
|
|
$products = sqlQueryBuilder()->select('p.id as id_product')
|
|
->from('cart', 'c')
|
|
->leftJoin('c', 'products', 'p', 'c.id_product=p.id')
|
|
->leftJoin('p', 'products_variations', 'pv2', 'pv2.id_product=p.id')
|
|
->leftJoin('c', 'products_variations', 'pv', 'pv.id=c.id_variation')
|
|
->where(Operator::equals($this->getSelectParams()))
|
|
->andWhere(Translation::joinTranslatedFields(VariationsTranslation::class,
|
|
function ($qb, $columnName, $translatedField) {
|
|
$qb->andWhere(
|
|
Operator::orX(
|
|
Operator::coalesce($translatedField, 'pv.figure').' = "N" ',
|
|
'c.id_variation IS NULL AND pv2.id IS NOT NULL'));
|
|
}, ['figure']))
|
|
->groupBy('c.id')
|
|
->sendToMaster()
|
|
->execute();
|
|
if (!empty($products->rowCount())) {
|
|
logError(__FILE__, __LINE__, 'WARN: Není vybrana varianta');
|
|
$this->errors[self::$ERR_NOT_SELECTED_VARIATION] = $errors = [$products->fetchOne()];
|
|
|
|
return 31;
|
|
}
|
|
|
|
// Check for products not on stock
|
|
if ($event->getErrors()[\Cart::$ERR_OUT_OF_STOCK] ?? false) {
|
|
$this->errors[self::$ERR_OUT_OF_STOCK] = $errors = $event->getErrors()[\Cart::$ERR_OUT_OF_STOCK];
|
|
|
|
return 30;
|
|
}
|
|
|
|
if (findModule('eshop_delivery')) {
|
|
if ($this->max_step != 0) {
|
|
$deliveryType = $this->getDeliveryType();
|
|
if ($deliveryType) {
|
|
$error = $deliveryType->check($this);
|
|
if ($error) {
|
|
return $error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function ensureCartInStore()
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
if ($dbcfg->order_availability === \Settings::ORDER_AVAILABILITY_ALL) {
|
|
return null;
|
|
}
|
|
|
|
// Check for products not on stock
|
|
$count = $this->getEnsureInStoreQueryBuilder()->execute();
|
|
|
|
sqlQueryBuilder()
|
|
->delete('cart')
|
|
->where(\Query\Operator::equals($this->getSelectParams()))
|
|
->andWhere('pieces <= 0')
|
|
->execute();
|
|
|
|
$this->invalidatePurchaseState();
|
|
|
|
if (findModule(\Modules::JS_SHOP)) {
|
|
$this->session->set(\KupShop\GraphQLBundle\EventListener\JsShopRefreshListener::SESSION_NAME, true);
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
protected function getEnsureInStoreQueryBuilder(): QueryBuilder
|
|
{
|
|
$qb = sqlQueryBuilder()
|
|
->update('cart', 'c')
|
|
->leftJoin('c', 'products', 'p', 'c.id_product=p.id')
|
|
->leftJoin('c', 'products_variations', 'pv', 'c.id_variation=pv.id')
|
|
->andWhere(\Query\Operator::equals($this->getSelectParams()));
|
|
|
|
$new_pieces = $this->getEnsureInStoreNewPiecesField($qb);
|
|
|
|
return $qb->set('c.pieces', $new_pieces)
|
|
->andWhere('p.id IS NOT NULL AND '.$new_pieces.' < c.pieces');
|
|
}
|
|
|
|
protected function getEnsureInStoreNewPiecesField(QueryBuilder $qb): string
|
|
{
|
|
$new_pieces = \Query\Product::getInStoreField(true, $qb);
|
|
if (findModule(\Modules::PRODUCTS_SUPPLIERS)) {
|
|
// in stock = in_store (pokud je > 0) + skladem u dodavatele
|
|
$new_pieces = "GREATEST(0, {$new_pieces})";
|
|
$new_pieces .= ' + (SELECT COALESCE(SUM(pos.in_store), 0) FROM products_of_suppliers pos WHERE p.id = pos.id_product AND ((pv.id IS NULL and pos.id_variation IS NULL) OR (pv.id=pos.id_variation)))';
|
|
}
|
|
|
|
$new_pieces = "GREATEST(0, {$new_pieces})";
|
|
|
|
$showMax = findModule('products', 'showMax', 0);
|
|
if ($showMax > 0) {
|
|
$new_pieces = "LEAST({$new_pieces}, COALESCE(pv.in_store_show_max, p.in_store_show_max, {$showMax}))";
|
|
}
|
|
|
|
return $new_pieces;
|
|
}
|
|
|
|
protected function getCheckNotInStoreQueryBuilder(): QueryBuilder
|
|
{
|
|
$qb = sqlQueryBuilder();
|
|
$qb->select('p.id, pv.id as id_variation', \Query\Product::getProductInStock($qb).' as in_store')
|
|
->from('cart', 'c')
|
|
->leftJoin('c', 'products', 'p', 'c.id_product = p.id')
|
|
->leftJoin('c', 'products_variations', 'pv', 'c.id_variation=pv.id')
|
|
->andWhere('p.id IS NOT NULL')
|
|
->andWhere(\Query\Operator::equals($this->getSelectParams()))
|
|
->having('in_store < SUM(c.pieces)')
|
|
->groupBy('p.id, pv.id');
|
|
|
|
if (findModule(Modules::PRODUCTS, 'order_not_in_store')) {
|
|
$qb->andWhere(\Query\Operator::not('FIND_IN_SET("PR", p.campaign)'));
|
|
}
|
|
|
|
return $qb;
|
|
}
|
|
|
|
/**
|
|
* @param $order Order
|
|
*/
|
|
protected function submitOrderData($order)
|
|
{
|
|
foreach ($this->getData() as $key => $value) {
|
|
$order->setData($key, $value);
|
|
}
|
|
$userContent = ServiceContainer::getService(\KupShop\OrderingBundle\Util\UserContent\UserContent::class);
|
|
if ($value = $userContent->getData('cart')) {
|
|
$order->setData('user-content', $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $order Order
|
|
*/
|
|
protected function submitOrderDeliveries($order)
|
|
{
|
|
// -------------------------------------------------------------------
|
|
// ZJISTENI ZDA JE E-PLATBA V ESHOPU A K TOMUTO DRUHU DORUCENI
|
|
// -------------------------------------------------------------------
|
|
if (findModule('deliveries')) {
|
|
if (!empty($this->deliveryData)) {
|
|
$order->setData('delivery_data', $this->deliveryData);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $order Order
|
|
*/
|
|
protected function submitOrderPayments($order)
|
|
{
|
|
// -------------------------------------------------------------------
|
|
// ZJISTENI ZDA JE E-PLATBA V ESHOPU A K TOMUTO DRUHU DORUCENI
|
|
// -------------------------------------------------------------------
|
|
if (findModule('payments')) {
|
|
$order->setData('payment_data', $this->paymentData);
|
|
}
|
|
}
|
|
|
|
protected function sendOrderEvent($order, $eventName)
|
|
{
|
|
$event = new OrderEvent($order);
|
|
$event->setCart($this);
|
|
$this->getEventDispatcher()->dispatch($event, $eventName);
|
|
}
|
|
|
|
protected function sendErrorIgnoringOrderEvent($order, $eventName)
|
|
{
|
|
$event = new OrderEvent($order);
|
|
$event->setCart($this);
|
|
$this->getErrorIgnoringEventDispatcher()->dispatch($event, $eventName);
|
|
}
|
|
|
|
protected function sendCartEvent($cart, $eventName, $event = null)
|
|
{
|
|
$event = $event ?: new CartEvent($cart);
|
|
$this->getEventDispatcher()->dispatch($event, $eventName);
|
|
|
|
return $event;
|
|
}
|
|
|
|
private $eventDispatcher;
|
|
|
|
protected function getEventDispatcher(): EventDispatcherInterface
|
|
{
|
|
if (!$this->eventDispatcher) {
|
|
$this->eventDispatcher = ServiceContainer::getService('event_dispatcher');
|
|
}
|
|
|
|
return $this->eventDispatcher;
|
|
}
|
|
|
|
private ?ErrorIgnoringEventDispatcher $errorIgnoringEventDispatcher = null;
|
|
|
|
protected function getErrorIgnoringEventDispatcher(): ErrorIgnoringEventDispatcher
|
|
{
|
|
if (!$this->errorIgnoringEventDispatcher) {
|
|
$this->errorIgnoringEventDispatcher = ServiceContainer::getService(ErrorIgnoringEventDispatcher::class);
|
|
}
|
|
|
|
return $this->errorIgnoringEventDispatcher;
|
|
}
|
|
|
|
/**
|
|
* @param bool $newsletter
|
|
*/
|
|
public function setNewsletter($newsletter)
|
|
{
|
|
$this->newsletter = $newsletter;
|
|
}
|
|
|
|
protected function submitOrderRegister()
|
|
{
|
|
$userContext = Contexts::get(UserContext::class);
|
|
if ($this->register && $userContext->getActiveId() > 0) {
|
|
$this->register = false;
|
|
}
|
|
|
|
if ($userContext->getActiveId() > 0) {
|
|
return $userContext->getActiveId();
|
|
}
|
|
|
|
if (!$this->register) {
|
|
return null;
|
|
}
|
|
|
|
// register user
|
|
try {
|
|
/* Pokud je doprava typu point (zasilkovna, ...), tak se ukladala adresa napr. zasilkovny do udaju uzivatele, coz je blbost.
|
|
* */
|
|
if (!$this->deliveryType->getDelivery()->requiresDeliveryAddress()) {
|
|
unset($this->delivery);
|
|
$this->delivery = $this->user->delivery;
|
|
$this->user->delivery = [];
|
|
}
|
|
|
|
$id = $this->user->update();
|
|
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
|
|
throw new \KupShop\OrderingBundle\Exception\CartValidationException(translate('userRegisterError', 'ordering'));
|
|
}
|
|
|
|
$this->user->updatePassword($this->register);
|
|
|
|
// Reload user
|
|
$this->user = User::createFromId($id);
|
|
$this->user->activateUser();
|
|
|
|
$this->requestStack->getMainRequest()?->attributes->set('gtm_registration', [
|
|
'email' => $this->user->email,
|
|
'firstname' => $this->user->name ?? '',
|
|
]);
|
|
|
|
if (findModule(\Modules::JS_SHOP)) {
|
|
$this->session->set(\KupShop\GraphQLBundle\EventListener\JsShopRefreshListener::SESSION_NAME, true);
|
|
}
|
|
|
|
return $id;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getErrors($type = null)
|
|
{
|
|
if (!is_null($type) && !empty($this->errors[$type])) {
|
|
return $this->errors[$type];
|
|
}
|
|
|
|
return $this->errors;
|
|
}
|
|
|
|
/**
|
|
* @param $product Product
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function roundPieces($product, $pieces)
|
|
{
|
|
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS_FLOAT)) {
|
|
$UnitsRounder = ServiceContainer::getService(\KupShop\UnitsBundle\Utils\PiecesRounder::class);
|
|
|
|
return $UnitsRounder->roundPieces($product, $pieces);
|
|
}
|
|
|
|
return intval($pieces);
|
|
}
|
|
|
|
private function kibanaLog(string $message, array $data = []): void
|
|
{
|
|
if (!isProduction()) {
|
|
return;
|
|
}
|
|
|
|
$logger = ServiceContainer::getService('logger');
|
|
$logger->notice($message, array_merge(
|
|
$data, [
|
|
'referer' => $_SERVER['HTTP_REFERER'],
|
|
])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Nesmí se volat, pouze pro Naty v HeurekaCartu, kde ví co dělá. Ostatní to nevědí, tak tohle nevolají!
|
|
*/
|
|
public function markInitialized(): static
|
|
{
|
|
$this->initialized = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
#[Required]
|
|
public function setRequestStack(RequestStack $requestStack): void
|
|
{
|
|
$this->requestStack = $requestStack;
|
|
}
|
|
}
|
|
|
|
if (empty($subclass)) {
|
|
class Cart extends CartBase
|
|
{
|
|
}
|
|
}
|