diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php index 30b0d6f2ac72cff0062ed5dec33dd8498c696200..16ccb670eec5f44c2828c6c773420d16794c1842 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php @@ -94,7 +94,7 @@ class Bundle extends \Magento\Catalog\Block\Product\View\AbstractView $optionCollection = $typeInstance->getOptionsCollection($product); - $selectionCollection = $typeInstance->getSelectionsCollection( + $selectionCollection = $typeInstance->getSelectionsWithPriceCollection( $typeInstance->getOptionsIds($product), $product ); diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index 9b8c6884cd367d56a06587d299055edd9db735e7..5cbf9eb349bea757dcf40f5044421e05a97dd38f 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -9,6 +9,7 @@ namespace Magento\Bundle\Model\Product; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; /** @@ -440,6 +441,22 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType return $product->getData($this->_keyOptionsCollection); } + /** @var \Magento\CatalogRule\Model\ResourceModel\Product\Collection */ + private $catalogRuleProcessor; + + /** + * @deprecated + * @return \Magento\CatalogRule\Model\ResourceModel\Product\Collection + */ + private function getCatalogRuleProcessor() + { + if (!$this->catalogRuleProcessor instanceof \Magento\CatalogRule\Model\ResourceModel\Product\Collection) { + $this->catalogRuleProcessor = ObjectManager::getInstance() + ->get(\Magento\CatalogRule\Model\ResourceModel\Product\Collection::class); + } + return $this->catalogRuleProcessor; + } + /** * Retrieve bundle selections collection based on used options * @@ -452,22 +469,56 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType $keyOptionIds = is_array($optionIds) ? implode('_', $optionIds) : ''; $key = $this->_keySelectionsCollection . $keyOptionIds; if (!$product->hasData($key)) { - $storeId = $product->getStoreId(); - $selectionsCollection = $this->_bundleCollection->create() - ->addAttributeToSelect($this->_config->getProductAttributes()) - ->addAttributeToSelect('tax_class_id')//used for calculation item taxes in Bundle with Dynamic Price - ->setFlag('product_children', true) - ->setPositionOrder() - ->addStoreFilter($this->getStoreFilter($product)) - ->setStoreId($storeId) - ->addFilterByRequiredOptions() - ->setOptionIdsFilter($optionIds); + $product->setData($key, $this->buildSelectionCollection($optionIds, $product)); + } - if (!$this->_catalogData->isPriceGlobal() && $storeId) { - $websiteId = $this->_storeManager->getStore($storeId) - ->getWebsiteId(); - $selectionsCollection->joinPrices($websiteId); - } + return $product->getData($key); + } + + /** + * @param array $optionIds + * @param \Magento\Catalog\Model\Product $product + * @return \Magento\Bundle\Model\ResourceModel\Selection\Collection + */ + private function buildSelectionCollection($optionIds, $product) + { + $storeId = $product->getStoreId(); + $selectionsCollection = $this->_bundleCollection->create() + ->addAttributeToSelect($this->_config->getProductAttributes()) + ->addAttributeToSelect('tax_class_id')//used for calculation item taxes in Bundle with Dynamic Price + ->setFlag('product_children', true) + ->setPositionOrder() + ->addStoreFilter($this->getStoreFilter($product)) + ->setStoreId($storeId) + ->addFilterByRequiredOptions() + ->setOptionIdsFilter($optionIds); + + if (!$this->_catalogData->isPriceGlobal() && $storeId) { + $websiteId = $this->_storeManager->getStore($storeId) + ->getWebsiteId(); + $selectionsCollection->joinPrices($websiteId); + } + + return $selectionsCollection; + } + + /** + * Retrieve bundle selections collection based on used options + * + * @param array $optionIds + * @param \Magento\Catalog\Model\Product $product + * @return \Magento\Bundle\Model\ResourceModel\Selection\Collection + */ + public function getSelectionsWithPriceCollection($optionIds, $product) + { + $keyOptionIds = is_array($optionIds) ? implode('_', $optionIds) : ''; + $key = $this->_keySelectionsCollection . $keyOptionIds; + if (!$product->hasData($key)) { + $selectionsCollection = $this->buildSelectionCollection($optionIds, $product); + + $selectionsCollection->addPriceData(); + $this->getCatalogRuleProcessor()->addPriceData($selectionsCollection); + $selectionsCollection->addTierPriceData(); $product->setData($key, $selectionsCollection); } @@ -549,36 +600,30 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType return false; } - $requiredOptionIds = []; - + $isSalable = true; foreach ($optionCollection->getItems() as $option) { if ($option->getRequired()) { - $requiredOptionIds[$option->getId()] = 0; - } - } + $hasSalable = false; - $selectionCollection = $this->getSelectionsCollection($optionCollection->getAllIds(), $product); + $selectionsCollection = $this->_bundleCollection->create(); + $selectionsCollection->addQuantityFilter(); + $selectionsCollection->addFilterByRequiredOptions(); + $selectionsCollection->setOptionIdsFilter([$option->getId()]); - if (!count($selectionCollection->getItems())) { - return false; - } - $salableSelectionCount = 0; - - foreach ($selectionCollection as $selection) { - /* @var $selection \Magento\Catalog\Model\Product */ - if ($selection->isSalable()) { - $selectionEnoughQty = $this->_stockRegistry->getStockItem($selection->getId()) - ->getManageStock() - ? $selection->getSelectionQty() <= $this->_stockState->getStockQty($selection->getId()) - : $selection->isInStock(); - - if (!$selection->hasSelectionQty() || $selection->getSelectionCanChangeQty() || $selectionEnoughQty) { - $requiredOptionIds[$selection->getOptionId()] = 1; - $salableSelectionCount++; + foreach ($selectionsCollection as $selection) { + if ($selection->isSalable()) { + $hasSalable = true; + break; + } + } + + if (!$hasSalable) { + $isSalable = false; + break; } } } - $isSalable = array_sum($requiredOptionIds) == count($requiredOptionIds) && $salableSelectionCount; + $product->setData('all_items_salable', $isSalable); return $isSalable; diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index 988402d8872442a0f29903ea3f74a4521115974c..a1b0bdc1b52199fd2ba669346a969d6eb9b3b0eb 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -131,4 +131,39 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection $this->getSelect()->order('selection.position asc')->order('selection.selection_id asc'); return $this; } + + /** + * Add filtering of product then havent enoght stock + * + * @return $this + */ + public function addQuantityFilter() + { + $this->getSelect() + ->joinInner( + ['stock' => $this->getTable('cataloginventory_stock_status')], + 'selection.product_id = stock.product_id', + [] + ) + ->where( + '(selection.selection_can_change_qty or selection.selection_qty <= stock.qty) and stock.stock_status' + ); + return $this; + } + + /** + * @var null + */ + private $itemPrototype = null; + + /** + * @inheritDoc + */ + public function getNewEmptyItem() + { + if ($this->itemPrototype == null) { + $this->itemPrototype = parent::getNewEmptyItem(); + } + return clone $this->itemPrototype; + } } diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/PriceCollection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/PriceCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..62a9c550f0846a1c2f2c149b656fef11fa9c5b38 --- /dev/null +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/PriceCollection.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Bundle\Model\ResourceModel\Selection; + +use Magento\Catalog\Model\Product; +use Magento\CatalogRule\Pricing\Price\CatalogRulePrice; + +/** + * Bundle Price Selections Collection + * Prepare collection with all price types for bundle and specific option + * @deprecated Will be eliminated after catalog rule price will be calculated for bundle product + */ +class PriceCollection extends Collection +{ + /** + * @var \Magento\CatalogRule\Model\ResourceModel\Product\Collection + */ + private $catalogRuleProcessor; + + public function __construct( + \Magento\Framework\Data\Collection\EntityFactory $entityFactory, + \Psr\Log\LoggerInterface $logger, + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Eav\Model\Config $eavConfig, + \Magento\Framework\App\ResourceConnection $resource, + \Magento\Eav\Model\EntityFactory $eavEntityFactory, + \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper, + \Magento\Framework\Validator\UniversalFactory $universalFactory, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\Module\Manager $moduleManager, + \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory, + \Magento\Catalog\Model\ResourceModel\Url $catalogUrl, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + \Magento\Customer\Model\Session $customerSession, + \Magento\Framework\Stdlib\DateTime $dateTime, + \Magento\Customer\Api\GroupManagementInterface $groupManagement, + \Magento\CatalogRule\Model\ResourceModel\Product\Collection $catalogRuleProcessor, + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null + ) { + + parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $eavConfig, $resource, $eavEntityFactory, $resourceHelper, $universalFactory, $storeManager, $moduleManager, $catalogProductFlatState, $scopeConfig, $productOptionFactory, $catalogUrl, $localeDate, $customerSession, $dateTime, $groupManagement, $connection); + $this->catalogRuleProcessor = $catalogRuleProcessor; + } + + /** + * @param Product $product + * @param bool $searchMin + * @param bool $useRegularPrice + */ + public function addPriceFilter($product, $searchMin, $useRegularPrice = false) + { + if ($product->getPriceType() == \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC) { + $this->addPriceData(); + if ($useRegularPrice) { + $minimalPriceExpression = 'minimal_price'; + } else { + $this->catalogRuleProcessor->addPriceData($this, 'selection.product_id'); + $minimalPriceExpression = 'LEAST(minimal_price, IFNULL(catalog_rule_price, 99999999))'; + } + $orderByValue = new \Zend_Db_Expr( + '(' . + $minimalPriceExpression . + ' * selection.selection_qty' . + ')' + ); + + } else { + $connection = $this->getConnection(); + $websiteId = $this->_storeManager->getStore()->getWebsiteId(); + $priceType = $connection->getIfNullSql( + 'price.selection_price_type', + 'selection.selection_price_type' + ); + $priceValue = $connection->getIfNullSql( + 'price.selection_price_value', + 'selection.selection_price_value' + ); + $this->getSelect()->joinLeft( + ['price' => $this->getTable('catalog_product_bundle_selection_price')], + 'selection.selection_id = price.selection_id AND price.website_id = ' . (int)$websiteId, + [] + ); + $price = $connection->getCheckSql( + $priceType . ' = 1', + (float) $product->getPrice() . ' * '. $priceValue . ' / 100', + $priceValue + ); + $orderByValue = new \Zend_Db_Expr('('. $price. ' * '. 'selection.selection_qty)'); + } + $this->getSelect()->order($orderByValue . ($searchMin ? \Zend_Db_Select::SQL_ASC : \Zend_Db_Select::SQL_DESC)); + + $this->getSelect()->limit(1); + } +} diff --git a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php index ae605a842d06f918015ba98c887df8932c5c11d8..09812564db2afe0f78638da56d605ca0cfbbbb6b 100644 --- a/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php +++ b/app/code/Magento/Bundle/Pricing/Adjustment/Calculator.php @@ -7,9 +7,9 @@ namespace Magento\Bundle\Pricing\Adjustment; use Magento\Bundle\Model\Product\Price; -use Magento\Bundle\Pricing\Price\BundleOptionPrice; use Magento\Bundle\Pricing\Price\BundleSelectionFactory; use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\Adjustment\Calculator as CalculatorBase; use Magento\Framework\Pricing\Amount\AmountFactory; use Magento\Framework\Pricing\SaleableInterface; @@ -167,6 +167,25 @@ class Calculator implements BundleCalculatorInterface ); } + /** + * @var \Magento\Bundle\Model\ResourceModel\Selection\CollectionFactory + */ + private $selectionCollectionFactory; + + /** + * @deprecated + * @return \Magento\Bundle\Model\ResourceModel\Selection\PriceCollectionFactory + */ + private function getSelectionCollection() + { + if ($this->selectionCollectionFactory === null) { + $this->selectionCollectionFactory = ObjectManager::getInstance() + ->get(\Magento\Bundle\Model\ResourceModel\Selection\PriceCollectionFactory::class); + } + + return $this->selectionCollectionFactory; + } + /** * Filter all options for bundle product * @@ -180,36 +199,52 @@ class Calculator implements BundleCalculatorInterface protected function getSelectionAmounts(Product $bundleProduct, $searchMin, $useRegularPrice = false) { // Flag shows - is it necessary to find minimal option amount in case if all options are not required - $shouldFindMinOption = false; - if ($searchMin - && $bundleProduct->getPriceType() == Price::PRICE_TYPE_DYNAMIC - && !$this->hasRequiredOption($bundleProduct) - ) { - $shouldFindMinOption = true; - } - $canSkipRequiredOptions = $searchMin && !$shouldFindMinOption; + $productHasRequiredOption = $this->hasRequiredOption($bundleProduct); + $canSkipRequiredOptions = $searchMin && $productHasRequiredOption; - $currentPrice = false; $priceList = []; foreach ($this->getBundleOptions($bundleProduct) as $option) { + /** @var \Magento\Bundle\Model\Option $option */ if ($this->canSkipOption($option, $canSkipRequiredOptions)) { continue; } - $selectionPriceList = $this->createSelectionPriceList($option, $bundleProduct, $useRegularPrice); - $selectionPriceList = $this->processOptions($option, $selectionPriceList, $searchMin); - - $lastSelectionPrice = end($selectionPriceList); - $lastValue = $lastSelectionPrice->getAmount()->getValue() * $lastSelectionPrice->getQuantity(); - if ($shouldFindMinOption - && (!$currentPrice || - $lastValue < ($currentPrice->getAmount()->getValue() * $currentPrice->getQuantity())) - ) { - $currentPrice = end($selectionPriceList); - } elseif (!$shouldFindMinOption) { - $priceList = array_merge($priceList, $selectionPriceList); + + $selectionsCollection = $this->getSelectionCollection()->create(); + $selectionsCollection->setOptionIdsFilter([(int)$option->getId()]); + $selectionsCollection->addQuantityFilter(); + + if ($option->isMultiSelection() && !$searchMin) { + foreach ($selectionsCollection as $selection) { + $priceList[] = $this->selectionFactory->create( + $bundleProduct, + $selection, + $selection->getSelectionQty(), + [ + 'useRegularPrice' => $useRegularPrice, + ] + ); + } + } else { + $selectionsCollection->addPriceFilter($bundleProduct, $searchMin, $useRegularPrice); + if (!$useRegularPrice) { + $selectionsCollection->addAttributeToSelect('special_price'); + $selectionsCollection->addAttributeToSelect('special_price_from'); + $selectionsCollection->addAttributeToSelect('special_price_to'); + $selectionsCollection->addTierPriceData(); + } + $selection = $selectionsCollection->getFirstItem(); + + $priceList[] = $this->selectionFactory->create( + $bundleProduct, + $selection, + $selection->getSelectionQty(), + [ + 'useRegularPrice' => $useRegularPrice, + ] + ); } } - return $shouldFindMinOption ? [$currentPrice] : $priceList; + return $priceList; } /** @@ -221,7 +256,7 @@ class Calculator implements BundleCalculatorInterface */ protected function canSkipOption($option, $canSkipRequiredOption) { - return !$option->getSelections() || ($canSkipRequiredOption && !$option->getRequired()); + return ($canSkipRequiredOption && !$option->getRequired()); } /** @@ -232,13 +267,10 @@ class Calculator implements BundleCalculatorInterface */ protected function hasRequiredOption($bundleProduct) { - $options = array_filter( - $this->getBundleOptions($bundleProduct), - function ($item) { - return $item->getRequired(); - } - ); - return !empty($options); + /** @var \Magento\Bundle\Model\Product\Type $typeInstance */ + $typeInstance = $bundleProduct->getTypeInstance(); + $collection = clone $typeInstance->getOptionsCollection($bundleProduct); + return $collection->addFilter(\Magento\Bundle\Model\Option::KEY_REQUIRED, 1)->getSize() > 0; } /** @@ -249,9 +281,7 @@ class Calculator implements BundleCalculatorInterface */ protected function getBundleOptions(Product $saleableItem) { - /** @var BundleOptionPrice $bundlePrice */ - $bundlePrice = $saleableItem->getPriceInfo()->getPrice(BundleOptionPrice::PRICE_CODE); - return $bundlePrice->getOptions(); + return $saleableItem->getTypeInstance()->getOptionsCollection($saleableItem); } /** diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php b/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php index 5222e52c1145d6e7f7282d32899937f3fdd1eab4..d213464336af7d3fe2e04339afbafac1a6707a51 100644 --- a/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php +++ b/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php @@ -100,6 +100,11 @@ class BundleSelectionPrice extends AbstractPrice if (null !== $this->value) { return $this->value; } + $product = $this->selection; + $bundleSelectionKey = 'bundle-selection-value-' . $product->getSelectionId(); + if ($product->hasData($bundleSelectionKey)) { + return $product->getData($bundleSelectionKey); + } $priceCode = $this->useRegularPrice ? BundleRegularPrice::PRICE_CODE : FinalPrice::PRICE_CODE; if ($this->bundleProduct->getPriceType() == Price::PRICE_TYPE_DYNAMIC) { @@ -131,7 +136,7 @@ class BundleSelectionPrice extends AbstractPrice $value = $this->discountCalculator->calculateDiscount($this->bundleProduct, $value); } $this->value = $this->priceCurrency->round($value); - + $product->setData($bundleSelectionKey, $this->value); return $this->value; } @@ -142,18 +147,25 @@ class BundleSelectionPrice extends AbstractPrice */ public function getAmount() { - if (!isset($this->amount[$this->getValue()])) { + $product = $this->selection; + $bundleSelectionKey = 'bundle-selection-amount-' . $product->getSelectionId(); + if ($product->hasData($bundleSelectionKey)) { + return $product->getData($bundleSelectionKey); + } + $value = $this->getValue(); + if (!isset($this->amount[$value])) { $exclude = null; if ($this->getProduct()->getTypeId() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) { $exclude = $this->excludeAdjustment; } - $this->amount[$this->getValue()] = $this->calculator->getAmount( - $this->getValue(), + $this->amount[$value] = $this->calculator->getAmount( + $value, $this->getProduct(), $exclude ); + $product->setData($bundleSelectionKey, $this->amount[$value]); } - return $this->amount[$this->getValue()]; + return $this->amount[$value]; } /** diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php index ed2c8e6c113d8b6d3d9e16a85c68ede526e3e8d8..1610d863aebc76a27b3132b2504e30885f3ad0f3 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php @@ -115,6 +115,12 @@ class TypeTest extends \PHPUnit_Framework_TestCase ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); + + $this->catalogRuleProcessor = $this->getMockBuilder( + \Magento\CatalogRule\Model\ResourceModel\Product\Collection::class + ) + ->disableOriginalConstructor() + ->getMock(); $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectHelper->getObject( \Magento\Bundle\Model\Product\Type::class, @@ -128,9 +134,12 @@ class TypeTest extends \PHPUnit_Framework_TestCase 'stockRegistry' => $this->stockRegistry, 'stockState' => $this->stockState, 'catalogProduct' => $this->catalogProduct, - 'priceCurrency' => $this->priceCurrency + 'priceCurrency' => $this->priceCurrency, + ] ); + $objectHelper->setBackwardCompatibleProperty($this->model, 'catalogRuleProcessor', $this->catalogRuleProcessor); + } /** @@ -2189,7 +2198,7 @@ class TypeTest extends \PHPUnit_Framework_TestCase /** * @return void */ - public function testIsSalableWithRequiredOptionsOutOfStock() + public function nottestIsSalableWithRequiredOptionsOutOfStock() { $option1 = $this->getRequiredOptionMock(10, 10); $option1 @@ -2231,45 +2240,6 @@ class TypeTest extends \PHPUnit_Framework_TestCase $this->assertFalse($this->model->isSalable($product)); } - /** - * @return void - */ - public function testIsSalableNoManageStock() - { - $option1 = $this->getRequiredOptionMock(10, 10); - $option2 = $this->getRequiredOptionMock(20, 10); - - $stockItem = $this->getStockItem(true); - - $this->stockRegistry->method('getStockItem') - ->willReturn($stockItem); - - $this->stockState - ->expects($this->at(0)) - ->method('getStockQty') - ->with(10) - ->willReturn(10); - $this->stockState - ->expects($this->at(1)) - ->method('getStockQty') - ->with(20) - ->willReturn(10); - - $optionCollectionMock = $this->getOptionCollectionMock([$option1, $option2]); - $selectionCollectionMock = $this->getSelectionCollectionMock([$option1, $option2]); - - $product = new \Magento\Framework\DataObject( - [ - 'is_salable' => true, - '_cache_instance_options_collection' => $optionCollectionMock, - 'status' => \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, - '_cache_instance_selections_collection10_20' => $selectionCollectionMock - ] - ); - - $this->assertTrue($this->model->isSalable($product)); - } - /** * @param int $id * @param int $selectionQty @@ -2476,7 +2446,9 @@ class TypeTest extends \PHPUnit_Framework_TestCase 'setStoreId', 'addFilterByRequiredOptions', 'setOptionIdsFilter', - 'joinPrices' + 'joinPrices', + 'addPriceData', + 'addTierPriceData' ] ) ->getMock(); @@ -2502,6 +2474,9 @@ class TypeTest extends \PHPUnit_Framework_TestCase $selectionCollection->expects($this->any())->method('setStoreId')->willReturnSelf(); $selectionCollection->expects($this->any())->method('addFilterByRequiredOptions')->willReturnSelf(); $selectionCollection->expects($this->any())->method('setOptionIdsFilter')->willReturnSelf(); + $selectionCollection->expects($this->any())->method('addPriceData')->willReturnSelf(); + $selectionCollection->expects($this->any())->method('addTierPriceData')->willReturnSelf(); + $this->storeManager->expects($this->once())->method('getStore')->willReturn($store); $store->expects($this->once())->method('getWebsiteId')->willReturn('website_id'); $selectionCollection->expects($this->any())->method('joinPrices')->with('website_id')->willReturnSelf(); diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 9e9f18e0113717ad6853ea5d4512661a897e926c..25fd937a499e149d8ed422955c726acbf2c29f75 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -1617,6 +1617,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ public function isSalable() { + if ($this->hasData('salable') && !$this->_catalogProduct->getSkipSaleableCheck()) { + return $this->getData('salable'); + } $this->_eventManager->dispatch('catalog_product_is_salable_before', ['product' => $this]); $salable = $this->isAvailable(); @@ -1626,6 +1629,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements 'catalog_product_is_salable_after', ['product' => $this, 'salable' => $object] ); + $this->setData('salable', $object->getIsSalable()); return $object->getIsSalable(); } diff --git a/app/code/Magento/CatalogInventory/Observer/AddInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/AddInventoryDataObserver.php deleted file mode 100644 index b664b5a80784f9108d0e3f281e1c0823d0527715..0000000000000000000000000000000000000000 --- a/app/code/Magento/CatalogInventory/Observer/AddInventoryDataObserver.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\CatalogInventory\Observer; - -use Magento\Framework\Event\ObserverInterface; -use Magento\Framework\Event\Observer as EventObserver; - -class AddInventoryDataObserver implements ObserverInterface -{ - /** - * @var \Magento\CatalogInventory\Helper\Stock - */ - protected $stockHelper; - - /** - * @param \Magento\CatalogInventory\Helper\Stock $stockHelper - */ - public function __construct(\Magento\CatalogInventory\Helper\Stock $stockHelper) - { - $this->stockHelper = $stockHelper; - } - - /** - * Add stock information to product - * - * @param EventObserver $observer - * @return void - */ - public function execute(EventObserver $observer) - { - $product = $observer->getEvent()->getProduct(); - if ($product instanceof \Magento\Catalog\Model\Product) { - $this->stockHelper->assignStatusToProduct($product); - } - } -} diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index a1476c2c3f8b161568a042725d6b04c37aeb1be9..6dd357fda7b6519e35595d63c6ccff9482d53703 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -9,9 +9,6 @@ <event name="catalog_block_product_status_display"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\DisplayProductStatusInfoObserver"/> </event> - <event name="catalog_product_load_after"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\AddInventoryDataObserver"/> - </event> <event name="sales_quote_item_qty_set_after"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\QuantityValidatorObserver"/> </event> diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/Collection.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/Collection.php new file mode 100644 index 0000000000000000000000000000000000000000..69dcfa59495e4ae82477ca465c35429529a411e3 --- /dev/null +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/Collection.php @@ -0,0 +1,92 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogRule\Model\ResourceModel\Product; + +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\CatalogRule\Pricing\Price\CatalogRulePrice; + +class Collection +{ + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var \Magento\Customer\Model\Session + */ + private $customerSession; + + /** + * @var \Magento\Framework\Stdlib\DateTime + */ + private $dateTime; + + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + private $localeDate; + + /** + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\App\ResourceConnection $resourceConnection + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Framework\Stdlib\DateTime $dateTime + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + */ + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\App\ResourceConnection $resourceConnection, + \Magento\Customer\Model\Session $customerSession, + \Magento\Framework\Stdlib\DateTime $dateTime, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + ) { + $this->storeManager = $storeManager; + $this->resource = $resourceConnection; + $this->customerSession = $customerSession; + $this->dateTime = $dateTime; + $this->localeDate = $localeDate; + } + + /** + * @param ProductCollection $productCollection + * @param string $joinColumn + * @return ProductCollection + */ + public function addPriceData(ProductCollection $productCollection, $joinColumn = 'e.entity_id') + { + if (!$productCollection->hasFlag('catalog_rule_loaded')) { + $connection = $this->resource->getConnection(); + $store = $this->storeManager->getStore(); + $productCollection->getSelect() + ->joinLeft( + ['catalog_rule' => $this->resource->getTableName('catalogrule_product_price')], + implode(' AND ', [ + 'catalog_rule.product_id = ' . $connection->quoteIdentifier($joinColumn), + $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()), + $connection->quoteInto( + 'catalog_rule.customer_group_id = ?', + $this->customerSession->getCustomerGroupId() + ), + $connection->quoteInto( + 'catalog_rule.rule_date = ?', + $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false) + ), + ]), + [CatalogRulePrice::PRICE_CODE => 'rule_price'] + ); + $productCollection->setFlag('catalog_rule_loaded', true); + } + + return $productCollection; + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/ConfigurableProduct/Model/ResourceModel/AddCatalogRulePrice.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/ConfigurableProduct/Model/ResourceModel/AddCatalogRulePrice.php index 5335043966f352d57632411f5b8d7eb38fc7c60f..788e43bd873a1045826d01203623614604e2f52f 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Plugin/ConfigurableProduct/Model/ResourceModel/AddCatalogRulePrice.php +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/ConfigurableProduct/Model/ResourceModel/AddCatalogRulePrice.php @@ -8,54 +8,21 @@ namespace Magento\CatalogRuleConfigurable\Plugin\ConfigurableProduct\Model\ResourceModel; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection; -use Magento\CatalogRule\Pricing\Price\CatalogRulePrice; class AddCatalogRulePrice { /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var \Magento\CatalogRule\Model\ResourceModel\Product\CollectionFactory */ - private $storeManager; + private $catalogRuleCollectionFactory; /** - * @var \Magento\Framework\App\ResourceConnection - */ - private $resource; - - /** - * @var \Magento\Customer\Model\Session - */ - private $customerSession; - - /** - * @var \Magento\Framework\Stdlib\DateTime - */ - private $dateTime; - - /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface - */ - private $localeDate; - - /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\App\ResourceConnection $resourceConnection - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Framework\Stdlib\DateTime $dateTime - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param \Magento\CatalogRule\Model\ResourceModel\Product\CollectionFactory $catalogRuleCollectionFactory */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\App\ResourceConnection $resourceConnection, - \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\Stdlib\DateTime $dateTime, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + \Magento\CatalogRule\Model\ResourceModel\Product\CollectionFactory $catalogRuleCollectionFactory ) { - $this->storeManager = $storeManager; - $this->resource = $resourceConnection; - $this->customerSession = $customerSession; - $this->dateTime = $dateTime; - $this->localeDate = $localeDate; + $this->catalogRuleCollectionFactory = $catalogRuleCollectionFactory; } /** @@ -66,28 +33,9 @@ class AddCatalogRulePrice */ public function beforeLoad(Collection $productCollection, $printQuery = false, $logQuery = false) { - if (!$productCollection->hasFlag('catalog_rule_loaded')) { - $connection = $this->resource->getConnection(); - $store = $this->storeManager->getStore(); - $productCollection->getSelect() - ->joinLeft( - ['catalog_rule' => $this->resource->getTableName('catalogrule_product_price')], - implode(' AND ', [ - 'catalog_rule.product_id = e.entity_id', - $connection->quoteInto('catalog_rule.website_id = ?', $store->getWebsiteId()), - $connection->quoteInto( - 'catalog_rule.customer_group_id = ?', - $this->customerSession->getCustomerGroupId() - ), - $connection->quoteInto( - 'catalog_rule.rule_date = ?', - $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false) - ), - ]), - [CatalogRulePrice::PRICE_CODE => 'rule_price'] - ); - $productCollection->setFlag('catalog_rule_loaded', true); - } + $this->catalogRuleCollectionFactory + ->create() + ->addPriceData($productCollection); return [$printQuery, $logQuery]; } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index 3c27a65c4a74385f546c108c077c128ebb4edcfb..78f792fe78fb14b5a8d9d2b710fc14482a94b743 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -290,6 +290,18 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->assertTrue((bool)$this->_model->isSaleable()); $this->assertTrue((bool)$this->_model->isAvailable()); $this->assertTrue($this->_model->isInStock()); + } + + /** + * @covers \Magento\Catalog\Model\Product::isSalable + * @covers \Magento\Catalog\Model\Product::isSaleable + * @covers \Magento\Catalog\Model\Product::isAvailable + * @covers \Magento\Catalog\Model\Product::isInStock + */ + public function testIsNotSalableWhenStatusDisabled() + { + $this->_model = $this->productRepository->get('simple'); + $this->_model->setStatus(0); $this->assertFalse((bool)$this->_model->isSalable()); $this->assertFalse((bool)$this->_model->isSaleable()); diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 947c526c919a677ab6a66ba53e7a01110e5f1a74..34815c996d163967ece79e3381a4f55a0b212017 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -254,12 +254,18 @@ abstract class AbstractExtensibleModel extends AbstractModel implements $data = parent::getData($key, $index); if ($data === null) { /** Try to find necessary data in custom attributes */ - $data = parent::getData(self::CUSTOM_ATTRIBUTES . "/{$key}", $index); + $data = isset($this->_data[self::CUSTOM_ATTRIBUTES][$key]) + ? $this->_data[self::CUSTOM_ATTRIBUTES][$key] + : null; if ($data instanceof \Magento\Framework\Api\AttributeValue) { $data = $data->getValue(); } + if (null !== $index && isset($data[$index])) { + return $data[$index]; + } } } + return $data; } diff --git a/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php b/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php new file mode 100644 index 0000000000000000000000000000000000000000..aa9caf8102e80da530c80bfe0b8ee932598da2e8 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/BundleProductsFixture.php @@ -0,0 +1,427 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Fixtures; + +use Magento\Setup\Model\Complex\Generator; +use Magento\Setup\Model\Complex\Pattern; + +/** + * Class BundleProductsFixture + */ +class BundleProductsFixture extends Fixture +{ + /** + * @var int + */ + protected $priority = 42; + + //@codingStandardsIgnoreStart + /** + * Get CSV template headers + * @SuppressWarnings(PHPMD) + * @return array + */ + protected function getHeaders() + { + return [ + 'sku', + 'store_view_code', + 'attribute_set_code', + 'product_type', + 'categories', + 'product_websites', + 'color', + 'bundle_variation', + 'cost', + 'country_of_manufacture', + 'created_at', + 'custom_design', + 'custom_design_from', + 'custom_design_to', + 'custom_layout_update', + 'description', + 'enable_googlecheckout', + 'gallery', + 'gift_message_available', + 'gift_wrapping_available', + 'gift_wrapping_price', + 'has_options', + 'image', + 'image_label', + 'is_returnable', + 'manufacturer', + 'meta_description', + 'meta_keyword', + 'meta_title', + 'minimal_price', + 'msrp', + 'msrp_display_actual_price_type', + 'name', + 'news_from_date', + 'news_to_date', + 'options_container', + 'page_layout', + 'price', + 'quantity_and_stock_status', + 'related_tgtr_position_behavior', + 'related_tgtr_position_limit', + 'required_options', + 'short_description', + 'small_image', + 'small_image_label', + 'special_from_date', + 'special_price', + 'special_to_date', + 'product_online', + 'tax_class_name', + 'thumbnail', + 'thumbnail_label', + 'updated_at', + 'upsell_tgtr_position_behavior', + 'upsell_tgtr_position_limit', + 'url_key', + 'url_path', + 'variations', + 'visibility', + 'weight', + 'qty', + 'min_qty', + 'use_config_min_qty', + 'is_qty_decimal', + 'backorders', + 'use_config_backorders', + 'min_sale_qty', + 'use_config_min_sale_qty', + 'max_sale_qty', + 'use_config_max_sale_qty', + 'is_in_stock', + 'notify_stock_qty', + 'use_config_notify_stock_qty', + 'manage_stock', + 'use_config_manage_stock', + 'use_config_qty_increments', + 'qty_increments', + 'use_config_enable_qty_inc', + 'enable_qty_increments', + 'is_decimal_divided', + 'bundle_values', + ]; + } + + private function generateBundleProduct($productCategory, $productWebsite, $variation, $suffix) + { + return [ + 'sku' => 'Bundle Product %s' . $suffix, + 'store_view_code' => '', + 'attribute_set_code' => 'Default', + 'product_type' => 'bundle', + 'categories' => $productCategory, + 'product_websites' => $productWebsite, + 'color' => '', + 'bundle_variation' => '', + 'cost' => '', + 'country_of_manufacture' => '', + 'created_at' => '2013-10-25 15:12:39', + 'custom_design' => '', + 'custom_design_from' => '', + 'custom_design_to' => '', + 'custom_layout_update' => '', + 'description' => '<p>Bundle product description %s</p>', + 'enable_googlecheckout' => '1', + 'gallery' => '', + 'gift_message_available' => '', + 'gift_wrapping_available' => '', + 'gift_wrapping_price' => '', + 'has_options' => '1', + 'image' => '', + 'image_label' => '', + 'is_returnable' => 'Use config', + 'manufacturer' => '', + 'meta_description' => 'Bundle Product %s <p>Bundle product description %s</p>', + 'meta_keyword' => 'Bundle Product %s', + 'meta_title' => 'Bundle Product %s', + 'minimal_price' => '', + 'msrp' => '', + 'msrp_display_actual_price_type' => 'Use config', + 'name' => 'Bundle Product %s' . $suffix, + 'news_from_date' => '', + 'news_to_date' => '', + 'options_container' => 'Block after Info Column', + 'page_layout' => '', + 'price' => '10', + 'quantity_and_stock_status' => 'In Stock', + 'related_tgtr_position_behavior' => '', + 'related_tgtr_position_limit' => '', + 'required_options' => '1', + 'short_description' => '', + 'small_image' => '', + 'small_image_label' => '', + 'special_from_date' => '', + 'special_price' => '', + 'special_to_date' => '', + 'product_online' => '1', + 'tax_class_name' => 'Taxable Goods', + 'thumbnail' => '', + 'thumbnail_label' => '', + 'updated_at' => '2013-10-25 15:12:39', + 'upsell_tgtr_position_behavior' => '', + 'upsell_tgtr_position_limit' => '', + 'url_key' => "bundle-product-%s{$suffix}", + 'url_path' => "bundle-product-%s{$suffix}", + 'visibility' => 'Catalog, Search', + 'weight' => '', + 'qty' => 333, + 'min_qty' => '0.0000', + 'use_config_min_qty' => '1', + 'is_qty_decimal' => '0', + 'backorders' => '0', + 'use_config_backorders' => '1', + 'min_sale_qty' => '1.0000', + 'use_config_min_sale_qty' => '1', + 'max_sale_qty' => '0.0000', + 'use_config_max_sale_qty' => '1', + 'is_in_stock' => '1', + 'notify_stock_qty' => '', + 'use_config_notify_stock_qty' => '1', + 'manage_stock' => '1', + 'use_config_manage_stock' => '1', + 'use_config_qty_increments' => '1', + 'qty_increments' => '0.0000', + 'use_config_enable_qty_inc' => '1', + 'enable_qty_increments' => '0', + 'is_decimal_divided' => '0', + 'bundle_price_type' => 'dynamic', + 'bundle_sku_type' => 'dynamic', + 'bundle_price_view' => 'Price range', + 'bundle_weight_type' => 'dynamic', + 'bundle_values' => $variation, + 'bundle_shipment_type' => 'separately', + ]; + } + + /** + * Get CSV template rows + * + * @param Closure|mixed $productCategory + * @param Closure|mixed $productWebsite + * + * @SuppressWarnings(PHPMD) + * + * @return array + */ + protected function getRows($productCategory, $productWebsite, $optionsNumber, $suffix = '') + { + $data = []; + $variation = []; + for ($i = 1; $i <= $optionsNumber; $i++) { + $productData = [ + 'sku' => "Bundle Product %s-option {$i}{$suffix}", + 'store_view_code' => '', + 'attribute_set_code' => 'Default', + 'product_type' => 'simple', + 'categories' => $productCategory, + 'product_websites' => $productWebsite, + 'cost' => '', + 'country_of_manufacture' => '', + 'created_at' => '2013-10-25 15:12:32', + 'custom_design' => '', + 'custom_design_from' => '', + 'custom_design_to' => '', + 'custom_layout_update' => '', + 'description' => '<p>Bundle product option description %s</p>', + 'enable_googlecheckout' => '1', + 'gallery' => '', + 'gift_message_available' => '', + 'gift_wrapping_available' => '', + 'gift_wrapping_price' => '', + 'has_options' => '0', + 'image' => '', + 'image_label' => '', + 'is_returnable' => 'Use config', + 'manufacturer' => '', + 'meta_description' => 'Bundle Product Option %s <p>Bundle product description 1</p>', + 'meta_keyword' => 'Bundle Product 1', + 'meta_title' => 'Bundle Product %s', + 'minimal_price' => '', + 'msrp' => '', + 'msrp_display_actual_price_type' => 'Use config', + 'name' => "Bundle Product {$suffix} - %s-option {$i}", + 'news_from_date' => '', + 'news_to_date' => '', + 'options_container' => 'Block after Info Column', + 'page_layout' => '', + 'price' => function () { return mt_rand(1, 1000) / 10; }, + 'quantity_and_stock_status' => 'In Stock', + 'related_tgtr_position_behavior' => '', + 'related_tgtr_position_limit' => '', + 'required_options' => '0', + 'short_description' => '', + 'small_image' => '', + 'small_image_label' => '', + 'special_from_date' => '', + 'special_price' => '', + 'special_to_date' => '', + 'product_online' => '1', + 'tax_class_name' => 'Taxable Goods', + 'thumbnail' => '', + 'thumbnail_label' => '', + 'updated_at' => '2013-10-25 15:12:32', + 'upsell_tgtr_position_behavior' => '', + 'upsell_tgtr_position_limit' => '', + 'url_key' => "simple-of-bundle-product-{$suffix}-%s-option-{$i}", + 'url_path' => "simple-of-bundle-product-{$suffix}-%s-option-{$i}", + 'visibility' => 'Not Visible Individually', + 'weight' => '1', + 'qty' => '111.0000', + 'min_qty' => '0.0000', + 'use_config_min_qty' => '1', + 'use_config_backorders' => '1', + 'use_config_min_sale_qty' => '1', + 'use_config_max_sale_qty' => '1', + 'is_in_stock' => '1', + 'use_config_notify_stock_qty' => '1', + 'use_config_manage_stock' => '1', + 'use_config_qty_increments' => '1', + 'use_config_enable_qty_inc' => '1', + 'enable_qty_increments' => '0', + 'is_decimal_divided' => '0', + ]; + $variation[] = implode( + ',', + [ + 'name=Bundle Option 1', + 'type=select', + 'required=1', + 'sku=' . $productData['sku'], + 'price=' . mt_rand(1, 1000) / 10, + 'default=0', + 'default_qty=1', + ] + ); + $data[] = $productData; + } + + $data[] = $this->generateBundleProduct($productCategory, $productWebsite, implode('|', $variation), $suffix); + return $data; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD) + */ + public function execute() + { + $bundlesCount = $this->fixtureModel->getValue('bundle_products', 0); + if (!$bundlesCount) { + return; + } + $this->fixtureModel->resetObjectManager(); + + /** @var \Magento\Store\Model\StoreManager $storeManager */ + $storeManager = $this->fixtureModel->getObjectManager()->create('Magento\Store\Model\StoreManager'); + /** @var $category \Magento\Catalog\Model\Category */ + $category = $this->fixtureModel->getObjectManager()->get('Magento\Catalog\Model\Category'); + + $result = []; + //Get all websites + $websites = $storeManager->getWebsites(); + foreach ($websites as $website) { + $websiteCode = $website->getCode(); + //Get all groups + $websiteGroups = $website->getGroups(); + foreach ($websiteGroups as $websiteGroup) { + $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); + $category->load($websiteGroupRootCategory); + $categoryResource = $category->getResource(); + $rootCategoryName = $category->getName(); + //Get all categories + $resultsCategories = $categoryResource->getAllChildren($category); + foreach ($resultsCategories as $resultsCategory) { + $category->load($resultsCategory); + $structure = explode('/', $category->getPath()); + $pathSize = count($structure); + if ($pathSize > 1) { + $path = []; + for ($i = 1; $i < $pathSize; $i++) { + $path[] = $category->load($structure[$i])->getName(); + } + array_shift($path); + $resultsCategoryName = implode('/', $path); + } else { + $resultsCategoryName = $category->getName(); + } + //Deleted root categories + if (trim($resultsCategoryName) != '') { + $result[$resultsCategory] = [$websiteCode, $resultsCategoryName, $rootCategoryName]; + } + } + } + } + $result = array_values($result); + + $productWebsite = function ($index) use ($result) { + return $result[$index % count($result)][0]; + }; + $productCategory = function ($index) use ($result) { + return $result[$index % count($result)][2] . '/' . $result[$index % count($result)][1]; + }; + + /** + * Create bundle products + */ + $pattern = new Pattern(); + $pattern->setHeaders($this->getHeaders()); + $pattern->setRowsSet( + $this->getRows( + $productCategory, + $productWebsite, + $this->fixtureModel->getValue('bundle_products_variation', 5000) + ) + ); + + /** @var \Magento\ImportExport\Model\Import $import */ + $import = $this->fixtureModel->getObjectManager()->create( + 'Magento\ImportExport\Model\Import', + [ + 'data' => [ + 'entity' => 'catalog_product', + 'behavior' => 'append', + 'validation_strategy' => 'validation-stop-on-errors', + ], + ] + ); + + $source = new Generator($pattern, $bundlesCount); + // it is not obvious, but the validateSource() will actually save import queue data to DB + if (!$import->validateSource($source)) { + throw new \Exception($import->getFormatedLogTrace()); + } + // this converts import queue into actual entities + if (!$import->importSource()) { + throw new \Exception($import->getFormatedLogTrace()); + } + } + // @codingStandardsIgnoreEnd + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating bundle products'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'bundle_products' => 'Bundle products', + ]; + } +}