Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions InventoryBundleProductIndexer/Indexer/SelectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

use Exception;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Eav\Model\Config;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
Expand All @@ -20,6 +24,8 @@

/**
* Get bundle product for given stock select builder.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SelectBuilder
{
Expand All @@ -43,22 +49,30 @@ class SelectBuilder
*/
private $metadataPool;

/**
* @var Config
*/
private $eavConfig;

/**
* @param ResourceConnection $resourceConnection
* @param IndexNameBuilder $indexNameBuilder
* @param IndexNameResolverInterface $indexNameResolver
* @param MetadataPool $metadataPool
* @param Config $eavConfig
*/
public function __construct(
ResourceConnection $resourceConnection,
IndexNameBuilder $indexNameBuilder,
IndexNameResolverInterface $indexNameResolver,
MetadataPool $metadataPool
MetadataPool $metadataPool,
Config $eavConfig
) {
$this->resourceConnection = $resourceConnection;
$this->indexNameBuilder = $indexNameBuilder;
$this->indexNameResolver = $indexNameResolver;
$this->metadataPool = $metadataPool;
$this->eavConfig = $eavConfig;
}

/**
Expand All @@ -82,6 +96,7 @@ public function execute(int $stockId): Select

$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$statusAttributeId = $this->getAttribute(ProductInterface::STATUS)->getId();

$select = $connection->select()
->from(
Expand All @@ -103,9 +118,25 @@ public function execute(int $stockId): Select
['parent_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')],
'parent_product_entity.' . $linkField . ' = parent_link.parent_product_id',
[]
)
->group(['parent_product_entity.sku']);
)->joinInner(
['product_status' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
"product_entity.$linkField = product_status.$linkField"
. " AND product_status.attribute_id = $statusAttributeId"
. ' AND product_status.value = ' . ProductStatus::STATUS_ENABLED,
[]
)->group(['parent_product_entity.sku']);

return $select;
}

/**
* Retrieve catalog_product attribute instance by attribute code
*
* @param string $attributeCode
* @return Attribute
*/
private function getAttribute($attributeCode): Attribute
{
return $this->eavConfig->getAttribute(Product::ENTITY, $attributeCode);
}
}
1 change: 1 addition & 0 deletions InventoryBundleProductIndexer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"magento/framework": "*",
"magento/module-bundle": "*",
"magento/module-catalog": "*",
"magento/module-eav": "*",
"magento/module-inventory-api": "*",
"magento/module-inventory-catalog-api": "*",
"magento/module-inventory-indexer": "*",
Expand Down
40 changes: 37 additions & 3 deletions InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

use Exception;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Eav\Model\Config;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
Expand All @@ -20,6 +24,11 @@
use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameResolverInterface;
use Magento\InventoryIndexer\Indexer\SelectBuilderInterface;

/**
* Get configurable product for given stock select builder
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SelectBuilder implements SelectBuilderInterface
{
/**
Expand All @@ -46,25 +55,33 @@ class SelectBuilder implements SelectBuilderInterface
*/
private $defaultStockProvider;

/**
* @var Config
*/
private $eavConfig;

/**
* @param ResourceConnection $resourceConnection
* @param IndexNameBuilder $indexNameBuilder
* @param IndexNameResolverInterface $indexNameResolver
* @param MetadataPool $metadataPool
* @param DefaultStockProviderInterface $defaultStockProvider
* @param Config $eavConfig
*/
public function __construct(
ResourceConnection $resourceConnection,
IndexNameBuilder $indexNameBuilder,
IndexNameResolverInterface $indexNameResolver,
MetadataPool $metadataPool,
DefaultStockProviderInterface $defaultStockProvider
DefaultStockProviderInterface $defaultStockProvider,
Config $eavConfig
) {
$this->resourceConnection = $resourceConnection;
$this->indexNameBuilder = $indexNameBuilder;
$this->indexNameResolver = $indexNameResolver;
$this->metadataPool = $metadataPool;
$this->defaultStockProvider = $defaultStockProvider;
$this->eavConfig = $eavConfig;
}

