Skip to content

Commit

Permalink
Merge pull request #7313 from magento-l3/L3_PR_21-12-10
Browse files Browse the repository at this point in the history
L3_PR_21-12-10
  • Loading branch information
fascinosum authored Dec 19, 2021
2 parents 82b7dba + 0f3d7e4 commit e8c31f5
Show file tree
Hide file tree
Showing 80 changed files with 3,235 additions and 524 deletions.
160 changes: 160 additions & 0 deletions app/code/Magento/Bundle/Model/Inventory/ChangeParentStockStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Bundle\Model\Inventory;

use Magento\Bundle\Model\Product\Type;
use Magento\CatalogInventory\Api\Data\StockItemInterface;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;

/***
* Update stock status of bundle products based on children products stock status
*/
class ChangeParentStockStatus
{
/**
* @var Type
*/
private $bundleType;

/**
* @var StockItemCriteriaInterfaceFactory
*/
private $criteriaInterfaceFactory;

/**
* @var StockItemRepositoryInterface
*/
private $stockItemRepository;

/**
* @var StockConfigurationInterface
*/
private $stockConfiguration;

/**
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
* @param StockItemRepositoryInterface $stockItemRepository
* @param StockConfigurationInterface $stockConfiguration
* @param Type $bundleType
*/
public function __construct(
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
StockItemRepositoryInterface $stockItemRepository,
StockConfigurationInterface $stockConfiguration,
Type $bundleType
) {
$this->bundleType = $bundleType;
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
$this->stockItemRepository = $stockItemRepository;
$this->stockConfiguration = $stockConfiguration;
}

/**
* Update stock status of bundle products based on children products stock status
*
* @param array $childrenIds
* @return void
*/
public function execute(array $childrenIds): void
{
$parentIds = $this->bundleType->getParentIdsByChild($childrenIds);
foreach (array_unique($parentIds) as $productId) {
$this->processStockForParent((int)$productId);
}
}

/**
* Update stock status of bundle product based on children products stock status
*
* @param int $productId
* @return void
*/
private function processStockForParent(int $productId): void
{
$stockItems = $this->getStockItems([$productId]);
$parentStockItem = $stockItems[$productId] ?? null;
if ($parentStockItem) {
$childrenIsInStock = $this->isChildrenInStock($productId);
if ($this->isNeedToUpdateParent($parentStockItem, $childrenIsInStock)) {
$parentStockItem->setIsInStock($childrenIsInStock);
$parentStockItem->setStockStatusChangedAuto(1);
$this->stockItemRepository->save($parentStockItem);
}
}
}

/**
* Returns stock status of bundle product based on children stock status
*
* Returns TRUE if any of the following conditions is true:
* - At least one product is in-stock in each required option
* - Any product is in-stock (if all options are optional)
*
* @param int $productId
* @return bool
*/
private function isChildrenInStock(int $productId) : bool
{
$childrenIsInStock = false;
$childrenIds = $this->bundleType->getChildrenIds($productId, true);
$stockItems = $this->getStockItems(array_merge(...array_values($childrenIds)));
foreach ($childrenIds as $childrenIdsPerOption) {
$childrenIsInStock = false;
foreach ($childrenIdsPerOption as $id) {
$stockItem = $stockItems[$id] ?? null;
if ($stockItem && $stockItem->getIsInStock()) {
$childrenIsInStock = true;
break;
}
}
if (!$childrenIsInStock) {
break;
}
}

return $childrenIsInStock;
}

/**
* Check if parent item should be updated
*
* @param StockItemInterface $parentStockItem
* @param bool $childrenIsInStock
* @return bool
*/
private function isNeedToUpdateParent(
StockItemInterface $parentStockItem,
bool $childrenIsInStock
): bool {
return $parentStockItem->getIsInStock() !== $childrenIsInStock &&
($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto());
}

/**
* Get stock items for provided product IDs
*
* @param array $productIds
* @return StockItemInterface[]
*/
private function getStockItems(array $productIds): array
{
$criteria = $this->criteriaInterfaceFactory->create();
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
$criteria->setProductsFilter(array_unique($productIds));
$stockItemCollection = $this->stockItemRepository->getList($criteria);

$stockItems = [];
foreach ($stockItemCollection->getItems() as $stockItem) {
$stockItems[$stockItem->getProductId()] = $stockItem;
}

return $stockItems;
}
}
39 changes: 39 additions & 0 deletions app/code/Magento/Bundle/Model/Inventory/ParentItemProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Bundle\Model\Inventory;

use Magento\Catalog\Api\Data\ProductInterface as Product;
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;

