diff --git a/vendor/magento/module-inventory-visual-merchandiser/Plugin/Model/Resolver/QuantityAndStockPlugin.php b/vendor/magento/module-inventory-visual-merchandiser/Plugin/Model/Resolver/QuantityAndStockPlugin.php index 23f33e7e413b..c67afaa05e05 100644 --- a/vendor/magento/module-inventory-visual-merchandiser/Plugin/Model/Resolver/QuantityAndStockPlugin.php +++ b/vendor/magento/module-inventory-visual-merchandiser/Plugin/Model/Resolver/QuantityAndStockPlugin.php @@ -7,19 +7,18 @@ namespace Magento\InventoryVisualMerchandiser\Plugin\Model\Resolver; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\DB\Select; +use Magento\Framework\DB\Ddl\Table; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; -use Magento\Inventory\Model\ResourceModel\SourceItem; use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; use Magento\InventorySalesApi\Api\StockResolverInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\VisualMerchandiser\Model\Resolver\QuantityAndStock; +use Magento\Inventory\Model\ResourceModel\SourceItem; /** * This plugin adds multi-source stock calculation capabilities to the Visual Merchandiser feature. @@ -28,35 +27,41 @@ */ class QuantityAndStockPlugin { + private const PARENT_STOCK_TABLE = 'tmp_parent_stock'; + + private const CHILD_RELATIONS = 'tmp_child_relations'; + + private const CHILD_STOCK = 'tmp_child_stock'; + /** * @var ResourceConnection */ - private $resource; + private ResourceConnection $resource; /** * @var StoreManagerInterface */ - private $storeManager; + private StoreManagerInterface $storeManager; /** * @var StockResolverInterface */ - private $stockResolver; + private StockResolverInterface $stockResolver; /** * @var StockIndexTableNameResolverInterface */ - private $stockIndexTableNameResolver; + private StockIndexTableNameResolverInterface $stockIndexTableNameResolver; /** * @var DefaultStockProviderInterface */ - private $defaultStockProvider; + private DefaultStockProviderInterface $defaultStockProvider; /** * @var MetadataPool */ - private $metadataPool; + private MetadataPool $metadataPool; /** * @param ResourceConnection $resource @@ -67,12 +72,12 @@ class QuantityAndStockPlugin * @param MetadataPool $metadataPool */ public function __construct( - ResourceConnection $resource, - StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver, + ResourceConnection $resource, + StoreManagerInterface $storeManager, + StockResolverInterface $stockResolver, StockIndexTableNameResolverInterface $stockIndexTableNameResolver, - DefaultStockProviderInterface $defaultStockProvider, - MetadataPool $metadataPool + DefaultStockProviderInterface $defaultStockProvider, + MetadataPool $metadataPool ) { $this->resource = $resource; $this->storeManager = $storeManager; @@ -89,7 +94,7 @@ public function __construct( * @param callable $proceed * @param Collection $collection * @return Collection - * @throws LocalizedException|\Zend_Db_Select_Exception + * @throws LocalizedException|\Zend_Db_Select_Exception|\Zend_Db_Exception * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundJoinStock(QuantityAndStock $subject, callable $proceed, Collection $collection): Collection @@ -102,56 +107,26 @@ public function aroundJoinStock(QuantityAndStock $subject, callable $proceed, Co } if ($websiteCode === 'admin') { - $productLinkField = $this->metadataPool->getMetadata(ProductInterface::class) - ->getLinkField(); - $collection->joinField( - 'parent_stock', - $this->resource->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM), - null, - 'sku = sku', - [], - 'left' - ); - $collection->joinField( - 'child_relation', - $this->resource->getTableName('catalog_product_relation'), - null, - 'parent_id = ' . $productLinkField, - null, - 'left' - ); + $parentStockTableName = $this->createParentProductsStockTemporaryTable(); + $childRelationsTableName = $this->createChildRelationsTemporaryTable(); + $childStockTableName = $this->createChildStockTemporaryTable($childRelationsTableName); + $collection->getSelect() ->joinLeft( - ['child_product' => $this->resource->getTableName('catalog_product_entity')], - 'at_child_relation.child_id = child_product.entity_id', - [] + ['ps' => $parentStockTableName], + 'e.sku = ps.sku', + ['parent_stock' => 'COALESCE(ps.parent_qty, 0)'] ) ->joinLeft( - ['child_stock' => $this->resource->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM)], - 'child_stock.sku = child_product.sku', - [] - ); - - $subSelect = clone($collection->getSelect()); - $subSelect->columns( - [ - 'SUM(IFNULL(at_parent_stock.quantity, 0)) as parent_stock', - 'SUM(IFNULL(child_stock.quantity, 0)) as child_stock' - ] - ); - $subSelect->group(['e.entity_id', 'at_parent_stock.source_code']); - - $collection->getSelect()->reset(); - $collection->getSelect()->from(['e' => $subSelect]); - $collection->getSelect()->columns( - new \Zend_Db_Expr( - 'IF( - SUM(IFNULL(parent_stock, 0)) = 0, - SUM(IFNULL(child_stock, 0)), - SUM(IFNULL(parent_stock, 0)) - ) AS stock' + ['cs' => $childStockTableName], + 'e.row_id = cs.parent_id', + ['child_stock' => 'COALESCE(cs.child_qty, 0)'] ) - ); + ->columns([ + 'stock' => new \Zend_Db_Expr( + 'IF(COALESCE(ps.parent_qty, 0) = 0, COALESCE(cs.child_qty, 0), COALESCE(ps.parent_qty, 0))' + ) + ]); } else { $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode); $stockId = (int)$stock->getStockId(); @@ -164,4 +139,102 @@ public function aroundJoinStock(QuantityAndStock $subject, callable $proceed, Co return $collection; } + + /** + * Create temporary table for parent product stock + * + * @return string + * @throws \Zend_Db_Exception + */ + private function createParentProductsStockTemporaryTable(): string + { + $connection = $this->resource->getConnection(); + $parentStockTableName = $this->resource->getTableName( + str_replace('.', '_', uniqid(self::PARENT_STOCK_TABLE, true)) + ); + $parentStockTable = $connection->newTable($parentStockTableName) + ->addColumn('sku', Table::TYPE_TEXT, 64, ['nullable' => false]) + ->addColumn('parent_qty', Table::TYPE_DECIMAL, '12,4', ['nullable' => false]) + ->addIndex('IDX_TMP_PARENT_STOCK_SKU', ['sku']) + ->setOption('temporary', true); + $connection->createTemporaryTable($parentStockTable); + + $select = $connection->select() + ->from( + $this->resource->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM), + ['sku', 'parent_qty' => new \Zend_Db_Expr('SUM(quantity)')] + ) + ->group('sku'); + $connection->query($connection->insertFromSelect($select, $parentStockTableName, ['sku', 'parent_qty'])); + + return $parentStockTableName; + } + + /** + * Create temporary table for determining child products + * + * @return string + * @throws \Zend_Db_Exception + */ + private function createChildRelationsTemporaryTable(): string + { + $connection = $this->resource->getConnection(); + $childRelationsTableName = $this->resource->getTableName( + str_replace('.', '_', uniqid(self::CHILD_RELATIONS, true)) + ); + + $childRelationsTable = $connection->newTable($childRelationsTableName) + ->addColumn('parent_id', Table::TYPE_INTEGER, null, ['nullable' => false]) + ->addColumn('child_sku', Table::TYPE_TEXT, 64, ['nullable' => false]) + ->addIndex('IDX_TMP_CHILD_REL_PARENT_ID', ['parent_id']) + ->addIndex('IDX_TMP_CHILD_REL_CHILD_SKU', ['child_sku']) + ->setOption('temporary', true); + $connection->createTemporaryTable($childRelationsTable); + + $select = $connection->select() + ->from(['r' => $this->resource->getTableName('catalog_product_relation')], ['parent_id']) + ->join( + ['c' => $this->resource->getTableName('catalog_product_entity')], + 'r.child_id = c.entity_id', + ['child_sku' => 'c.sku'] + ); + $connection->query( + $connection->insertFromSelect($select, $childRelationsTableName, ['parent_id', 'child_sku']) + ); + + return $childRelationsTableName; + } + + /** + * Create temporary table for child product stock + * + * @param string $childRelationsTableName + * @return string + * @throws \Zend_Db_Exception + */ + private function createChildStockTemporaryTable(string $childRelationsTableName): string + { + $connection = $this->resource->getConnection(); + $childStockTableName = $this->resource->getTableName( + str_replace('.', '_', uniqid(self::CHILD_STOCK, true)) + ); + $childStockTable = $connection->newTable($childStockTableName) + ->addColumn('parent_id', Table::TYPE_INTEGER, null, ['nullable' => false]) + ->addColumn('child_qty', Table::TYPE_DECIMAL, '12,4', ['nullable' => false]) + ->addIndex('IDX_TMP_CHILD_STOCK_PARENT_ID', ['parent_id']) + ->setOption('temporary', true); + $connection->createTemporaryTable($childStockTable); + + $select = $connection->select() + ->from(['cr' => $childRelationsTableName], ['parent_id']) + ->join( + ['isi' => $this->resource->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM)], + 'cr.child_sku = isi.sku', + ['child_qty' => new \Zend_Db_Expr('SUM(isi.quantity)')] + ) + ->group('cr.parent_id'); + $connection->query($connection->insertFromSelect($select, $childStockTableName, ['parent_id', 'child_qty'])); + + return $childStockTableName; + } }