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 { } }