/**
* Bundle product stock item processor
*/
class ParentItemProcessor implements ParentItemProcessorInterface
{
/**
* @var ChangeParentStockStatus
*/
private $changeParentStockStatus;

/**
* @param ChangeParentStockStatus $changeParentStockStatus
*/
public function __construct(
ChangeParentStockStatus $changeParentStockStatus
) {
$this->changeParentStockStatus = $changeParentStockStatus;
}

/**
* @inheritdoc
*/
public function process(Product $product)
{
$this->changeParentStockStatus->execute([$product->getId()]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
<actionGroup name="AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup">
<annotations>
<description>Assert special price attribute value from the catalog product grid page</description>
</annotations>
<arguments>
<argument name="specialPriceColumn" type="string" defaultValue="Special Price"/>
<argument name="expectedValue" type="string" defaultValue=""/>
</arguments>
<!-- Check the special price column are present in catalog product grid -->
<seeElement selector="{{AdminProductGridSection.columnHeader(specialPriceColumn)}}" stepKey="seeSpecialPriceColumn"/>
<!-- Grab the special price value from the catalog product grid -->
<grabTextFrom selector="{{AdminProductGridSection.productGridCell('1', specialPriceColumn)}}" stepKey="getSpecialPrice"/>
<assertStringContainsString stepKey="assertSpecialPricePercentageSymbol">
<expectedResult type="string">{{expectedValue}}</expectedResult>
<actualResult type="variable">$getSpecialPrice</actualResult>
</assertStringContainsString>
</actionGroup>
</actionGroups>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?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="AdminBundleProductPriceSymbolValidationInGridTest">
<annotations>
<features value="Bundle"/>
<stories value="Bundle Products Special Price Column in admin Grid should have % sign not currency sign"/>
<title value="Admin to validate the bundle products special price column in grid should display percentage symbol instead of currency sign"/>
<description value="Admin to validate the bundle products special price column in grid should display percentage symbol instead of currency sign"/>
<severity value="AVERAGE"/>
<testCaseId value="AC-1378"/>
<useCaseId value="ACP2E-64"/>
<group value="Bundle"/>
</annotations>
<before>
<!-- Create a simple product -->
<createData entity="SimpleProduct2" stepKey="simpleProduct1"/>
<!-- Admin login -->
<actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/>
<!-- Navigate to catalog product grid page -->
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/>
<!-- Open the column dropdown to add the special price from the catalog product grid -->
<actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdownSpecialPrice"/>
<actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkSpecialPriceOption">
<argument name="optionName" value="Special Price"/>
</actionGroup>
<actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdownSpecialPrice"/>
<!-- It takes a few seconds for column update to be saved -->
<!-- waitForPageLoad won't work here since saving is happening with a short delay -->
<wait time="5" stepKey="waitForColumnUpdateToSave"/>
</before>
<after>
<!-- Navigate to catalog product grid page -->
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPage"/>
<!-- Clean applied product filters before delete -->
<actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="clearAppliedFilters"/>
<!-- Delete all the products from the catalog product grid -->
<actionGroup ref="DeleteProductsIfTheyExistActionGroup" stepKey="deleteAllProducts"/>
<!-- Set product grid to default columns -->
<actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="setProductGridToDefaultColumns"/>
<!-- Logging out -->
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
</after>
<!-- Go to bundle product creation page -->
<actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewBundleProductPage">
<argument name="productType" value="{{BundleProduct.type}}"/>
<argument name="attributeSetId" value="{{BundleProduct.set}}"/>
</actionGroup>
<!-- Sets the provided Special Price on the Admin Product creation/edit page. -->
<actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice">
<argument name="price" value="{{SimpleProductWithSpecialPrice.special_price}}"/>
</actionGroup>
<!-- Fill up the new product form with data -->
<actionGroup ref="CreateBasicBundleProductActionGroup" stepKey="createBundledProduct">
<argument name="bundleProduct" value="BundleProduct"/>
</actionGroup>
<!-- Add the bundle option to the product -->
<actionGroup ref="AddBundleOptionWithOneProductActionGroup" stepKey="addBundleOption">
<argument name="x" value="0"/>
<argument name="n" value="1"/>
<argument name="prodOneSku" value="$$simpleProduct1.sku$$"/>
<argument name="prodTwoSku" value=""/>
<argument name="optionTitle" value="{{BundleProduct.optionTitle1}}"/>
<argument name="inputType" value="{{BundleProduct.optionInputType1}}"/>
</actionGroup>
<!-- Save the bundle product -->
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/>
<!-- Navigate to catalog product grid page -->
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndexPageAfterProdSave"/>
<!-- Search the created bundle product with sku -->
<actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterBundleProductGridBySku">
<argument name="sku" value="{{BundleProduct.sku}}"/>
</actionGroup>
<!-- Asserting with the special price value contains the percentage value -->
<actionGroup ref="AdminAssertSpecialPriceAttributeValueOnProductGridPageActionGroup" stepKey="assertSpecialPricePercentageSymbol">
<argument name="expectedValue" value="90.00%"/>
</actionGroup>
</test>
</tests>
Loading

0 comments on commit e8c31f5

Please sign in to comment.