123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Bundle\Model\Product;
- use Magento\Customer\Api\GroupManagementInterface;
- use Magento\Framework\Pricing\PriceCurrencyInterface;
- use Magento\Framework\App\ObjectManager;
- use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
- /**
- * @api
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @since 100.0.2
- */
- class Price extends \Magento\Catalog\Model\Product\Type\Price
- {
- /**
- * Fixed bundle price type
- */
- const PRICE_TYPE_FIXED = 1;
- /**
- * Dynamic bundle price type
- */
- const PRICE_TYPE_DYNAMIC = 0;
- /**
- * Flag which indicates - is min/max prices have been calculated by index
- *
- * @var bool
- */
- protected $_isPricesCalculatedByIndex;
- /**
- * Catalog data
- *
- * @var \Magento\Catalog\Helper\Data
- */
- protected $_catalogData = null;
- /**
- * Serializer interface instance.
- *
- * @var \Magento\Framework\Serialize\Serializer\Json
- */
- private $serializer;
- /**
- * Constructor
- *
- * @param \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @param \Magento\Customer\Model\Session $customerSession
- * @param \Magento\Framework\Event\ManagerInterface $eventManager
- * @param PriceCurrencyInterface $priceCurrency
- * @param GroupManagementInterface $groupManagement
- * @param \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
- * @param \Magento\Catalog\Helper\Data $catalogData
- * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
- * @param ProductTierPriceExtensionFactory|null $tierPriceExtensionFactory
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Customer\Model\Session $customerSession,
- \Magento\Framework\Event\ManagerInterface $eventManager,
- PriceCurrencyInterface $priceCurrency,
- GroupManagementInterface $groupManagement,
- \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory,
- \Magento\Framework\App\Config\ScopeConfigInterface $config,
- \Magento\Catalog\Helper\Data $catalogData,
- \Magento\Framework\Serialize\Serializer\Json $serializer = null,
- ProductTierPriceExtensionFactory $tierPriceExtensionFactory = null
- ) {
- $this->_catalogData = $catalogData;
- $this->serializer = $serializer ?: ObjectManager::getInstance()
- ->get(\Magento\Framework\Serialize\Serializer\Json::class);
- parent::__construct(
- $ruleFactory,
- $storeManager,
- $localeDate,
- $customerSession,
- $eventManager,
- $priceCurrency,
- $groupManagement,
- $tierPriceFactory,
- $config,
- $tierPriceExtensionFactory
- );
- }
- /**
- * Is min/max prices have been calculated by index
- *
- * @return bool
- * @SuppressWarnings(PHPMD.BooleanGetMethodName)
- */
- public function getIsPricesCalculatedByIndex()
- {
- return $this->_isPricesCalculatedByIndex;
- }
- /**
- * Return product base price
- *
- * @param \Magento\Catalog\Model\Product $product
- * @return float
- */
- public function getPrice($product)
- {
- if ($product->getPriceType() == self::PRICE_TYPE_FIXED) {
- return $product->getData('price');
- } else {
- return 0;
- }
- }
- /**
- * Get Total price for Bundle items
- *
- * @param \Magento\Catalog\Model\Product $product
- * @param null|float $qty
- * @return float
- */
- public function getTotalBundleItemsPrice($product, $qty = null)
- {
- $price = 0.0;
- if ($product->hasCustomOptions()) {
- $selectionIds = $this->getBundleSelectionIds($product);
- if ($selectionIds) {
- $selections = $product->getTypeInstance()->getSelectionsByIds($selectionIds, $product);
- $selections->addTierPriceData();
- $this->_eventManager->dispatch(
- 'prepare_catalog_product_collection_prices',
- ['collection' => $selections, 'store_id' => $product->getStoreId()]
- );
- foreach ($selections->getItems() as $selection) {
- if ($selection->isSalable()) {
- $selectionQty = $product->getCustomOption('selection_qty_' . $selection->getSelectionId());
- if ($selectionQty) {
- $price += $this->getSelectionFinalTotalPrice(
- $product,
- $selection,
- $qty,
- $selectionQty->getValue()
- );
- }
- }
- }
- }
- }
- return $price;
- }
- /**
- * Retrieve array of bundle selection IDs
- *
- * @param \Magento\Catalog\Model\Product $product
- * @return array
- */
- protected function getBundleSelectionIds(\Magento\Catalog\Model\Product $product)
- {
- $customOption = $product->getCustomOption('bundle_selection_ids');
- if ($customOption) {
- $selectionIds = $this->serializer->unserialize($customOption->getValue());
- if (is_array($selectionIds) && !empty($selectionIds)) {
- return $selectionIds;
- }
- }
- return [];
- }
- /**
- * Get product final price
- *
- * @param float $qty
- * @param \Magento\Catalog\Model\Product $product
- * @return float
- */
- public function getFinalPrice($qty, $product)
- {
- if ($qty === null && $product->getCalculatedFinalPrice() !== null) {
- return $product->getCalculatedFinalPrice();
- }
- $finalPrice = $this->getBasePrice($product, $qty);
- $product->setFinalPrice($finalPrice);
- $this->_eventManager->dispatch('catalog_product_get_final_price', ['product' => $product, 'qty' => $qty]);
- $finalPrice = $product->getData('final_price');
- $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice);
- $finalPrice += $this->getTotalBundleItemsPrice($product, $qty);
- $finalPrice = max(0, $finalPrice);
- $product->setFinalPrice($finalPrice);
- return $finalPrice;
- }
- /**
- * Returns final price of a child product
- *
- * @param \Magento\Catalog\Model\Product $product
- * @param float $productQty
- * @param \Magento\Catalog\Model\Product $childProduct
- * @param float $childProductQty
- * @return float
- */
- public function getChildFinalPrice($product, $productQty, $childProduct, $childProductQty)
- {
- return $this->getSelectionFinalTotalPrice($product, $childProduct, $productQty, $childProductQty, false);
- }
- /**
- * Retrieve Price considering tier price
- *
- * @param \Magento\Catalog\Model\Product $product
- * @param string|null $which
- * @param bool|null $includeTax
- * @param bool $takeTierPrice
- * @return float|array
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public function getTotalPrices($product, $which = null, $includeTax = null, $takeTierPrice = true)
- {
- // check calculated price index
- if ($product->getData('min_price') && $product->getData('max_price')) {
- $minimalPrice = $this->_catalogData->getTaxPrice($product, $product->getData('min_price'), $includeTax);
- $maximalPrice = $this->_catalogData->getTaxPrice($product, $product->getData('max_price'), $includeTax);
- $this->_isPricesCalculatedByIndex = true;
- } else {
- /**
- * Check if product price is fixed
- */
- $finalPrice = $product->getFinalPrice();
- if ($product->getPriceType() == self::PRICE_TYPE_FIXED) {
- $minimalPrice = $maximalPrice = $this->_catalogData->getTaxPrice($product, $finalPrice, $includeTax);
- } else {
- // PRICE_TYPE_DYNAMIC
- $minimalPrice = $maximalPrice = 0;
- }
- $options = $this->getOptions($product);
- $minPriceFounded = false;
- if ($options) {
- foreach ($options as $option) {
- /* @var $option \Magento\Bundle\Model\Option */
- $selections = $option->getSelections();
- if ($selections) {
- $selectionMinimalPrices = [];
- $selectionMaximalPrices = [];
- foreach ($option->getSelections() as $selection) {
- /* @var $selection \Magento\Bundle\Model\Selection */
- if (!$selection->isSalable()) {
- /**
- * @todo CatalogInventory Show out of stock Products
- */
- continue;
- }
- $qty = $selection->getSelectionQty();
- $item = $product->getPriceType() == self::PRICE_TYPE_FIXED ? $product : $selection;
- $selectionMinimalPrices[] = $this->_catalogData->getTaxPrice(
- $item,
- $this->getSelectionFinalTotalPrice(
- $product,
- $selection,
- 1,
- $qty,
- true,
- $takeTierPrice
- ),
- $includeTax
- );
- $selectionMaximalPrices[] = $this->_catalogData->getTaxPrice(
- $item,
- $this->getSelectionFinalTotalPrice(
- $product,
- $selection,
- 1,
- null,
- true,
- $takeTierPrice
- ),
- $includeTax
- );
- }
- if (count($selectionMinimalPrices)) {
- $selMinPrice = min($selectionMinimalPrices);
- if ($option->getRequired()) {
- $minimalPrice += $selMinPrice;
- $minPriceFounded = true;
- } elseif (true !== $minPriceFounded) {
- $selMinPrice += $minimalPrice;
- $minPriceFounded = false === $minPriceFounded ? $selMinPrice : min(
- $minPriceFounded,
- $selMinPrice
- );
- }
- if ($option->isMultiSelection()) {
- $maximalPrice += array_sum($selectionMaximalPrices);
- } else {
- $maximalPrice += max($selectionMaximalPrices);
- }
- }
- }
- }
- }
- // condition is TRUE when all product options are NOT required
- if (!is_bool($minPriceFounded)) {
- $minimalPrice = $minPriceFounded;
- }
- $customOptions = $product->getOptions();
- if ($product->getPriceType() == self::PRICE_TYPE_FIXED && $customOptions) {
- foreach ($customOptions as $customOption) {
- /* @var $customOption \Magento\Catalog\Model\Product\Option */
- $values = $customOption->getValues();
- if ($values) {
- $prices = [];
- foreach ($values as $value) {
- /* @var $value \Magento\Catalog\Model\Product\Option\Value */
- $valuePrice = $value->getPrice(true);
- $prices[] = $valuePrice;
- }
- if (count($prices)) {
- if ($customOption->getIsRequire()) {
- $minimalPrice += $this->_catalogData->getTaxPrice($product, min($prices), $includeTax);
- }
- $multiTypes = [
- \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX,
- \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE,
- ];
- if (in_array($customOption->getType(), $multiTypes)) {
- $maximalValue = array_sum($prices);
- } else {
- $maximalValue = max($prices);
- }
- $maximalPrice += $this->_catalogData->getTaxPrice($product, $maximalValue, $includeTax);
- }
- } else {
- $valuePrice = $customOption->getPrice(true);
- if ($customOption->getIsRequire()) {
- $minimalPrice += $this->_catalogData->getTaxPrice($product, $valuePrice, $includeTax);
- }
- $maximalPrice += $this->_catalogData->getTaxPrice($product, $valuePrice, $includeTax);
- }
- }
- }
- $this->_isPricesCalculatedByIndex = false;
- }
- if ($which == 'max') {
- return $maximalPrice;
- } elseif ($which == 'min') {
- return $minimalPrice;
- }
- return [$minimalPrice, $maximalPrice];
- }
- /**
- * Get Options with attached Selections collection
- *
- * @param \Magento\Catalog\Model\Product $product
- * @return \Magento\Bundle\Model\ResourceModel\Option\Collection
- */
- public function getOptions($product)
- {
- $product->getTypeInstance()->setStoreFilter($product->getStoreId(), $product);
- $optionCollection = $product->getTypeInstance()->getOptionsCollection($product);
- $selectionCollection = $product->getTypeInstance()->getSelectionsCollection(
- $product->getTypeInstance()->getOptionsIds($product),
- $product
- );
- return $optionCollection->appendSelections($selectionCollection, false, false);
- }
- /**
- * Calculate price of selection
- *
- * @param \Magento\Catalog\Model\Product $bundleProduct
- * @param \Magento\Catalog\Model\Product $selectionProduct
- * @param float|null $selectionQty
- * @param null|bool $multiplyQty Whether to multiply selection's price by its quantity
- * @return float
- *
- * @see \Magento\Bundle\Model\Product\Price::getSelectionFinalTotalPrice()
- */
- public function getSelectionPrice($bundleProduct, $selectionProduct, $selectionQty = null, $multiplyQty = true)
- {
- return $this->getSelectionFinalTotalPrice($bundleProduct, $selectionProduct, 0, $selectionQty, $multiplyQty);
- }
- /**
- * Calculate selection price for front view (with applied special of bundle)
- *
- * @param \Magento\Catalog\Model\Product $bundleProduct
- * @param \Magento\Catalog\Model\Product $selectionProduct
- * @param float $qty
- * @return float
- */
- public function getSelectionPreFinalPrice($bundleProduct, $selectionProduct, $qty = null)
- {
- return $this->getSelectionPrice($bundleProduct, $selectionProduct, $qty);
- }
- /**
- * Calculate final price of selection
- * with take into account tier price
- *
- * @param \Magento\Catalog\Model\Product $bundleProduct
- * @param \Magento\Catalog\Model\Product $selectionProduct
- * @param float $bundleQty
- * @param float $selectionQty
- * @param bool $multiplyQty
- * @param bool $takeTierPrice
- * @return float
- */
- public function getSelectionFinalTotalPrice(
- $bundleProduct,
- $selectionProduct,
- $bundleQty,
- $selectionQty,
- $multiplyQty = true,
- $takeTierPrice = true
- ) {
- if (null === $bundleQty) {
- $bundleQty = 1.;
- }
- if ($selectionQty === null) {
- $selectionQty = $selectionProduct->getSelectionQty();
- }
- if ($bundleProduct->getPriceType() == self::PRICE_TYPE_DYNAMIC) {
- $price = $selectionProduct->getFinalPrice($takeTierPrice ? $selectionQty : 1);
- } else {
- if ($selectionProduct->getSelectionPriceType()) {
- // percent
- $product = clone $bundleProduct;
- $product->setFinalPrice($this->getPrice($product));
- $this->_eventManager->dispatch(
- 'catalog_product_get_final_price',
- ['product' => $product, 'qty' => $bundleQty]
- );
- $price = $product->getData('final_price') * ($selectionProduct->getSelectionPriceValue() / 100);
- } else {
- // fixed
- $price = $selectionProduct->getSelectionPriceValue();
- }
- }
- if ($multiplyQty) {
- $price *= $selectionQty;
- }
- return min(
- $price,
- $this->_applyTierPrice($bundleProduct, $bundleQty, $price),
- $this->_applySpecialPrice($bundleProduct, $price)
- );
- }
- /**
- * Apply tier price for bundle
- *
- * @param \Magento\Catalog\Model\Product $product
- * @param float $qty
- * @param float $finalPrice
- * @return float
- */
- protected function _applyTierPrice($product, $qty, $finalPrice)
- {
- if ($qty === null) {
- return $finalPrice;
- }
- $tierPrice = $product->getTierPrice($qty);
- if (is_numeric($tierPrice)) {
- $tierPrice = $finalPrice - $finalPrice * ($tierPrice / 100);
- $finalPrice = min($finalPrice, $tierPrice);
- }
- return $finalPrice;
- }
- /**
- * Get product tier price by qty
- *
- * @param float $qty
- * @param \Magento\Catalog\Model\Product $product
- * @return float|array
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function getTierPrice($qty, $product)
- {
- $allCustomersGroupId = $this->_groupManagement->getAllCustomersGroup()->getId();
- $prices = $product->getData('tier_price');
- if ($prices === null) {
- if ($attribute = $product->getResource()->getAttribute('tier_price')) {
- $attribute->getBackend()->afterLoad($product);
- $prices = $product->getData('tier_price');
- }
- }
- if ($prices === null || !is_array($prices)) {
- if ($qty !== null) {
- return $product->getPrice();
- }
- return [
- [
- 'price' => $product->getPrice(),
- 'website_price' => $product->getPrice(),
- 'price_qty' => 1,
- 'cust_group' => $allCustomersGroupId,
- ]
- ];
- }
- $custGroup = $this->_getCustomerGroupId($product);
- if ($qty) {
- $prevQty = 1;
- $prevPrice = 0;
- $prevGroup = $allCustomersGroupId;
- foreach ($prices as $price) {
- if (empty($price['percentage_value'])) {
- // can use only percentage tier price
- continue;
- }
- if ($price['cust_group'] != $custGroup && $price['cust_group'] != $allCustomersGroupId) {
- // tier not for current customer group nor is for all groups
- continue;
- }
- if ($qty < $price['price_qty']) {
- // tier is higher than product qty
- continue;
- }
- if ($price['price_qty'] < $prevQty) {
- // higher tier qty already found
- continue;
- }
- if ($price['price_qty'] == $prevQty
- && $prevGroup != $allCustomersGroupId
- && $price['cust_group'] == $allCustomersGroupId
- ) {
- // found tier qty is same as current tier qty but current tier group is ALL_GROUPS
- continue;
- }
- if ($price['percentage_value'] > $prevPrice) {
- $prevPrice = $price['percentage_value'];
- $prevQty = $price['price_qty'];
- $prevGroup = $price['cust_group'];
- }
- }
- return $prevPrice;
- } else {
- $qtyCache = [];
- foreach ($prices as $i => $price) {
- if ($price['cust_group'] != $custGroup && $price['cust_group'] != $allCustomersGroupId) {
- unset($prices[$i]);
- } elseif (isset($qtyCache[$price['price_qty']])) {
- $j = $qtyCache[$price['price_qty']];
- if ($prices[$j]['website_price'] < $price['website_price']) {
- unset($prices[$j]);
- $qtyCache[$price['price_qty']] = $i;
- } else {
- unset($prices[$i]);
- }
- } else {
- $qtyCache[$price['price_qty']] = $i;
- }
- }
- }
- return $prices ? $prices : [];
- }
- /**
- * Calculate and apply special price
- *
- * @param float $finalPrice
- * @param float $specialPrice
- * @param string $specialPriceFrom
- * @param string $specialPriceTo
- * @param mixed $store
- * @return float
- */
- public function calculateSpecialPrice(
- $finalPrice,
- $specialPrice,
- $specialPriceFrom,
- $specialPriceTo,
- $store = null
- ) {
- if ($specialPrice !== null && $specialPrice != false) {
- if ($this->_localeDate->isScopeDateInInterval($store, $specialPriceFrom, $specialPriceTo)) {
- $specialPrice = $finalPrice * ($specialPrice / 100);
- $finalPrice = min($finalPrice, $specialPrice);
- }
- }
- return $finalPrice;
- }
- /**
- * Returns the lowest price after applying any applicable bundle discounts
- *
- * @param /Magento/Catalog/Model/Product $bundleProduct
- * @param float|string $price
- * @param int $bundleQty
- * @return float
- */
- public function getLowestPrice($bundleProduct, $price, $bundleQty = 1)
- {
- $price = (float)$price;
- return min(
- $price,
- $this->_applyTierPrice($bundleProduct, $bundleQty, $price),
- $this->_applySpecialPrice($bundleProduct, $price)
- );
- }
- }
|