Skip to content

Commit 0a55921

Browse files
authored
Merge pull request #6340 from magento-tsg-csl3/2.4-develop-pr45
[TSG-CSL3] For 2.4 (pr45)
2 parents c644b3d + 6fa94aa commit 0a55921

File tree

17 files changed

+813
-146
lines changed

17 files changed

+813
-146
lines changed

app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
1818
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
1919
use Magento\Catalog\Model\Product\Attribute\Source\Status;
20+
use Magento\CatalogInventory\Model\Stock;
2021

2122
/**
2223
* Bundle products Price indexer resource model
@@ -624,6 +625,13 @@ private function calculateDynamicBundleSelectionPrice($dimensions)
624625
'tier_price' => $tierExpr,
625626
]
626627
);
628+
$select->join(
629+
['si' => $this->getTable('cataloginventory_stock_status')],
630+
'si.product_id = bs.product_id',
631+
[]
632+
);
633+
$select->where('si.stock_status = ?', Stock::STOCK_IN_STOCK);
634+
627635
$this->tableMaintainer->insertFromSelect($select, $this->getBundleSelectionTable(), []);
628636
}
629637

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
9+
<test name="StorefrontBundlePlaceOrderWithVirtualAndSimpleChildrenTest">
10+
<annotations>
11+
<features value="Bundle"/>
12+
<stories value="Bundle product placing order"/>
13+
<title value="Admin should be able to invoice order for the bundle product with virtual and simple products in options"/>
14+
<description value="Place order for bundle product and create invoice"/>
15+
<severity value="MAJOR"/>
16+
<testCaseId value="MC-38683"/>
17+
<useCaseId value="MC-37663"/>
18+
<group value="Bundle"/>
19+
</annotations>
20+
<before>
21+
<createData entity="CustomerEntityOne" stepKey="createCustomer"/>
22+
<!--Create bundle product with fixed price with simple and virtual products in options-->
23+
<createData entity="SimpleProduct2" stepKey="createSimpleProduct">
24+
<field key="price">100.00</field>
25+
</createData>
26+
<createData entity="VirtualProduct" stepKey="createVirtualProduct">
27+
<field key="price">50.00</field>
28+
</createData>
29+
<createData entity="ApiFixedBundleProduct" stepKey="createFixedBundleProduct"/>
30+
<createData entity="DropDownBundleOption" stepKey="createFirstBundleOption">
31+
<requiredEntity createDataKey="createFixedBundleProduct"/>
32+
</createData>
33+
<createData entity="DropDownBundleOption" stepKey="createSecondBundleOption">
34+
<requiredEntity createDataKey="createFixedBundleProduct"/>
35+
</createData>
36+
<createData entity="ApiBundleLink" stepKey="firstLinkOptionToFixedProduct">
37+
<requiredEntity createDataKey="createFixedBundleProduct"/>
38+
<requiredEntity createDataKey="createFirstBundleOption"/>
39+
<requiredEntity createDataKey="createSimpleProduct"/>
40+
</createData>
41+
<createData entity="ApiBundleLink" stepKey="secondLinkOptionToFixedProduct">
42+
<requiredEntity createDataKey="createFixedBundleProduct"/>
43+
<requiredEntity createDataKey="createSecondBundleOption"/>
44+
<requiredEntity createDataKey="createVirtualProduct"/>
45+
</createData>
46+
<actionGroup stepKey="loginToAdminPanel" ref="AdminLoginActionGroup"/>
47+
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToProductEditPage">
48+
<argument name="productId" value="$createFixedBundleProduct.id$"/>
49+
</actionGroup>
50+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/>
51+
<!--Perform reindex and flush cache-->
52+
<actionGroup ref="AdminReindexAndFlushCache" stepKey="reindexAndFlushCache"/>
53+
</before>
54+
<after>
55+
<deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProductForBundleItem"/>
56+
<deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProductForBundleItem"/>
57+
<deleteData createDataKey="createFixedBundleProduct" stepKey="deleteBundleProduct"/>
58+
<deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/>
59+
<actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductsGridFilters"/>
60+
<waitForPageLoad stepKey="waitForClearProductsGridFilters"/>
61+
</after>
62+
<!--Login customer on storefront-->
63+
<actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer">
64+
<argument name="Customer" value="$$createCustomer$$" />
65+
</actionGroup>
66+
<!--Open Product Page-->
67+
<actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openBundleProductPage">
68+
<argument name="product" value="$createFixedBundleProduct$"/>
69+
</actionGroup>
70+
<!--Add bundle to cart-->
71+
<actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickAddToCart">
72+
<argument name="productUrl" value="$createFixedBundleProduct.name$"/>
73+
</actionGroup>
74+
<actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart">
75+
<argument name="quantity" value="1"/>
76+
</actionGroup>
77+
<!--Navigate to checkout-->
78+
<actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/>
79+
<!--Click next button to open payment section-->
80+
<actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/>
81+
<!--Click place order-->
82+
<actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/>
83+
<grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/>
84+
<!--Order review page has address that was created during checkout-->
85+
<actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById">
86+
<argument name="orderId" value="{$grabOrderNumber}"/>
87+
</actionGroup>
88+
<!--Create Invoice for this Order-->
89+
<actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="createInvoice"/>
90+
<actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/>
91+
</test>
92+
</tests>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<!--Update Product Name and Description attribute-->
12+
<actionGroup name="AdminMassUpdateProductQtyAndStockStatusActionGroup">
13+
<arguments>
14+
<argument name="attributes"/>
15+
<argument name="product"/>
16+
</arguments>
17+
<!--Filter product in product grid-->
18+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageFirstTime"/>
19+
<waitForPageLoad stepKey="waitForProductGridPageLoad"/>
20+
<conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/>
21+
<click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/>
22+
<fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{product.name}}" stepKey="fillProductNameFilter"/>
23+
<fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/>
24+
<selectOption selector="{{AdminProductGridFilterSection.typeFilter}}" userInput="{{product.type_id}}" stepKey="selectionProductType"/>
25+
<click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/>
26+
<!--Select first product from grid and open mass action-->
27+
<click selector="{{AdminProductGridSection.productGridCheckboxOnRow('1')}}" stepKey="clickCheckbox"/>
28+
<click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickDropdown"/>
29+
<click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickOption"/>
30+
<waitForPageLoad stepKey="waitForUploadPage"/>
31+
<seeInCurrentUrl url="{{ProductAttributesEditPage.url}}" stepKey="seeAttributePageEditUrl"/>
32+
<!--Update inventory attributes and save-->
33+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.inventory}}" stepKey="openInvetoryTab"/>
34+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeQty}}" stepKey="uncheckChangeQty"/>
35+
<fillField selector="{{AdminUpdateAttributesAdvancedInventorySection.qty}}" userInput="{{attributes.qty}}" stepKey="fillFieldName"/>
36+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeStockAvailability}}" stepKey="uncheckChangeStockAvailability"/>
37+
<selectOption selector="{{AdminUpdateAttributesAdvancedInventorySection.stockAvailability}}" userInput="{{attributes.stockAvailability}}" stepKey="selectStatus"/>
38+
<click selector="{{AdminUpdateAttributesSection.saveButton}}" stepKey="save"/>
39+
<waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitVisibleSuccessMessage"/>
40+
<see selector="{{AdminMessagesSection.success}}" userInput="Message is added to queue" stepKey="seeSuccessMessage"/>
41+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageSecondTime"/>
42+
<waitForPageLoad stepKey="waitForProductGridPage"/>
43+
<conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersAfterMassAction"/>
44+
</actionGroup>
45+
</actionGroups>

app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@
1212
<data key="name" unique="suffix">New Bundle Product Name</data>
1313
<data key="description" unique="suffix">This is the description</data>
1414
</entity>
15+
<entity name="UpdateAttributeQtyAndStockToInStock" type="productAttributeMassUpdate">
16+
<data key="qty">10</data>
17+
<data key="stockAvailability">In Stock</data>
18+
</entity>
19+
<entity name="UpdateAttributeQtyAndStockToOutOfStock" type="productAttributeMassUpdate">
20+
<data key="qty">0</data>
21+
<data key="stockAvailability">Out of Stock</data>
22+
</entity>
1523
</entities>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
10+
<section name="AdminUpdateAttributesAdvancedInventorySection">
11+
<element name="inventory" type="button" selector="#attributes_update_tabs_inventory"/>
12+
<element name="changeQty" type="checkbox" selector="#inventory_qty_checkbox"/>
13+
<element name="qty" type="input" selector="#inventory_qty"/>
14+
<element name="changeStockAvailability" type="checkbox" selector="#inventory_stock_availability_checkbox"/>
15+
<element name="stockAvailability" type="select" selector="//select[@name='inventory[is_in_stock]']"/>
16+
</section>
17+
</sections>

app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\CatalogInventory\Plugin;
79

10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
812
use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save;
913
use Magento\CatalogInventory\Api\Data\StockItemInterface;
14+
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
1015

1116
/**
12-
* MassUpdate product attribute.
17+
* Around plugin for MassUpdate product attribute via product grid.
18+
*
1319
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1420
*/
1521
class MassUpdateProductAttribute
@@ -49,6 +55,15 @@ class MassUpdateProductAttribute
4955
*/
5056
private $messageManager;
5157