/**
Expand All @@ -88,6 +105,7 @@ public function execute(int $stockId): Select

$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$statusAttributeId = $this->getAttribute(ProductInterface::STATUS)->getId();

$select = $connection->select()
->from(
Expand All @@ -114,9 +132,25 @@ public function execute(int $stockId): Select
'inventory_stock_item.product_id = parent_product_entity.entity_id'
. ' AND inventory_stock_item.stock_id = ' . $this->defaultStockProvider->getId(),
[]
)
->group(['parent_product_entity.sku']);
)->joinInner(
['product_status' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
"product_entity.$linkField = product_status.$linkField"
. " AND product_status.attribute_id = $statusAttributeId"
. ' AND product_status.value = ' . ProductStatus::STATUS_ENABLED,
[]
)->group(['parent_product_entity.sku']);

return $select;
}

/**
* Retrieve catalog_product attribute instance by attribute code
*
* @param string $attributeCode
* @return Attribute
*/
private function getAttribute($attributeCode): Attribute
{
return $this->eavConfig->getAttribute(Product::ENTITY, $attributeCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="ConfigurableProductNotVisibleAfterDisablingChildProductsTest">
<annotations>
<stories value="Configurable Product Custom Stock."/>
<title value="Configurable product should be not visible after disabling child products"/>
<description value="Verify, configurable product is not displayed on category page after disabling first child product and set out of stock to second"/>
<testCaseId value="MC-38896"/>
<useCaseId value="MC-38590"/>
<severity value="AVERAGE"/>
<group value="msi"/>
</annotations>
<before>
<!--Create test data.-->
<!-- Create the category to put the product in -->
<createData entity="ApiCategory" stepKey="createCategory"/>
<!-- Create the configurable product based on the data in the /data folder -->
<createData entity="ApiConfigurableProduct" stepKey="createConfigProduct">
<requiredEntity createDataKey="createCategory"/>
</createData>
<!-- Make the configurable product have two options, that are children of the default attribute set -->
<createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/>
<createData entity="productAttributeOption1" stepKey="createFirstConfigProductAttributeOption">
<requiredEntity createDataKey="createConfigProductAttribute"/>
</createData>
<createData entity="productAttributeOption2" stepKey="createSecondConfigProductAttributeOption">
<requiredEntity createDataKey="createConfigProductAttribute"/>
</createData>
<createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet">
<requiredEntity createDataKey="createConfigProductAttribute"/>
</createData>
<getData entity="ProductAttributeOptionGetter" index="1" stepKey="getFirstConfigAttributeOption">
<requiredEntity createDataKey="createConfigProductAttribute"/>
</getData>
<getData entity="ProductAttributeOptionGetter" index="2" stepKey="getSecondConfigAttributeOption">
<requiredEntity createDataKey="createConfigProductAttribute"/>
</getData>
<!-- Create the 2 children that will be a part of the configurable product -->
<createData entity="ApiSimpleOne" stepKey="createFirstConfigChildProduct">
<requiredEntity createDataKey="createConfigProductAttribute"/>
<requiredEntity createDataKey="getFirstConfigAttributeOption"/>
</createData>
<createData entity="ApiSimpleTwo" stepKey="createSecondConfigChildProduct">
<requiredEntity createDataKey="createConfigProductAttribute"/>
<requiredEntity createDataKey="getSecondConfigAttributeOption"/>
</createData>
<!-- Assign the two products to the configurable product -->
<createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption">
<requiredEntity createDataKey="createConfigProduct"/>
<requiredEntity createDataKey="createConfigProductAttribute"/>
<requiredEntity createDataKey="getFirstConfigAttributeOption"/>
<requiredEntity createDataKey="getSecondConfigAttributeOption"/>
</createData>
<createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddChild">
<requiredEntity createDataKey="createConfigProduct"/>
<requiredEntity createDataKey="createFirstConfigChildProduct"/>
</createData>
<createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddChild">
<requiredEntity createDataKey="createConfigProduct"/>
<requiredEntity createDataKey="createSecondConfigChildProduct"/>
</createData>
<createData entity="Simple_US_Customer" stepKey="customer"/>
<createData entity="_minimalSource" stepKey="createSource"/>
<createData entity="BasicMsiStockWithMainWebsite1" stepKey="stock"/>
<createData entity="SourceStockLinked1" stepKey="linkStockAndSource">
<requiredEntity createDataKey="stock"/>
<requiredEntity createDataKey="createSource"/>
</createData>
<actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminArea"/>
<!--Assign additional source to configurable product.-->
<amOnPage url="{{AdminProductEditPage.url($createFirstConfigChildProduct.id$)}}" stepKey="openProductEditPage"/>
<actionGroup ref="UnassignSourceFromProductActionGroup" stepKey="unassignDefaultSourceFromProduct">
<argument name="sourceCode" value="{{_defaultSource.name}}"/>
</actionGroup>
<actionGroup ref="AdminAssignSourceToProductAndSetSourceQuantityActionGroup" stepKey="assignCreatedSourceToFirstChildProduct">
<argument name="sourceCode" value="$createSource.source[source_code]$"/>
</actionGroup>
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveFirstChildProduct"/>
<!--Assign additional source to configurable product second.-->
<amOnPage url="{{AdminProductEditPage.url($createSecondConfigChildProduct.id$)}}" stepKey="openSecondProductEditPage"/>
<actionGroup ref="UnassignSourceFromProductActionGroup" stepKey="unassignDefaultSourceFromSecondProduct">
<argument name="sourceCode" value="{{_defaultSource.name}}"/>
</actionGroup>
<actionGroup ref="AdminAssignSourceToProductAndSetSourceQuantityActionGroup" stepKey="assignCreatedSourceToSecondChildProduct">
<argument name="sourceCode" value="$createSource.source[source_code]$"/>
</actionGroup>
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveSecondChildProduct"/>
<actionGroup ref="AdminReindexAndFlushCache" stepKey="reindexAndFlushCache"/>
</before>
<after>
<deleteData createDataKey="createCategory" stepKey="deleteCategory"/>
<deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/>
<deleteData createDataKey="createFirstConfigChildProduct" stepKey="deleteFirstConfigChildProduct"/>
<deleteData createDataKey="createSecondConfigChildProduct" stepKey="deleteSecondConfigChildProduct"/>
<deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/>
<!--Assign Default Stock to Main Website.-->
<actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignMainWebsiteToDefaultStock">
<argument name="stockName" value="{{_defaultStock.name}}"/>
<argument name="websiteName" value="{{_defaultWebsite.name}}"/>
</actionGroup>
<deleteData createDataKey="stock" stepKey="deleteStock"/>
<!--Disable source.-->
<actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableSources"/>
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/>
<!-- Reindex invalidated indices after product attribute has been created/deleted -->
<magentoCron groups="index" stepKey="reindexInvalidatedIndices"/>
</after>
<!--Verify product is visible on storefront.-->
<actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="openCategoryPageOnFrontend">
<argument name="category" value="$createCategory$"/>
</actionGroup>
<actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" stepKey="checkProductOnCategoryPage">
<argument name="productName" value="$$createConfigProduct.name$$"/>
</actionGroup>
<!--Open first child product in Admin. Make it disabled (Enable Product = No)-->
<amOnPage url="{{AdminProductEditPage.url($$createFirstConfigChildProduct.id$$)}}" stepKey="openProductEditPageForDisablingProduct"/>
<actionGroup ref="AdminSetProductDisabledActionGroup" stepKey="disableProduct"/>
<actionGroup ref="SaveProductFormActionGroup" stepKey="clickSaveProduct"/>
<!--Open second child product and set product stock status to out of stock-->
<amOnPage url="{{AdminProductEditPage.url($$createSecondConfigChildProduct.id$$)}}" stepKey="openProductEditPageForDisablingSource"/>
<actionGroup ref="AdminChangeSourceStockStatusActionGroup" stepKey="setProductStatusToOutOfStock">
<argument name="sourceCode" value="$createSource.source[source_code]$"/>
<argument name="sourceStatus" value="{{SourceStatusOutOfStock.value}}"/>
</actionGroup>
<actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveProduct"/>
<!--Verify product is not visible on storefront.-->
<actionGroup ref="AssertStorefrontProductAbsentOnCategoryPageActionGroup" stepKey="doNotSeeProductOnCategoryPage">
<argument name="categoryUrlKey" value="$$createCategory.name$$"/>
<argument name="productName" value="$$createConfigProduct.name$$"/>
</actionGroup>
</test>
</tests>
1 change: 1 addition & 0 deletions InventoryConfigurableProductIndexer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"php": "~7.3.0||~7.4.0",
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-eav": "*",
"magento/module-inventory-api": "*",
"magento/module-inventory-catalog-api": "*",
"magento/module-inventory-indexer": "*",
Expand Down