58+
/**
59+
* @var ParentItemProcessorInterface[]
60+
*/
61+
private $parentItemProcessorPool;
62+
63+
/**
64+
* @var ProductRepositoryInterface
65+
*/
66+
private $productRepository;
5267
/**
5368
* @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
5469
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
@@ -57,6 +72,8 @@ class MassUpdateProductAttribute
5772
* @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
5873
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
5974
* @param \Magento\Framework\Message\ManagerInterface $messageManager
75+
* @param ProductRepositoryInterface $productRepository
76+
* @param ParentItemProcessorInterface[] $parentItemProcessorPool
6077
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
6178
*/
6279
public function __construct(
@@ -66,7 +83,9 @@ public function __construct(
6683
\Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository,
6784
\Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
6885
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
69-
\Magento\Framework\Message\ManagerInterface $messageManager
86+
\Magento\Framework\Message\ManagerInterface $messageManager,
87+
ProductRepositoryInterface $productRepository,
88+
array $parentItemProcessorPool = []
7089
) {
7190
$this->stockIndexerProcessor = $stockIndexerProcessor;
7291
$this->dataObjectHelper = $dataObjectHelper;
@@ -75,6 +94,8 @@ public function __construct(
7594
$this->stockConfiguration = $stockConfiguration;
7695
$this->attributeHelper = $attributeHelper;
7796
$this->messageManager = $messageManager;
97+
$this->productRepository = $productRepository;
98+
$this->parentItemProcessorPool = $parentItemProcessorPool;
7899
}
79100

80101
/**
@@ -145,6 +166,7 @@ private function addConfigSettings($inventoryData)
145166
private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
146167
{
147168
foreach ($productIds as $productId) {
169+
$product = $this->productRepository->getById($productId);
148170
$stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
149171
if (!$stockItemDo->getProductId()) {
150172
$inventoryData['product_id'] = $productId;
@@ -153,7 +175,21 @@ private function updateInventoryInProducts($productIds, $websiteId, $inventoryDa
153175
$this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
154176
$stockItemDo->setItemId($stockItemId);
155177
$this->stockItemRepository->save($stockItemDo);
178+
$this->processParents($product);
156179
}
157180
$this->stockIndexerProcessor->reindexList($productIds);
158181
}
182+
183+
/**
184+
* Process stock data for parent products
185+
*
186+
* @param ProductInterface $product
187+
* @return void
188+
*/
189+
private function processParents(ProductInterface $product): void
190+
{
191+
foreach ($this->parentItemProcessorPool as $processor) {
192+
$processor->process($product);
193+
}
194+
}
159195
}

app/code/Magento/DownloadableImportExport/Model/Export/RowCustomizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function prepareData($collection, $productIds): void
8282
->addAttributeToSelect('samples_title');
8383
// set global scope during export
8484
$this->storeManager->setCurrentStore(Store::DEFAULT_STORE_ID);
85-
foreach ($collection as $product) {
85+
foreach ($productCollection as $product) {
8686
$productLinks = $this->linkRepository->getLinksByProduct($product);
8787
$productSamples = $this->sampleRepository->getSamplesByProduct($product);
8888
$this->downloadableData[$product->getId()] = [];
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\DownloadableImportExport\Test\Unit\Model\Export\Product;
9+
10+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
11+
use Magento\Downloadable\Model\LinkRepository;
12+
use Magento\Downloadable\Model\Product\Type as Type;
13+
use Magento\Downloadable\Model\SampleRepository;
14+
use Magento\DownloadableImportExport\Model\Export\RowCustomizer;
15+
use Magento\Store\Model\Store;
16+
use Magento\Store\Model\StoreManagerInterface;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Class to test Customizes output during export
22+
*/
23+
class RowCustomizerTest extends TestCase
24+
{
25+
/**
26+
* @var LinkRepository|MockObject
27+
*/
28+
private $linkRepository;
29+
30+
/**
31+
* @var SampleRepository|MockObject
32+
*/
33+
private $sampleRepository;
34+
35+
/**
36+
* @var StoreManagerInterface|MockObject
37+
*/
38+
private $storeManager;
39+
40+
/**
41+
* @var RowCustomizer
42+
*/
43+
private $rowCustomizer;
44+
45+
/**
46+
* @inheritdoc
47+
*/
48+
protected function setUp(): void
49+
{
50+
$this->linkRepository = $this->createMock(LinkRepository::class);
51+
$this->sampleRepository = $this->createMock(SampleRepository::class);
52+
$this->storeManager = $this->createMock(StoreManagerInterface::class);
53+
54+
$this->rowCustomizer = new RowCustomizer(
55+
$this->storeManager,
56+
$this->linkRepository,
57+
$this->sampleRepository
58+
);
59+
}
60+
61+
/**
62+
* Test to Prepare downloadable data for export
63+
*/
64+
public function testPrepareData()
65+
{
66+
$productIds = [1, 2, 3];
67+
$collection = $this->createMock(ProductCollection::class);
68+
$collection->expects($this->at(0))
69+
->method('addAttributeToFilter')
70+
->with('entity_id', ['in' => $productIds])
71+
->willReturnSelf();
72+
$collection->expects($this->at(1))
73+
->method('addAttributeToFilter')
74+
->with('type_id', ['eq' => Type::TYPE_DOWNLOADABLE])
75+
->willReturnSelf();
76+
$collection->method('addAttributeToSelect')->willReturnSelf();
77+
$collection->method('getIterator')->willReturn(new \ArrayIterator([]));
78+
79+
$this->storeManager->expects($this->once())
80+
->method('setCurrentStore')
81+
->with(Store::DEFAULT_STORE_ID);
82+
83+
$this->rowCustomizer->prepareData($collection, $productIds);
84+
}
85+
}

0 commit comments

Comments
 (0)